From d575de817bdd4c995c8322f3727a24a446317936 Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 22 Dec 2021 21:41:59 +1300 Subject: [PATCH 1/6] Refactor value setting into separate setOptionValue --- index.js | 75 +++++++++++++++++++++++++++----------------------------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/index.js b/index.js index ca430e1..98e63bf 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,35 @@ 'use strict'; +function setOptionValue(parseOptions, option, value, result) { + const multiple = parseOptions.multiples && + parseOptions.multiples.includes(option); + const withValue = parseOptions.withValue && + parseOptions.withValue.includes(option); + const isFlag = !withValue && value === undefined; + + // Normal flag: !withValue && value === undefined + // Normal value, withValue && value !== undefined + // Special case: withValue && value === undefined, store undefined not flag + // Special case: !withValue && value !== undefined, store value not flag + + // Flags + // Only mark flags for plain flag without a value, expected or otherwise. + if (isFlag) + result.flags[option] = true; + + // Values + if (multiple) { + // Always store value in array, including for flags. + const val = isFlag ? true : value; + if (result.values[option]) + result.values[option].concat(val); + else + result.values[option] = [val]; + } else if (!isFlag) { + result.values[option] = value; + } +} + const parseArgs = ( argv = process.argv.slice(require.main ? 2 : 1), options = {} @@ -38,59 +68,26 @@ const parseArgs = ( 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, - ); + setOptionValue(options, argParts[0], argParts[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); } From ef59a23220bbdb39add90bf874949c121ff9b60f Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 24 Dec 2021 11:02:34 +1300 Subject: [PATCH 2/6] Take everything after '=' as value --- index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 98e63bf..009595d 100644 --- a/index.js +++ b/index.js @@ -66,9 +66,8 @@ const parseArgs = ( arg = arg.replace(/^-+/, ''); if (arg.includes('=')) { - // withValue equals(=) case - const argParts = arg.split('='); - setOptionValue(options, argParts[0], argParts[1], result); + 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 From 3ed9760b4bfa294a32f6f70c6436b3653553db46 Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 24 Dec 2021 11:41:01 +1300 Subject: [PATCH 3/6] Cosmetic changes, comments --- index.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 69f2d79..5941805 100644 --- a/index.js +++ b/index.js @@ -5,15 +5,17 @@ function setOptionValue(parseOptions, option, value, result) { parseOptions.multiples.includes(option); const withValue = parseOptions.withValue && parseOptions.withValue.includes(option); - const isFlag = !withValue && value === undefined; // Normal flag: !withValue && value === undefined // Normal value, withValue && value !== undefined - // Special case: withValue && value === undefined, store undefined not flag - // Special case: !withValue && value !== undefined, store value not flag + // 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; From 0a6307a09b92adee22add0cb8ff66bb5f6b9a232 Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 28 Dec 2021 17:21:47 +1300 Subject: [PATCH 4/6] Update tests --- index.js | 7 ++++--- test/index.js | 43 ++++++++++++++++++++++++++++++++----------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/index.js b/index.js index a708445..3a00804 100644 --- a/index.js +++ b/index.js @@ -55,8 +55,8 @@ function setOptionValue(parseOptions, option, value, result) { if (multiple) { // Always store value in array, including for flags. const val = isFlag ? true : value; - if (result.values[option]) - result.values[option].concat(val); + if (result.values[option] !== undefined) + result.values[option].push(val); else result.values[option] = [val]; } else if (!isFlag) { @@ -99,7 +99,8 @@ const parseArgs = ( if (arg.includes('=')) { const index = arg.indexOf('='); - setOptionValue(options, arg.slice(0, index), arg.slice(index + 1), result); + 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 diff --git a/test/index.js b/test/index.js index 36eb4c6..b88d05a 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,31 @@ 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, 'arg value is passed'); + + 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, 'arg value is passed'); + + 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 +81,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 +97,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 +114,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 +131,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 +148,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 +165,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 +180,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); From 45e0025667d9438f791d8a14aa03d7906d8a391b Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 29 Dec 2021 12:15:42 +1300 Subject: [PATCH 5/6] add test for --foo=a=b --- test/index.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/test/index.js b/test/index.js index b88d05a..604ee30 100644 --- a/test/index.js +++ b/test/index.js @@ -46,23 +46,34 @@ test('args equals are passed "withValue"', function (t) { t.end() }) -test('when zero config option with equals then option treated as withValue"', function (t) { +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, 'arg value is passed'); + t.deepEqual(args, expected); t.end(); }); -test('when option in withValue is followed by option instead of value then value is undefined"', function (t) { +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, 'arg value is passed'); + 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(); }); From 41c0b6558c87fbfb938daf9407653606988108a8 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 9 Jan 2022 21:47:33 +1300 Subject: [PATCH 6/6] Add explanation of values for multiple, so code makes more sense. --- index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/index.js b/index.js index 3a00804..c4274ae 100644 --- a/index.js +++ b/index.js @@ -54,6 +54,9 @@ function setOptionValue(parseOptions, option, value, result) { // 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);