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) => { let next = grouped[i+1]; if ((a[0].type === 'option') && next && (next[0].type === 'arg_or_operand')) { let next_shortened = next.map(n => n.arg); 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); }); } }); return out; }