Last active
June 2, 2020 14:38
-
-
Save oncomouse/a653ad14a7b405b8d170b3028defe042 to your computer and use it in GitHub Desktop.
ARGV Splitter and Parser for Node.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
Usage: | |
// test.js | |
const argv = require('./argv-split') | |
const arguments = argv(process.argv.slice(2)) | |
console.log(arguments) | |
> node test.js -abc 123 --foo=bar -de6 --flag1 -r5 file1 file2 | |
{ | |
a: true, | |
b: true, | |
c: 123, | |
foo: 'bar', | |
d: true, | |
e: 6, | |
flag1: true, | |
r: 5, | |
_: [ 'file1', 'file2' ] | |
} | |
*/ | |
const argv = function (argv) { | |
// This class tracks a copy of argv and marks arguments for removal as they | |
// are processed. This allows us to run reduce() on ARGV and still safely do | |
// read-ahead for key value pairs. | |
class CleanManager { | |
constructor(argv) { | |
this.CLEANED = Symbol('CLEANED'); | |
this.cleaned = argv.slice(); | |
} | |
clean(index) { | |
this.cleaned[index] = this.CLEANED; | |
} | |
isCleaned(index) { | |
return this.cleaned[index] === this.CLEANED; | |
} | |
removeCleaned() { | |
return this.cleaned.filter(x => x !== this.CLEANED); | |
} | |
} | |
const clean = new CleanManager(argv); | |
// Check if there is a multi-key (e.g. -abc) | |
function getKeys(arg) { | |
if (arg.indexOf('--') === 0) { | |
return [arg.replace(/^--/, '')]; | |
} else { | |
return arg.replace(/^-/, '').split(''); | |
} | |
} | |
function extractNumber(value) { | |
const int = parseFloat(value, 10); | |
return isNaN(int) ? value : int; | |
} | |
function setValue(arg, value) { | |
return getKeys(arg).reduce((obj, key) => ({...obj, [key]: value}), {}) | |
} | |
// Handles the two instances where arguments can be multiple short keys (e.g. | |
// -abc). | |
function setMultiKey(output, arg, value) { | |
const subArgv = getKeys(arg); | |
return Object.assign(output, setValue(subArgv.slice(0, -1).join(''), true), setValue(subArgv[subArgv.length - 1], extractNumber(value))); | |
} | |
// Check if the argument is a switch (starts w/ - or --): | |
function isSwitch(arg, index) { | |
return !clean.isCleaned(index) && arg.indexOf('-') === 0; | |
} | |
const output = argv.reduce((output, arg, index) => { | |
// If argument has already been processed (usually as a value) or argument | |
// is a file arg (usually at the end of the argument chain), skip it: | |
if (!isSwitch(arg, index)) return output; | |
// Handle a key/value pair (e.g. --key=value or -k=value): | |
if (arg.indexOf('=') >= 0) { | |
const [key, value] = arg.split(/=/); | |
clean.clean(index); | |
return Object.assign(output, setValue(key, extractNumber(value))); | |
} | |
// Handle a short key/number-value pair (e.g. -k9 or -x5): | |
const keyValueShort = arg.match(/^-([A-Za-z]+)([0-9]+)/); | |
if (keyValueShort !== null) { | |
const [, key, value] = keyValueShort; | |
clean.clean(index); | |
// This handles arguments of the form: -abc5 | |
// I thought about what happens if you pass something like -abc5d and | |
// can see a case for why this *should* parse it as all true except c, | |
// which is 5, but I think we have to draw the line somewhere. | |
if (key.length > 1) { | |
return setMultiKey(output, key, extractNumber(value)); | |
} | |
return Object.assign(output, {[key]: extractNumber(value)}); | |
} | |
// Handle boolean arguments (e.g. --key or -k, with no following value): | |
// Also handles combo boolean switches (e.g. -abc with no following value): | |
if (index + 1 === argv.length || isSwitch(argv[index + 1], index + 1)) { | |
clean.clean(index); | |
return Object.assign(output, setValue(arg, true)); | |
} | |
// Handle a combo argument (e.g. -abc 123, where a & b are flags and c is 123): | |
if (/^-[^-]{2,}/.test(arg)) { | |
clean.clean(index); | |
clean.clean(index + 1); | |
return setMultiKey(output, arg, extractNumber(argv[index + 1])); | |
} | |
// Handle a standard key value pair (e.g. -k value OR --key value): | |
clean.clean(index); | |
clean.clean(index + 1); | |
return Object.assign(output, setValue(arg, extractNumber(argv[index + 1]))); | |
}, {}); | |
return { | |
...output, | |
_: clean.removeCleaned(), | |
} | |
} | |
module.exports = argv; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment