@thisismanta/pessimist
Version:
This is a Node.js library that helps derive `process.argv` array into a flexible, value-strict, TypeScript-friendly object.
191 lines (190 loc) • 8.38 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseArguments = parseArguments;
const kebabCase_1 = __importDefault(require("lodash/kebabCase"));
const parseBoolean_1 = require("./parseBoolean");
function parseArguments(inputs, defaultHash, options) {
var _a, _b, _c, _d;
const logicHash = {};
for (const name in defaultHash) {
const formalName = name;
if (name.length === 1 && name !== '-') {
logicHash['-' + name] = { formalName, negated: false };
}
else {
const kebabName = (0, kebabCase_1.default)(name);
logicHash['--' + kebabName] = { formalName, negated: false };
if (kebabName.startsWith('no-')) {
logicHash['--' + kebabName.substring(3)] = { formalName, negated: true };
}
else {
logicHash['--no-' + kebabName] = { formalName, negated: true };
}
}
}
if (typeof options?.aliases === 'object' && options.aliases !== null) {
for (const aliasName in options.aliases) {
const targetName = options.aliases[aliasName];
if (typeof targetName !== 'string') {
throw new Error(`Expected the right-hand side of the alias to be a string: ${aliasName} → ${targetName}`);
}
const negationOperatorCount = targetName.match(/^\!+/)?.[0].length ?? 0;
const formalName = targetName.substring(negationOperatorCount);
const negated = negationOperatorCount % 2 === 1;
if (!isKnownName(formalName)) {
throw new Error(`Expected the right-hand side of the alias to be one of the known names: ${aliasName} → ${targetName}`);
}
if (aliasName.length === 1 && aliasName !== '-') {
logicHash[_a = '-' + aliasName] ?? (logicHash[_a] = { formalName, negated });
}
else {
const kebabName = (0, kebabCase_1.default)(aliasName);
logicHash[_b = '--' + kebabName] ?? (logicHash[_b] = { formalName, negated });
if (kebabName.startsWith('no-')) {
logicHash[_c = '--' + kebabName.substring(3)] ?? (logicHash[_c] = { formalName, negated: !negated });
}
else {
logicHash[_d = '--no-' + kebabName] ?? (logicHash[_d] = { formalName, negated: !negated });
}
}
}
}
const [positionalArguments, namedArguments] = inputs
.filter(input => /^-+$/.test(input) === false)
.reduce(([positionalArguments, namedArguments], raw) => {
if (raw.startsWith('--')) {
const delimiterIndex = raw.indexOf('=');
const actualName = delimiterIndex === -1 ? raw : raw.substring(0, delimiterIndex);
const value = delimiterIndex === -1 ? undefined : raw.substring(delimiterIndex + 1);
const kebabName = '--' + (0, kebabCase_1.default)(actualName);
if (kebabName in logicHash === false) {
throw new Error(`Unexpected an unknown argument: ${raw}`);
}
const { formalName, negated } = logicHash[kebabName];
namedArguments.push({
raw,
formalName,
negated,
value,
});
}
else if (raw.startsWith('-')) {
const delimiterIndex = raw.indexOf('=');
if (delimiterIndex === -1) {
for (const name of raw.substring(1).split('')) {
const kebabName = '-' + name;
if (kebabName in logicHash === false) {
throw new Error(`Unexpected an unknown argument: ${raw}`);
}
const { formalName, negated } = logicHash[kebabName];
if (typeof defaultHash[formalName] !== 'boolean') {
throw new Error(`Unexpected the short-hand argument ${raw} which is a non-Boolean`);
}
namedArguments.push({
raw,
formalName,
negated,
value: undefined,
});
}
}
else if (delimiterIndex === 2) {
const actualName = raw.substring(0, delimiterIndex);
if (actualName in logicHash === false) {
throw new Error(`Unexpected an unknown argument: ${raw}`);
}
const { formalName, negated } = logicHash[actualName];
const value = raw.substring(delimiterIndex + 1);
namedArguments.push({
raw,
formalName,
negated,
value,
});
}
else {
throw new Error(`Unexpected an unknown argument: ${raw}`);
}
}
else {
positionalArguments.push(raw);
}
return [positionalArguments, namedArguments];
}, [[], []]);
const outputHash = Object.assign({}, defaultHash);
for (const { raw, formalName, negated, value, } of namedArguments) {
const defaultValue = defaultHash[formalName];
if (typeof defaultValue === 'boolean') {
const parsedValue = (0, parseBoolean_1.parseBoolean)(value, true);
outputHash[formalName] = negated ? !parsedValue : parsedValue;
}
else if (typeof defaultValue === 'number') {
if (negated) {
if (value) {
throw new Error(`Unexpected a value when using "no" name prefix: ${raw}`);
}
else {
outputHash[formalName] = defaultHash[formalName];
}
}
else {
outputHash[formalName] = value === undefined ? NaN : parseFloat(value);
}
}
else if (typeof defaultValue === 'string') {
if (negated) {
if (value === undefined || outputHash[formalName] === value) {
outputHash[formalName] = '';
}
}
else if (value === undefined) {
throw new Error(`Expected a value: ${raw}`);
}
else {
outputHash[formalName] = value;
}
}
else if (Array.isArray(defaultValue)) {
if (negated) {
if (value === undefined) {
outputHash[formalName] = [];
}
else {
outputHash[formalName] = difference(outputHash[formalName], [value]);
}
}
else {
if (value === undefined) {
throw new Error(`Expected a value: ${raw}`);
}
if (outputHash[formalName].includes(value)) {
outputHash[formalName] = difference(outputHash[formalName], [value]);
}
outputHash[formalName] = [...outputHash[formalName], value];
}
}
}
if (options?.exclusives) {
const inputFormalNames = Array.from(new Set(namedArguments.map(({ formalName }) => formalName)));
const formalNameToRaw = Object.fromEntries(namedArguments.map(({ formalName, raw }) => [formalName, raw]));
for (const group of options.exclusives) {
const intersections = intersect(group, inputFormalNames);
if (intersections.length > 1) {
throw new Error('Unexpected mutual exclusive arguments: ' + intersections.map(field => formalNameToRaw[field]).join(' '));
}
}
}
return Object.assign(outputHash, positionalArguments, { length: positionalArguments.length });
function isKnownName(name) {
return name in defaultHash;
}
}
function difference(sourceList, subtractorList) {
return sourceList.filter(item => !subtractorList.includes(item));
}
function intersect(sourceList, comparingList) {
return sourceList.filter(item => comparingList.includes(item));
}