Skip to content
Closed
84 changes: 43 additions & 41 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,41 @@ function getMainArgs() {
return process.argv.slice(2);
}

function setOptionValue(parseOptions, option, value, result) {
const multiple = parseOptions.multiples &&
parseOptions.multiples.includes(option);
const withValue = parseOptions.withValue &&
parseOptions.withValue.includes(option);

// Normal flag: !withValue && value === undefined
// Normal value, withValue && value !== undefined
// Special case: withValue && value === undefined
// store as normal for withValue with value undefined
// Special case: !withValue && value !== undefined
// store as normal for withValue (and not a flag)

// Flags
// Only mark flags for plain flag without a value, expected or otherwise.
const isFlag = !withValue && value === undefined;
if (isFlag)
result.flags[option] = true;

// Values
if (multiple) {
// Always store value in array, including for flags.
// result.values[option] starts out not present,
// first value is added as new array [val],
// subsequent values are pushed to existing array.
const val = isFlag ? true : value;
if (result.values[option] !== undefined)
result.values[option].push(val);
else
result.values[option] = [val];
} else if (!isFlag) {
result.values[option] = value;
}
}

const parseArgs = (
argv = getMainArgs(),
options = {}
Expand Down Expand Up @@ -66,61 +101,28 @@ const parseArgs = (
arg = arg.slice(2); // remove leading --

if (arg.includes('=')) {
// withValue equals(=) case
const argParts = arg.split('=');

result.flags[argParts[0]] = true;
// If withValue option is specified, take 2nd part after '=' as value,
// else set value as undefined
const val = options.withValue &&
options.withValue.includes(argParts[0]) ?
argParts[1] : undefined;
// Append value to previous values array for case of multiples
// option, else add to empty array
result.values[argParts[0]] = [].concat(
options.multiples &&
options.multiples.includes(argParts[0]) &&
result.values[argParts[0]] || [],
val,
);
const index = arg.indexOf('=');
setOptionValue(options,
arg.slice(0, index), arg.slice(index + 1), result);
} else if (pos + 1 < argv.length && !argv[pos + 1].startsWith('-')) {
// withValue option should also support setting values when '=
// isn't used ie. both --foo=b and --foo b should work

result.flags[arg] = true;
// If withValue option is specified, take next position arguement as
// If withValue option is specified, take next position argument as
// value and then increment pos so that we don't re-evaluate that
// arg, else set value as undefined ie. --foo b --bar c, after setting
// b as the value for foo, evaluate --bar next and skip 'b'
const val = options.withValue && options.withValue.includes(arg) ?
argv[++pos] :
undefined;
// Append value to previous values array for case of multiples
// option, else add to empty array
result.values[arg] = [].concat(
options.multiples && options.multiples.includes(arg) &&
result.values[arg] ?
result.values[arg] :
[],
val);
setOptionValue(options, arg, val, result);
} else {
// Cases when an arg is specified without a value, example
// '--foo --bar' <- 'foo' and 'bar' flags should be set to true and
// shave value as undefined
result.flags[arg] = true;
// Append undefined to previous values array for case of
// multiples option, else add to empty array
result.values[arg] = [].concat(
options.multiples && options.multiples.includes(arg) &&
result.values[arg] ?
result.values[arg] :
[],
undefined
);
// No argument available as a value.
setOptionValue(options, arg, undefined, result);
}

} else {
// Arguements without a dash prefix are considered "positional"
// Arguments without a dash prefix are considered "positional"
result.positionals.push(arg);
}

Expand Down
54 changes: 43 additions & 11 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ test('Everything after a bare `--` is considered a positional argument', functio

test('args are true', function (t) {
const passedArgs = ['--foo', '--bar']
const expected = { flags: { foo: true, bar: true}, values: {foo: [undefined], bar: [undefined]}, positionals: [] }
const expected = { flags: { foo: true, bar: true}, values: {}, positionals: [] }
const args = parseArgs(passedArgs)

t.deepEqual(args, expected, 'args are true')
Expand All @@ -27,7 +27,7 @@ test('args are true', function (t) {

test('arg is true and positional is identified', function (t) {
const passedArgs = ['--foo=a', '--foo', 'b']
const expected = { flags: { foo: true}, values: { foo: [undefined]}, positionals: ['b'] }
const expected = { flags: { foo: true}, values: { foo: 'a'}, positionals: ['b'] }
const args = parseArgs(passedArgs)

t.deepEqual(args, expected, 'arg is true and positional is identified')
Expand All @@ -38,18 +38,50 @@ test('arg is true and positional is identified', function (t) {
test('args equals are passed "withValue"', function (t) {
const passedArgs = ['--so=wat']
const passedOptions = { withValue: ['so'] }
const expected = { flags: { so: true}, values: { so: ["wat"]}, positionals: [] }
const expected = { flags: {}, values: { so: "wat" }, positionals: [] }
const args = parseArgs(passedArgs, passedOptions)

t.deepEqual(args, expected, 'arg value is passed')

t.end()
})

test('when zero config option with equals then option treated as withValue', function (t) {
const passedArgs = ['--so=wat'];
const expected = { flags: {}, values: { so: "wat" }, positionals: [] };
const args = parseArgs(passedArgs);

t.deepEqual(args, expected);

t.end();
});

test('when option in withValue is followed by option instead of value then value is undefined', function (t) {
const passedArgs = ['--foo', '--bar'];
const passedOptions = { withValue: ['foo'] };
const expected = { flags: { 'bar': true }, values: { foo: undefined }, positionals: [] };
const args = parseArgs(passedArgs, passedOptions);

t.deepEqual(args, expected);

t.end();
});

test('when option=a=b (value includes =) then value is what follows first =', function (t) {
const passedArgs = ['--foo=b=ar'];
const passedOptions = { withValue: ['foo'] };
const expected = { flags: {}, values: { foo: 'b=ar' }, positionals: [] };
const args = parseArgs(passedArgs, passedOptions);

t.deepEqual(args, expected);

t.end();
});

test('same arg is passed twice "withValue" and last value is recorded', function (t) {
const passedArgs = ['--foo=a', '--foo', 'b']
const passedOptions = { withValue: ['foo'] }
const expected = { flags: { foo: true}, values: { foo: ['b']}, positionals: [] }
const expected = { flags: {}, values: { foo: 'b' }, positionals: [] }
const args = parseArgs(passedArgs, passedOptions)

t.deepEqual(args, expected, 'last arg value is passed')
Expand All @@ -60,7 +92,7 @@ test('same arg is passed twice "withValue" and last value is recorded', function
test('args are passed "withValue" and "multiples"', function (t) {
const passedArgs = ['--foo=a', '--foo', 'b']
const passedOptions = { withValue: ['foo'], multiples: ['foo'] }
const expected = { flags: { foo: true}, values: { foo: ['a', 'b']}, positionals: [] }
const expected = { flags: {}, values: { foo: ['a', 'b'] }, positionals: [] }
const args = parseArgs(passedArgs, passedOptions)

t.deepEqual(args, expected, 'both arg values are passed')
Expand All @@ -76,7 +108,7 @@ test('correct default args when use node -p', function(t) {
const result = parseArgs();

const expected = { flags: { foo: true },
values: { foo: [undefined] },
values: {},
positionals: [] };
t.deepEqual(result, expected);

Expand All @@ -93,7 +125,7 @@ test('correct default args when use node --print', function(t) {
const result = parseArgs();

const expected = { flags: { foo: true },
values: { foo: [undefined] },
values: {},
positionals: [] };
t.deepEqual(result, expected);

Expand All @@ -110,7 +142,7 @@ test('correct default args when use node -e', function(t) {
const result = parseArgs();

const expected = { flags: { foo: true },
values: { foo: [undefined] },
values: {},
positionals: [] };
t.deepEqual(result, expected);

Expand All @@ -127,7 +159,7 @@ test('correct default args when use node --eval', function(t) {
const result = parseArgs();

const expected = { flags: { foo: true },
values: { foo: [undefined] },
values: {},
positionals: [] };
t.deepEqual(result, expected);

Expand All @@ -144,7 +176,7 @@ test('correct default args when normal arguments', function(t) {
const result = parseArgs();

const expected = { flags: { foo: true },
values: { foo: [undefined] },
values: {},
positionals: [] };
t.deepEqual(result, expected);

Expand All @@ -159,7 +191,7 @@ test('excess leading dashes on options are retained', function(t) {
const passedOptions = { };
const expected = {
flags: { '-triple': true },
values: { '-triple': [undefined] },
values: {},
positionals: []
};
const result = parseArgs(passedArgs, passedOptions);
Expand Down