diff --git a/index.js b/index.js index 997bad5..c4274ae 100644 --- a/index.js +++ b/index.js @@ -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 = {} @@ -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); } diff --git a/test/index.js b/test/index.js index 36eb4c6..604ee30 100644 --- a/test/index.js +++ b/test/index.js @@ -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') @@ -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') @@ -38,7 +38,7 @@ 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') @@ -46,10 +46,42 @@ test('args equals are passed "withValue"', function (t) { 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') @@ -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') @@ -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); @@ -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); @@ -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); @@ -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); @@ -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); @@ -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);