yaro
Version:
Fast, minimalist, pluggable and functional command line parsing. Create CLIs easily. A wrapper package exporting everything from several Yaro packages.
164 lines (138 loc) • 4.77 kB
JavaScript
// SPDX-License-Identifier: MPL-2.0
/*
node ./example.js git remote add ens \
--zaz -vvv -wwHw -f -p ho --snake=bar --some-flag --foo-bar ok \
--no-barry -xyz --no-bars --foo --joo GG HH JJ --bar \
--koo=AA BB CC -q EE RR WW -k=1 one two too --qux -c xaxa.config.js \
-a='-b 123' -u '-d12' -l '"-v13"' -o='-r7at' -r 'qux zaz' \
-e '--roo sa3re' --tar '-v ./some folder/and.js' \
--includes a.js,boo.js,qux.js --azzy oh --includes sasa.js,ok.js \
--includes=ab,ce,dd -- cmd --verbose --dry-run --no-show-stack
*/
export const yaroParser = parser;
export const parse = parser;
export { parser };
export default function parser(argv, config = {}) {
const res = { _: [] };
if (!argv) {
return res;
}
if (!Array.isArray(argv)) {
throw new TypeError('expects `argv` to be an array');
}
const options = { array: {}, ...config };
// @ts-ignore
options.autoCount = config.autoCount ?? config.autocount ?? true;
// @ts-ignore
delete options.autocount;
// @ts-ignore
options.camelCase = config.camelCase ?? config.camelcase ?? false;
// @ts-ignore
delete options.camelcase;
let argument;
let previous = {};
let doubleDashIndex;
let isShort;
let isLong;
let isValue;
let isNo;
let isNegate;
let key;
let index;
const firstFlagIndex = argv.findIndex((x) => x[0] === '-');
let arguments_ = [];
if (firstFlagIndex === 0) {
arguments_ = argv;
} else if (firstFlagIndex > 0) {
arguments_ = argv.slice(firstFlagIndex);
// @ts-ignore
res._.push(...argv.slice(0, firstFlagIndex));
} else if (firstFlagIndex === -1) {
arguments_ = [];
// @ts-ignore
res._ = argv;
return res;
}
// eslint-disable-next-line no-plusplus
for (index = 0; index < arguments_.length; index++) {
argument = arguments_[index];
const argumentLength = argument.length;
doubleDashIndex = argument[0] === '-' && argument[1] === '-';
if (argumentLength === 2 && doubleDashIndex) {
doubleDashIndex = index;
break;
}
isShort = argumentLength > 1 && argument[0] === '-' && argument[1] !== '-';
isLong = argumentLength > 2 && doubleDashIndex;
isValue = !isShort && !isLong;
isNo = argument[2] === 'n' && argument[3] === 'o' && argument[4] === '-';
isNegate = isLong && isNo;
key = null;
if (isNegate) {
key = argument.slice(5);
} else if (isLong) {
key = argument.slice(2);
} else if (isShort) {
key = argument.slice(1);
}
const equalsIndex = key?.indexOf('=') || 0;
const usesEquals = equalsIndex > 0; // -f=a, --foo=bar
const temporary = key;
key = usesEquals ? key.slice(0, equalsIndex) : key;
let value = null;
if (isNegate) {
value = false;
} else if ((isShort || isLong) && !isValue) {
value = usesEquals ? temporary.slice(equalsIndex + 1) : true;
} else if (isValue) {
value = argument;
}
// key will be null if it's a value,
// so we should get the previous flag's key
if (key && key.indexOf(' ') >= 1) {
value = argument;
const kkk = previous.key;
key = kkk;
isValue = true;
} else {
key = key === null && isValue && previous.isFlag === true ? previous.key : key;
}
// @ts-ignore
if (key && !isValue && options.camelCase && isLong && key.indexOf('-') > 0) {
key = key.replaceAll(/([a-z])-([a-z])/g, (_, p1, p2) => p1 + p2.toUpperCase());
}
const shouldAppendOne = key === null && isValue && previous.isFlag === false;
const shouldAppenTwo = isValue && previous.isFlag && previous.value !== true;
const shouldAppend = shouldAppendOne || shouldAppenTwo;
if (shouldAppend) {
const kkk = previous.key;
key = kkk;
value = `${previous.value} ${value}`;
}
previous = { isFlag: !isValue, key, value };
if (options.array[key] && !(key in res)) {
res[key] = value === true ? [] : [value];
} else if (options.array[key] && key in res) {
res[key] = value === true ? res[key] : res[key].concat(value);
} else if (key !== null) {
if (isShort && key.length > 1 && !isValue && !usesEquals) {
for (const ch of key) {
// if it exists we should try to check if it's `true` bare (no value)
// or if it has value. if it is true, we make it 2 (number) cuz that's the 2nd occurance
// @ts-ignore
if (options.autoCount && ch in res) {
res[ch] = res[ch] === true ? 2 : res[ch] + 1;
} else {
res[ch] = true;
}
}
} else {
res[key] = value;
}
}
}
if (typeof doubleDashIndex === 'number') {
res['--'] = arguments_.slice(doubleDashIndex + 1);
}
return { ...res };
}