Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: separate out logic for setting value #26

Closed
wants to merge 11 commits into from
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);
ljharb marked this conversation as resolved.
Show resolved Hide resolved
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)
Eomm marked this conversation as resolved.
Show resolved Hide resolved

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

// 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)
ljharb marked this conversation as resolved.
Show resolved Hide resolved
result.values[option].push(val);
ljharb marked this conversation as resolved.
Show resolved Hide resolved
else
result.values[option] = [val];
} else if (!isFlag) {
result.values[option] = value;
ljharb marked this conversation as resolved.
Show resolved Hide resolved
}
}

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