| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 749a4b3 commit 8c73279
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1024,6 +1024,9 @@ equality. | |||
| 1024 | 1024 | <!-- YAML | |
| 1025 | 1025 | added: v16.17.0 | |
| 1026 | 1026 | changes: | |
| 1027 | + - version: REPLACEME | ||
| 1028 | + pr-url: https://github.com/nodejs/node/pull/44631 | ||
| 1029 | + description: Add support for default values in input `config`. | ||
| 1027 | 1030 | - version: v16.17.0 | |
| 1028 | 1031 | pr-url: https://github.com/nodejs/node/pull/43459 | |
| 1029 | 1032 | description: add support for returning detailed parse information | |
@@ -1044,6 +1047,9 @@ changes: | |||
| 1044 | 1047 | times. If `true`, all values will be collected in an array. If | |
| 1045 | 1048 | `false`, values for the option are last-wins. **Default:** `false`. | |
| 1046 | 1049 | * `short` {string} A single character alias for the option. | |
| 1050 | + * `default` {string | boolean | string\[] | boolean\[]} The default option | ||
| 1051 | + value when it is not set by args. It must be of the same type as the | ||
| 1052 | + the `type` property. When `multiple` is `true`, it must be an array. | ||
| 1047 | 1053 | * `strict` {boolean} Should an error be thrown when unknown arguments | |
| 1048 | 1054 | are encountered, or when arguments are passed that do not match the | |
| 1049 | 1055 | `type` configured in `options`. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -20,8 +20,10 @@ const { | |||
| 20 | 20 | const { | |
| 21 | 21 | validateArray, | |
| 22 | 22 | validateBoolean, | |
| 23 | + validateBooleanArray, | ||
| 23 | 24 | validateObject, | |
| 24 | 25 | validateString, | |
| 26 | + validateStringArray, | ||
| 25 | 27 | validateUnion, | |
| 26 | 28 | } = require('internal/validators'); | |
| 27 | 29 | ||
@@ -34,6 +36,7 @@ const { | |||
| 34 | 36 | isOptionLikeValue, | |
| 35 | 37 | isShortOptionAndValue, | |
| 36 | 38 | isShortOptionGroup, | |
| 39 | + useDefaultValueOption, | ||
| 37 | 40 | objectGetOwn, | |
| 38 | 41 | optionsGetOwn, | |
| 39 | 42 | } = require('internal/util/parse_args/utils'); | |
@@ -143,6 +146,24 @@ function storeOption(longOption, optionValue, options, values) { | |||
| 143 | 146 | } | |
| 144 | 147 | } | |
| 145 | 148 | ||
| 149 | + /** | ||
| 150 | + * Store the default option value in `values`. | ||
| 151 | + * | ||
| 152 | + * @param {string} longOption - long option name e.g. 'foo' | ||
| 153 | + * @param {string | ||
| 154 | + * | boolean | ||
| 155 | + * | string[] | ||
| 156 | + * | boolean[]} optionValue - default value from option config | ||
| 157 | + * @param {object} values - option values returned in `values` by parseArgs | ||
| 158 | + */ | ||
| 159 | + function storeDefaultOption(longOption, optionValue, values) { | ||
| 160 | + if (longOption === '__proto__') { | ||
| 161 | + return; // No. Just no. | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + values[longOption] = optionValue; | ||
| 165 | + } | ||
| 166 | + | ||
| 146 | 167 | /** | |
| 147 | 168 | * Process args and turn into identified tokens: | |
| 148 | 169 | * - option (along with value, if any) | |
@@ -290,7 +311,8 @@ const parseArgs = (config = kEmptyObject) => { | |||
| 290 | 311 | validateObject(optionConfig, `options.${longOption}`); | |
| 291 | 312 | ||
| 292 | 313 | // type is required | |
| 293 | - validateUnion(objectGetOwn(optionConfig, 'type'), `options.${longOption}.type`, ['string', 'boolean']); | ||
| 314 | + const optionType = objectGetOwn(optionConfig, 'type'); | ||
| 315 | + validateUnion(optionType, `options.${longOption}.type`, ['string', 'boolean']); | ||
| 294 | 316 | ||
| 295 | 317 | if (ObjectHasOwn(optionConfig, 'short')) { | |
| 296 | 318 | const shortOption = optionConfig.short; | |
@@ -304,8 +326,24 @@ const parseArgs = (config = kEmptyObject) => { | |||
| 304 | 326 | } | |
| 305 | 327 | } | |
| 306 | 328 | ||
| 329 | + const multipleOption = objectGetOwn(optionConfig, 'multiple'); | ||
| 307 | 330 | if (ObjectHasOwn(optionConfig, 'multiple')) { | |
| 308 | - validateBoolean(optionConfig.multiple, `options.${longOption}.multiple`); | ||
| 331 | + validateBoolean(multipleOption, `options.${longOption}.multiple`); | ||
| 332 | + } | ||
| 333 | + | ||
| 334 | + const defaultValue = objectGetOwn(optionConfig, 'default'); | ||
| 335 | + if (defaultValue !== undefined) { | ||
| 336 | + let validator; | ||
| 337 | + switch (optionType) { | ||
| 338 | + case 'string': | ||
| 339 | + validator = multipleOption ? validateStringArray : validateString; | ||
| 340 | + break; | ||
| 341 | + | ||
| 342 | + case 'boolean': | ||
| 343 | + validator = multipleOption ? validateBooleanArray : validateBoolean; | ||
| 344 | + break; | ||
| 345 | + } | ||
| 346 | + validator(defaultValue, `options.${longOption}.default`); | ||
| 309 | 347 | } | |
| 310 | 348 | } | |
| 311 | 349 | ); | |
@@ -336,6 +374,20 @@ const parseArgs = (config = kEmptyObject) => { | |||
| 336 | 374 | } | |
| 337 | 375 | }); | |
| 338 | 376 | ||
| 377 | + // Phase 3: fill in default values for missing args | ||
| 378 | + ArrayPrototypeForEach(ObjectEntries(options), ({ 0: longOption, | ||
| 379 | + 1: optionConfig }) => { | ||
| 380 | + const mustSetDefault = useDefaultValueOption(longOption, | ||
| 381 | + optionConfig, | ||
| 382 | + result.values); | ||
| 383 | + if (mustSetDefault) { | ||
| 384 | + storeDefaultOption(longOption, | ||
| 385 | + objectGetOwn(optionConfig, 'default'), | ||
| 386 | + result.values); | ||
| 387 | + } | ||
| 388 | + }); | ||
| 389 | + | ||
| 390 | + | ||
| 339 | 391 | return result; | |
| 340 | 392 | }; | |
| 341 | 393 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -170,6 +170,19 @@ function findLongOptionForShort(shortOption, options) { | |||
| 170 | 170 | return longOptionEntry?.[0] ?? shortOption; | |
| 171 | 171 | } | |
| 172 | 172 | ||
| 173 | + /** | ||
| 174 | + * Check if the given option includes a default value | ||
| 175 | + * and that option has not been set by the input args. | ||
| 176 | + * | ||
| 177 | + * @param {string} longOption - long option name e.g. 'foo' | ||
| 178 | + * @param {object} optionConfig - the option configuration properties | ||
| 179 | + * @param {object} values - option values returned in `values` by parseArgs | ||
| 180 | + */ | ||
| 181 | + function useDefaultValueOption(longOption, optionConfig, values) { | ||
| 182 | + return objectGetOwn(optionConfig, 'default') !== undefined && | ||
| 183 | + values[longOption] === undefined; | ||
| 184 | + } | ||
| 185 | + | ||
| 173 | 186 | module.exports = { | |
| 174 | 187 | findLongOptionForShort, | |
| 175 | 188 | isLoneLongOption, | |
@@ -179,6 +192,7 @@ module.exports = { | |||
| 179 | 192 | isOptionLikeValue, | |
| 180 | 193 | isShortOptionAndValue, | |
| 181 | 194 | isShortOptionGroup, | |
| 195 | + useDefaultValueOption, | ||
| 182 | 196 | objectGetOwn, | |
| 183 | 197 | optionsGetOwn, | |
| 184 | 198 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -269,6 +269,36 @@ const validateArray = hideStackFrames((value, name, minLength = 0) => { | |||
| 269 | 269 | } | |
| 270 | 270 | }); | |
| 271 | 271 | ||
| 272 | + /** | ||
| 273 | + * @callback validateStringArray | ||
| 274 | + * @param {*} value | ||
| 275 | + * @param {string} name | ||
| 276 | + * @returns {asserts value is string[]} | ||
| 277 | + */ | ||
| 278 | + | ||
| 279 | + /** @type {validateStringArray} */ | ||
| 280 | + function validateStringArray(value, name) { | ||
| 281 | + validateArray(value, name); | ||
| 282 | + for (let i = 0; i < value.length; i++) { | ||
| 283 | + validateString(value[i], `${name}[${i}]`); | ||
| 284 | + } | ||
| 285 | + } | ||
| 286 | + | ||
| 287 | + /** | ||
| 288 | + * @callback validateBooleanArray | ||
| 289 | + * @param {*} value | ||
| 290 | + * @param {string} name | ||
| 291 | + * @returns {asserts value is boolean[]} | ||
| 292 | + */ | ||
| 293 | + | ||
| 294 | + /** @type {validateBooleanArray} */ | ||
| 295 | + function validateBooleanArray(value, name) { | ||
| 296 | + validateArray(value, name); | ||
| 297 | + for (let i = 0; i < value.length; i++) { | ||
| 298 | + validateBoolean(value[i], `${name}[${i}]`); | ||
| 299 | + } | ||
| 300 | + } | ||
| 301 | + | ||
| 272 | 302 | // eslint-disable-next-line jsdoc/require-returns-check | |
| 273 | 303 | /** | |
| 274 | 304 | * @param {*} signal | |
@@ -414,6 +444,8 @@ module.exports = { | |||
| 414 | 444 | isUint32, | |
| 415 | 445 | parseFileMode, | |
| 416 | 446 | validateArray, | |
| 447 | + validateStringArray, | ||
| 448 | + validateBooleanArray, | ||
| 417 | 449 | validateBoolean, | |
| 418 | 450 | validateBuffer, | |
| 419 | 451 | validateCallback, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -823,3 +823,172 @@ test('tokens: strict:false with -- --', () => { | |||
| 823 | 823 | const { tokens } = parseArgs({ strict: false, args, tokens: true }); | |
| 824 | 824 | assert.deepStrictEqual(tokens, expectedTokens); | |
| 825 | 825 | }); | |
| 826 | + | ||
| 827 | + test('default must be a boolean when option type is boolean', () => { | ||
| 828 | + const args = []; | ||
| 829 | + const options = { alpha: { type: 'boolean', default: 'not a boolean' } }; | ||
| 830 | + assert.throws(() => { | ||
| 831 | + parseArgs({ args, options }); | ||
| 832 | + }, /"options\.alpha\.default" property must be of type boolean/ | ||
| 833 | + ); | ||
| 834 | + }); | ||
| 835 | + | ||
| 836 | + test('default must accept undefined value', () => { | ||
| 837 | + const args = []; | ||
| 838 | + const options = { alpha: { type: 'boolean', default: undefined } }; | ||
| 839 | + const result = parseArgs({ args, options }); | ||
| 840 | + const expected = { | ||
| 841 | + values: { | ||
| 842 | + __proto__: null, | ||
| 843 | + }, | ||
| 844 | + positionals: [] | ||
| 845 | + }; | ||
| 846 | + assert.deepStrictEqual(result, expected); | ||
| 847 | + }); | ||
| 848 | + | ||
| 849 | + test('default must be a boolean array when option type is boolean and multiple', () => { | ||
| 850 | + const args = []; | ||
| 851 | + const options = { alpha: { type: 'boolean', multiple: true, default: 'not an array' } }; | ||
| 852 | + assert.throws(() => { | ||
| 853 | + parseArgs({ args, options }); | ||
| 854 | + }, /"options\.alpha\.default" property must be an instance of Array/ | ||
| 855 | + ); | ||
| 856 | + }); | ||
| 857 | + | ||
| 858 | + test('default must be a boolean array when option type is string and multiple is true', () => { | ||
| 859 | + const args = []; | ||
| 860 | + const options = { alpha: { type: 'boolean', multiple: true, default: [true, true, 42] } }; | ||
| 861 | + assert.throws(() => { | ||
| 862 | + parseArgs({ args, options }); | ||
| 863 | + }, /"options\.alpha\.default\[2\]" property must be of type boolean/ | ||
| 864 | + ); | ||
| 865 | + }); | ||
| 866 | + | ||
| 867 | + test('default must be a string when option type is string', () => { | ||
| 868 | + const args = []; | ||
| 869 | + const options = { alpha: { type: 'string', default: true } }; | ||
| 870 | + assert.throws(() => { | ||
| 871 | + parseArgs({ args, options }); | ||
| 872 | + }, /"options\.alpha\.default" property must be of type string/ | ||
| 873 | + ); | ||
| 874 | + }); | ||
| 875 | + | ||
| 876 | + test('default must be an array when option type is string and multiple is true', () => { | ||
| 877 | + const args = []; | ||
| 878 | + const options = { alpha: { type: 'string', multiple: true, default: 'not an array' } }; | ||
| 879 | + assert.throws(() => { | ||
| 880 | + parseArgs({ args, options }); | ||
| 881 | + }, /"options\.alpha\.default" property must be an instance of Array/ | ||
| 882 | + ); | ||
| 883 | + }); | ||
| 884 | + | ||
| 885 | + test('default must be a string array when option type is string and multiple is true', () => { | ||
| 886 | + const args = []; | ||
| 887 | + const options = { alpha: { type: 'string', multiple: true, default: ['str', 42] } }; | ||
| 888 | + assert.throws(() => { | ||
| 889 | + parseArgs({ args, options }); | ||
| 890 | + }, /"options\.alpha\.default\[1\]" property must be of type string/ | ||
| 891 | + ); | ||
| 892 | + }); | ||
| 893 | + | ||
| 894 | + test('default accepted input when multiple is true', () => { | ||
| 895 | + const args = ['--inputStringArr', 'c', '--inputStringArr', 'd', '--inputBoolArr', '--inputBoolArr']; | ||
| 896 | + const options = { | ||
| 897 | + inputStringArr: { type: 'string', multiple: true, default: ['a', 'b'] }, | ||
| 898 | + emptyStringArr: { type: 'string', multiple: true, default: [] }, | ||
| 899 | + fullStringArr: { type: 'string', multiple: true, default: ['a', 'b'] }, | ||
| 900 | + inputBoolArr: { type: 'boolean', multiple: true, default: [false, true, false] }, | ||
| 901 | + emptyBoolArr: { type: 'boolean', multiple: true, default: [] }, | ||
| 902 | + fullBoolArr: { type: 'boolean', multiple: true, default: [false, true, false] }, | ||
| 903 | + }; | ||
| 904 | + const expected = { values: { __proto__: null, | ||
| 905 | + inputStringArr: ['c', 'd'], | ||
| 906 | + inputBoolArr: [true, true], | ||
| 907 | + emptyStringArr: [], | ||
| 908 | + fullStringArr: ['a', 'b'], | ||
| 909 | + emptyBoolArr: [], | ||
| 910 | + fullBoolArr: [false, true, false] }, | ||
| 911 | + positionals: [] }; | ||
| 912 | + const result = parseArgs({ args, options }); | ||
| 913 | + assert.deepStrictEqual(result, expected); | ||
| 914 | + }); | ||
| 915 | + | ||
| 916 | + test('when default is set, the option must be added as result', () => { | ||
| 917 | + const args = []; | ||
| 918 | + const options = { | ||
| 919 | + a: { type: 'string', default: 'HELLO' }, | ||
| 920 | + b: { type: 'boolean', default: false }, | ||
| 921 | + c: { type: 'boolean', default: true } | ||
| 922 | + }; | ||
| 923 | + const expected = { values: { __proto__: null, a: 'HELLO', b: false, c: true }, positionals: [] }; | ||
| 924 | + | ||
| 925 | + const result = parseArgs({ args, options }); | ||
| 926 | + assert.deepStrictEqual(result, expected); | ||
| 927 | + }); | ||
| 928 | + | ||
| 929 | + test('when default is set, the args value takes precedence', () => { | ||
| 930 | + const args = ['--a', 'WORLD', '--b', '-c']; | ||
| 931 | + const options = { | ||
| 932 | + a: { type: 'string', default: 'HELLO' }, | ||
| 933 | + b: { type: 'boolean', default: false }, | ||
| 934 | + c: { type: 'boolean', default: true } | ||
| 935 | + }; | ||
| 936 | + const expected = { values: { __proto__: null, a: 'WORLD', b: true, c: true }, positionals: [] }; | ||
| 937 | + | ||
| 938 | + const result = parseArgs({ args, options }); | ||
| 939 | + assert.deepStrictEqual(result, expected); | ||
| 940 | + }); | ||
| 941 | + | ||
| 942 | + test('tokens should not include the default options', () => { | ||
| 943 | + const args = []; | ||
| 944 | + const options = { | ||
| 945 | + a: { type: 'string', default: 'HELLO' }, | ||
| 946 | + b: { type: 'boolean', default: false }, | ||
| 947 | + c: { type: 'boolean', default: true } | ||
| 948 | + }; | ||
| 949 | + | ||
| 950 | + const expectedTokens = []; | ||
| 951 | + | ||
| 952 | + const { tokens } = parseArgs({ args, options, tokens: true }); | ||
| 953 | + assert.deepStrictEqual(tokens, expectedTokens); | ||
| 954 | + }); | ||
| 955 | + | ||
| 956 | + test('tokens:true should not include the default options after the args input', () => { | ||
| 957 | + const args = ['--z', 'zero', 'positional-item']; | ||
| 958 | + const options = { | ||
| 959 | + z: { type: 'string' }, | ||
| 960 | + a: { type: 'string', default: 'HELLO' }, | ||
| 961 | + b: { type: 'boolean', default: false }, | ||
| 962 | + c: { type: 'boolean', default: true } | ||
| 963 | + }; | ||
| 964 | + | ||
| 965 | + const expectedTokens = [ | ||
| 966 | + { kind: 'option', name: 'z', rawName: '--z', index: 0, value: 'zero', inlineValue: false }, | ||
| 967 | + { kind: 'positional', index: 2, value: 'positional-item' }, | ||
| 968 | + ]; | ||
| 969 | + | ||
| 970 | + const { tokens } = parseArgs({ args, options, tokens: true, allowPositionals: true }); | ||
| 971 | + assert.deepStrictEqual(tokens, expectedTokens); | ||
| 972 | + }); | ||
| 973 | + | ||
| 974 | + test('proto as default value must be ignored', () => { | ||
| 975 | + const args = []; | ||
| 976 | + const options = Object.create(null); | ||
| 977 | + | ||
| 978 | + // eslint-disable-next-line no-proto | ||
| 979 | + options.__proto__ = { type: 'string', default: 'HELLO' }; | ||
| 980 | + | ||
| 981 | + const result = parseArgs({ args, options, allowPositionals: true }); | ||
| 982 | + const expected = { values: { __proto__: null }, positionals: [] }; | ||
| 983 | + assert.deepStrictEqual(result, expected); | ||
| 984 | + }); | ||
| 985 | + | ||
| 986 | + | ||
| 987 | + test('multiple as false should expect a String', () => { | ||
| 988 | + const args = []; | ||
| 989 | + const options = { alpha: { type: 'string', multiple: false, default: ['array'] } }; | ||
| 990 | + assert.throws(() => { | ||
| 991 | + parseArgs({ args, options }); | ||
| 992 | + }, /"options\.alpha\.default" property must be of type string/ | ||
| 993 | + ); | ||
| 994 | + }); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments