module.exports = parse; // https://unix.stackexchange.com/questions/364383/confusion-about-changing-meaning-of-arguments-and-options-is-there-an-official // https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/basedefs/V1_chap12.html#tag_12_01 // 'options' are --foo, -f // // 'option-arguments' are (non-option) things that follow an option, and that the option expects // eg. 'filename.txt' in './example --input filename.txt' // // 'operands' are (non-option) things that follow an option, but aren't option-arguments // eg 'filename.txt' in './example --verbose filename.txt' /** * @returns [ { string: boolean } | { string: string[] } ] **/ function parse(argv) { argv = process.argv.slice(2); // Label everything in argv as either // an 'option' or an 'arg_or_operand' let mapped = argv.flatMap((a) => { // A single long option if (a.startsWith('--')) { return { arg: a.substring(2), type: 'option' } } // A single short option if (a.startsWith('-') && (a.length === 2)) { return { arg: a.substring(1), type: 'option' } } // Multiple short options if (a.startsWith('-')) { let splitGroup = a.substring(1).split(''); return splitGroup.map((sg) => { return {arg: sg, type: 'option'} }); } // An option-argument or operand return { arg: a, type: 'arg_or_operand' }; }); // Group consecutive option-args/operands let wrapped = mapped.map((a) => [a]); let grouped = []; wrapped.forEach((a, i) => { if (i === 0 || a[0].type === 'option') { grouped.push(a); return; } let prev = grouped.pop(); if (prev[0].type === 'option') { grouped.push(prev); grouped.push(a); return; } grouped.push(prev.concat(a)); return; }); let out = [] grouped.forEach((a, i) => { console.log('current', a); let next = grouped[i+1]; console.log('next', next); if ((a[0].type === 'option') && next && (next[0].type === 'arg_or_operand')) { console.log('next is a value'); let next_shortened = next.map(n => n.arg); console.log('short', next_shortened); let o = {}; o[a[0].arg] = next_shortened; out.push(o); grouped.splice(i, 1); } else { let pair = {}; pair[a[0].arg] = true; a.forEach((b) => { let o = {}; o[b.arg] = true; out.push(o); }); } }); console.log('out', out); } /* // this is good, but doesn't handle multiple operands in a row let out = []; mapped.forEach((a, i) => { console.log(a, i); if ((a.type === 'flag') && (mapped[i+1].type === 'arg_or_operand')) { console.log('next is a value'); let pair = {}; pair[a.arg] = mapped[i+1].arg; out.push(pair); mapped.splice(i, 1); } else { let pair = {}; pair[a.arg] = true; out.push(pair); } }); //console.log(out); } */ /* argv.forEach((arg) => { // We'll treat `--long` flags and flags without dash prefixes the same, // so just discard double-dash prefixes if (arg.startsWith('--')) { arg = arg.substring(2); } // Handle short flags, including // un-grouping grouped ones (eg `ps -aux`) if (arg.startsWith('-')) { console.log('startswith -; length:', arg.length); console.log('lose first char:', arg.substring(1)); console.log('split keys vals:', arg.split('=')); // If there's a key=value assignment, // don't treat it like a group if (arg.split('=').length > 1) { let [key, value] = processArg(arg); finalOutput[key] = value; return; } if (arg.length < 2) { // It's not grouped; just remove the '-' and treat it like the rest, later arg = arg.substring(1); let [key, value] = processArg(arg); finalOutput[key] = value; return; } else { arg = arg.substring(1); let ungrouped = arg.split(''); ungrouped.forEach(a => output[a] = true); console.log('ungr', ungrouped); return; } } console.log('out:', output); output[arg] = true; }); return finalOutput; } function processArg(a) { let out = {}; let split = a.split('=') if (split.length > 2) { throw new Error(`Too many '=' in argument ${a}`); } let [key, value] = split; if (split.length === 2) { return [key, value]; } else { return [key, true]; } } */