From 2bc395fb28300222e7c93c2682eb37311065198f Mon Sep 17 00:00:00 2001 From: laggingreflex Date: Sat, 6 Oct 2018 19:09:21 +0000 Subject: [PATCH 001/206] fix: flatten-duplicate-arrays:false for more than 2 arrays (#128) --- index.js | 2 +- test/yargs-parser.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index a3849bce..5fdb78e5 100644 --- a/index.js +++ b/index.js @@ -639,7 +639,7 @@ function parse (args, opts) { o[key] = increment(o[key]) } else if (Array.isArray(o[key])) { if (duplicate && isTypeArray && isValueArray) { - o[key] = configuration['flatten-duplicate-arrays'] ? o[key].concat(value) : [o[key]].concat([value]) + o[key] = configuration['flatten-duplicate-arrays'] ? o[key].concat(value) : (Array.isArray(o[key][0]) ? o[key] : [o[key]]).concat([value]) } else if (!duplicate && Boolean(isTypeArray) === Boolean(isValueArray)) { o[key] = value } else { diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 29104246..24a8f121 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -2088,6 +2088,16 @@ describe('yargs-parser', function () { parsed['x'].should.deep.equal([['a', 'b'], ['c', 'd']]) }) + it('nests duplicate array types of more than 2', function () { + var parsed = parser('-x a b -x c d -x e f -x g h', { + array: ['x'], + configuration: { + 'flatten-duplicate-arrays': false + } + }) + + parsed['x'].should.deep.equal([['a', 'b'], ['c', 'd'], ['e', 'f'], ['g', 'h']]) + }) it('doesn\'t nests single arrays', function () { var parsed = parser('-x a b', { array: ['x'], From 08c011796f83e84fe50ffb066a87ae2effadfc75 Mon Sep 17 00:00:00 2001 From: laggingreflex Date: Sat, 6 Oct 2018 19:22:53 +0000 Subject: [PATCH 002/206] feat: also add camelCase array options (#125) --- index.js | 11 +++++++++++ package.json | 3 ++- test/yargs-parser.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 5fdb78e5..2e5ddd32 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ var camelCase = require('camelcase') +var decamelize = require('decamelize') var path = require('path') var tokenizeArgString = require('./lib/tokenize-arg-string') var util = require('util') @@ -674,6 +675,16 @@ function parse (args, opts) { } } }) + // For "--optionName", also set argv['option-name'] + flags.aliases[key].concat(key).forEach(function (x) { + if (/[A-Z]/.test(x) && configuration['camel-case-expansion']) { + var c = decamelize(x, '-') + if (c !== key && flags.aliases[key].indexOf(c) === -1) { + flags.aliases[key].push(c) + newAliases[c] = true + } + } + }) flags.aliases[key].forEach(function (x) { flags.aliases[x] = [key].concat(flags.aliases[key].filter(function (y) { return x !== y diff --git a/package.json b/package.json index 74b2efee..933ca217 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "standard-version": "^4.3.0" }, "dependencies": { - "camelcase": "^4.1.0" + "camelcase": "^4.1.0", + "decamelize": "^2.0.0" }, "files": [ "lib", diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 24a8f121..62a4965f 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -1529,6 +1529,35 @@ describe('yargs-parser', function () { var result = parser(['-x', 'val1', '-x', 'val1']) result.should.have.property('x').that.is.an('array').and.to.deep.equal(['val1', 'val1']) }) + + it('should eat camelCase switch with camelCase array option', function () { + var result = parser(['--someOption', '1', '2'], { + array: ['someOption'] + }) + Array.isArray(result.someOption).should.equal(true) + result.someOption.should.deep.equal([1, 2]) + }) + it('should eat hyphenated switch with hyphenated array option', function () { + var result = parser(['--some-option', '1', '2'], { + array: ['some-option'] + }) + Array.isArray(result['some-option']).should.equal(true) + result['some-option'].should.deep.equal([1, 2]) + }) + it('should eat camelCase switch with hyphenated array option', function () { + var result = parser(['--someOption', '1', '2'], { + array: ['some-option'] + }) + Array.isArray(result['some-option']).should.equal(true) + result['some-option'].should.deep.equal([1, 2]) + }) + it('should eat hyphenated switch with camelCase array option', function () { + var result = parser(['--some-option', '1', '2'], { + array: ['someOption'] + }) + Array.isArray(result['someOption']).should.equal(true) + result['someOption'].should.deep.equal([1, 2]) + }) }) describe('nargs', function () { From 8b5e4702dfb0ab54716f29955b6915bbd49b6162 Mon Sep 17 00:00:00 2001 From: Jakob Krigovsky Date: Sat, 6 Oct 2018 21:23:17 +0200 Subject: [PATCH 003/206] docs: use SVG for Travis CI badge (#127) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c433384..138f73c8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # yargs-parser -[![Build Status](https://travis-ci.org/yargs/yargs-parser.png)](https://travis-ci.org/yargs/yargs-parser) +[![Build Status](https://travis-ci.org/yargs/yargs-parser.svg)](https://travis-ci.org/yargs/yargs-parser) [![Coverage Status](https://coveralls.io/repos/yargs/yargs-parser/badge.svg?branch=)](https://coveralls.io/r/yargs/yargs-parser?branch=master) [![NPM version](https://img.shields.io/npm/v/yargs-parser.svg)](https://www.npmjs.com/package/yargs-parser) [![Windows Tests](https://img.shields.io/appveyor/ci/bcoe/yargs-parser/master.svg?label=Windows%20Tests)](https://ci.appveyor.com/project/bcoe/yargs-parser) From 0e366e38a8fc034d57fd1096372c1e0c1a969c18 Mon Sep 17 00:00:00 2001 From: Jonathan Siegel Date: Sat, 6 Oct 2018 15:23:48 -0400 Subject: [PATCH 004/206] docs: coders care 'bout spellin'. (#129) --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 2e5ddd32..357fe227 100644 --- a/index.js +++ b/index.js @@ -137,7 +137,7 @@ function parse (args, opts) { var next var value - // -- seperated by = + // -- separated by = if (arg.match(/^--.+=/) || ( !configuration['short-option-groups'] && arg.match(/^-.+=/) )) { From dc788daac7845f5cf7a44c2f73c84087441264ef Mon Sep 17 00:00:00 2001 From: iilei Date: Sat, 6 Oct 2018 21:35:55 +0200 Subject: [PATCH 005/206] fix: hyphenated flags combined with dot notation broke parsing (#131) BREAKING CHANGE: the argv object is now populated differently (correctly) when hyphens and dot notation are used in conjunction. --- index.js | 5 ++++- test/yargs-parser.js | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 357fe227..caf8a646 100644 --- a/index.js +++ b/index.js @@ -372,7 +372,10 @@ function parse (args, opts) { unsetDefaulted(key) if (/-/.test(key) && configuration['camel-case-expansion']) { - addNewAlias(key, camelCase(key)) + var alias = key.split('.').map(function (prop) { + return camelCase(prop) + }).join('.') + addNewAlias(key, alias) } var value = processValue(key, val) diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 62a4965f..b6e2396e 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -1115,6 +1115,15 @@ describe('yargs-parser', function () { argv.fooBar.should.equal(99) }) + + // Fixes: https://github.com/yargs/yargs-parser/issues/77 + it('should combine dot-notation and camel-case expansion', function () { + var argv = parser(['--dot-notation.foo.bar']) + + argv.should.satisfy(function (args) { + return args.dotNotation.foo.bar + }) + }) }) it('should define option as boolean and set default to true', function () { From 7df1bd478f99605090248b513e8c7941abea8fb8 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Sat, 6 Oct 2018 12:44:43 -0700 Subject: [PATCH 006/206] chore: remove deploy logic, now that I use 2FA --- .travis.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index a0fa326c..e1e7f5e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,3 @@ node_js: - "6" - "node" after_script: npm run coverage -deploy: - provider: npm - email: ben@npmjs.com - api_key: $NPM_TOKEN - on: - tags: true From 77ae1d4e1c2590eeca025952671fff935ab7e884 Mon Sep 17 00:00:00 2001 From: Volker Mische Date: Sat, 6 Oct 2018 21:54:00 +0200 Subject: [PATCH 007/206] fix: make requiresArg work in conjunction with arrays (#136) --- index.js | 8 ++++---- test/yargs-parser.js | 11 +++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index caf8a646..e0054fd0 100644 --- a/index.js +++ b/index.js @@ -167,12 +167,12 @@ function parse (args, opts) { )) { key = arg.match(/^--?(.+)/)[1] - // nargs format = '--foo a b c' - if (checkAllAliases(key, flags.nargs)) { - i = eatNargs(i, key, args) // array format = '--foo a b c' - } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) { + if (checkAllAliases(key, flags.arrays) && args.length > i + 1) { i = eatArray(i, key, args) + // nargs format = '--foo a b c' + } else if (checkAllAliases(key, flags.nargs)) { + i = eatNargs(i, key, args) } else { next = args[i + 1] diff --git a/test/yargs-parser.js b/test/yargs-parser.js index b6e2396e..e483536e 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -1702,6 +1702,17 @@ describe('yargs-parser', function () { result.foo.should.eql('a') }) + + it('should be ignored if input is an array', function () { + var result = parser(['--foo', 'a', 'b'], { + array: 'foo', + narg: { + foo: 1 + } + }) + + result.foo.should.eql(['a', 'b']) + }) }) describe('env vars', function () { From 6dc42a190a97a779ac01b6ddf3d405bddefb0e96 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Sat, 6 Oct 2018 13:03:29 -0700 Subject: [PATCH 008/206] chore: update dependencies BREAKING CHANGE: drops Node 4 support --- .travis.yml | 2 +- package.json | 21 ++++++++++++--------- test/yargs-parser.js | 22 +++++++++++----------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/.travis.yml b/.travis.yml index e1e7f5e2..1797b138 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: node_js os: - linux node_js: - - "4" - "6" + - "8" - "node" after_script: npm run coverage diff --git a/package.json b/package.json index 933ca217..e574e428 100644 --- a/package.json +++ b/package.json @@ -26,19 +26,22 @@ "author": "Ben Coe ", "license": "ISC", "devDependencies": { - "chai": "^3.5.0", - "coveralls": "^2.11.12", - "mocha": "^3.0.1", - "nyc": "^11.4.1", - "standard": "^10.0.2", - "standard-version": "^4.3.0" + "chai": "^4.2.0", + "coveralls": "^3.0.2", + "mocha": "^5.2.0", + "nyc": "^13.0.1", + "standard": "^12.0.1", + "standard-version": "^4.4.0" }, "dependencies": { - "camelcase": "^4.1.0", - "decamelize": "^2.0.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" }, "files": [ "lib", "index.js" - ] + ], + "engine": { + "node": ">=6" + } } diff --git a/test/yargs-parser.js b/test/yargs-parser.js index e483536e..7f220a17 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -146,7 +146,7 @@ describe('yargs-parser', function () { '--other', '-99', '-220' ], { array: 'bounds', - narg: {'other': 2} + narg: { 'other': 2 } }) argv._.should.deep.equal([-33, -177, 33]) @@ -1419,12 +1419,12 @@ describe('yargs-parser', function () { describe('array', function () { it('should group values into an array if the same option is specified multiple times (duplicate-arguments-array=true)', function () { - var parse = parser(['-v', 'a', '-v', 'b', '-v', 'c'], {configuration: {'duplicate-arguments-array': true}}) + var parse = parser(['-v', 'a', '-v', 'b', '-v', 'c'], { configuration: { 'duplicate-arguments-array': true } }) parse.should.have.property('v').and.deep.equal(['a', 'b', 'c']) parse.should.have.property('_').with.length(0) }) it('should keep only the last value if the same option is specified multiple times (duplicate-arguments-false)', function () { - var parse = parser(['-v', 'a', '-v', 'b', '-v', 'c'], {configuration: {'duplicate-arguments-array': false}}) + var parse = parser(['-v', 'a', '-v', 'b', '-v', 'c'], { configuration: { 'duplicate-arguments-array': false } }) parse.should.have.property('v').and.equal('c') parse.should.have.property('_').with.length(0) }) @@ -1503,8 +1503,8 @@ describe('yargs-parser', function () { var result = parser(['-a=hello', 'world', '-b', '33', '22', '--foo', 'red', 'green', '--bar=cat', 'dog'], { - array: ['a', 'b', 'foo', 'bar'] - }) + array: ['a', 'b', 'foo', 'bar'] + }) Array.isArray(result.a).should.equal(true) result.a.should.include('hello') @@ -2166,7 +2166,7 @@ describe('yargs-parser', function () { } }) - parsed['x'].should.deep.equal({foo: ['a', 'b']}) + parsed['x'].should.deep.equal({ foo: ['a', 'b'] }) }) }) @@ -2400,13 +2400,13 @@ describe('yargs-parser', function () { number: ['d'], count: ['e'], normalize: ['f'], - narg: {g: 2}, + narg: { g: 2 }, coerce: { h: function (arg) { return arg } }, - configuration: {'set-placeholder-key': true} + configuration: { 'set-placeholder-key': true } }) parsed.should.have.property('a') expect(parsed.a).to.be.equal(undefined) @@ -2427,8 +2427,8 @@ describe('yargs-parser', function () { it('should not set placeholder for key with a default value', function () { var parsed = parser([], { string: ['a'], - default: {a: 'hello'}, - configuration: {'set-placeholder-key': true} + default: { a: 'hello' }, + configuration: { 'set-placeholder-key': true } }) parsed.a.should.equal('hello') }) @@ -2639,7 +2639,7 @@ describe('yargs-parser', function () { var argv = parser([ '--foo', 'bar' ], { array: ['a'], normalize: ['a'], - configObjects: [{'a': ['bin/../a.txt', 'bin/../b.txt']}] + configObjects: [{ 'a': ['bin/../a.txt', 'bin/../b.txt'] }] }) argv.a.should.deep.equal(['a.txt', 'b.txt']) }) From 4b8cfce511edce5fe6f3c5a6d7eb147b56cd8f52 Mon Sep 17 00:00:00 2001 From: iilei Date: Sat, 6 Oct 2018 23:15:13 +0200 Subject: [PATCH 009/206] feat: array.type can now be provided, supporting coercion (#132) --- README.md | 2 ++ index.js | 18 +++++++++++++- test/yargs-parser.js | 57 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 138f73c8..1cccd3ad 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,8 @@ Parses command line arguments returning a simple mapping of keys and values. * `opts`: provide a set of hints indicating how `args` should be parsed: * `opts.alias`: an object representing the set of aliases for a key: `{alias: {foo: ['f']}}`. * `opts.array`: indicate that keys should be parsed as an array: `{array: ['foo', 'bar']}`. + Indicate that keys should be parsed as an array and coerced to booleans / numbers: + `{array: [{ key: 'foo', boolean: true }, {key: 'bar', number: true}]}`. * `opts.boolean`: arguments should be parsed as booleans: `{boolean: ['x', 'y']}`. * `opts.config`: indicate a key that represents a path to a configuration file (this file will be loaded and parsed). * `opts.coerce`: provide a custom synchronous function that returns a coerced value from the argument provided diff --git a/index.js b/index.js index e0054fd0..d003297a 100644 --- a/index.js +++ b/index.js @@ -52,7 +52,23 @@ function parse (args, opts) { var negative = /^-[0-9]+(\.[0-9]+)?/ var negatedBoolean = new RegExp('^--' + configuration['negation-prefix'] + '(.+)') - ;[].concat(opts.array).filter(Boolean).forEach(function (key) { + ;[].concat(opts.array).filter(Boolean).forEach(function (opt) { + var key = opt.key || opt + + // assign to flags[bools|strings|numbers] + const assignment = Object.keys(opt).map(function (key) { + return ({ + boolean: 'bools', + string: 'strings', + number: 'numbers' + })[key] + }).filter(Boolean).pop() + + // assign key to be coerced + if (assignment) { + flags[assignment][key] = true + } + flags.arrays[key] = true flags.keys.push(key) }) diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 7f220a17..7b52a0be 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -1567,6 +1567,63 @@ describe('yargs-parser', function () { Array.isArray(result['someOption']).should.equal(true) result['someOption'].should.deep.equal([1, 2]) }) + + // see https://github.com/yargs/yargs-parser/issues/6 + it('should respect the type `boolean` option for arrays', function () { + var result = parser(['-x=true', 'false'], { + array: [{ key: 'x', boolean: true }] + }) + result.should.have.property('x').that.is.an('array').and.to.deep.equal([true, false]) + }) + + it('should respect the type `number` option for arrays', function () { + var result = parser(['-x=5', '2'], { + array: [{ key: 'x', number: true }] + }) + result.should.have.property('x').that.is.an('array').and.to.deep.equal([5, 2]) + }) + + it('should respect the type `string` option for arrays', function () { + var result = parser(['-x=5', '2'], { + configuration: { + 'parse-numbers': true + }, + array: [{ key: 'x', string: true }] + }) + result.should.have.property('x').that.is.an('array').and.to.deep.equal(['5', '2']) + }) + + it('should eat non-hyphenated arguments until hyphenated option is hit - combined with coercion', function () { + var result = parser([ + '-a=hello', 'world', + '-b', '33', '22', + '--foo', 'true', 'false', + '--bar=cat', 'dog' + ], { + array: [ + 'a', + { key: 'b', integer: true }, + { key: 'foo', boolean: true }, + 'bar' + ] + }) + + Array.isArray(result.a).should.equal(true) + result.a.should.include('hello') + result.a.should.include('world') + + Array.isArray(result.b).should.equal(true) + result.b.should.include(33) + result.b.should.include(22) + + Array.isArray(result.foo).should.equal(true) + result.foo.should.include(true) + result.foo.should.include(false) + + Array.isArray(result.bar).should.equal(true) + result.bar.should.include('cat') + result.bar.should.include('dog') + }) }) describe('nargs', function () { From 68dd3a18f04b819c25b48f523a861c81c17c82b8 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Sat, 6 Oct 2018 15:44:50 -0700 Subject: [PATCH 010/206] chore(release): 11.0.0 --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45bb0e4c..741bec6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,35 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +# [11.0.0](https://github.com/yargs/yargs-parser/compare/v10.1.0...v11.0.0) (2018-10-06) + + +### Bug Fixes + +* flatten-duplicate-arrays:false for more than 2 arrays ([#128](https://github.com/yargs/yargs-parser/issues/128)) ([2bc395f](https://github.com/yargs/yargs-parser/commit/2bc395f)) +* hyphenated flags combined with dot notation broke parsing ([#131](https://github.com/yargs/yargs-parser/issues/131)) ([dc788da](https://github.com/yargs/yargs-parser/commit/dc788da)) +* make requiresArg work in conjunction with arrays ([#136](https://github.com/yargs/yargs-parser/issues/136)) ([77ae1d4](https://github.com/yargs/yargs-parser/commit/77ae1d4)) + + +### Chores + +* update dependencies ([6dc42a1](https://github.com/yargs/yargs-parser/commit/6dc42a1)) + + +### Features + +* also add camelCase array options ([#125](https://github.com/yargs/yargs-parser/issues/125)) ([08c0117](https://github.com/yargs/yargs-parser/commit/08c0117)) +* array.type can now be provided, supporting coercion ([#132](https://github.com/yargs/yargs-parser/issues/132)) ([4b8cfce](https://github.com/yargs/yargs-parser/commit/4b8cfce)) + + +### BREAKING CHANGES + +* drops Node 4 support +* the argv object is now populated differently (correctly) when hyphens and dot notation are used in conjunction. + + + # [10.1.0](https://github.com/yargs/yargs-parser/compare/v10.0.0...v10.1.0) (2018-06-29) diff --git a/package.json b/package.json index e574e428..fda87143 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "10.1.0", + "version": "11.0.0", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From ee56e3116800824b2b0d6a749950fb7eaac296ff Mon Sep 17 00:00:00 2001 From: coderaiser Date: Fri, 9 Nov 2018 18:12:20 +0200 Subject: [PATCH 011/206] fix: handling of one char alias (#139) --- index.js | 2 +- test/yargs-parser.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index d003297a..98ee7ae2 100644 --- a/index.js +++ b/index.js @@ -696,7 +696,7 @@ function parse (args, opts) { }) // For "--optionName", also set argv['option-name'] flags.aliases[key].concat(key).forEach(function (x) { - if (/[A-Z]/.test(x) && configuration['camel-case-expansion']) { + if (x.length > 1 && /[A-Z]/.test(x) && configuration['camel-case-expansion']) { var c = decamelize(x, '-') if (c !== key && flags.aliases[key].indexOf(c) === -1) { flags.aliases[key].push(c) diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 7b52a0be..b0e8555b 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -1218,6 +1218,21 @@ describe('yargs-parser', function () { result.should.have.property('someOption', 'asdf') }) + it('should not apply camel-case logic to 1-character options', function () { + var result = parser(['-p', 'hello'], { + alias: { + p: 'parallel', + P: 'parallel-series' + } + }) + + result.should.not.have.property('P', 'hello') + result.should.not.have.property('parallel-series', 'hello') + result.should.not.have.property('parallelSeries', 'hello') + result.should.have.property('parallel', 'hello') + result.should.have.property('p', 'hello') + }) + it('should provide aliases of options with dashes as camelCase properties', function () { var result = parser([], { alias: { From a849fce0ba37414b3f3c2e01a547654dc1035623 Mon Sep 17 00:00:00 2001 From: Wout Mertens Date: Fri, 9 Nov 2018 17:14:21 +0100 Subject: [PATCH 012/206] feat: add halt-at-non-option configuration option (#130) --- README.md | 21 +++++++++++++++++++++ index.js | 13 ++++++++----- test/yargs-parser.js | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1cccd3ad..ca926583 100644 --- a/README.md +++ b/README.md @@ -321,6 +321,27 @@ node example.js -a 1 -c 2 { _: [], a: 1, b: undefined, c: 2 } ``` +### halt at non-option + +* default: `false`. +* key: `halt-at-non-option`. + +Should parsing stop at the first text argument? This is similar to how e.g. `ssh` parses its command line. + +_If disabled:_ + +```sh +node example.js -a run b -x y +{ _: [ 'run', 'b', 'y' ], a: true, x: true } +``` + +_If enabled:_ + +```sh +node example.js -a run b -x y +{ _: [ 'run', 'b', '-x', 'y' ], a: true } +``` + ## Special Thanks The yargs project evolves from optimist and minimist. It owes its diff --git a/index.js b/index.js index 98ee7ae2..917189a9 100644 --- a/index.js +++ b/index.js @@ -22,7 +22,8 @@ function parse (args, opts) { 'flatten-duplicate-arrays': true, 'populate--': false, 'combine-arrays': false, - 'set-placeholder-key': false + 'set-placeholder-key': false, + 'halt-at-non-option': false }, opts.configuration) var defaults = opts.default || {} var configObjects = opts.configObjects || [] @@ -139,10 +140,6 @@ function parse (args, opts) { }) var notFlags = [] - if (args.indexOf('--') !== -1) { - notFlags = args.slice(args.indexOf('--') + 1) - args = args.slice(0, args.indexOf('--')) - } for (var i = 0; i < args.length; i++) { var arg = args[i] @@ -299,6 +296,12 @@ function parse (args, opts) { } } } + } else if (arg === '--') { + notFlags = args.slice(i + 1) + break + } else if (configuration['halt-at-non-option']) { + notFlags = args.slice(i) + break } else { argv._.push(maybeCoerceNumber('_', arg)) } diff --git a/test/yargs-parser.js b/test/yargs-parser.js index b0e8555b..a1d17de2 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -2514,6 +2514,39 @@ describe('yargs-parser', function () { parsed.should.not.have.property('a.b') }) }) + + describe('halt-at-non-option', function () { + it('gets the entire rest of line', function () { + var parse = parser(['--foo', './file.js', '--foo', '--bar'], { + configuration: { 'halt-at-non-option': true }, + boolean: ['foo', 'bar'] + }) + parse.should.deep.equal({ foo: true, _: ['./file.js', '--foo', '--bar'] }) + }) + + it('is not influenced by --', function () { + var parse = parser( + ['--foo', './file.js', '--foo', '--', 'barbar', '--bar'], + { configuration: { 'halt-at-non-option': true }, boolean: ['foo', 'bar'] } + ) + parse.should.deep.equal({ + foo: true, + _: ['./file.js', '--foo', '--', 'barbar', '--bar'] + }) + }) + + it('is not influenced by unknown options', function () { + var parse = parser( + ['-v', '--long', 'arg', './file.js', '--foo', '--', 'barbar'], + { configuration: { 'halt-at-non-option': true }, boolean: ['foo'] } + ) + parse.should.deep.equal({ + v: true, + long: 'arg', + _: ['./file.js', '--foo', '--', 'barbar'] + }) + }) + }) }) // addresses: https://github.com/yargs/yargs-parser/issues/41 From f94e536481a167375f396ad0798257ab78b85ab4 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Fri, 9 Nov 2018 16:25:04 -0800 Subject: [PATCH 013/206] chore: switch to Travis for Windows tests (#147) --- .travis.yml | 2 ++ README.md | 1 - appveyor.yml | 20 -------------------- 3 files changed, 2 insertions(+), 21 deletions(-) delete mode 100644 appveyor.yml diff --git a/.travis.yml b/.travis.yml index 1797b138..bf5a96a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,10 @@ language: node_js os: - linux + - windows node_js: - "6" - "8" + - "10" - "node" after_script: npm run coverage diff --git a/README.md b/README.md index ca926583..5847dffe 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![Build Status](https://travis-ci.org/yargs/yargs-parser.svg)](https://travis-ci.org/yargs/yargs-parser) [![Coverage Status](https://coveralls.io/repos/yargs/yargs-parser/badge.svg?branch=)](https://coveralls.io/r/yargs/yargs-parser?branch=master) [![NPM version](https://img.shields.io/npm/v/yargs-parser.svg)](https://www.npmjs.com/package/yargs-parser) -[![Windows Tests](https://img.shields.io/appveyor/ci/bcoe/yargs-parser/master.svg?label=Windows%20Tests)](https://ci.appveyor.com/project/bcoe/yargs-parser) [![Standard Version](https://img.shields.io/badge/release-standard%20version-brightgreen.svg)](https://github.com/conventional-changelog/standard-version) diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 396e9fb8..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,20 +0,0 @@ -environment: - matrix: - - nodejs_version: '6' - - nodejs_version: '8' - - nodejs_version: '10' -install: - - ps: Install-Product node $env:nodejs_version - - set CI=true - - set PATH=%APPDATA%\npm;%PATH% - - npm install -matrix: - fast_finish: true -build: off -version: '{build}' -shallow_clone: true -clone_depth: 1 -test_script: - - node --version - - npm --version - - npm test From 1eb726b30e78f6f76c1ba66dbcb49a92cbc4b6ff Mon Sep 17 00:00:00 2001 From: Benjamin Coe Date: Fri, 9 Nov 2018 16:26:54 -0800 Subject: [PATCH 014/206] chore(release): 11.1.0 --- CHANGELOG.md | 15 +++++++++++++++ package.json | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 741bec6d..41b52912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +# [11.1.0](https://github.com/yargs/yargs-parser/compare/v11.0.0...v11.1.0) (2018-11-10) + + +### Bug Fixes + +* handling of one char alias ([#139](https://github.com/yargs/yargs-parser/issues/139)) ([ee56e31](https://github.com/yargs/yargs-parser/commit/ee56e31)) + + +### Features + +* add halt-at-non-option configuration option ([#130](https://github.com/yargs/yargs-parser/issues/130)) ([a849fce](https://github.com/yargs/yargs-parser/commit/a849fce)) + + + # [11.0.0](https://github.com/yargs/yargs-parser/compare/v10.1.0...v11.0.0) (2018-10-06) diff --git a/package.json b/package.json index fda87143..fe28e11e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "11.0.0", + "version": "11.1.0", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From f4a3063b17c4d921bb1e9551e23d32c9fb7c68b5 Mon Sep 17 00:00:00 2001 From: Christopher Hiller Date: Mon, 19 Nov 2018 09:52:59 -0800 Subject: [PATCH 015/206] revert: make requiresArg work in conjunction with arrays (#136) --- index.js | 8 ++++---- test/yargs-parser.js | 11 ----------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/index.js b/index.js index 917189a9..69fa72dc 100644 --- a/index.js +++ b/index.js @@ -180,12 +180,12 @@ function parse (args, opts) { )) { key = arg.match(/^--?(.+)/)[1] - // array format = '--foo a b c' - if (checkAllAliases(key, flags.arrays) && args.length > i + 1) { - i = eatArray(i, key, args) // nargs format = '--foo a b c' - } else if (checkAllAliases(key, flags.nargs)) { + if (checkAllAliases(key, flags.nargs)) { i = eatNargs(i, key, args) + // array format = '--foo a b c' + } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) { + i = eatArray(i, key, args) } else { next = args[i + 1] diff --git a/test/yargs-parser.js b/test/yargs-parser.js index a1d17de2..81dff196 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -1774,17 +1774,6 @@ describe('yargs-parser', function () { result.foo.should.eql('a') }) - - it('should be ignored if input is an array', function () { - var result = parser(['--foo', 'a', 'b'], { - array: 'foo', - narg: { - foo: 1 - } - }) - - result.foo.should.eql(['a', 'b']) - }) }) describe('env vars', function () { From 1c5d556d12e1fb5c67e3ce057226e6c01cd5b213 Mon Sep 17 00:00:00 2001 From: Christopher Hiller Date: Mon, 19 Nov 2018 09:55:34 -0800 Subject: [PATCH 016/206] test: add test for config object priority (#149) --- .gitignore | 1 + test/yargs-parser.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/.gitignore b/.gitignore index db9974ff..a7502d1d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules .DS_Store package-lock.json ./test/fixtures/package.json +coverage diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 81dff196..f6420932 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -747,6 +747,20 @@ describe('yargs-parser', function () { bar: 'bar' }) }) + + it('should load objects with first object having greatest priority', function () { + var argv = parser(['--foo', 'bar'], { + configObjects: [{ + bar: 'baz' + }, { + bar: 'quux', + foo: 'spam' + }] + }) + + argv.should.have.property('foo', 'bar') + argv.should.have.property('bar', 'baz') + }) }) describe('dot notation', function () { From 79cda989595a7da4a9fd3f39120da5001f68899c Mon Sep 17 00:00:00 2001 From: Jack Robinson Date: Mon, 19 Nov 2018 17:57:58 +0000 Subject: [PATCH 017/206] fix: ensure empty string is added into argv._ (#140) --- lib/tokenize-arg-string.js | 1 + test/tokenize-arg-string.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/tokenize-arg-string.js b/lib/tokenize-arg-string.js index 6c8d23ef..73e14cd6 100644 --- a/lib/tokenize-arg-string.js +++ b/lib/tokenize-arg-string.js @@ -25,6 +25,7 @@ module.exports = function (argString) { // don't split the string if we're in matching // opening or closing single and double quotes. if (c === opening) { + if (!args[i]) args[i] = '' opening = null continue } else if ((c === "'" || c === '"') && !opening) { diff --git a/test/tokenize-arg-string.js b/test/tokenize-arg-string.js index 2f7cffcc..8bb6a95f 100644 --- a/test/tokenize-arg-string.js +++ b/test/tokenize-arg-string.js @@ -32,6 +32,20 @@ describe('TokenizeArgString', function () { args[2].should.equal('--bar=foo bar') }) + it('handles single quoted empty string', function () { + var args = tokenizeArgString('--foo \'\' --bar=\'\'') + args[0].should.equal('--foo') + args[1].should.equal('') + args[2].should.equal('--bar=') + }) + + it('handles double quoted empty string', function () { + var args = tokenizeArgString('--foo "" --bar=""') + args[0].should.equal('--foo') + args[1].should.equal('') + args[2].should.equal('--bar=') + }) + it('handles quoted string with embeded quotes', function () { var args = tokenizeArgString('--foo "hello \'world\'" --bar=\'foo "bar"\'') args[0].should.equal('--foo') From ee122f89ab9417241b06c6b3ce6aabb2ac8ce5f2 Mon Sep 17 00:00:00 2001 From: Benjamin Coe Date: Mon, 19 Nov 2018 13:27:23 -0800 Subject: [PATCH 018/206] chore(release): 11.1.1 --- CHANGELOG.md | 15 +++++++++++++++ package.json | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41b52912..06c42c3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +## [11.1.1](https://github.com/yargs/yargs-parser/compare/v11.1.0...v11.1.1) (2018-11-19) + + +### Bug Fixes + +* ensure empty string is added into argv._ ([#140](https://github.com/yargs/yargs-parser/issues/140)) ([79cda98](https://github.com/yargs/yargs-parser/commit/79cda98)) + + +### Reverts + +* make requiresArg work in conjunction with arrays ([#136](https://github.com/yargs/yargs-parser/issues/136)) ([f4a3063](https://github.com/yargs/yargs-parser/commit/f4a3063)) + + + # [11.1.0](https://github.com/yargs/yargs-parser/compare/v11.0.0...v11.1.0) (2018-11-10) diff --git a/package.json b/package.json index fe28e11e..8fcb5479 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "11.1.0", + "version": "11.1.1", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From 2fb71b2b1d25316f94786a385616ebe34a6cbd73 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sun, 27 Jan 2019 18:32:55 -0800 Subject: [PATCH 019/206] fix: better handling of quoted strings (#153) BREAKING CHANGE: quotes and beginning and endings of strings are not removed during parsing. --- example.js | 2 +- index.js | 9 +++++++++ lib/tokenize-arg-string.js | 2 -- test/tokenize-arg-string.js | 24 ++++++++++++------------ test/yargs-parser.js | 27 +++++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 15 deletions(-) diff --git a/example.js b/example.js index 1c1ea630..cbfe167d 100755 --- a/example.js +++ b/example.js @@ -1,3 +1,3 @@ var parser = require('./') -var parse = parser(['-cats', 'meow']) +var parse = parser('--foo "-bar"') console.log(parse) diff --git a/index.js b/index.js index 69fa72dc..b628e7dd 100644 --- a/index.js +++ b/index.js @@ -9,6 +9,7 @@ function parse (args, opts) { // allow a string argument to be passed in rather // than an argv array. args = tokenizeArgString(args) + // aliases might have transitive relationships, normalize this. var aliases = combineAliases(opts.alias || {}) var configuration = assign({ @@ -450,6 +451,14 @@ function parse (args, opts) { } function processValue (key, val) { + // strings may be quoted, clean this up as we assign values. + if (typeof val === 'string' && + (val[0] === "'" || val[0] === '"') && + val[val.length - 1] === val[0] + ) { + val = val.substring(1, val.length - 1) + } + // handle parsing boolean arguments --foo=true --bar false. if (checkAllAliases(key, flags.bools) || checkAllAliases(key, flags.counts)) { if (typeof val === 'string') val = val === 'true' diff --git a/lib/tokenize-arg-string.js b/lib/tokenize-arg-string.js index 73e14cd6..569f61ad 100644 --- a/lib/tokenize-arg-string.js +++ b/lib/tokenize-arg-string.js @@ -27,10 +27,8 @@ module.exports = function (argString) { if (c === opening) { if (!args[i]) args[i] = '' opening = null - continue } else if ((c === "'" || c === '"') && !opening) { opening = c - continue } if (!args[i]) args[i] = '' diff --git a/test/tokenize-arg-string.js b/test/tokenize-arg-string.js index 8bb6a95f..e33c2466 100644 --- a/test/tokenize-arg-string.js +++ b/test/tokenize-arg-string.js @@ -15,42 +15,42 @@ describe('TokenizeArgString', function () { it('handles quoted string with no spaces', function () { var args = tokenizeArgString("--foo 'hello'") args[0].should.equal('--foo') - args[1].should.equal('hello') + args[1].should.equal("'hello'") }) it('handles single quoted string with spaces', function () { var args = tokenizeArgString("--foo 'hello world' --bar='foo bar'") args[0].should.equal('--foo') - args[1].should.equal('hello world') - args[2].should.equal('--bar=foo bar') + args[1].should.equal("'hello world'") + args[2].should.equal("--bar='foo bar'") }) it('handles double quoted string with spaces', function () { var args = tokenizeArgString('--foo "hello world" --bar="foo bar"') args[0].should.equal('--foo') - args[1].should.equal('hello world') - args[2].should.equal('--bar=foo bar') + args[1].should.equal('"hello world"') + args[2].should.equal('--bar="foo bar"') }) it('handles single quoted empty string', function () { var args = tokenizeArgString('--foo \'\' --bar=\'\'') args[0].should.equal('--foo') - args[1].should.equal('') - args[2].should.equal('--bar=') + args[1].should.equal("''") + args[2].should.equal("--bar=''") }) it('handles double quoted empty string', function () { var args = tokenizeArgString('--foo "" --bar=""') args[0].should.equal('--foo') - args[1].should.equal('') - args[2].should.equal('--bar=') + args[1].should.equal('""') + args[2].should.equal('--bar=""') }) it('handles quoted string with embeded quotes', function () { var args = tokenizeArgString('--foo "hello \'world\'" --bar=\'foo "bar"\'') args[0].should.equal('--foo') - args[1].should.equal('hello \'world\'') - args[2].should.equal('--bar=foo "bar"') + args[1].should.equal('"hello \'world\'"') + args[2].should.equal('--bar=\'foo "bar"\'') }) // https://github.com/yargs/yargs-parser/pull/100 @@ -59,6 +59,6 @@ describe('TokenizeArgString', function () { var args = tokenizeArgString(' foo bar "foo bar" ') args[0].should.equal('foo') expect(args[1]).equal('bar') - expect(args[2]).equal('foo bar') + expect(args[2]).equal('"foo bar"') }) }) diff --git a/test/yargs-parser.js b/test/yargs-parser.js index f6420932..ea9f70e9 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -2796,4 +2796,31 @@ describe('yargs-parser', function () { argv.foo[3].bla.should.equal('banana') }) }) + + // see: https://github.com/yargs/yargs-parser/issues/145 + describe('strings with quotes and dashes', () => { + it('handles double quoted strings', function () { + const args = parser('--foo "hello world" --bar="goodnight\'moon"') + args.foo.should.equal('hello world') + args.bar.should.equal('goodnight\'moon') + const args2 = parser(['--foo', '"hello world"', '--bar="goodnight\'moon"']) + args2.foo.should.equal('hello world') + args2.bar.should.equal('goodnight\'moon') + }) + + it('handles single quoted strings', function () { + const args = parser("--foo 'hello world' --bar='goodnight\"moon'") + args.foo.should.equal('hello world') + args.bar.should.equal('goodnight"moon') + const args2 = parser(['--foo', "'hello world'", "--bar='goodnight\"moon'"]) + args2.foo.should.equal('hello world') + args2.bar.should.equal('goodnight"moon') + }) + + it('handles strings with dashes', function () { + const args = parser('--foo "-hello world" --bar="--goodnight moon"') + args.foo.should.equal('-hello world') + args.bar.should.equal('--goodnight moon') + }) + }) }) From a02b1d5c55d4860e0b92f0c65158dace6c872ca7 Mon Sep 17 00:00:00 2001 From: Benjamin Coe Date: Sun, 27 Jan 2019 20:42:20 -0800 Subject: [PATCH 020/206] doc: remove incorrect documenntation regarding opts.-- --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 5847dffe..e1f8abe1 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,6 @@ Parses command line arguments returning a simple mapping of keys and values. * `opts.string`: keys should be treated as strings (even if they resemble a number `-x 33`). * `opts.configuration`: provide configuration options to the yargs-parser (see: [configuration](#configuration)). * `opts.number`: keys should be treated as numbers. - * `opts['--']`: arguments after the end-of-options flag `--` will be set to the `argv.['--']` array instead of being set to the `argv._` array. **returns:** From 5a7c46a0c707fbd975150352619a0e90c42df227 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Mon, 28 Jan 2019 10:49:42 -0800 Subject: [PATCH 021/206] feat: default value is now used if no right-hand value provided for numbers/strings (#156) BREAKING CHANGE: a flag with no right-hand value no longer populates defaulted options with `undefined`. --- index.js | 22 +++++++++++++++++----- test/yargs-parser.js | 24 +++++++++++++++++++++++- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index b628e7dd..796e9704 100644 --- a/index.js +++ b/index.js @@ -200,7 +200,7 @@ function parse (args, opts) { setArg(key, next) i++ } else { - setArg(key, defaultForType(guessType(key, flags))) + setArg(key, defaultValue(key)) } } @@ -220,7 +220,7 @@ function parse (args, opts) { setArg(key, next) i++ } else { - setArg(key, defaultForType(guessType(key, flags))) + setArg(key, defaultValue(key)) } } else if (arg.match(/^-[^-]+/) && !arg.match(negative)) { letters = arg.slice(1, -1).split('') @@ -267,7 +267,7 @@ function parse (args, opts) { broken = true break } else { - setArg(letters[j], defaultForType(guessType(letters[j], flags))) + setArg(letters[j], defaultValue(letters[j])) } } @@ -293,7 +293,7 @@ function parse (args, opts) { setArg(key, next) i++ } else { - setArg(key, defaultForType(guessType(key, flags))) + setArg(key, defaultValue(key)) } } } @@ -749,6 +749,18 @@ function parse (args, opts) { }) } + // make a best effor to pick a default value + // for an option based on name and type. + function defaultValue (key) { + if (!checkAllAliases(key, flags.bools) && + !checkAllAliases(key, flags.counts) && + `${key}` in defaults) { + return defaults[key] + } else { + return defaultForType(guessType(key)) + } + } + // return a default value, given the type of a flag., // e.g., key of type 'string' will default to '', rather than 'true'. function defaultForType (type) { @@ -763,7 +775,7 @@ function parse (args, opts) { } // given a flag, enforce a default type. - function guessType (key, flags) { + function guessType (key) { var type = 'boolean' if (checkAllAliases(key, flags.strings)) type = 'string' diff --git a/test/yargs-parser.js b/test/yargs-parser.js index ea9f70e9..60debc5d 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -255,7 +255,7 @@ describe('yargs-parser', function () { }) // Fixes: https://github.com/bcoe/yargs/issues/68 - it('should parse flag arguments with no right-hand-value as strings, if defined as strings', function () { + it('should parse flag arguments with no right-hand value as strings, if defined as strings', function () { var s = parser([ '-s' ], { string: ['s'] }).s @@ -2823,4 +2823,26 @@ describe('yargs-parser', function () { args.bar.should.equal('--goodnight moon') }) }) + + // see: https://github.com/yargs/yargs-parser/issues/144 + it('number/string types should use default when no right-hand value', () => { + let argv = parser([ '--foo' ], { + number: ['foo'], + default: { + foo: 99 + } + }) + argv.foo.should.equal(99) + + argv = parser([ '-b' ], { + alias: { + bar: 'b' + }, + string: ['bar'], + default: { + bar: 'hello' + } + }) + argv.bar.should.equal('hello') + }) }) From ea6ce0572b32797e965c620ee93a47a9a02201ee Mon Sep 17 00:00:00 2001 From: Benjamin Coe Date: Mon, 28 Jan 2019 16:48:06 -0800 Subject: [PATCH 022/206] chore(release): 12.0.0 --- CHANGELOG.md | 21 +++++++++++++++++++++ package.json | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06c42c3c..c57d34a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +# [12.0.0](https://github.com/yargs/yargs-parser/compare/v11.1.1...v12.0.0) (2019-01-29) + + +### Bug Fixes + +* better handling of quoted strings ([#153](https://github.com/yargs/yargs-parser/issues/153)) ([2fb71b2](https://github.com/yargs/yargs-parser/commit/2fb71b2)) + + +### Features + +* default value is now used if no right-hand value provided for numbers/strings ([#156](https://github.com/yargs/yargs-parser/issues/156)) ([5a7c46a](https://github.com/yargs/yargs-parser/commit/5a7c46a)) + + +### BREAKING CHANGES + +* a flag with no right-hand value no longer populates defaulted options with `undefined`. +* quotes and beginning and endings of strings are not removed during parsing. + + + ## [11.1.1](https://github.com/yargs/yargs-parser/compare/v11.1.0...v11.1.1) (2018-11-19) diff --git a/package.json b/package.json index 8fcb5479..6cad847c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "11.1.1", + "version": "12.0.0", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From 18d0fd582996b3004bc92f2b46b9cd9776550414 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sat, 2 Feb 2019 13:24:23 -0800 Subject: [PATCH 023/206] feat: don't coerce number from string with leading '0' or '+' (#158) BREAKING CHANGE: options with leading '+' or '0' now parse as strings --- index.js | 7 ++++++- test/yargs-parser.js | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 796e9704..20d472d1 100644 --- a/index.js +++ b/index.js @@ -786,9 +786,14 @@ function parse (args, opts) { } function isNumber (x) { + if (x === null || x === undefined) return false + // if loaded from config, may already be a number. if (typeof x === 'number') return true + // hexadecimal. if (/^0x[0-9a-f]+$/i.test(x)) return true - return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x) + // don't treat 0123 as a number; as it drops the leading '0'. + if (x.length > 1 && x[0] === '0') return false + return /^[-]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x) } function isUndefined (num) { diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 60debc5d..ee76e417 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -2770,6 +2770,24 @@ describe('yargs-parser', function () { argv.foo.should.equal(9.39404959509494e+22) }) + // see: https://github.com/yargs/yargs/issues/1099 + it('does not magically convert options with leading + to number', () => { + const argv = parser(['--foo', '+5550100', '--bar', '+5550100'], { + number: 'bar' + }) + argv.foo.should.equal('+5550100') + argv.bar.should.equal(5550100) + }) + + // see: https://github.com/yargs/yargs/issues/1099 + it('does not magically convert options with leading 0 to number', () => { + const argv = parser(['--foo', '000000', '--bar', '000000'], { + number: 'bar' + }) + argv.foo.should.equal('000000') + argv.bar.should.equal(0) + }) + // see: https://github.com/yargs/yargs-parser/issues/101 describe('dot-notation array arguments combined with string arguments', function () { it('parses correctly when dot-notation argument is first', function () { From 710eebaf30fa0c0008c470f6c52cc11602511776 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Sat, 2 Feb 2019 13:26:28 -0800 Subject: [PATCH 024/206] chore(release): 13.0.0 --- CHANGELOG.md | 15 +++++++++++++++ package.json | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c57d34a6..466ddfed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +# [13.0.0](https://github.com/yargs/yargs-parser/compare/v12.0.0...v13.0.0) (2019-02-02) + + +### Features + +* don't coerce number from string with leading '0' or '+' ([#158](https://github.com/yargs/yargs-parser/issues/158)) ([18d0fd5](https://github.com/yargs/yargs-parser/commit/18d0fd5)) + + +### BREAKING CHANGES + +* options with leading '+' or '0' now parse as strings + + + # [12.0.0](https://github.com/yargs/yargs-parser/compare/v11.1.1...v12.0.0) (2019-01-29) diff --git a/package.json b/package.json index 6cad847c..feca2c0f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "12.0.0", + "version": "13.0.0", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From 1404f79dd9bfda5fbab3d4474e3132e1bfff31a7 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Sat, 2 Feb 2019 13:28:02 -0800 Subject: [PATCH 025/206] doc: fix typo in CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 466ddfed..79de745b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ All notable changes to this project will be documented in this file. See [standa ### BREAKING CHANGES * a flag with no right-hand value no longer populates defaulted options with `undefined`. -* quotes and beginning and endings of strings are not removed during parsing. +* quotes at beginning and endings of strings are not removed during parsing. From c0cd851bbaae7ce8532ff2d54a6968fb960fdea3 Mon Sep 17 00:00:00 2001 From: Corey Farrell Date: Sun, 5 May 2019 00:37:18 -0400 Subject: [PATCH 026/206] refactor: Use Object.assign instead of custom function. (#168) --- index.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/index.js b/index.js index 20d472d1..4bc053fc 100644 --- a/index.js +++ b/index.js @@ -12,7 +12,7 @@ function parse (args, opts) { // aliases might have transitive relationships, normalize this. var aliases = combineAliases(opts.alias || {}) - var configuration = assign({ + var configuration = Object.assign({ 'short-option-groups': true, 'camel-case-expansion': true, 'dot-notation': true, @@ -856,20 +856,6 @@ function combineAliases (aliases) { return combined } -function assign (defaults, configuration) { - var o = {} - configuration = configuration || {} - - Object.keys(defaults).forEach(function (k) { - o[k] = defaults[k] - }) - Object.keys(configuration).forEach(function (k) { - o[k] = configuration[k] - }) - - return o -} - // this function should only be called when a count is given as an arg // it is NOT called to set a default value // thus we can start the count at 1 instead of 0 From f184308809ac40d4f1ccf48b93f44f6695c4050a Mon Sep 17 00:00:00 2001 From: Corey Farrell Date: Sun, 5 May 2019 00:37:49 -0400 Subject: [PATCH 027/206] refactor: remove usage of `arguments` (#169) --- index.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 4bc053fc..67c1385a 100644 --- a/index.js +++ b/index.js @@ -33,9 +33,7 @@ function parse (args, opts) { var notFlagsArgv = notFlagsOption ? '--' : '_' var newAliases = {} // allow a i18n handler to be passed in, default to a fake one (util.format). - var __ = opts.__ || function (str) { - return util.format.apply(util, Array.prototype.slice.call(arguments)) - } + var __ = opts.__ || util.format var error = null var flags = { aliases: {}, @@ -687,8 +685,8 @@ function parse (args, opts) { } // extend the aliases list with inferred aliases. - function extendAliases () { - Array.prototype.slice.call(arguments).forEach(function (obj) { + function extendAliases (...args) { + args.forEach(function (obj) { Object.keys(obj || {}).forEach(function (key) { // short-circuit if we've already added a key // to the aliases array, for example it might From 0ae7fcbc528bc7630b713b7e09b7a3a6b5fced9c Mon Sep 17 00:00:00 2001 From: Corey Farrell Date: Sun, 5 May 2019 00:38:31 -0400 Subject: [PATCH 028/206] feat: support boolean which do not consume next argument. (#171) --- index.js | 2 +- test/yargs-parser.js | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 67c1385a..546cae06 100644 --- a/index.js +++ b/index.js @@ -186,7 +186,7 @@ function parse (args, opts) { } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) { i = eatArray(i, key, args) } else { - next = args[i + 1] + next = flags.nargs[key] === 0 ? undefined : args[i + 1] if (next !== undefined && (!next.match(/^-/) || next.match(negative)) && diff --git a/test/yargs-parser.js b/test/yargs-parser.js index ee76e417..c625ce97 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -194,6 +194,17 @@ describe('yargs-parser', function () { parse.should.have.property('_').and.deep.equal(['aaatrueaaa', 'moo', 'aaafalseaaa']) }) + it('should not use next value for boolean configured with zero narg', function () { + var parse = parser(['--all', 'false'], { + boolean: ['all'], + narg: { + all: 0 + } + }) + parse.should.have.property('all', true).and.be.a('boolean') + parse.should.have.property('_').and.deep.equal(['false']) + }) + it('should allow defining options as boolean in groups', function () { var parse = parser([ '-x', '-z', 'one', 'two', 'three' ], { boolean: ['x', 'y', 'z'] From a3936aa21e89b9a7e4f3bc05d3e56c1eff6a6a79 Mon Sep 17 00:00:00 2001 From: Corey Farrell Date: Sun, 5 May 2019 00:40:33 -0400 Subject: [PATCH 029/206] feat: add `strip-aliased` and `strip-dashed` configuration options. (#172) --- README.md | 43 ++++++++++++++++++++++++++++ index.js | 21 +++++++++++++- test/yargs-parser.js | 67 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e1f8abe1..94bbe46b 100644 --- a/README.md +++ b/README.md @@ -340,6 +340,49 @@ node example.js -a run b -x y { _: [ 'run', 'b', '-x', 'y' ], a: true } ``` +### strip aliased + +* default: `false` +* key: `strip-aliased` + +Should aliases be removed before returning results? + +_If disabled:_ + +```sh +node example.js --test-field 1 +{ _: [], 'test-field': 1, testField: 1, 'test-alias': 1, testAlias: 1 } +``` + +_If enabled:_ + +```sh +node example.js --test-field 1 +{ _: [], 'test-field': 1, testField: 1 } +``` + +### strip dashed + +* default: `false` +* key: `strip-dashed` + +Should dashed keys be removed before returning results? This option has no effect if +`camel-case-exansion` is disabled. + +_If disabled:_ + +```sh +node example.js --test-field 1 +{ _: [], 'test-field': 1, testField: 1 } +``` + +_If enabled:_ + +```sh +node example.js --test-field 1 +{ _: [], testField: 1 } +``` + ## Special Thanks The yargs project evolves from optimist and minimist. It owes its diff --git a/index.js b/index.js index 546cae06..ed54b8d6 100644 --- a/index.js +++ b/index.js @@ -24,7 +24,9 @@ function parse (args, opts) { 'populate--': false, 'combine-arrays': false, 'set-placeholder-key': false, - 'halt-at-non-option': false + 'halt-at-non-option': false, + 'strip-aliased': false, + 'strip-dashed': false }, opts.configuration) var defaults = opts.default || {} var configObjects = opts.configObjects || [] @@ -331,6 +333,23 @@ function parse (args, opts) { argv[notFlagsArgv].push(key) }) + if (configuration['camel-case-expansion'] && configuration['strip-dashed']) { + Object.keys(argv).filter(key => key !== '--' && key.includes('-')).forEach(key => { + delete argv[key] + }) + } + + if (configuration['strip-aliased']) { + // XXX Switch to [].concat(...Object.values(aliases)) once node.js 6 is dropped + ;[].concat(...Object.keys(aliases).map(k => aliases[k])).forEach(alias => { + if (configuration['camel-case-expansion']) { + delete argv[alias.split('.').map(prop => camelCase(prop)).join('.')] + } + + delete argv[alias] + }) + } + // how many arguments should we consume, based // on the nargs option? function eatNargs (i, key, args) { diff --git a/test/yargs-parser.js b/test/yargs-parser.js index c625ce97..fcff562a 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -2874,4 +2874,71 @@ describe('yargs-parser', function () { }) argv.bar.should.equal('hello') }) + + describe('stripping', function () { + it('strip-dashed removes expected fields from argv', function () { + const argv = parser([ '--test-value', '1' ], { + number: ['test-value'], + alias: { + 'test-value': ['alt-test'] + }, + configuration: { + 'strip-dashed': true + } + }) + argv.should.deep.equal({ + _: [], + 'testValue': 1, + 'altTest': 1 + }) + }) + + it('strip-aliased removes expected fields from argv', function () { + const argv = parser([ '--test-value', '1' ], { + number: ['test-value'], + alias: { + 'test-value': ['alt-test'] + }, + configuration: { + 'strip-aliased': true + } + }) + argv.should.deep.equal({ + _: [], + 'test-value': 1, + 'testValue': 1 + }) + }) + + it('strip-aliased and strip-dashed combined removes expected fields from argv', function () { + const argv = parser([ '--test-value', '1' ], { + number: ['test-value'], + alias: { + 'test-value': ['alt-test'] + }, + configuration: { + 'strip-aliased': true, + 'strip-dashed': true + } + }) + argv.should.deep.equal({ + _: [], + 'testValue': 1 + }) + }) + + it('ignores strip-dashed if camel-case-expansion is disabled', function () { + const argv = parser([ '--test-value', '1' ], { + number: ['test-value'], + configuration: { + 'camel-case-expansion': false, + 'strip-dashed': true + } + }) + argv.should.deep.equal({ + _: [], + 'test-value': 1 + }) + }) + }) }) From b1012f8faa543a13df79dc045e32e1d0aa3ad03f Mon Sep 17 00:00:00 2001 From: Juerg B <44573692+juergba@users.noreply.github.com> Date: Sun, 5 May 2019 06:42:47 +0200 Subject: [PATCH 030/206] docs: fix example halt-at-non-option (#165) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 94bbe46b..5ddc3fa8 100644 --- a/README.md +++ b/README.md @@ -324,20 +324,20 @@ node example.js -a 1 -c 2 * default: `false`. * key: `halt-at-non-option`. -Should parsing stop at the first text argument? This is similar to how e.g. `ssh` parses its command line. +Should parsing stop at the first positional argument? This is similar to how e.g. `ssh` parses its command line. _If disabled:_ ```sh node example.js -a run b -x y -{ _: [ 'run', 'b', 'y' ], a: true, x: true } +{ _: [ 'b' ], a: 'run', x: 'y' } ``` _If enabled:_ ```sh node example.js -a run b -x y -{ _: [ 'run', 'b', '-x', 'y' ], a: true } +{ _: [ 'b', '-x', 'y' ], a: 'run' } ``` ### strip aliased From 89aa3cda0db159e6514f7dbd572c582dbde2e595 Mon Sep 17 00:00:00 2001 From: bcoe Date: Sun, 5 May 2019 14:30:33 -0700 Subject: [PATCH 031/206] chore: update dev deps --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index feca2c0f..20766637 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,9 @@ "chai": "^4.2.0", "coveralls": "^3.0.2", "mocha": "^5.2.0", - "nyc": "^13.0.1", + "nyc": "^14.1.0", "standard": "^12.0.1", - "standard-version": "^4.4.0" + "standard-version": "^6.0.0" }, "dependencies": { "camelcase": "^5.0.0", From 69ddfedf03024ac0e9e45d0a18daa0964e4c1c34 Mon Sep 17 00:00:00 2001 From: bcoe Date: Sun, 5 May 2019 14:30:55 -0700 Subject: [PATCH 032/206] chore(release): 13.1.0 --- CHANGELOG.md | 12 +++++++++++- package.json | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79de745b..0b87756b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,17 @@ -# Change Log +# Changelog All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [13.1.0](https://github.com/yargs/yargs-parser/compare/v13.0.0...v13.1.0) (2019-05-05) + + +### Features + +* add `strip-aliased` and `strip-dashed` configuration options. ([#172](https://github.com/yargs/yargs-parser/issues/172)) ([a3936aa](https://github.com/yargs/yargs-parser/commit/a3936aa)) +* support boolean which do not consume next argument. ([#171](https://github.com/yargs/yargs-parser/issues/171)) ([0ae7fcb](https://github.com/yargs/yargs-parser/commit/0ae7fcb)) + + + # [13.0.0](https://github.com/yargs/yargs-parser/compare/v12.0.0...v13.0.0) (2019-02-02) diff --git a/package.json b/package.json index 20766637..529e1fee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "13.0.0", + "version": "13.1.0", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From 981e1512d15a0af757a74a9cd75c448259aa8efa Mon Sep 17 00:00:00 2001 From: YuLe Date: Mon, 6 May 2019 12:19:30 +0800 Subject: [PATCH 033/206] refactor: remove duplicate check for `args[i]` in tokenize-arg-string.js (#175) --- lib/tokenize-arg-string.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/tokenize-arg-string.js b/lib/tokenize-arg-string.js index 569f61ad..aa19e076 100644 --- a/lib/tokenize-arg-string.js +++ b/lib/tokenize-arg-string.js @@ -25,7 +25,6 @@ module.exports = function (argString) { // don't split the string if we're in matching // opening or closing single and double quotes. if (c === opening) { - if (!args[i]) args[i] = '' opening = null } else if ((c === "'" || c === '"') && !opening) { opening = c From 7f33140c2898bb77ae55f33e402ed4085981de74 Mon Sep 17 00:00:00 2001 From: Juerg B <44573692+juergba@users.noreply.github.com> Date: Fri, 7 Jun 2019 19:19:17 +0200 Subject: [PATCH 034/206] docs: add description of "opts.configObjects" (#178) --- README.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5ddc3fa8..dde9f84d 100644 --- a/README.md +++ b/README.md @@ -58,21 +58,24 @@ Parses command line arguments returning a simple mapping of keys and values. * `args`: a string or array of strings representing the options to parse. * `opts`: provide a set of hints indicating how `args` should be parsed: * `opts.alias`: an object representing the set of aliases for a key: `{alias: {foo: ['f']}}`. - * `opts.array`: indicate that keys should be parsed as an array: `{array: ['foo', 'bar']}`. - Indicate that keys should be parsed as an array and coerced to booleans / numbers: - `{array: [{ key: 'foo', boolean: true }, {key: 'bar', number: true}]}`. + * `opts.array`: indicate that keys should be parsed as an array: `{array: ['foo', 'bar']}`.
+ Indicate that keys should be parsed as an array and coerced to booleans / numbers:
+ `{array: [{ key: 'foo', boolean: true }, {key: 'bar', number: true}]}`. * `opts.boolean`: arguments should be parsed as booleans: `{boolean: ['x', 'y']}`. - * `opts.config`: indicate a key that represents a path to a configuration file (this file will be loaded and parsed). * `opts.coerce`: provide a custom synchronous function that returns a coerced value from the argument provided - (or throws an error), e.g. `{coerce: {foo: function (arg) {return modifiedArg}}}`. + (or throws an error). For arrays the function is called only once for the entire array:
+ `{coerce: {foo: function (arg) {return modifiedArg}}}`. + * `opts.config`: indicate a key that represents a path to a configuration file (this file will be loaded and parsed). + * `opts.configObjects`: configuration objects to parse, their properties will be set as arguments:
+ `{configObjects: [{'x': 5, 'y': 33}, {'z': 44}]}`. + * `opts.configuration`: provide configuration options to the yargs-parser (see: [configuration](#configuration)). * `opts.count`: indicate a key that should be used as a counter, e.g., `-vvv` = `{v: 3}`. * `opts.default`: provide default values for keys: `{default: {x: 33, y: 'hello world!'}}`. * `opts.envPrefix`: environment variables (`process.env`) with the prefix provided should be parsed. * `opts.narg`: specify that a key requires `n` arguments: `{narg: {x: 2}}`. * `opts.normalize`: `path.normalize()` will be applied to values set to this key. - * `opts.string`: keys should be treated as strings (even if they resemble a number `-x 33`). - * `opts.configuration`: provide configuration options to the yargs-parser (see: [configuration](#configuration)). * `opts.number`: keys should be treated as numbers. + * `opts.string`: keys should be treated as strings (even if they resemble a number `-x 33`). **returns:** From 6055974a23b1a5fb8769eff433e7dd122cefba79 Mon Sep 17 00:00:00 2001 From: Chen Gang Date: Sat, 8 Jun 2019 01:26:00 +0800 Subject: [PATCH 035/206] fix: should populate "_" when given config with "short-option-groups" false (#179) --- index.js | 2 +- test/yargs-parser.js | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index ed54b8d6..3b772185 100644 --- a/index.js +++ b/index.js @@ -177,7 +177,7 @@ function parse (args, opts) { // -- seperated by space. } else if (arg.match(/^--.+/) || ( - !configuration['short-option-groups'] && arg.match(/^-.+/) + !configuration['short-option-groups'] && arg.match(/^-[^-]+/) )) { key = arg.match(/^--?(.+)/)[1] diff --git a/test/yargs-parser.js b/test/yargs-parser.js index fcff562a..6106e953 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -2442,6 +2442,19 @@ describe('yargs-parser', function () { result.should.not.have.property('--') }) + it('should populate "_" when given config with "short-option-groups" false', function () { + var result = parser.detailed([ + '--', 'foo' + ], { + configuration: { + 'short-option-groups': false + } + }) + result.argv.should.deep.equal({ '_': ['foo'] }) + result.argv.should.not.have.property('--') + result.newAliases.should.deep.equal({}) + }) + it('should populate the "--" if populate-- is "true"', function () { var result = parser([ '--name=meowmers', 'bare', '-cats', 'woo', 'moxy', From 57b788303e1119ba5260b273e486b73d0d638ad8 Mon Sep 17 00:00:00 2001 From: Juerg B <44573692+juergba@users.noreply.github.com> Date: Fri, 7 Jun 2019 19:27:13 +0200 Subject: [PATCH 036/206] fix: convert values to strings when tokenizing (#167) --- lib/tokenize-arg-string.js | 4 +++- test/tokenize-arg-string.js | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/tokenize-arg-string.js b/lib/tokenize-arg-string.js index aa19e076..fe05e27f 100644 --- a/lib/tokenize-arg-string.js +++ b/lib/tokenize-arg-string.js @@ -1,6 +1,8 @@ // take an un-split argv string and tokenize it. module.exports = function (argString) { - if (Array.isArray(argString)) return argString + if (Array.isArray(argString)) { + return argString.map(e => typeof e !== 'string' ? e + '' : e) + } argString = argString.trim() diff --git a/test/tokenize-arg-string.js b/test/tokenize-arg-string.js index e33c2466..6795e1d2 100644 --- a/test/tokenize-arg-string.js +++ b/test/tokenize-arg-string.js @@ -12,6 +12,12 @@ describe('TokenizeArgString', function () { args[1].should.equal('99') }) + it('handles unquoted numbers', function () { + var args = tokenizeArgString(['--foo', 9]) + args[0].should.equal('--foo') + args[1].should.equal('9') + }) + it('handles quoted string with no spaces', function () { var args = tokenizeArgString("--foo 'hello'") args[0].should.equal('--foo') From 47ccb0b7fcca1b989ef97bb084e4aa2aaf2a7666 Mon Sep 17 00:00:00 2001 From: Juerg B <44573692+juergba@users.noreply.github.com> Date: Fri, 7 Jun 2019 19:30:56 +0200 Subject: [PATCH 037/206] fix: nargs should allow duplicates when duplicate-arguments-array=false (#164) --- index.js | 8 ++++++++ test/yargs-parser.js | 11 ++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 3b772185..a6c95ba3 100644 --- a/index.js +++ b/index.js @@ -684,6 +684,14 @@ function parse (args, opts) { var isValueArray = Array.isArray(value) var duplicate = configuration['duplicate-arguments-array'] + // nargs has higher priority than duplicate + if (!duplicate && checkAllAliases(key, flags.nargs)) { + duplicate = true + if ((!isUndefined(o[key]) && flags.nargs[key] === 1) || (Array.isArray(o[key]) && o[key].length === flags.nargs[key])) { + o[key] = undefined + } + } + if (value === increment) { o[key] = increment(o[key]) } else if (Array.isArray(o[key])) { diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 6106e953..a8628261 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -2200,6 +2200,16 @@ describe('yargs-parser', function () { parsed['x'].should.equal('b') }) + it('does not interfere with nargs', function () { + var parsed = parser('-x a b c -x o p q', { + narg: { x: 3 }, + configuration: { + 'duplicate-arguments-array': false + } + }) + + parsed['x'].should.deep.equal(['o', 'p', 'q']) + }) }) describe('flatten duplicate arrays', function () { @@ -2243,7 +2253,6 @@ describe('yargs-parser', function () { parsed['x'].should.deep.equal(['a', 'b']) }) - it('flattens duplicate array type, when argument uses dot notation', function () { var parsed = parser('-x.foo a -x.foo b', { array: ['x.foo'], From 7e01a2c8d1ba75d9d5472e5839e2852bdf2af3db Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sun, 9 Jun 2019 20:12:37 -0500 Subject: [PATCH 038/206] chore: release 13.1.1 (#183) * updated CHANGELOG.md * updated package.json --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b87756b..df11c002 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [13.1.1](https://www.github.com/yargs/yargs-parser/compare/v13.1.0...v13.1.1) (2019-06-10) + + +### Bug Fixes + +* convert values to strings when tokenizing ([#167](https://www.github.com/yargs/yargs-parser/issues/167)) ([57b7883](https://www.github.com/yargs/yargs-parser/commit/57b7883)) +* nargs should allow duplicates when duplicate-arguments-array=false ([#164](https://www.github.com/yargs/yargs-parser/issues/164)) ([47ccb0b](https://www.github.com/yargs/yargs-parser/commit/47ccb0b)) +* should populate "_" when given config with "short-option-groups" false ([#179](https://www.github.com/yargs/yargs-parser/issues/179)) ([6055974](https://www.github.com/yargs/yargs-parser/commit/6055974)) + ## [13.1.0](https://github.com/yargs/yargs-parser/compare/v13.0.0...v13.1.0) (2019-05-05) diff --git a/package.json b/package.json index 529e1fee..35a3885d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "13.1.0", + "version": "13.1.1", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From 2f2643602f6310078c6ec141bd395435818f90ef Mon Sep 17 00:00:00 2001 From: Juerg B <44573692+juergba@users.noreply.github.com> Date: Sun, 23 Jun 2019 00:35:03 +0200 Subject: [PATCH 039/206] fix!: maybeCoerceNumber now takes precedence over coerce return value (#182) --- index.js | 4 ++-- test/yargs-parser.js | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index a6c95ba3..315d1aab 100644 --- a/index.js +++ b/index.js @@ -497,7 +497,7 @@ function parse (args, opts) { } function maybeCoerceNumber (key, value) { - if (!checkAllAliases(key, flags.strings) && !checkAllAliases(key, flags.coercions)) { + if (!checkAllAliases(key, flags.strings)) { const shouldCoerceNumber = isNumber(value) && configuration['parse-numbers'] && ( Number.isSafeInteger(Math.floor(value)) ) @@ -604,7 +604,7 @@ function parse (args, opts) { coerce = checkAllAliases(key, flags.coercions) if (typeof coerce === 'function') { try { - var value = coerce(argv[key]) + var value = maybeCoerceNumber(key, coerce(argv[key])) ;([].concat(flags.aliases[key] || [], key)).forEach(ali => { applied[ali] = argv[ali] = value }) diff --git a/test/yargs-parser.js b/test/yargs-parser.js index a8628261..c908187b 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -2147,14 +2147,18 @@ describe('yargs-parser', function () { }) it('parses number if option explicitly set to number type', function () { - var parsed = parser(['--foo', '5', '--bar', '6'], { - number: 'bar', + var parsed = parser(['--foo', '5', '--bar', '6', '--baz', '7'], { + number: ['bar', 'baz'], + coerce: { + 'baz': val => val + }, configuration: { 'parse-numbers': false } }) expect(parsed['foo']).to.equal('5') expect(parsed['bar']).to.equal(6) + expect(parsed['baz']).to.equal(7) }) }) From 17ca3bdaada25605ab06fe72946d2da4b52087fa Mon Sep 17 00:00:00 2001 From: Juerg B <44573692+juergba@users.noreply.github.com> Date: Mon, 24 Jun 2019 23:43:25 +0200 Subject: [PATCH 040/206] fix!: boolean now behaves the same as other array types (#184) BREAKING CHANGE: we have dropped the broken "defaulted" functionality; we would like to revisit adding this in the future. --- index.js | 29 +++--------------------- test/yargs-parser.js | 54 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 54 insertions(+), 29 deletions(-) diff --git a/index.js b/index.js index 315d1aab..4fd05531 100644 --- a/index.js +++ b/index.js @@ -46,7 +46,6 @@ function parse (args, opts) { counts: {}, normalize: {}, configs: {}, - defaulted: {}, nargs: {}, coercions: {}, keys: [] @@ -132,14 +131,6 @@ function parse (args, opts) { }) var argv = { _: [] } - - Object.keys(flags.bools).forEach(function (key) { - if (Object.prototype.hasOwnProperty.call(defaults, key)) { - setArg(key, defaults[key]) - setDefaulted(key) - } - }) - var notFlags = [] for (var i = 0; i < args.length; i++) { @@ -406,8 +397,6 @@ function parse (args, opts) { } function setArg (key, val) { - unsetDefaulted(key) - if (/-/.test(key) && configuration['camel-case-expansion']) { var alias = key.split('.').map(function (prop) { return camelCase(prop) @@ -560,7 +549,7 @@ function parse (args, opts) { } else { // setting arguments via CLI takes precedence over // values within the config file. - if (!hasKey(argv, fullKey.split('.')) || (flags.defaulted[fullKey]) || (flags.arrays[fullKey] && configuration['combine-arrays'])) { + if (!hasKey(argv, fullKey.split('.')) || (flags.arrays[fullKey] && configuration['combine-arrays'])) { setArg(fullKey, value) } } @@ -589,7 +578,7 @@ function parse (args, opts) { return camelCase(key) }) - if (((configOnly && flags.configs[keys.join('.')]) || !configOnly) && (!hasKey(argv, keys) || flags.defaulted[keys.join('.')])) { + if (((configOnly && flags.configs[keys.join('.')]) || !configOnly) && !hasKey(argv, keys)) { setArg(keys.join('.'), process.env[envVar]) } } @@ -704,7 +693,7 @@ function parse (args, opts) { } } else if (o[key] === undefined && isTypeArray) { o[key] = isValueArray ? value : [value] - } else if (duplicate && !(o[key] === undefined || checkAllAliases(key, flags.bools) || checkAllAliases(keys.join('.'), flags.bools) || checkAllAliases(key, flags.counts))) { + } else if (duplicate && !(o[key] === undefined || checkAllAliases(key, flags.counts))) { o[key] = [ o[key], value ] } else { o[key] = value @@ -762,18 +751,6 @@ function parse (args, opts) { return isSet } - function setDefaulted (key) { - [].concat(flags.aliases[key] || [], key).forEach(function (k) { - flags.defaulted[k] = true - }) - } - - function unsetDefaulted (key) { - [].concat(flags.aliases[key] || [], key).forEach(function (k) { - delete flags.defaulted[k] - }) - } - // make a best effor to pick a default value // for an option based on name and type. function defaultValue (key) { diff --git a/test/yargs-parser.js b/test/yargs-parser.js index c908187b..abcfaafd 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -1108,7 +1108,7 @@ describe('yargs-parser', function () { }) }) - describe('with implied false default', function () { + describe('without any default value', function () { var opts = null beforeEach(function () { @@ -1125,8 +1125,8 @@ describe('yargs-parser', function () { parser(['--no-flag'], opts).flag.should.be.false // eslint-disable-line }) - it('should set false if no flag in arg', function () { - expect(parser([], opts).flag).to.be.undefined // eslint-disable-line + it('should not add property if no flag in arg', function () { + parser([''], opts).should.not.have.property('flag') }) }) @@ -2334,6 +2334,18 @@ describe('yargs-parser', function () { parsed['x'].should.deep.equal(3) }) }) + describe('type=boolean', function () { + it('[-x true -x true -x false] => false', function () { + var parsed = parser('-x true -x true -x false', { + boolean: ['x'], + configuration: { + 'duplicate-arguments-array': false, + 'flatten-duplicate-arrays': false + } + }) + parsed['x'].should.deep.equal(false) + }) + }) }) describe('duplicate=false, flatten=true,', function () { describe('type=array', function () { @@ -2370,6 +2382,18 @@ describe('yargs-parser', function () { parsed['x'].should.deep.equal(3) }) }) + describe('type=boolean', function () { + it('[-x true -x true -x false] => false', function () { + var parsed = parser('-x true -x true -x false', { + boolean: ['x'], + configuration: { + 'duplicate-arguments-array': false, + 'flatten-duplicate-arrays': true + } + }) + parsed['x'].should.deep.equal(false) + }) + }) }) describe('duplicate=true, flatten=true,', function () { describe('type=array', function () { @@ -2406,6 +2430,18 @@ describe('yargs-parser', function () { parsed['x'].should.deep.equal([1, 2, 3]) }) }) + describe('type=boolean', function () { + it('[-x true -x true -x false] => [true, true, false]', function () { + var parsed = parser('-x true -x true -x false', { + boolean: ['x'], + configuration: { + 'duplicate-arguments-array': true, + 'flatten-duplicate-arrays': true + } + }) + parsed['x'].should.deep.equal([true, true, false]) + }) + }) }) describe('duplicate=true, flatten=false,', function () { describe('type=array', function () { @@ -2442,6 +2478,18 @@ describe('yargs-parser', function () { parsed['x'].should.deep.equal([1, 2, 3]) }) }) + describe('type=boolean', function () { + it('[-x true -x true -x false] => [true, true, false]', function () { + var parsed = parser('-x true -x true -x false', { + boolean: ['x'], + configuration: { + 'duplicate-arguments-array': true, + 'flatten-duplicate-arrays': false + } + }) + parsed['x'].should.deep.equal([true, true, false]) + }) + }) }) }) From 31c204b35eb35e9a3c382f8068e7cb80674a2f22 Mon Sep 17 00:00:00 2001 From: Juerg B <44573692+juergba@users.noreply.github.com> Date: Sun, 14 Jul 2019 01:54:37 +0200 Subject: [PATCH 041/206] feat!: maybeCoerceNumber() now takes into account arrays (#187) BREAKING CHANGE: unless "parse-numbers" is set to "false", arrays of numeric strings are now parsed as numbers, rather than strings. --- index.js | 6 ++++-- test/yargs-parser.js | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 4fd05531..11eac1e5 100644 --- a/index.js +++ b/index.js @@ -470,7 +470,9 @@ function parse (args, opts) { if (typeof val === 'string') val = val === 'true' } - var value = maybeCoerceNumber(key, val) + var value = Array.isArray(val) + ? val.map(function (v) { return maybeCoerceNumber(key, v) }) + : maybeCoerceNumber(key, val) // increment a count given as arg (either no value or value parsed as boolean) if (checkAllAliases(key, flags.counts) && (isUndefined(value) || typeof value === 'boolean')) { @@ -486,7 +488,7 @@ function parse (args, opts) { } function maybeCoerceNumber (key, value) { - if (!checkAllAliases(key, flags.strings)) { + if (!checkAllAliases(key, flags.strings) && !checkAllAliases(key, flags.bools) && !Array.isArray(value)) { const shouldCoerceNumber = isNumber(value) && configuration['parse-numbers'] && ( Number.isSafeInteger(Math.floor(value)) ) diff --git a/test/yargs-parser.js b/test/yargs-parser.js index abcfaafd..773cfe32 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -2160,6 +2160,19 @@ describe('yargs-parser', function () { expect(parsed['bar']).to.equal(6) expect(parsed['baz']).to.equal(7) }) + + it('should coerce elements of number typed arrays to numbers', function () { + var parsed = parser(['--foo', '4', '--foo', '5', '2'], { + array: ['foo'], + configObjects: [{ foo: ['1', '2', '3'] }], + configuration: { + 'combine-arrays': true, + 'flatten-duplicate-arrays': false + } + }) + + expect(parsed['foo']).to.deep.equal([[4], [5, 2], [1, 2, 3]]) + }) }) describe('boolean negation', function () { @@ -2445,7 +2458,7 @@ describe('yargs-parser', function () { }) describe('duplicate=true, flatten=false,', function () { describe('type=array', function () { - it('[-x 1 -x 2 -x 3] => [1, 2, 3]', function () { + it('[-x 1 -x 2 -x 3] => [[1], [2], [3]]', function () { var parsed = parser('-x 1 -x 2 -x 3', { array: ['x'], configuration: { @@ -2453,7 +2466,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': false } }) - parsed['x'].should.deep.equal([1, 2, 3]) + parsed['x'].should.deep.equal([[1], [2], [3]]) }) it('[-x 1 2 3 -x 2 3 4] => [[1, 2, 3], [ 2, 3, 4]]', function () { var parsed = parser('-x 1 2 3 -x 2 3 4', { From c5a1db06eabc4dd249adf2a471156bd133b693bf Mon Sep 17 00:00:00 2001 From: Juerg B <44573692+juergba@users.noreply.github.com> Date: Tue, 23 Jul 2019 20:42:54 +0200 Subject: [PATCH 042/206] fix: eatNargs() for 'opt.narg === 0' and boolean typed options (#188) --- index.js | 15 +++++++++++---- test/yargs-parser.js | 20 +++++++++++--------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index 11eac1e5..1b821031 100644 --- a/index.js +++ b/index.js @@ -173,13 +173,14 @@ function parse (args, opts) { key = arg.match(/^--?(.+)/)[1] // nargs format = '--foo a b c' - if (checkAllAliases(key, flags.nargs)) { + // should be truthy even if: flags.nargs[key] === 0 + if (checkAllAliases(key, flags.nargs) !== false) { i = eatNargs(i, key, args) // array format = '--foo a b c' } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) { i = eatArray(i, key, args) } else { - next = flags.nargs[key] === 0 ? undefined : args[i + 1] + next = args[i + 1] if (next !== undefined && (!next.match(/^-/) || next.match(negative)) && @@ -266,7 +267,8 @@ function parse (args, opts) { if (!broken && key !== '-') { // nargs format = '-f a b c' - if (checkAllAliases(key, flags.nargs)) { + // should be truthy even if: flags.nargs[key] === 0 + if (checkAllAliases(key, flags.nargs) !== false) { i = eatNargs(i, key, args) // array format = '-f a b c' } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) { @@ -347,6 +349,11 @@ function parse (args, opts) { var ii const toEat = checkAllAliases(key, flags.nargs) + if (toEat === 0) { + setArg(key, defaultValue(key)) + return i + } + // nargs will not consume flag arguments, e.g., -abc, --foo, // and terminates when one is observed. var available = 0 @@ -747,7 +754,7 @@ function parse (args, opts) { var toCheck = [].concat(flags.aliases[key] || [], key) toCheck.forEach(function (key) { - if (flag[key]) isSet = flag[key] + if (flag.hasOwnProperty(key)) isSet = flag[key] }) return isSet diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 773cfe32..d5532ace 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -194,15 +194,17 @@ describe('yargs-parser', function () { parse.should.have.property('_').and.deep.equal(['aaatrueaaa', 'moo', 'aaafalseaaa']) }) - it('should not use next value for boolean configured with zero narg', function () { - var parse = parser(['--all', 'false'], { - boolean: ['all'], - narg: { - all: 0 - } - }) - parse.should.have.property('all', true).and.be.a('boolean') - parse.should.have.property('_').and.deep.equal(['false']) + it('should not use next value for boolean/number/string configured with zero narg', function () { + var parse = parser(['--bool', 'false', '--nr', '7', '--str', 'foo'], { + boolean: ['bool'], + number: ['nr'], + string: ['str'], + narg: { bool: 0, nr: 0, str: 0 } + }) + parse.should.have.property('bool', true).and.be.a('boolean') + parse.should.have.property('nr', undefined).and.be.a('undefined') + parse.should.have.property('str', '').and.be.a('string') + parse.should.have.property('_').and.deep.equal(['false', 7, 'foo']) }) it('should allow defining options as boolean in groups', function () { From 7d42572acf7e81f1d78f357b3229d415abe3d6c5 Mon Sep 17 00:00:00 2001 From: Juerg B <44573692+juergba@users.noreply.github.com> Date: Tue, 30 Jul 2019 22:01:30 +0200 Subject: [PATCH 043/206] fix: boolean arrays with default values (#185) --- index.js | 48 +++++++++++++++++++++----------------------- test/yargs-parser.js | 24 ++++++++++++++++++++++ 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/index.js b/index.js index 1b821031..79d433c4 100644 --- a/index.js +++ b/index.js @@ -156,7 +156,7 @@ function parse (args, opts) { args.splice(i + 1, 0, m[2]) i = eatNargs(i, m[1], args) // arrays format = '--f=a b c' - } else if (checkAllAliases(m[1], flags.arrays) && args.length > i + 1) { + } else if (checkAllAliases(m[1], flags.arrays)) { args.splice(i + 1, 0, m[2]) i = eatArray(i, m[1], args) } else { @@ -164,7 +164,7 @@ function parse (args, opts) { } } else if (arg.match(negatedBoolean) && configuration['boolean-negation']) { key = arg.match(negatedBoolean)[1] - setArg(key, false) + setArg(key, checkAllAliases(key, flags.arrays) ? [false] : false) // -- seperated by space. } else if (arg.match(/^--.+/) || ( @@ -177,7 +177,7 @@ function parse (args, opts) { if (checkAllAliases(key, flags.nargs) !== false) { i = eatNargs(i, key, args) // array format = '--foo a b c' - } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) { + } else if (checkAllAliases(key, flags.arrays)) { i = eatArray(i, key, args) } else { next = args[i + 1] @@ -230,7 +230,7 @@ function parse (args, opts) { args.splice(i + 1, 0, value) i = eatNargs(i, key, args) // array format = '-f=a b c' - } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) { + } else if (checkAllAliases(key, flags.arrays)) { args.splice(i + 1, 0, value) i = eatArray(i, key, args) } else { @@ -271,7 +271,7 @@ function parse (args, opts) { if (checkAllAliases(key, flags.nargs) !== false) { i = eatNargs(i, key, args) // array format = '-f a b c' - } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) { + } else if (checkAllAliases(key, flags.arrays)) { i = eatArray(i, key, args) } else { next = args[i + 1] @@ -376,30 +376,27 @@ function parse (args, opts) { // following it... YUM! // e.g., --foo apple banana cat becomes ["apple", "banana", "cat"] function eatArray (i, key, args) { - var start = i + 1 - var argsToSet = [] - var multipleArrayFlag = i > 0 - for (var ii = i + 1; ii < args.length; ii++) { - if (/^-/.test(args[ii]) && !negative.test(args[ii])) { - if (ii === start) { - setArg(key, defaultForType('array')) - } - multipleArrayFlag = true - break + let argsToSet = [] + let next = args[i + 1] + + if (checkAllAliases(key, flags.bools) && !(/^(true|false)$/.test(next))) { + argsToSet.push(true) + } else if (isUndefined(next) || (/^-/.test(next) && !negative.test(next))) { + // for keys without value ==> argsToSet remains an empty [] + // set user default value, if available + if (defaults.hasOwnProperty(key)) { + argsToSet.push(defaults[key]) } - i = ii - argsToSet.push(args[ii]) - } - if (multipleArrayFlag) { - setArg(key, argsToSet.map(function (arg) { - return processValue(key, arg) - })) } else { - argsToSet.forEach(function (arg) { - setArg(key, arg) - }) + for (var ii = i + 1; ii < args.length; ii++) { + next = args[ii] + if (/^-/.test(next) && !negative.test(next)) break + i = ii + argsToSet.push(processValue(key, next)) + } } + setArg(key, argsToSet) return i } @@ -791,6 +788,7 @@ function parse (args, opts) { if (checkAllAliases(key, flags.strings)) type = 'string' else if (checkAllAliases(key, flags.numbers)) type = 'number' + else if (checkAllAliases(key, flags.bools)) type = 'boolean' else if (checkAllAliases(key, flags.arrays)) type = 'array' return type diff --git a/test/yargs-parser.js b/test/yargs-parser.js index d5532ace..65d90e40 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -1492,6 +1492,14 @@ describe('yargs-parser', function () { result.should.have.property('b').and.deep.equal([]) }) + it('should place default of argument in array, when default provided', function () { + var result = parser(['-b'], { + array: 'b', + default: { 'b': 33 } + }) + result.should.have.property('b').and.deep.equal([33]) + }) + it('should place value of argument in array, when one argument provided', function () { var result = parser(['-b', '33'], { array: ['b'] @@ -1618,6 +1626,22 @@ describe('yargs-parser', function () { result.should.have.property('x').that.is.an('array').and.to.deep.equal([true, false]) }) + it('should respect type `boolean` without value for arrays', function () { + var result = parser(['-x', '-x'], { + array: [{ key: 'x', boolean: true }], + configuration: { 'flatten-duplicate-arrays': false } + }) + result.x.should.deep.equal([[true], [true]]) + }) + + it('should respect `boolean negation` for arrays', function () { + var result = parser(['--no-bool', '--no-bool'], { + array: [{ key: 'bool', boolean: true }], + configuration: { 'flatten-duplicate-arrays': false } + }) + result.bool.should.deep.equal([[false], [false]]) + }) + it('should respect the type `number` option for arrays', function () { var result = parser(['-x=5', '2'], { array: [{ key: 'x', number: true }] From f8a2d3f26a84cc57e210deb1aa27b580e4b06388 Mon Sep 17 00:00:00 2001 From: Juerg B <44573692+juergba@users.noreply.github.com> Date: Tue, 20 Aug 2019 03:07:29 +0200 Subject: [PATCH 044/206] fix: take into account aliases when appending arrays from config object (#199) --- index.js | 2 +- test/yargs-parser.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 79d433c4..7f00f363 100644 --- a/index.js +++ b/index.js @@ -555,7 +555,7 @@ function parse (args, opts) { } else { // setting arguments via CLI takes precedence over // values within the config file. - if (!hasKey(argv, fullKey.split('.')) || (flags.arrays[fullKey] && configuration['combine-arrays'])) { + if (!hasKey(argv, fullKey.split('.')) || (checkAllAliases(fullKey, flags.arrays) && configuration['combine-arrays'])) { setArg(fullKey, value) } } diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 65d90e40..e51bb3dc 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -774,6 +774,21 @@ describe('yargs-parser', function () { argv.should.have.property('foo', 'bar') argv.should.have.property('bar', 'baz') }) + + it('should combine array typed options with alias and camel-case', function () { + var argv = parser(['--camEl', 'foo', '--camEl', 'bar', '-a', 'red'], { + array: ['cam-el', 'apple'], + alias: { apple: 'a' }, + configObjects: [{ camEl: 'baz' }, { a: 'sweet' }], + configuration: { + 'combine-arrays': true, + 'camel-case-expansion': true + } + }) + + argv['cam-el'].should.deep.equal(['foo', 'bar', 'baz']) + argv.apple.should.deep.equal(['red', 'sweet']) + }) }) describe('dot notation', function () { From 89d5a158900d923e8de21c9b6994d2b5736dfbe7 Mon Sep 17 00:00:00 2001 From: 0xflotus <0xflotus@gmail.com> Date: Tue, 20 Aug 2019 03:09:52 +0200 Subject: [PATCH 045/206] docs: fix typoe (#195) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dde9f84d..95d70b9e 100644 --- a/README.md +++ b/README.md @@ -370,7 +370,7 @@ node example.js --test-field 1 * key: `strip-dashed` Should dashed keys be removed before returning results? This option has no effect if -`camel-case-exansion` is disabled. +`camel-case-expansion` is disabled. _If disabled:_ From d3d9027f9c9235be8e7a650d0e33d4d55287e0e2 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Tue, 20 Aug 2019 04:10:24 +0300 Subject: [PATCH 046/206] docs: fix typos (#194) --- index.js | 6 +++--- test/tokenize-arg-string.js | 2 +- test/yargs-parser.js | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 7f00f363..cf7872c5 100644 --- a/index.js +++ b/index.js @@ -166,7 +166,7 @@ function parse (args, opts) { key = arg.match(negatedBoolean)[1] setArg(key, checkAllAliases(key, flags.arrays) ? [false] : false) - // -- seperated by space. + // -- separated by space. } else if (arg.match(/^--.+/) || ( !configuration['short-option-groups'] && arg.match(/^-[^-]+/) )) { @@ -196,12 +196,12 @@ function parse (args, opts) { } } - // dot-notation flag seperated by '='. + // dot-notation flag separated by '='. } else if (arg.match(/^-.\..+=/)) { m = arg.match(/^-([^=]+)=([\s\S]*)$/) setArg(m[1], m[2]) - // dot-notation flag seperated by space. + // dot-notation flag separated by space. } else if (arg.match(/^-.\..+/)) { next = args[i + 1] key = arg.match(/^-(.\..+)/)[1] diff --git a/test/tokenize-arg-string.js b/test/tokenize-arg-string.js index 6795e1d2..8575de65 100644 --- a/test/tokenize-arg-string.js +++ b/test/tokenize-arg-string.js @@ -52,7 +52,7 @@ describe('TokenizeArgString', function () { args[2].should.equal('--bar=""') }) - it('handles quoted string with embeded quotes', function () { + it('handles quoted string with embedded quotes', function () { var args = tokenizeArgString('--foo "hello \'world\'" --bar=\'foo "bar"\'') args[0].should.equal('--foo') args[1].should.equal('"hello \'world\'"') diff --git a/test/yargs-parser.js b/test/yargs-parser.js index e51bb3dc..cb1342cf 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -83,7 +83,7 @@ describe('yargs-parser', function () { parse.should.have.property('_').with.length(0) }) - it('should explicitly set a boolean option to false if preceeded by "--no-"', function () { + it('should explicitly set a boolean option to false if preceded by "--no-"', function () { var parse = parser(['--no-moo']) parse.should.have.property('moo', false) parse.should.have.property('_').with.length(0) @@ -894,12 +894,12 @@ describe('yargs-parser', function () { argv.foo.bar.cool.should.eql(11) }) - it("should allow flags to use dot notation, when seperated by '='", function () { + it("should allow flags to use dot notation, when separated by '='", function () { var argv = parser(['-f.foo=99']) argv.f.foo.should.eql(99) }) - it("should allow flags to use dot notation, when seperated by ' '", function () { + it("should allow flags to use dot notation, when separated by ' '", function () { var argv = parser(['-f.foo', '99']) argv.f.foo.should.eql(99) }) From 7909cc4679c76770d4d7950ecd527da823b08f91 Mon Sep 17 00:00:00 2001 From: Eric Henderson Date: Fri, 6 Sep 2019 15:26:33 -0400 Subject: [PATCH 047/206] feat: add configuration option to "collect-unknown-options" (#181) --- README.md | 22 ++++ index.js | 75 +++++++++++- test/yargs-parser.js | 272 ++++++++++++++++++++++++++++++++++--------- 3 files changed, 314 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 95d70b9e..f754df09 100644 --- a/README.md +++ b/README.md @@ -386,6 +386,28 @@ node example.js --test-field 1 { _: [], testField: 1 } ``` +### collect unknown options + +* default: `false` +* key: `collect-unknown-options` + +Should unknown options be collected into `_`? An unknown option is one that is not +configured in `opts`. + +_If disabled_ + +```sh +node example.js --unknown-option --known-option 2 +{ _: [], unknownOption: true, knownOption: 2 } +``` + +_If enabled_ + +```sh +node example.js --unknown-option --known-option 2 +{ _: ['--unknown-option'], knownOption: 2 } +``` + ## Special Thanks The yargs project evolves from optimist and minimist. It owes its diff --git a/index.js b/index.js index cf7872c5..08f9852c 100644 --- a/index.js +++ b/index.js @@ -26,7 +26,8 @@ function parse (args, opts) { 'set-placeholder-key': false, 'halt-at-non-option': false, 'strip-aliased': false, - 'strip-dashed': false + 'strip-dashed': false, + 'collect-unknown-options': false }, opts.configuration) var defaults = opts.default || {} var configObjects = opts.configObjects || [] @@ -142,8 +143,10 @@ function parse (args, opts) { var next var value + if (configuration['collect-unknown-options'] && isUnknownOption(arg)) { + argv._.push(arg) // -- separated by = - if (arg.match(/^--.+=/) || ( + } else if (arg.match(/^--.+=/) || ( !configuration['short-option-groups'] && arg.match(/^-.+=/) )) { // Using [\s\S] instead of . because js doesn't support the @@ -757,6 +760,74 @@ function parse (args, opts) { return isSet } + function hasAnyFlag (key) { + var isSet = false + // XXX Switch to [].concat(...Object.values(flags)) once node.js 6 is dropped + var toCheck = [].concat(...Object.keys(flags).map(k => flags[k])) + + toCheck.forEach(function (flag) { + if (flag[key]) isSet = flag[key] + }) + + return isSet + } + + function hasFlagsMatching (arg, ...patterns) { + var hasFlag = false + var toCheck = [].concat(...patterns) + toCheck.forEach(function (pattern) { + var match = arg.match(pattern) + if (match && hasAnyFlag(match[1])) { + hasFlag = true + } + }) + return hasFlag + } + + // based on a simplified version of the short flag group parsing logic + function hasAllShortFlags (arg) { + // if this is a negative number, or doesn't start with a single hyphen, it's not a short flag group + if (arg.match(negative) || !arg.match(/^-[^-]+/)) { return false } + var hasAllFlags = true + var letters = arg.slice(1).split('') + var next + for (var j = 0; j < letters.length; j++) { + next = arg.slice(j + 2) + + if (!hasAnyFlag(letters[j])) { + hasAllFlags = false + break + } + + if ((letters[j + 1] && letters[j + 1] === '=') || + next === '-' || + (/[A-Za-z]/.test(letters[j]) && /^-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) || + (letters[j + 1] && letters[j + 1].match(/\W/))) { + break + } + } + return hasAllFlags + } + + function isUnknownOption (arg) { + // ignore negative numbers + if (arg.match(negative)) { return false } + // if this is a short option group and all of them are configured, it isn't unknown + if (hasAllShortFlags(arg)) { return false } + // e.g. '--count=2' + const flagWithEquals = /^-+([^=]+?)=[\s\S]*$/ + // e.g. '-a' or '--arg' + const normalFlag = /^-+([^=]+?)$/ + // e.g. '-a-' + const flagEndingInHyphen = /^-+([^=]+?)-$/ + // e.g. '-abc123' + const flagEndingInDigits = /^-+([^=]+?)\d+$/ + // e.g. '-a/usr/local' + const flagEndingInNonWordCharacters = /^-+([^=]+?)\W+.*$/ + // check the different types of flag styles, including negatedBoolean, a pattern defined near the start of the parse method + return !hasFlagsMatching(arg, flagWithEquals, negatedBoolean, normalFlag, flagEndingInHyphen, flagEndingInDigits, flagEndingInNonWordCharacters) + } + // make a best effor to pick a default value // for an option based on name and type. function defaultValue (key) { diff --git a/test/yargs-parser.js b/test/yargs-parser.js index cb1342cf..27f80740 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -9,7 +9,7 @@ var path = require('path') describe('yargs-parser', function () { it('should parse a "short boolean"', function () { - var parse = parser([ '-b' ]) + var parse = parser(['-b']) parse.should.not.have.property('--') parse.should.have.property('b').to.be.ok.and.be.a('boolean') parse.should.have.property('_').with.length(0) @@ -166,7 +166,7 @@ describe('yargs-parser', function () { }) it('should not set the next value as the value of a short option if that option is explicitly defined as a boolean', function () { - var parse = parser([ '-t', 'moo' ], { + var parse = parser(['-t', 'moo'], { boolean: 't' }) parse.should.have.property('t', true).and.be.a('boolean') @@ -208,7 +208,7 @@ describe('yargs-parser', function () { }) it('should allow defining options as boolean in groups', function () { - var parse = parser([ '-x', '-z', 'one', 'two', 'three' ], { + var parse = parser(['-x', '-z', 'one', 'two', 'three'], { boolean: ['x', 'y', 'z'] }) parse.should.have.property('x', true).and.be.a('boolean') @@ -243,25 +243,25 @@ describe('yargs-parser', function () { }) it('should not convert numbers to type number if explicitly defined as strings', function () { - var s = parser([ '-s', '0001234' ], { + var s = parser(['-s', '0001234'], { string: 's' }).s s.should.be.a('string').and.equal('0001234') - var x = parser([ '-x', '56' ], { + var x = parser(['-x', '56'], { string: ['x'] }).x x.should.be.a('string').and.equal('56') }) it('should default numbers to undefined', function () { - var n = parser([ '-n' ], { + var n = parser(['-n'], { number: ['n'] }).n expect(n).to.equal(undefined) }) it('should default number to NaN if value is not a valid number', function () { - var n = parser([ '-n', 'string' ], { + var n = parser(['-n', 'string'], { number: ['n'] }).n expect(n).to.deep.equal(NaN) @@ -269,24 +269,24 @@ describe('yargs-parser', function () { // Fixes: https://github.com/bcoe/yargs/issues/68 it('should parse flag arguments with no right-hand value as strings, if defined as strings', function () { - var s = parser([ '-s' ], { + var s = parser(['-s'], { string: ['s'] }).s s.should.be.a('string').and.equal('') - s = parser([ '-sf' ], { + s = parser(['-sf'], { string: ['s'] }).s s.should.be.a('string').and.equal('') - s = parser([ '--string' ], { + s = parser(['--string'], { string: ['string'] }).string s.should.be.a('string').and.equal('') }) it('should leave all non-hyphenated values as strings if _ is defined as a string', function () { - var s = parser([ ' ', ' ' ], { + var s = parser([' ', ' '], { string: ['_'] })._ s.should.have.length(2) @@ -296,7 +296,7 @@ describe('yargs-parser', function () { describe('normalize', function () { it('should normalize redundant paths', function () { - var a = parser([ '-s', ['', 'tmp', '..', ''].join(path.sep) ], { + var a = parser(['-s', ['', 'tmp', '..', ''].join(path.sep)], { alias: { s: ['save'] }, @@ -316,7 +316,7 @@ describe('yargs-parser', function () { }) it('should normalize when key is also an array', function () { - var a = parser([ '-s', ['', 'tmp', '..', ''].join(path.sep), ['', 'path', 'to', 'new', 'dir', '..', '..', ''].join(path.sep) ], { + var a = parser(['-s', ['', 'tmp', '..', ''].join(path.sep), ['', 'path', 'to', 'new', 'dir', '..', '..', ''].join(path.sep)], { alias: { s: ['save'] }, @@ -331,7 +331,7 @@ describe('yargs-parser', function () { describe('alias', function () { it('should set alias value to the same value as the full option', function () { - var argv = parser([ '-f', '11', '--zoom', '55' ], { + var argv = parser(['-f', '11', '--zoom', '55'], { alias: { z: ['zoom'] } @@ -342,7 +342,7 @@ describe('yargs-parser', function () { }) it('should allow multiple aliases to be specified', function () { - var argv = parser([ '-f', '11', '--zoom', '55' ], { + var argv = parser(['-f', '11', '--zoom', '55'], { alias: { z: ['zm', 'zoom'] } @@ -385,7 +385,7 @@ describe('yargs-parser', function () { }) it('should allow transitive aliases to be specified', function () { - var argv = parser([ '-f', '11', '--zoom', '55' ], { + var argv = parser(['-f', '11', '--zoom', '55'], { alias: { z: 'zm', zm: 'zoom' @@ -431,7 +431,7 @@ describe('yargs-parser', function () { // See: https://github.com/chevex/yargs/issues/12 it('should load options and values from default config if specified', function () { - var argv = parser([ '--foo', 'bar' ], { + var argv = parser(['--foo', 'bar'], { alias: { z: 'zoom' }, @@ -520,7 +520,7 @@ describe('yargs-parser', function () { }) it("should allow config to be set as flag in 'option'", function () { - var argv = parser([ '--settings', jsonPath, '--foo', 'bar' ], { + var argv = parser(['--settings', jsonPath, '--foo', 'bar'], { alias: { z: 'zoom' }, @@ -534,7 +534,7 @@ describe('yargs-parser', function () { it('should load options and values from a JS file when config has .js extention', function () { var jsPath = path.resolve(__dirname, './fixtures/settings.js') - var argv = parser([ '--settings', jsPath, '--foo', 'bar' ], { + var argv = parser(['--settings', jsPath, '--foo', 'bar'], { config: ['settings'] }) @@ -599,7 +599,7 @@ describe('yargs-parser', function () { it('allows a custom parsing function to be provided', function () { var jsPath = path.resolve(__dirname, './fixtures/config.txt') - var argv = parser([ '--settings', jsPath, '--foo', 'bar' ], { + var argv = parser(['--settings', jsPath, '--foo', 'bar'], { config: { settings: function (configPath) { // as an example, parse an environment @@ -624,7 +624,7 @@ describe('yargs-parser', function () { it('allows a custom parsing function to be provided as an alias', function () { var jsPath = path.resolve(__dirname, './fixtures/config.json') - var argv = parser([ '--settings', jsPath, '--foo', 'bar' ], { + var argv = parser(['--settings', jsPath, '--foo', 'bar'], { config: { s: function (configPath) { return JSON.parse(fs.readFileSync(configPath, 'utf-8')) @@ -666,7 +666,7 @@ describe('yargs-parser', function () { describe('config objects', function () { it('should load options from config object', function () { - var argv = parser([ '--foo', 'bar' ], { + var argv = parser(['--foo', 'bar'], { configObjects: [{ apple: 'apple', banana: 42, @@ -855,7 +855,7 @@ describe('yargs-parser', function () { } }) - ;('foo.bar' in argv).should.equal(false) + ; ('foo.bar' in argv).should.equal(false) }) it('should respect .string() for dot notation arguments', function () { @@ -912,7 +912,7 @@ describe('yargs-parser', function () { }) it('should set boolean and alias using explicit true', function () { - var aliased = [ '-h', 'true' ] + var aliased = ['-h', 'true'] var aliasedArgv = parser(aliased, { boolean: ['h'], alias: { @@ -978,12 +978,12 @@ describe('yargs-parser', function () { }) it('should set n to the numeric value 123', function () { - var argv = parser([ '-n123' ]) + var argv = parser(['-n123']) argv.should.have.property('n', 123) }) it('should set n to the numeric value 123, with n at the end of a group', function () { - var argv = parser([ '-ab5n123' ]) + var argv = parser(['-ab5n123']) argv.should.have.property('a', true) argv.should.have.property('b', true) argv.should.have.property('5', true) @@ -992,12 +992,12 @@ describe('yargs-parser', function () { }) it('should set n to the numeric value 123, with = as separator', function () { - var argv = parser([ '-n=123' ]) + var argv = parser(['-n=123']) argv.should.have.property('n', 123) }) it('should set n to the numeric value 123, with n at the end of a group and = as separator', function () { - var argv = parser([ '-ab5n=123' ]) + var argv = parser(['-ab5n=123']) argv.should.have.property('a', true) argv.should.have.property('b', true) argv.should.have.property('5', true) @@ -1008,7 +1008,7 @@ describe('yargs-parser', function () { describe('whitespace', function () { it('should be whitespace', function () { - var argv = parser([ '-x', '\t' ]) + var argv = parser(['-x', '\t']) argv.should.have.property('x', '\t') }) }) @@ -1049,7 +1049,7 @@ describe('yargs-parser', function () { function checkStringArg (opts, hasAlias) { it('should set defaults even if arg looks like a string', function () { - var result = parser([ '--flag', 'extra' ], opts) + var result = parser(['--flag', 'extra'], opts) result.should.have.property('flag', true) result.should.have.property('_').and.deep.equal(['extra']) if (hasAlias) { @@ -1215,7 +1215,7 @@ describe('yargs-parser', function () { } it('should provide count options with dashes as camelCase properties', function () { - var result = parser([ '--some-option', '--some-option', '--some-option' ], { + var result = parser(['--some-option', '--some-option', '--some-option'], { count: ['some-option'] }) @@ -1352,7 +1352,7 @@ describe('yargs-parser', function () { }) it('should set - as the value of s when s is set as a string', function () { - var argv = parser([ '-s', '-' ], { + var argv = parser(['-s', '-'], { string: ['s'] }) @@ -1406,13 +1406,13 @@ describe('yargs-parser', function () { }) it('should not consume the next argument', function () { - var parsed = parser([ '-v', 'moo' ], { + var parsed = parser(['-v', 'moo'], { count: 'v' }) parsed.v.should.equal(1) parsed.should.have.property('_').and.deep.equal(['moo']) - parsed = parser([ '--verbose', 'moomoo', '--verbose' ], { + parsed = parser(['--verbose', 'moomoo', '--verbose'], { count: 'verbose' }) parsed.verbose.should.equal(2) @@ -1709,7 +1709,7 @@ describe('yargs-parser', function () { describe('nargs', function () { it('should allow the number of arguments following a key to be specified', function () { - var result = parser([ '--foo', 'apple', 'bar' ], { + var result = parser(['--foo', 'apple', 'bar'], { narg: { foo: 2 } @@ -1744,7 +1744,7 @@ describe('yargs-parser', function () { }) it('should apply nargs to flag arguments', function () { - var result = parser([ '-f', 'apple', 'bar', 'blerg' ], { + var result = parser(['-f', 'apple', 'bar', 'blerg'], { narg: { f: 2 } @@ -1793,7 +1793,7 @@ describe('yargs-parser', function () { }) it('allows multiple nargs to be set at the same time', function () { - var result = parser([ '--foo', 'apple', 'bar', '--bar', 'banana', '-f' ], { + var result = parser(['--foo', 'apple', 'bar', '--bar', 'banana', '-f'], { narg: { foo: 2, bar: 1 @@ -2689,6 +2689,172 @@ describe('yargs-parser', function () { }) }) }) + + describe('collect-unknown-options = true', function () { + it('should ignore unknown options in long format separated by =', function () { + const argv = parser('--known-arg=1 --unknown-arg=2', { + number: ['known-arg'], + configuration: { + 'collect-unknown-options': true + } + }) + argv.should.deep.equal({ + _: ['--unknown-arg=2'], + 'known-arg': 1, + 'knownArg': 1 + }) + }) + it('should ignore unknown options in boolean negations', function () { + const argv = parser('--no-known-arg --no-unknown-arg', { + boolean: ['known-arg'], + configuration: { + 'collect-unknown-options': true + } + }) + argv.should.deep.equal({ + _: ['--no-unknown-arg'], + 'known-arg': false, + 'knownArg': false + }) + }) + it('should ignore unknown options in long format separated by space', function () { + const argv = parser('--known-arg a --unknown-arg b', { + string: ['known-arg'], + configuration: { + 'collect-unknown-options': true + } + }) + argv.should.deep.equal({ + _: ['--unknown-arg', 'b'], + 'known-arg': 'a', + 'knownArg': 'a' + }) + }) + it('should ignore unknown options in short dot format separated by equals', function () { + const argv = parser('-k.arg=a -u.arg=b', { + string: ['k.arg'], + configuration: { + 'collect-unknown-options': true + } + }) + argv.should.deep.equal({ + _: ['-u.arg=b'], + 'k': { + 'arg': 'a' + } + }) + }) + it('should ignore unknown options in short dot format separated by space', function () { + const argv = parser('-k.arg 1 -u.arg 2', { + number: ['k.arg'], + configuration: { + 'collect-unknown-options': true + } + }) + argv.should.deep.equal({ + _: ['-u.arg', '2'], + 'k': { + 'arg': 1 + } + }) + }) + it('should ignore unknown options in short format separated by equals', function () { + const argv = parser('-k=a -u=b', { + string: ['k'], + configuration: { + 'collect-unknown-options': true + } + }) + argv.should.deep.equal({ + _: ['-u=b'], + 'k': 'a' + }) + }) + it('should ignore unknown options in short format followed by hyphen', function () { + const argv = parser('-k- -u-', { + string: ['k'], + configuration: { + 'collect-unknown-options': true + } + }) + argv.should.deep.equal({ + _: ['-u-'], + 'k': '-' + }) + }) + it('should ignore unknown options in short format separated by space', function () { + const argv = parser('-k 1 -u 2', { + number: ['k'], + configuration: { + 'collect-unknown-options': true + } + }) + argv.should.deep.equal({ + _: ['-u', '2'], + 'k': 1 + }) + }) + it('should ignore unknown options in short format followed by a number', function () { + const argv = parser('-k1 -u2', { + number: ['k'], + configuration: { + 'collect-unknown-options': true + } + }) + argv.should.deep.equal({ + _: ['-u2'], + 'k': 1 + }) + }) + it('should ignore unknown options in short format followed by a non-word character', function () { + const argv = parser('-k/1/ -u/2/', { + string: ['k'], + configuration: { + 'collect-unknown-options': true + } + }) + argv.should.deep.equal({ + _: ['-u/2/'], + 'k': '/1/' + }) + }) + it('should ignore unknown options in short format with multiple flags in one argument where an unknown flag is before the end', function () { + const argv = parser('-kuv', { + boolean: ['k', 'v'], + configuration: { + 'collect-unknown-options': true + } + }) + argv.should.deep.equal({ + _: ['-kuv'] + }) + }) + it('should parse known options in short format with multiple flags in one argument where no unknown flag is in the argument', function () { + const argv = parser('-kv', { + boolean: ['k', 'v'], + configuration: { + 'collect-unknown-options': true + } + }) + argv.should.deep.equal({ + _: [], + k: true, + v: true + }) + }) + it('should parse negative numbers', function () { + const argv = parser('-k -33', { + boolean: ['k'], + configuration: { + 'collect-unknown-options': true + } + }) + argv.should.deep.equal({ + _: [-33], + k: true + }) + }) + }) }) // addresses: https://github.com/yargs/yargs-parser/issues/41 @@ -2832,9 +2998,9 @@ describe('yargs-parser', function () { bar: fancyNumberParser } }) - ;(typeof parsed.foo).should.equal('string') + ; (typeof parsed.foo).should.equal('string') parsed.foo.should.equal('88888889999990000998989898989898') - ;(typeof parsed.bar).should.equal('number') + ; (typeof parsed.bar).should.equal('number') parsed.bar.should.equal(998) }) @@ -2868,7 +3034,7 @@ describe('yargs-parser', function () { runcount++ return undefined } - parser([ '--foo', 'bar' ], { + parser(['--foo', 'bar'], { alias: { foo: ['f', 'foo-bar', 'bar'], b: ['bar'] @@ -2883,7 +3049,7 @@ describe('yargs-parser', function () { // see: https://github.com/yargs/yargs-parser/issues/37 it('normalizes all paths in array when provided via config object', function () { - var argv = parser([ '--foo', 'bar' ], { + var argv = parser(['--foo', 'bar'], { array: ['a'], normalize: ['a'], configObjects: [{ 'a': ['bin/../a.txt', 'bin/../b.txt'] }] @@ -2893,17 +3059,17 @@ describe('yargs-parser', function () { // see: https://github.com/yargs/yargs/issues/963 it('does not magically convert numeric strings larger than Number.MAX_SAFE_INTEGER', () => { - const argv = parser([ '--foo', '93940495950949399948393' ]) + const argv = parser(['--foo', '93940495950949399948393']) argv.foo.should.equal('93940495950949399948393') }) it('does not magically convert scientific notation larger than Number.MAX_SAFE_INTEGER', () => { - const argv = parser([ '--foo', '33e99999' ]) + const argv = parser(['--foo', '33e99999']) argv.foo.should.equal('33e99999') }) it('converts numeric options larger than Number.MAX_SAFE_INTEGER to number', () => { - const argv = parser([ '--foo', '93940495950949399948393' ], { + const argv = parser(['--foo', '93940495950949399948393'], { number: ['foo'] }) argv.foo.should.equal(9.39404959509494e+22) @@ -2930,21 +3096,21 @@ describe('yargs-parser', function () { // see: https://github.com/yargs/yargs-parser/issues/101 describe('dot-notation array arguments combined with string arguments', function () { it('parses correctly when dot-notation argument is first', function () { - var argv = parser([ '--foo.bar', 'baz', '--foo', 'bux' ]) + var argv = parser(['--foo.bar', 'baz', '--foo', 'bux']) Array.isArray(argv.foo).should.equal(true) argv.foo[0].bar.should.equal('baz') argv.foo[1].should.equal('bux') }) it('parses correctly when dot-notation argument is last', function () { - var argv = parser([ '--foo', 'bux', '--foo.bar', 'baz' ]) + var argv = parser(['--foo', 'bux', '--foo.bar', 'baz']) Array.isArray(argv.foo).should.equal(true) argv.foo[0].should.equal('bux') argv.foo[1].bar.should.equal('baz') }) it('parses correctly when there are multiple dot-notation arguments', function () { - var argv = parser([ '--foo.first', 'firstvalue', '--foo', 'bux', '--foo.bar', 'baz', '--foo.bla', 'banana' ]) + var argv = parser(['--foo.first', 'firstvalue', '--foo', 'bux', '--foo.bar', 'baz', '--foo.bla', 'banana']) Array.isArray(argv.foo).should.equal(true) argv.foo.length.should.equal(4) argv.foo[0].first.should.equal('firstvalue') @@ -2983,7 +3149,7 @@ describe('yargs-parser', function () { // see: https://github.com/yargs/yargs-parser/issues/144 it('number/string types should use default when no right-hand value', () => { - let argv = parser([ '--foo' ], { + let argv = parser(['--foo'], { number: ['foo'], default: { foo: 99 @@ -2991,7 +3157,7 @@ describe('yargs-parser', function () { }) argv.foo.should.equal(99) - argv = parser([ '-b' ], { + argv = parser(['-b'], { alias: { bar: 'b' }, @@ -3005,7 +3171,7 @@ describe('yargs-parser', function () { describe('stripping', function () { it('strip-dashed removes expected fields from argv', function () { - const argv = parser([ '--test-value', '1' ], { + const argv = parser(['--test-value', '1'], { number: ['test-value'], alias: { 'test-value': ['alt-test'] @@ -3022,7 +3188,7 @@ describe('yargs-parser', function () { }) it('strip-aliased removes expected fields from argv', function () { - const argv = parser([ '--test-value', '1' ], { + const argv = parser(['--test-value', '1'], { number: ['test-value'], alias: { 'test-value': ['alt-test'] @@ -3039,7 +3205,7 @@ describe('yargs-parser', function () { }) it('strip-aliased and strip-dashed combined removes expected fields from argv', function () { - const argv = parser([ '--test-value', '1' ], { + const argv = parser(['--test-value', '1'], { number: ['test-value'], alias: { 'test-value': ['alt-test'] @@ -3056,7 +3222,7 @@ describe('yargs-parser', function () { }) it('ignores strip-dashed if camel-case-expansion is disabled', function () { - const argv = parser([ '--test-value', '1' ], { + const argv = parser(['--test-value', '1'], { number: ['test-value'], configuration: { 'camel-case-expansion': false, From 051601951c458e1294270c6baca79b4aff32f8b6 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Fri, 6 Sep 2019 21:26:52 +0200 Subject: [PATCH 048/206] docs: fix typo require (#200) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f754df09..1110bbca 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ node example.js --foo=33 --bar hello _or parse a string!_ ```js -var argv = require('./')('--foo=99 --bar=33') +var argv = require('yargs-parser')('--foo=99 --bar=33') console.log(argv) ``` From ac11361ab90204698499ca06e292fac47088971a Mon Sep 17 00:00:00 2001 From: bcoe Date: Fri, 6 Sep 2019 12:33:00 -0700 Subject: [PATCH 049/206] chore(release): 14.0.0 --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df11c002..a8d22d3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [14.0.0](https://github.com/yargs/yargs-parser/compare/v13.1.1...v14.0.0) (2019-09-06) + + +### Bug Fixes + +* boolean arrays with default values ([#185](https://github.com/yargs/yargs-parser/issues/185)) ([7d42572](https://github.com/yargs/yargs-parser/commit/7d42572)) +* boolean now behaves the same as other array types ([#184](https://github.com/yargs/yargs-parser/issues/184)) ([17ca3bd](https://github.com/yargs/yargs-parser/commit/17ca3bd)) +* eatNargs() for 'opt.narg === 0' and boolean typed options ([#188](https://github.com/yargs/yargs-parser/issues/188)) ([c5a1db0](https://github.com/yargs/yargs-parser/commit/c5a1db0)) +* maybeCoerceNumber now takes precedence over coerce return value ([#182](https://github.com/yargs/yargs-parser/issues/182)) ([2f26436](https://github.com/yargs/yargs-parser/commit/2f26436)) +* take into account aliases when appending arrays from config object ([#199](https://github.com/yargs/yargs-parser/issues/199)) ([f8a2d3f](https://github.com/yargs/yargs-parser/commit/f8a2d3f)) + + +### Features + +* add configuration option to "collect-unknown-options" ([#181](https://github.com/yargs/yargs-parser/issues/181)) ([7909cc4](https://github.com/yargs/yargs-parser/commit/7909cc4)) +* maybeCoerceNumber() now takes into account arrays ([#187](https://github.com/yargs/yargs-parser/issues/187)) ([31c204b](https://github.com/yargs/yargs-parser/commit/31c204b)) + + +### BREAKING CHANGES + +* unless "parse-numbers" is set to "false", arrays of numeric strings are now parsed as numbers, rather than strings. +* we have dropped the broken "defaulted" functionality; we would like to revisit adding this in the future. +* maybeCoerceNumber now takes precedence over coerce return value (#182) + + + ### [13.1.1](https://www.github.com/yargs/yargs-parser/compare/v13.1.0...v13.1.1) (2019-06-10) diff --git a/package.json b/package.json index 35a3885d..806485e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "13.1.1", + "version": "14.0.0", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From ef771ca6fa344e7cd30de5828b16ae965eddb409 Mon Sep 17 00:00:00 2001 From: Eric Henderson Date: Sun, 6 Oct 2019 19:56:42 -0400 Subject: [PATCH 050/206] feat!: rework `collect-unknown-options` into `unknown-options-as-args`, providing more comprehensive functionality --- README.md | 14 ++++----- index.js | 29 +++++++++---------- test/yargs-parser.js | 68 +++++++++++++++++++++++++++++++++++--------- 3 files changed, 74 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 1110bbca..5f1ccb98 100644 --- a/README.md +++ b/README.md @@ -386,26 +386,26 @@ node example.js --test-field 1 { _: [], testField: 1 } ``` -### collect unknown options +### unknown options as args * default: `false` -* key: `collect-unknown-options` +* key: `unknown-options-as-args` -Should unknown options be collected into `_`? An unknown option is one that is not +Should unknown options be treated like regular arguments? An unknown option is one that is not configured in `opts`. _If disabled_ ```sh -node example.js --unknown-option --known-option 2 -{ _: [], unknownOption: true, knownOption: 2 } +node example.js --unknown-option --known-option 2 --string-option --unknown-option2 +{ _: [], unknownOption: true, knownOption: 2, stringOption: '', unknownOption2: true } ``` _If enabled_ ```sh -node example.js --unknown-option --known-option 2 -{ _: ['--unknown-option'], knownOption: 2 } +node example.js --unknown-option --known-option 2 --string-option --unknown-option2 +{ _: ['--unknown-option'], knownOption: 2, stringOption: '--unknown-option2' } ``` ## Special Thanks diff --git a/index.js b/index.js index 08f9852c..a5f7ae41 100644 --- a/index.js +++ b/index.js @@ -27,7 +27,7 @@ function parse (args, opts) { 'halt-at-non-option': false, 'strip-aliased': false, 'strip-dashed': false, - 'collect-unknown-options': false + 'unknown-options-as-args': false }, opts.configuration) var defaults = opts.default || {} var configObjects = opts.configObjects || [] @@ -143,7 +143,7 @@ function parse (args, opts) { var next var value - if (configuration['collect-unknown-options'] && isUnknownOption(arg)) { + if (isUnknownOptionAsArg(arg)) { argv._.push(arg) // -- separated by = } else if (arg.match(/^--.+=/) || ( @@ -361,7 +361,7 @@ function parse (args, opts) { // and terminates when one is observed. var available = 0 for (ii = i + 1; ii < args.length; ii++) { - if (!args[ii].match(/^-[^0-9]/)) available++ + if (!args[ii].match(/^-[^0-9]/) || isUnknownOptionAsArg(args[ii])) available++ else break } @@ -384,7 +384,7 @@ function parse (args, opts) { if (checkAllAliases(key, flags.bools) && !(/^(true|false)$/.test(next))) { argsToSet.push(true) - } else if (isUndefined(next) || (/^-/.test(next) && !negative.test(next))) { + } else if (isUndefined(next) || (/^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next))) { // for keys without value ==> argsToSet remains an empty [] // set user default value, if available if (defaults.hasOwnProperty(key)) { @@ -393,7 +393,7 @@ function parse (args, opts) { } else { for (var ii = i + 1; ii < args.length; ii++) { next = args[ii] - if (/^-/.test(next) && !negative.test(next)) break + if (/^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next)) break i = ii argsToSet.push(processValue(key, next)) } @@ -761,27 +761,20 @@ function parse (args, opts) { } function hasAnyFlag (key) { - var isSet = false // XXX Switch to [].concat(...Object.values(flags)) once node.js 6 is dropped var toCheck = [].concat(...Object.keys(flags).map(k => flags[k])) - toCheck.forEach(function (flag) { - if (flag[key]) isSet = flag[key] + return toCheck.some(function (flag) { + return flag[key] }) - - return isSet } function hasFlagsMatching (arg, ...patterns) { - var hasFlag = false var toCheck = [].concat(...patterns) - toCheck.forEach(function (pattern) { + return toCheck.some(function (pattern) { var match = arg.match(pattern) - if (match && hasAnyFlag(match[1])) { - hasFlag = true - } + return match && hasAnyFlag(match[1]) }) - return hasFlag } // based on a simplified version of the short flag group parsing logic @@ -809,6 +802,10 @@ function parse (args, opts) { return hasAllFlags } + function isUnknownOptionAsArg (arg) { + return configuration['unknown-options-as-args'] && isUnknownOption(arg) + } + function isUnknownOption (arg) { // ignore negative numbers if (arg.match(negative)) { return false } diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 27f80740..349549b4 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -2690,12 +2690,12 @@ describe('yargs-parser', function () { }) }) - describe('collect-unknown-options = true', function () { + describe('unknown-options-as-args = true', function () { it('should ignore unknown options in long format separated by =', function () { const argv = parser('--known-arg=1 --unknown-arg=2', { number: ['known-arg'], configuration: { - 'collect-unknown-options': true + 'unknown-options-as-args': true } }) argv.should.deep.equal({ @@ -2708,7 +2708,7 @@ describe('yargs-parser', function () { const argv = parser('--no-known-arg --no-unknown-arg', { boolean: ['known-arg'], configuration: { - 'collect-unknown-options': true + 'unknown-options-as-args': true } }) argv.should.deep.equal({ @@ -2721,7 +2721,7 @@ describe('yargs-parser', function () { const argv = parser('--known-arg a --unknown-arg b', { string: ['known-arg'], configuration: { - 'collect-unknown-options': true + 'unknown-options-as-args': true } }) argv.should.deep.equal({ @@ -2734,7 +2734,7 @@ describe('yargs-parser', function () { const argv = parser('-k.arg=a -u.arg=b', { string: ['k.arg'], configuration: { - 'collect-unknown-options': true + 'unknown-options-as-args': true } }) argv.should.deep.equal({ @@ -2748,7 +2748,7 @@ describe('yargs-parser', function () { const argv = parser('-k.arg 1 -u.arg 2', { number: ['k.arg'], configuration: { - 'collect-unknown-options': true + 'unknown-options-as-args': true } }) argv.should.deep.equal({ @@ -2762,7 +2762,7 @@ describe('yargs-parser', function () { const argv = parser('-k=a -u=b', { string: ['k'], configuration: { - 'collect-unknown-options': true + 'unknown-options-as-args': true } }) argv.should.deep.equal({ @@ -2774,7 +2774,7 @@ describe('yargs-parser', function () { const argv = parser('-k- -u-', { string: ['k'], configuration: { - 'collect-unknown-options': true + 'unknown-options-as-args': true } }) argv.should.deep.equal({ @@ -2786,7 +2786,7 @@ describe('yargs-parser', function () { const argv = parser('-k 1 -u 2', { number: ['k'], configuration: { - 'collect-unknown-options': true + 'unknown-options-as-args': true } }) argv.should.deep.equal({ @@ -2794,11 +2794,51 @@ describe('yargs-parser', function () { 'k': 1 }) }) + it('should allow an unknown arg to be used as the value of another flag in short form', function () { + const argv = parser('-k -u', { + string: ['k'], + narg: { 'k': 1 }, + configuration: { + 'unknown-options-as-args': true + } + }) + argv.should.deep.equal({ + _: [], + 'k': '-u' + }) + }) + it('should allow an unknown arg to be used as the value of another flag in long form', function () { + const argv = parser('--known-arg --unknown-arg', { + string: ['known-arg'], + narg: { 'known-arg': 1 }, + configuration: { + 'unknown-options-as-args': true + } + }) + argv.should.deep.equal({ + _: [], + 'knownArg': '--unknown-arg', + 'known-arg': '--unknown-arg' + }) + }) + it('should allow an unknown arg to be used as the value of another flag in array form', function () { + const argv = parser('--known-arg --unknown-arg1 --unknown-arg2', { + array: ['known-arg'], + configuration: { + 'unknown-options-as-args': true + } + }) + argv.should.deep.equal({ + _: [], + 'knownArg': ['--unknown-arg1', '--unknown-arg2'], + 'known-arg': ['--unknown-arg1', '--unknown-arg2'] + }) + }) it('should ignore unknown options in short format followed by a number', function () { const argv = parser('-k1 -u2', { number: ['k'], configuration: { - 'collect-unknown-options': true + 'unknown-options-as-args': true } }) argv.should.deep.equal({ @@ -2810,7 +2850,7 @@ describe('yargs-parser', function () { const argv = parser('-k/1/ -u/2/', { string: ['k'], configuration: { - 'collect-unknown-options': true + 'unknown-options-as-args': true } }) argv.should.deep.equal({ @@ -2822,7 +2862,7 @@ describe('yargs-parser', function () { const argv = parser('-kuv', { boolean: ['k', 'v'], configuration: { - 'collect-unknown-options': true + 'unknown-options-as-args': true } }) argv.should.deep.equal({ @@ -2833,7 +2873,7 @@ describe('yargs-parser', function () { const argv = parser('-kv', { boolean: ['k', 'v'], configuration: { - 'collect-unknown-options': true + 'unknown-options-as-args': true } }) argv.should.deep.equal({ @@ -2846,7 +2886,7 @@ describe('yargs-parser', function () { const argv = parser('-k -33', { boolean: ['k'], configuration: { - 'collect-unknown-options': true + 'unknown-options-as-args': true } }) argv.should.deep.equal({ From eab0cb6511adcf8db9405307fc082493b4709bba Mon Sep 17 00:00:00 2001 From: bcoe Date: Sun, 6 Oct 2019 17:08:06 -0700 Subject: [PATCH 051/206] chore(release): 15.0.0 --- CHANGELOG.md | 14 ++++++++++++++ package.json | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8d22d3f..18ed4b34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [15.0.0](https://github.com/yargs/yargs-parser/compare/v14.0.0...v15.0.0) (2019-10-07) + + +### Features + +* rework `collect-unknown-options` into `unknown-options-as-args`, providing more comprehensive functionality ([ef771ca](https://github.com/yargs/yargs-parser/commit/ef771ca)) + + +### BREAKING CHANGES + +* rework `collect-unknown-options` into `unknown-options-as-args`, providing more comprehensive functionality + + + ## [14.0.0](https://github.com/yargs/yargs-parser/compare/v13.1.1...v14.0.0) (2019-09-06) diff --git a/package.json b/package.json index 806485e8..f2914039 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "14.0.0", + "version": "15.0.0", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From afcaecbaad6c8fd83e6433e6ed36ae3312a85ab5 Mon Sep 17 00:00:00 2001 From: Mael Le Guen Date: Wed, 9 Oct 2019 18:22:42 +0200 Subject: [PATCH 052/206] refactor: remove Array.forEach used in place of Array.some/find --- index.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index a5f7ae41..395f5fee 100644 --- a/index.js +++ b/index.js @@ -748,16 +748,11 @@ function parse (args, opts) { }) } - // check if a flag is set for any of a key's aliases. + // return the 1st set flag for any of a key's aliases (or false if no flag set) function checkAllAliases (key, flag) { - var isSet = false var toCheck = [].concat(flags.aliases[key] || [], key) - - toCheck.forEach(function (key) { - if (flag.hasOwnProperty(key)) isSet = flag[key] - }) - - return isSet + let setAlias = toCheck.find(key => flag.hasOwnProperty(key)) + return setAlias ? flag[setAlias] : false } function hasAnyFlag (key) { From 3fee2d895e9da14af978bbd1c7c9c20170c3aa59 Mon Sep 17 00:00:00 2001 From: Daniel Stockman Date: Tue, 15 Oct 2019 09:25:50 -0700 Subject: [PATCH 053/206] fix(unknown-options-as-args): '--' is not an unknown option (#207) --- index.js | 3 ++- test/yargs-parser.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 395f5fee..03dd1407 100644 --- a/index.js +++ b/index.js @@ -143,7 +143,8 @@ function parse (args, opts) { var next var value - if (isUnknownOptionAsArg(arg)) { + // any unknown option (except for end-of-options, "--") + if (arg !== '--' && isUnknownOptionAsArg(arg)) { argv._.push(arg) // -- separated by = } else if (arg.match(/^--.+=/) || ( diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 349549b4..79ccf978 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -2894,6 +2894,36 @@ describe('yargs-parser', function () { k: true }) }) + + it('should not identify "--" as an unknown option', function () { + const argv = parser('-a -k one -1 -- -b -k two -2', { + boolean: ['k'], + configuration: { + 'unknown-options-as-args': true + } + }) + argv.should.deep.equal({ + _: ['-a', 'one', -1, '-b', '-k', 'two', '-2'], + k: true + }) + }) + + it('should not identify "--" as an unknown option when "populate--" is true', function () { + const argv = parser('-a -k one -1 -- -b -k two -2', { + boolean: ['k'], + configuration: { + 'populate--': true, + 'unknown-options-as-args': true + } + }) + argv.should.deep.equal({ + // populate argv._ with everything before the -- + _: ['-a', 'one', -1], + // and argv['--'] with everything after the -- + '--': ['-b', '-k', 'two', '-2'], + k: true + }) + }) }) }) From 850bbdafcf8a4998f374dfce993422855d10716d Mon Sep 17 00:00:00 2001 From: Eric Henderson Date: Sat, 26 Oct 2019 15:56:12 -0400 Subject: [PATCH 054/206] fix: support negative numbers with decimal places (#208) --- index.js | 6 +++--- test/yargs-parser.js | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 03dd1407..3ce3e223 100644 --- a/index.js +++ b/index.js @@ -51,7 +51,7 @@ function parse (args, opts) { coercions: {}, keys: [] } - var negative = /^-[0-9]+(\.[0-9]+)?/ + var negative = /^-([0-9]+(\.[0-9]+)?|\.[0-9]+)$/ var negatedBoolean = new RegExp('^--' + configuration['negation-prefix'] + '(.+)') ;[].concat(opts.array).filter(Boolean).forEach(function (opt) { @@ -206,7 +206,7 @@ function parse (args, opts) { setArg(m[1], m[2]) // dot-notation flag separated by space. - } else if (arg.match(/^-.\..+/)) { + } else if (arg.match(/^-.\..+/) && !arg.match(negative)) { next = args[i + 1] key = arg.match(/^-(.\..+)/)[1] @@ -362,7 +362,7 @@ function parse (args, opts) { // and terminates when one is observed. var available = 0 for (ii = i + 1; ii < args.length; ii++) { - if (!args[ii].match(/^-[^0-9]/) || isUnknownOptionAsArg(args[ii])) available++ + if (!args[ii].match(/^-[^0-9]/) || args[ii].match(negative) || isUnknownOptionAsArg(args[ii])) available++ else break } diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 79ccf978..1ecc37bd 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -159,6 +159,21 @@ describe('yargs-parser', function () { argv.other.should.deep.equal([-99, -220]) }) + it('should handle negative (and positive) numbers with decimal places, with or without a leading 0', function () { + var argv = parser([ + '-0.1', '-1.1', '-.5', '-.1', '.1', '.5', + '--bounds', '-5.1', '-.1', '.1', + '--other', '.9', '-.5' + ], { + array: 'bounds', + narg: { 'other': 2 } + }) + + argv._.should.deep.equal([-0.1, -1.1, -0.5, -0.1, 0.1, 0.5]) + argv.bounds.should.deep.equal([-5.1, -0.1, 0.1]) + argv.other.should.deep.equal([0.9, -0.5]) + }) + it('should set the value of a single short option to the next supplied value, even if the value is empty', function () { var parse = parser(['-p', '']) parse.should.have.property('p', '') From f5f9e5a7ea91821f7c95e8eb4c71dc74de1bc907 Mon Sep 17 00:00:00 2001 From: Juerg B <44573692+juergba@users.noreply.github.com> Date: Sat, 26 Oct 2019 22:17:43 +0200 Subject: [PATCH 055/206] fix: address issue with array options with array default values (#206) --- index.js | 3 ++- test/yargs-parser.js | 17 ++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index 3ce3e223..be074d4e 100644 --- a/index.js +++ b/index.js @@ -389,7 +389,8 @@ function parse (args, opts) { // for keys without value ==> argsToSet remains an empty [] // set user default value, if available if (defaults.hasOwnProperty(key)) { - argsToSet.push(defaults[key]) + let defVal = defaults[key] + argsToSet = Array.isArray(defVal) ? defVal : [defVal] } } else { for (var ii = i + 1; ii < args.length; ii++) { diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 1ecc37bd..0dca6ba9 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -1516,18 +1516,21 @@ describe('yargs-parser', function () { }) it('should default argument to empty array if no value given', function () { - var result = parser(['-b'], { - array: 'b' + var result = parser(['-b', '--tag'], { + array: ['b', 'tag'], + default: { 'tag': [] } }) - result.should.have.property('b').and.deep.equal([]) + result.b.should.deep.equal([]) + result.tag.should.deep.equal([]) }) it('should place default of argument in array, when default provided', function () { - var result = parser(['-b'], { - array: 'b', - default: { 'b': 33 } + var result = parser(['-b', '--tag'], { + array: ['b', 'tag'], + default: { 'b': 33, 'tag': ['foo'] } }) - result.should.have.property('b').and.deep.equal([33]) + result.b.should.deep.equal([33]) + result.tag.should.deep.equal(['foo']) }) it('should place value of argument in array, when one argument provided', function () { From f942242d970e60ce483f617005c424b8e3acbacd Mon Sep 17 00:00:00 2001 From: bcoe Date: Sat, 26 Oct 2019 13:40:13 -0700 Subject: [PATCH 056/206] refactor: let to const --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index be074d4e..22d1c4ed 100644 --- a/index.js +++ b/index.js @@ -389,7 +389,7 @@ function parse (args, opts) { // for keys without value ==> argsToSet remains an empty [] // set user default value, if available if (defaults.hasOwnProperty(key)) { - let defVal = defaults[key] + const defVal = defaults[key] argsToSet = Array.isArray(defVal) ? defVal : [defVal] } } else { From a44ffb35a96af1770a79ea434443dbdde14bfc15 Mon Sep 17 00:00:00 2001 From: bcoe Date: Sat, 26 Oct 2019 13:43:08 -0700 Subject: [PATCH 057/206] build: add release-please configuration --- .github/release-please.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/release-please.yml diff --git a/.github/release-please.yml b/.github/release-please.yml new file mode 100644 index 00000000..85344b92 --- /dev/null +++ b/.github/release-please.yml @@ -0,0 +1 @@ +releaseType: node From f3a9316e470b0cc5c01981c9614ee935835d719b Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sat, 26 Oct 2019 14:54:20 -0700 Subject: [PATCH 058/206] build!: moving to c8 for coverage and dropping Node 6 from build matrix (#209) --- .travis.yml | 13 ++++++++++--- package.json | 6 +++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index bf5a96a7..73a542d9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,15 @@ os: - linux - windows node_js: - - "6" - "8" - "10" - - "node" -after_script: npm run coverage + - "12" + - "13" + +jobs: + include: + - stage: coverage + node_js: "13" + script: + - npm t + - npm run coverage diff --git a/package.json b/package.json index f2914039..7de02a6f 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,9 @@ "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { - "test": "nyc mocha test/*.js", + "test": "c8 --reporter=text --reporter=html mocha test/*.js", "posttest": "standard", - "coverage": "nyc report --reporter=text-lcov | coveralls", + "coverage": "c8 report --reporter=text-lcov | coveralls", "release": "standard-version" }, "repository": { @@ -26,10 +26,10 @@ "author": "Ben Coe ", "license": "ISC", "devDependencies": { + "c8": "^6.0.0", "chai": "^4.2.0", "coveralls": "^3.0.2", "mocha": "^5.2.0", - "nyc": "^14.1.0", "standard": "^12.0.1", "standard-version": "^6.0.0" }, From 212814fe5219d0033abffac90aecc908f2f399ec Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Sat, 26 Oct 2019 19:51:02 -0700 Subject: [PATCH 059/206] chore: release 16.0.0 (#210) --- CHANGELOG.md | 18 ++++++++++++++++++ package.json | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18ed4b34..6315104e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [16.0.0](https://www.github.com/yargs/yargs-parser/compare/v15.0.0...v16.0.0) (2019-10-26) + + +### ⚠ BREAKING CHANGES + +* moving to c8 for coverage and dropping Node 6 from build matrix (#209) + +### Bug Fixes + +* **unknown-options-as-args:** '--' is not an unknown option ([#207](https://www.github.com/yargs/yargs-parser/issues/207)) ([3fee2d8](https://www.github.com/yargs/yargs-parser/commit/3fee2d895e9da14af978bbd1c7c9c20170c3aa59)) +* address issue with array options with array default values ([#206](https://www.github.com/yargs/yargs-parser/issues/206)) ([f5f9e5a](https://www.github.com/yargs/yargs-parser/commit/f5f9e5a7ea91821f7c95e8eb4c71dc74de1bc907)) +* support negative numbers with decimal places ([#208](https://www.github.com/yargs/yargs-parser/issues/208)) ([850bbda](https://www.github.com/yargs/yargs-parser/commit/850bbdafcf8a4998f374dfce993422855d10716d)) + + +### Build System + +* moving to c8 for coverage and dropping Node 6 from build matrix ([#209](https://www.github.com/yargs/yargs-parser/issues/209)) ([f3a9316](https://www.github.com/yargs/yargs-parser/commit/f3a9316e470b0cc5c01981c9614ee935835d719b)) + ## [15.0.0](https://github.com/yargs/yargs-parser/compare/v14.0.0...v15.0.0) (2019-10-07) diff --git a/package.json b/package.json index 7de02a6f..98d41f7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "15.0.0", + "version": "16.0.0", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From 84a401f0fa3095e0a19661670d1570d0c3b9d3c9 Mon Sep 17 00:00:00 2001 From: Juerg B <44573692+juergba@users.noreply.github.com> Date: Mon, 28 Oct 2019 23:35:02 +0100 Subject: [PATCH 060/206] feat!: populate error if incompatible narg/count or array/count options are used (#191) --- index.js | 16 ++++++++++++++++ test/yargs-parser.js | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/index.js b/index.js index 22d1c4ed..3178f61c 100644 --- a/index.js +++ b/index.js @@ -131,6 +131,8 @@ function parse (args, opts) { }) }) + checkConfiguration() + var argv = { _: [] } var notFlags = [] @@ -874,6 +876,20 @@ function parse (args, opts) { return num === undefined } + // check user configuration settings for inconsistencies + function checkConfiguration () { + // count keys should not be set as array/narg + Object.keys(flags.counts).find(key => { + if (checkAllAliases(key, flags.arrays)) { + error = Error(__('Invalid configuration: %s, opts.count excludes opts.array.', key)) + return true + } else if (checkAllAliases(key, flags.nargs)) { + error = Error(__('Invalid configuration: %s, opts.count excludes opts.narg.', key)) + return true + } + }) + } + return { argv: argv, error: error, diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 0dca6ba9..f4d5d865 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -1487,6 +1487,24 @@ describe('yargs-parser', function () { ], { count: 'v' }) parsed.v.should.equal(8) }) + + it('should add an error if counter is also set as array', function () { + var argv = parser.detailed(['--counter', '--counter', '--counter'], { + count: ['counter'], + array: ['counter'] + }) + + argv.error.message.should.equal('Invalid configuration: counter, opts.count excludes opts.array.') + }) + + it('should add an error if counter is also set as narg', function () { + var argv = parser.detailed(['--counter', 'foo', 'bar'], { + count: ['counter'], + narg: { 'counter': 2 } + }) + + argv.error.message.should.equal('Invalid configuration: counter, opts.count excludes opts.narg.') + }) }) describe('array', function () { From a525234558c847deedd73f8792e0a3b77b26e2c0 Mon Sep 17 00:00:00 2001 From: Juerg B <44573692+juergba@users.noreply.github.com> Date: Fri, 1 Nov 2019 23:18:04 +0100 Subject: [PATCH 061/206] feat: options that have had their default value used are now tracked (#211) --- README.md | 8 ++++++-- index.js | 7 +++++-- test/yargs-parser.js | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5f1ccb98..b8596727 100644 --- a/README.md +++ b/README.md @@ -99,10 +99,14 @@ yargs engine. * `argv`: an object representing the parsed value of `args` * `key/value`: key value pairs for each argument and their aliases. * `_`: an array representing the positional arguments. + * [optional] `--`: an array with arguments after the end-of-options flag `--`. * `error`: populated with an error object if an exception occurred during parsing. * `aliases`: the inferred list of aliases built by combining lists in `opts.alias`. -* `newAliases`: any new aliases added via camel-case expansion. -* `configuration`: the configuration loaded from the `yargs` stanza in package.json. +* `newAliases`: any new aliases added via camel-case expansion: + * `boolean`: `{ fooBar: true }` +* `defaulted`: any new argument created by `opts.default`, no aliases included. + * `boolean`: `{ foo: true }` +* `configuration`: given by default settings and `opts.configuration`. diff --git a/index.js b/index.js index 3178f61c..2a55b372 100644 --- a/index.js +++ b/index.js @@ -35,6 +35,7 @@ function parse (args, opts) { var notFlagsOption = configuration['populate--'] var notFlagsArgv = notFlagsOption ? '--' : '_' var newAliases = {} + var defaulted = {} // allow a i18n handler to be passed in, default to a fake one (util.format). var __ = opts.__ || util.format var error = null @@ -317,7 +318,7 @@ function parse (args, opts) { applyEnvVars(argv, false) setConfig(argv) setConfigObjects() - applyDefaultsAndAliases(argv, flags.aliases, defaults) + applyDefaultsAndAliases(argv, flags.aliases, defaults, true) applyCoercions(argv) if (configuration['set-placeholder-key']) setPlaceholderKeys(argv) @@ -627,10 +628,11 @@ function parse (args, opts) { return argv } - function applyDefaultsAndAliases (obj, aliases, defaults) { + function applyDefaultsAndAliases (obj, aliases, defaults, canLog = false) { Object.keys(defaults).forEach(function (key) { if (!hasKey(obj, key.split('.'))) { setKey(obj, key.split('.'), defaults[key]) + if (canLog) defaulted[key] = true ;(aliases[key] || []).forEach(function (x) { if (hasKey(obj, x.split('.'))) return @@ -895,6 +897,7 @@ function parse (args, opts) { error: error, aliases: flags.aliases, newAliases: newAliases, + defaulted: defaulted, configuration: configuration } } diff --git a/test/yargs-parser.js b/test/yargs-parser.js index f4d5d865..5ad0da0f 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -1215,6 +1215,43 @@ describe('yargs-parser', function () { parse.should.have.property('t', false).and.be.a('boolean') parse.should.have.property('_').and.deep.equal(['moo']) }) + + describe('track defaulted', function () { + it('should log defaulted options - not specified by user', function () { + var parsed = parser.detailed('', { + default: { foo: 'abc', 'bar.prop': 33, baz: 'x' }, + configObjects: [{ baz: 'xyz' }] + }) + parsed.argv.should.deep.equal({ '_': [], baz: 'xyz', foo: 'abc', bar: { prop: 33 } }) + parsed.defaulted.should.deep.equal({ foo: true, 'bar.prop': true }) + }) + + it('should not log defaulted options - specified without value', function () { + var parsed = parser.detailed('--foo --bar.prop', { + default: { foo: 'abc', 'bar.prop': 33 } + }) + parsed.argv.should.deep.equal({ '_': [], foo: 'abc', bar: { prop: 33 } }) + parsed.defaulted.should.deep.equal({}) + }) + + it('should log defaulted options - no aliases included', function () { + var parsed = parser.detailed('', { + default: { kaa: 'abc' }, + alias: { foo: 'kaa' } + }) + parsed.argv.should.deep.equal({ '_': [], kaa: 'abc', foo: 'abc' }) + parsed.defaulted.should.deep.equal({ kaa: true }) + }) + + it('setting an alias excludes associated key from defaulted', function () { + var parsed = parser.detailed('--foo abc', { + default: { kaa: 'abc' }, + alias: { foo: 'kaa' } + }) + parsed.argv.should.deep.equal({ '_': [], kaa: 'abc', foo: 'abc' }) + parsed.defaulted.should.deep.equal({}) + }) + }) }) describe('camelCase', function () { From b3a7a89c4fc9f17d2f1bdef33a08505d4dfd9bd8 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Fri, 1 Nov 2019 15:24:18 -0700 Subject: [PATCH 062/206] build: releases now handled by release-please (#212) --- .github/release-please.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/release-please.yml b/.github/release-please.yml index 85344b92..ec34eea8 100644 --- a/.github/release-please.yml +++ b/.github/release-please.yml @@ -1 +1,3 @@ releaseType: node +handleGHRelease: true + From 920320ad9861bbfd58eda39221ae211540fc1daf Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Fri, 1 Nov 2019 15:32:23 -0700 Subject: [PATCH 063/206] revert: revert 16.0.0 CHANGELOG entry Release-As: 16.1.0 --- CHANGELOG.md | 18 ------------------ package.json | 2 +- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6315104e..18ed4b34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,24 +2,6 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. -## [16.0.0](https://www.github.com/yargs/yargs-parser/compare/v15.0.0...v16.0.0) (2019-10-26) - - -### ⚠ BREAKING CHANGES - -* moving to c8 for coverage and dropping Node 6 from build matrix (#209) - -### Bug Fixes - -* **unknown-options-as-args:** '--' is not an unknown option ([#207](https://www.github.com/yargs/yargs-parser/issues/207)) ([3fee2d8](https://www.github.com/yargs/yargs-parser/commit/3fee2d895e9da14af978bbd1c7c9c20170c3aa59)) -* address issue with array options with array default values ([#206](https://www.github.com/yargs/yargs-parser/issues/206)) ([f5f9e5a](https://www.github.com/yargs/yargs-parser/commit/f5f9e5a7ea91821f7c95e8eb4c71dc74de1bc907)) -* support negative numbers with decimal places ([#208](https://www.github.com/yargs/yargs-parser/issues/208)) ([850bbda](https://www.github.com/yargs/yargs-parser/commit/850bbdafcf8a4998f374dfce993422855d10716d)) - - -### Build System - -* moving to c8 for coverage and dropping Node 6 from build matrix ([#209](https://www.github.com/yargs/yargs-parser/issues/209)) ([f3a9316](https://www.github.com/yargs/yargs-parser/commit/f3a9316e470b0cc5c01981c9614ee935835d719b)) - ## [15.0.0](https://github.com/yargs/yargs-parser/compare/v14.0.0...v15.0.0) (2019-10-07) diff --git a/package.json b/package.json index 98d41f7c..7de02a6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "16.0.0", + "version": "15.0.0", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From 36d31b6666b634c056a04ab52b352b6077b30a56 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2019 15:36:05 -0700 Subject: [PATCH 064/206] chore: release 16.1.0 (#215) --- CHANGELOG.md | 17 +++++++++++++++++ package.json | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18ed4b34..75cc3c19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [16.1.0](https://www.github.com/yargs/yargs-parser/compare/v16.0.0...v16.1.0) (2019-11-01) + + +### ⚠ BREAKING CHANGES + +* populate error if incompatible narg/count or array/count options are used (#191) + +### Features + +* options that have had their default value used are now tracked ([#211](https://www.github.com/yargs/yargs-parser/issues/211)) ([a525234](https://www.github.com/yargs/yargs-parser/commit/a525234558c847deedd73f8792e0a3b77b26e2c0)) +* populate error if incompatible narg/count or array/count options are used ([#191](https://www.github.com/yargs/yargs-parser/issues/191)) ([84a401f](https://www.github.com/yargs/yargs-parser/commit/84a401f0fa3095e0a19661670d1570d0c3b9d3c9)) + + +### Reverts + +* revert 16.0.0 CHANGELOG entry ([920320a](https://www.github.com/yargs/yargs-parser/commit/920320ad9861bbfd58eda39221ae211540fc1daf)) + ## [15.0.0](https://github.com/yargs/yargs-parser/compare/v14.0.0...v15.0.0) (2019-10-07) diff --git a/package.json b/package.json index 7de02a6f..93d834da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "15.0.0", + "version": "16.1.0", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From ec68ef5e30264b3e53545e3deb46897f5e664d8b Mon Sep 17 00:00:00 2001 From: Benjamin Coe Date: Fri, 1 Nov 2019 15:41:47 -0700 Subject: [PATCH 065/206] nit: extra newline was bothering me --- .github/release-please.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/release-please.yml b/.github/release-please.yml index ec34eea8..3c065997 100644 --- a/.github/release-please.yml +++ b/.github/release-please.yml @@ -1,3 +1,2 @@ releaseType: node handleGHRelease: true - From 4317f00bb3451c1539aa1d2402871f58b549a7ff Mon Sep 17 00:00:00 2001 From: Corey Farrell Date: Mon, 2 Dec 2019 13:16:26 -0500 Subject: [PATCH 066/206] chore: fix misspelling of package.json `engines` field (#225) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 93d834da..21c64286 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "lib", "index.js" ], - "engine": { + "engines": { "node": ">=6" } } From bc023e3b13e20a118353f9507d1c999bf388a346 Mon Sep 17 00:00:00 2001 From: Mael Le Guen Date: Sun, 9 Feb 2020 04:16:46 +0100 Subject: [PATCH 067/206] fix: address bugs with "uknown-options-as-args" --- index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 2a55b372..243c1dee 100644 --- a/index.js +++ b/index.js @@ -762,11 +762,11 @@ function parse (args, opts) { } function hasAnyFlag (key) { - // XXX Switch to [].concat(...Object.values(flags)) once node.js 6 is dropped - var toCheck = [].concat(...Object.keys(flags).map(k => flags[k])) + // XXX Switch to [].concat(Object.values(flags)) once node.js 6 is dropped + var toCheck = [].concat(Object.keys(flags).map(k => flags[k])) return toCheck.some(function (flag) { - return flag[key] + return Array.isArray(flag) ? flag.includes(key) : flag.hasOwnProperty(key) && flag[key] }) } From 1587b6d91db853a9109f1be6b209077993fee4de Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sat, 8 Feb 2020 22:49:11 -0800 Subject: [PATCH 068/206] fix!: support keys that collide with object prototypes (#234) BREAKING CHANGE: objects used during parsing are now created with a null prototype. There may be some scenarios where this change in behavior leaks externally. --- index.js | 221 ++++++++++++++++++------------------ lib/tokenize-arg-string.js | 12 +- package.json | 2 +- test/fixtures/config.json | 3 +- test/tokenize-arg-string.js | 20 ++-- test/yargs-parser.js | 150 ++++++++++++++++++++++-- 6 files changed, 273 insertions(+), 135 deletions(-) diff --git a/index.js b/index.js index 243c1dee..0e2381dc 100644 --- a/index.js +++ b/index.js @@ -1,18 +1,18 @@ -var camelCase = require('camelcase') -var decamelize = require('decamelize') -var path = require('path') -var tokenizeArgString = require('./lib/tokenize-arg-string') -var util = require('util') +const camelCase = require('camelcase') +const decamelize = require('decamelize') +const path = require('path') +const tokenizeArgString = require('./lib/tokenize-arg-string') +const util = require('util') function parse (args, opts) { - if (!opts) opts = {} + opts = Object.assign(Object.create(null), opts) // allow a string argument to be passed in rather // than an argv array. args = tokenizeArgString(args) // aliases might have transitive relationships, normalize this. - var aliases = combineAliases(opts.alias || {}) - var configuration = Object.assign({ + const aliases = combineAliases(Object.assign(Object.create(null), opts.alias)) + const configuration = Object.assign({ 'short-option-groups': true, 'camel-case-expansion': true, 'dot-notation': true, @@ -29,34 +29,33 @@ function parse (args, opts) { 'strip-dashed': false, 'unknown-options-as-args': false }, opts.configuration) - var defaults = opts.default || {} - var configObjects = opts.configObjects || [] - var envPrefix = opts.envPrefix - var notFlagsOption = configuration['populate--'] - var notFlagsArgv = notFlagsOption ? '--' : '_' - var newAliases = {} - var defaulted = {} + const defaults = Object.assign(Object.create(null), opts.default) + const configObjects = opts.configObjects || [] + const envPrefix = opts.envPrefix + const notFlagsOption = configuration['populate--'] + const notFlagsArgv = notFlagsOption ? '--' : '_' + const newAliases = Object.create(null) + const defaulted = Object.create(null) // allow a i18n handler to be passed in, default to a fake one (util.format). - var __ = opts.__ || util.format - var error = null - var flags = { - aliases: {}, - arrays: {}, - bools: {}, - strings: {}, - numbers: {}, - counts: {}, - normalize: {}, - configs: {}, - nargs: {}, - coercions: {}, + const __ = opts.__ || util.format + const flags = { + aliases: Object.create(null), + arrays: Object.create(null), + bools: Object.create(null), + strings: Object.create(null), + numbers: Object.create(null), + counts: Object.create(null), + normalize: Object.create(null), + configs: Object.create(null), + nargs: Object.create(null), + coercions: Object.create(null), keys: [] } - var negative = /^-([0-9]+(\.[0-9]+)?|\.[0-9]+)$/ - var negatedBoolean = new RegExp('^--' + configuration['negation-prefix'] + '(.+)') + const negative = /^-([0-9]+(\.[0-9]+)?|\.[0-9]+)$/ + const negatedBoolean = new RegExp('^--' + configuration['negation-prefix'] + '(.+)') ;[].concat(opts.array).filter(Boolean).forEach(function (opt) { - var key = opt.key || opt + const key = opt.key || opt // assign to flags[bools|strings|numbers] const assignment = Object.keys(opt).map(function (key) { @@ -132,19 +131,25 @@ function parse (args, opts) { }) }) + let error = null checkConfiguration() - var argv = { _: [] } - var notFlags = [] + let notFlags = [] - for (var i = 0; i < args.length; i++) { - var arg = args[i] - var broken - var key - var letters - var m - var next - var value + const argv = Object.assign(Object.create(null), { _: [] }) + // TODO(bcoe): for the first pass at removing object prototype we didn't + // remove all prototypes from objects returned by this API, we might want + // to gradually move towards doing so. + const argvReturn = {} + + for (let i = 0; i < args.length; i++) { + const arg = args[i] + let broken + let key + let letters + let m + let next + let value // any unknown option (except for end-of-options, "--") if (arg !== '--' && isUnknownOptionAsArg(arg)) { @@ -225,7 +230,7 @@ function parse (args, opts) { letters = arg.slice(1, -1).split('') broken = false - for (var j = 0; j < letters.length; j++) { + for (let j = 0; j < letters.length; j++) { next = arg.slice(j + 2) if (letters[j + 1] && letters[j + 1] === '=') { @@ -353,7 +358,7 @@ function parse (args, opts) { // how many arguments should we consume, based // on the nargs option? function eatNargs (i, key, args) { - var ii + let ii const toEat = checkAllAliases(key, flags.nargs) if (toEat === 0) { @@ -363,7 +368,7 @@ function parse (args, opts) { // nargs will not consume flag arguments, e.g., -abc, --foo, // and terminates when one is observed. - var available = 0 + let available = 0 for (ii = i + 1; ii < args.length; ii++) { if (!args[ii].match(/^-[^0-9]/) || args[ii].match(negative) || isUnknownOptionAsArg(args[ii])) available++ else break @@ -391,12 +396,12 @@ function parse (args, opts) { } else if (isUndefined(next) || (/^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next))) { // for keys without value ==> argsToSet remains an empty [] // set user default value, if available - if (defaults.hasOwnProperty(key)) { + if (defaults[key] !== undefined) { const defVal = defaults[key] argsToSet = Array.isArray(defVal) ? defVal : [defVal] } } else { - for (var ii = i + 1; ii < args.length; ii++) { + for (let ii = i + 1; ii < args.length; ii++) { next = args[ii] if (/^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next)) break i = ii @@ -410,15 +415,14 @@ function parse (args, opts) { function setArg (key, val) { if (/-/.test(key) && configuration['camel-case-expansion']) { - var alias = key.split('.').map(function (prop) { + const alias = key.split('.').map(function (prop) { return camelCase(prop) }).join('.') addNewAlias(key, alias) } - var value = processValue(key, val) - - var splitKey = key.split('.') + const value = processValue(key, val) + const splitKey = key.split('.') setKey(argv, splitKey, value) // handle populating aliases of the full key @@ -435,7 +439,7 @@ function parse (args, opts) { x = x.split('.') // expand alias with nested objects in key - var a = [].concat(splitKey) + const a = [].concat(splitKey) a.shift() // nuke the old key. x = x.concat(a) @@ -445,14 +449,15 @@ function parse (args, opts) { // Set normalize getter and setter when key is in 'normalize' but isn't an array if (checkAllAliases(key, flags.normalize) && !checkAllAliases(key, flags.arrays)) { - var keys = [key].concat(flags.aliases[key] || []) + const keys = [key].concat(flags.aliases[key] || []) keys.forEach(function (key) { - argv.__defineSetter__(key, function (v) { - val = path.normalize(v) - }) - - argv.__defineGetter__(key, function () { - return typeof val === 'string' ? path.normalize(val) : val + Object.defineProperty(argvReturn, key, { + get () { + return val + }, + set (value) { + val = typeof value === 'string' ? path.normalize(value) : value + } }) }) } @@ -482,7 +487,7 @@ function parse (args, opts) { if (typeof val === 'string') val = val === 'true' } - var value = Array.isArray(val) + let value = Array.isArray(val) ? val.map(function (v) { return maybeCoerceNumber(key, v) }) : maybeCoerceNumber(key, val) @@ -512,18 +517,18 @@ function parse (args, opts) { // set args from config.json file, this should be // applied last so that defaults can be applied. function setConfig (argv) { - var configLookup = {} + const configLookup = Object.create(null) // expand defaults/aliases, in-case any happen to reference // the config.json file. applyDefaultsAndAliases(configLookup, flags.aliases, defaults) Object.keys(flags.configs).forEach(function (configKey) { - var configPath = argv[configKey] || configLookup[configKey] + const configPath = argv[configKey] || configLookup[configKey] if (configPath) { try { - var config = null - var resolvedConfigPath = path.resolve(process.cwd(), configPath) + let config = null + const resolvedConfigPath = path.resolve(process.cwd(), configPath) if (typeof flags.configs[configKey] === 'function') { try { @@ -551,8 +556,8 @@ function parse (args, opts) { // it recursively checks nested objects. function setConfigObject (config, prev) { Object.keys(config).forEach(function (key) { - var value = config[key] - var fullKey = prev ? prev + '.' + key : key + const value = config[key] + const fullKey = prev ? prev + '.' + key : key // if the value is an inner object and we have dot-notation // enabled, treat inner objects in config the same as @@ -581,11 +586,11 @@ function parse (args, opts) { function applyEnvVars (argv, configOnly) { if (typeof envPrefix === 'undefined') return - var prefix = typeof envPrefix === 'string' ? envPrefix : '' + const prefix = typeof envPrefix === 'string' ? envPrefix : '' Object.keys(process.env).forEach(function (envVar) { if (prefix === '' || envVar.lastIndexOf(prefix, 0) === 0) { // get array of nested keys and convert them to camel case - var keys = envVar.split('__').map(function (key, i) { + const keys = envVar.split('__').map(function (key, i) { if (i === 0) { key = key.substring(prefix.length) } @@ -600,16 +605,17 @@ function parse (args, opts) { } function applyCoercions (argv) { - var coerce - var applied = {} + let coerce + const applied = new Set() Object.keys(argv).forEach(function (key) { - if (!applied.hasOwnProperty(key)) { // If we haven't already coerced this option via one of its aliases + if (!applied.has(key)) { // If we haven't already coerced this option via one of its aliases coerce = checkAllAliases(key, flags.coercions) if (typeof coerce === 'function') { try { - var value = maybeCoerceNumber(key, coerce(argv[key])) + const value = maybeCoerceNumber(key, coerce(argv[key])) ;([].concat(flags.aliases[key] || [], key)).forEach(ali => { - applied[ali] = argv[ali] = value + applied.add(ali) + argv[ali] = value }) } catch (err) { error = err @@ -643,7 +649,7 @@ function parse (args, opts) { } function hasKey (obj, keys) { - var o = obj + let o = obj if (!configuration['dot-notation']) keys = [keys.join('.')] @@ -651,14 +657,14 @@ function parse (args, opts) { o = (o[key] || {}) }) - var key = keys[keys.length - 1] + const key = keys[keys.length - 1] if (typeof o !== 'object') return false else return key in o } function setKey (obj, keys, value) { - var o = obj + let o = obj if (!configuration['dot-notation']) keys = [keys.join('.')] @@ -682,11 +688,10 @@ function parse (args, opts) { } }) - var key = keys[keys.length - 1] - - var isTypeArray = checkAllAliases(keys.join('.'), flags.arrays) - var isValueArray = Array.isArray(value) - var duplicate = configuration['duplicate-arguments-array'] + const key = keys[keys.length - 1] + const isTypeArray = checkAllAliases(keys.join('.'), flags.arrays) + const isValueArray = Array.isArray(value) + let duplicate = configuration['duplicate-arguments-array'] // nargs has higher priority than duplicate if (!duplicate && checkAllAliases(key, flags.nargs)) { @@ -728,7 +733,7 @@ function parse (args, opts) { // For "--option-name", also set argv.optionName flags.aliases[key].concat(key).forEach(function (x) { if (/-/.test(x) && configuration['camel-case-expansion']) { - var c = camelCase(x) + const c = camelCase(x) if (c !== key && flags.aliases[key].indexOf(c) === -1) { flags.aliases[key].push(c) newAliases[c] = true @@ -738,7 +743,7 @@ function parse (args, opts) { // For "--optionName", also set argv['option-name'] flags.aliases[key].concat(key).forEach(function (x) { if (x.length > 1 && /[A-Z]/.test(x) && configuration['camel-case-expansion']) { - var c = decamelize(x, '-') + const c = decamelize(x, '-') if (c !== key && flags.aliases[key].indexOf(c) === -1) { flags.aliases[key].push(c) newAliases[c] = true @@ -756,24 +761,23 @@ function parse (args, opts) { // return the 1st set flag for any of a key's aliases (or false if no flag set) function checkAllAliases (key, flag) { - var toCheck = [].concat(flags.aliases[key] || [], key) - let setAlias = toCheck.find(key => flag.hasOwnProperty(key)) + const toCheck = [].concat(flags.aliases[key] || [], key) + const keys = Object.keys(flag) + let setAlias = toCheck.find(key => keys.includes(key)) return setAlias ? flag[setAlias] : false } function hasAnyFlag (key) { - // XXX Switch to [].concat(Object.values(flags)) once node.js 6 is dropped - var toCheck = [].concat(Object.keys(flags).map(k => flags[k])) - + const toCheck = [].concat(Object.keys(flags).map(k => flags[k])) return toCheck.some(function (flag) { - return Array.isArray(flag) ? flag.includes(key) : flag.hasOwnProperty(key) && flag[key] + return Array.isArray(flag) ? flag.includes(key) : flag[key] }) } function hasFlagsMatching (arg, ...patterns) { - var toCheck = [].concat(...patterns) + const toCheck = [].concat(...patterns) return toCheck.some(function (pattern) { - var match = arg.match(pattern) + const match = arg.match(pattern) return match && hasAnyFlag(match[1]) }) } @@ -782,10 +786,10 @@ function parse (args, opts) { function hasAllShortFlags (arg) { // if this is a negative number, or doesn't start with a single hyphen, it's not a short flag group if (arg.match(negative) || !arg.match(/^-[^-]+/)) { return false } - var hasAllFlags = true - var letters = arg.slice(1).split('') - var next - for (var j = 0; j < letters.length; j++) { + let hasAllFlags = true + let next + const letters = arg.slice(1).split('') + for (let j = 0; j < letters.length; j++) { next = arg.slice(j + 2) if (!hasAnyFlag(letters[j])) { @@ -841,7 +845,7 @@ function parse (args, opts) { // return a default value, given the type of a flag., // e.g., key of type 'string' will default to '', rather than 'true'. function defaultForType (type) { - var def = { + const def = { boolean: true, string: '', number: undefined, @@ -853,13 +857,11 @@ function parse (args, opts) { // given a flag, enforce a default type. function guessType (key) { - var type = 'boolean' - + let type = 'boolean' if (checkAllAliases(key, flags.strings)) type = 'string' else if (checkAllAliases(key, flags.numbers)) type = 'number' else if (checkAllAliases(key, flags.bools)) type = 'boolean' else if (checkAllAliases(key, flags.arrays)) type = 'array' - return type } @@ -893,11 +895,11 @@ function parse (args, opts) { } return { - argv: argv, + argv: Object.assign(argvReturn, argv), error: error, - aliases: flags.aliases, - newAliases: newAliases, - defaulted: defaulted, + aliases: Object.assign({}, flags.aliases), + newAliases: Object.assign({}, newAliases), + defaulted: Object.assign({}, defaulted), configuration: configuration } } @@ -905,9 +907,9 @@ function parse (args, opts) { // if any aliases reference each other, we should // merge them together. function combineAliases (aliases) { - var aliasArrays = [] - var change = true - var combined = {} + const aliasArrays = [] + const combined = Object.create(null) + let change = true // turn alias lookup hash {key: ['alias1', 'alias2']} into // a simple array ['key', 'alias1', 'alias2'] @@ -921,9 +923,9 @@ function combineAliases (aliases) { // made in an iteration. while (change) { change = false - for (var i = 0; i < aliasArrays.length; i++) { - for (var ii = i + 1; ii < aliasArrays.length; ii++) { - var intersect = aliasArrays[i].filter(function (v) { + for (let i = 0; i < aliasArrays.length; i++) { + for (let ii = i + 1; ii < aliasArrays.length; ii++) { + const intersect = aliasArrays[i].filter(function (v) { return aliasArrays[ii].indexOf(v) !== -1 }) @@ -957,8 +959,7 @@ function increment (orig) { } function Parser (args, opts) { - var result = parse(args.slice(), opts) - + const result = parse(args.slice(), opts) return result.argv } diff --git a/lib/tokenize-arg-string.js b/lib/tokenize-arg-string.js index fe05e27f..5bd5bad5 100644 --- a/lib/tokenize-arg-string.js +++ b/lib/tokenize-arg-string.js @@ -6,13 +6,13 @@ module.exports = function (argString) { argString = argString.trim() - var i = 0 - var prevC = null - var c = null - var opening = null - var args = [] + let i = 0 + let prevC = null + let c = null + let opening = null + let args = [] - for (var ii = 0; ii < argString.length; ii++) { + for (let ii = 0; ii < argString.length; ii++) { prevC = c c = argString.charAt(ii) diff --git a/package.json b/package.json index 21c64286..1b3f7b09 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "author": "Ben Coe ", "license": "ISC", "devDependencies": { - "c8": "^6.0.0", + "c8": "^7.0.1", "chai": "^4.2.0", "coveralls": "^3.0.2", "mocha": "^5.2.0", diff --git a/test/fixtures/config.json b/test/fixtures/config.json index 43376463..1f571f24 100644 --- a/test/fixtures/config.json +++ b/test/fixtures/config.json @@ -3,5 +3,6 @@ "z": 55, "foo": "baz", "version": "1.0.2", - "truthy": true + "truthy": true, + "toString": "method name" } diff --git a/test/tokenize-arg-string.js b/test/tokenize-arg-string.js index 8575de65..2f35895c 100644 --- a/test/tokenize-arg-string.js +++ b/test/tokenize-arg-string.js @@ -1,52 +1,52 @@ /* global describe, it */ -var tokenizeArgString = require('../lib/tokenize-arg-string') +const tokenizeArgString = require('../lib/tokenize-arg-string') require('chai').should() -var expect = require('chai').expect +const expect = require('chai').expect describe('TokenizeArgString', function () { it('handles unquoted string', function () { - var args = tokenizeArgString('--foo 99') + const args = tokenizeArgString('--foo 99') args[0].should.equal('--foo') args[1].should.equal('99') }) it('handles unquoted numbers', function () { - var args = tokenizeArgString(['--foo', 9]) + const args = tokenizeArgString(['--foo', 9]) args[0].should.equal('--foo') args[1].should.equal('9') }) it('handles quoted string with no spaces', function () { - var args = tokenizeArgString("--foo 'hello'") + const args = tokenizeArgString("--foo 'hello'") args[0].should.equal('--foo') args[1].should.equal("'hello'") }) it('handles single quoted string with spaces', function () { - var args = tokenizeArgString("--foo 'hello world' --bar='foo bar'") + const args = tokenizeArgString("--foo 'hello world' --bar='foo bar'") args[0].should.equal('--foo') args[1].should.equal("'hello world'") args[2].should.equal("--bar='foo bar'") }) it('handles double quoted string with spaces', function () { - var args = tokenizeArgString('--foo "hello world" --bar="foo bar"') + const args = tokenizeArgString('--foo "hello world" --bar="foo bar"') args[0].should.equal('--foo') args[1].should.equal('"hello world"') args[2].should.equal('--bar="foo bar"') }) it('handles single quoted empty string', function () { - var args = tokenizeArgString('--foo \'\' --bar=\'\'') + const args = tokenizeArgString('--foo \'\' --bar=\'\'') args[0].should.equal('--foo') args[1].should.equal("''") args[2].should.equal("--bar=''") }) it('handles double quoted empty string', function () { - var args = tokenizeArgString('--foo "" --bar=""') + const args = tokenizeArgString('--foo "" --bar=""') args[0].should.equal('--foo') args[1].should.equal('""') args[2].should.equal('--bar=""') @@ -62,7 +62,7 @@ describe('TokenizeArgString', function () { // https://github.com/yargs/yargs-parser/pull/100 // https://github.com/yargs/yargs-parser/pull/106 it('ignores unneeded spaces', function () { - var args = tokenizeArgString(' foo bar "foo bar" ') + const args = tokenizeArgString(' foo bar "foo bar" ') args[0].should.equal('foo') expect(args[1]).equal('bar') expect(args[2]).equal('"foo bar"') diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 5ad0da0f..ebe0c89c 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -2,10 +2,10 @@ require('chai').should() -var expect = require('chai').expect -var fs = require('fs') -var parser = require('../') -var path = require('path') +const { expect } = require('chai') +const fs = require('fs') +const parser = require('../') +const path = require('path') describe('yargs-parser', function () { it('should parse a "short boolean"', function () { @@ -441,9 +441,8 @@ describe('yargs-parser', function () { parse.should.have.property('_').with.length(0) }) + let jsonPath = path.resolve(__dirname, './fixtures/config.json') describe('config', function () { - var jsonPath = path.resolve(__dirname, './fixtures/config.json') - // See: https://github.com/chevex/yargs/issues/12 it('should load options and values from default config if specified', function () { var argv = parser(['--foo', 'bar'], { @@ -1222,7 +1221,7 @@ describe('yargs-parser', function () { default: { foo: 'abc', 'bar.prop': 33, baz: 'x' }, configObjects: [{ baz: 'xyz' }] }) - parsed.argv.should.deep.equal({ '_': [], baz: 'xyz', foo: 'abc', bar: { prop: 33 } }) + expect(parsed.argv).to.eql({ '_': [], baz: 'xyz', foo: 'abc', bar: { prop: 33 } }) parsed.defaulted.should.deep.equal({ foo: true, 'bar.prop': true }) }) @@ -2997,6 +2996,20 @@ describe('yargs-parser', function () { k: true }) }) + // see: https://github.com/yargs/yargs/issues/1489 + it('should identify "hasOwnProperty" as unknown option', () => { + const argv = parser('--known-arg=1 --hasOwnProperty=33', { + number: ['known-arg'], + configuration: { + 'unknown-options-as-args': true + } + }) + argv.should.deep.equal({ + _: ['--hasOwnProperty=33'], + 'known-arg': 1, + 'knownArg': 1 + }) + }) }) }) @@ -3378,4 +3391,127 @@ describe('yargs-parser', function () { }) }) }) + + describe('prototype collisions', () => { + it('parses unknown argument colliding with prototype', () => { + var parse = parser(['--toString']) + parse.toString.should.equal(true) + }) + + it('parses unknown argument colliding with prototype, when unknown options as args', () => { + var parse = parser(['--toString'], { + configuration: { + 'unknown-options-as-args': true + } + }) + parse._.should.include('--toString') + }) + + it('handles "alias" colliding with prototype', () => { + var parse = parser(['-t', '99'], { + alias: { + toString: ['t'] + } + }) + parse.toString.should.equal(99) + parse.t.should.equal(99) + parse['to-string'].should.equal(99) + }) + + it('handles multiple args colliding with alias', () => { + var parse = parser(['--toString', '88', '--toString', '99']) + parse.toString.should.eql([88, 99]) + }) + + it('handle dot notation colliding with alias', () => { + var parse = parser(['--toString.cool', 'apple']) + parse.toString.cool.should.equal('apple') + }) + + it('handles "arrays" colliding with prototype', () => { + var parse = parser(['--toString', '99', '100'], { + array: ['toString'] + }) + parse.toString.should.eql([99, 100]) + }) + + it('handles "arrays" colliding with prototype', () => { + var parse = parser(['--toString', '99', '100'], { + array: ['toString'] + }) + parse.toString.should.eql([99, 100]) + }) + + it('handles "strings" colliding with prototype', () => { + var parse = parser(['--toString', '99'], { + string: ['toString'] + }) + parse.toString.should.eql('99') + }) + + it('handles "numbers" colliding with prototype', () => { + var parse = parser(['--toString', '99'], { + number: ['toString'], + configuration: { + 'parse-numbers': false + } + }) + parse.toString.should.eql(99) + }) + + it('handles "counts" colliding with prototype', () => { + var parse = parser(['--toString', '--toString', '--toString'], { + count: ['toString'] + }) + parse.toString.should.eql(3) + }) + + it('handles "normalize" colliding with prototype', () => { + var parse = parser(['--toString', './node_modules/chai'], { + normalize: ['toString'] + }) + parse.toString.should.include('node_modules') + }) + + it('handles "normalize" colliding with prototype', () => { + var parse = parser(['--toString', './node_modules/chai'], { + normalize: ['toString'] + }) + parse.toString.should.include('node_modules') + }) + + it('handles key in configuration file that collides with prototype', function () { + var argv = parser(['--foo', 'bar'], { + alias: { + z: 'zoom' + }, + default: { + settings: jsonPath + }, + config: 'settings' + }) + argv.toString.should.equal('method name') + }) + + it('handles "nargs" colliding with prototype', () => { + var parse = parser(['--toString', 'apple', 'banana', 'batman', 'robin'], { + narg: { + toString: 3 + } + }) + parse.toString.should.eql(['apple', 'banana', 'batman']) + parse._.should.eql(['robin']) + }) + + it('handles "coercions" colliding with prototype', () => { + var parse = parser(['--toString', '33'], { + coerce: { + toString: (val) => { + return val * 2 + } + } + }) + parse.toString.should.equal(66) + }) + }) }) From 978c69ce691457cb930435ab5cbadabb78c37a50 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sun, 9 Feb 2020 12:59:55 -0800 Subject: [PATCH 069/206] chore: add renovate configuration (#239) --- renovate.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 00000000..aecd3f8d --- /dev/null +++ b/renovate.json @@ -0,0 +1,8 @@ +{ + "extends": [ + "config:base", + ], + "pinVersions": false, + "rebaseStalePrs": true, + "gitAuthor": null +} From 53f97fbfbe32b72e77e53aab5ae54f8393062e8a Mon Sep 17 00:00:00 2001 From: bcoe Date: Sun, 9 Feb 2020 13:05:22 -0800 Subject: [PATCH 070/206] chore: fix renovate config --- renovate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index aecd3f8d..f6dd45e1 100644 --- a/renovate.json +++ b/renovate.json @@ -1,6 +1,6 @@ { "extends": [ - "config:base", + "config:base" ], "pinVersions": false, "rebaseStalePrs": true, From d36cdfa854254d7c7e0fe1d583818332ac46c2a5 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sun, 9 Feb 2020 13:42:41 -0800 Subject: [PATCH 071/206] fix: unknown options terminated with digits now handled by unknown-options-as-args (#238) --- index.js | 2 +- package.json | 7 +++---- test/yargs-parser.js | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 0e2381dc..1693fe86 100644 --- a/index.js +++ b/index.js @@ -823,7 +823,7 @@ function parse (args, opts) { // e.g. '-a-' const flagEndingInHyphen = /^-+([^=]+?)-$/ // e.g. '-abc123' - const flagEndingInDigits = /^-+([^=]+?)\d+$/ + const flagEndingInDigits = /^-+([^=]+?\d+)$/ // e.g. '-a/usr/local' const flagEndingInNonWordCharacters = /^-+([^=]+?)\W+.*$/ // check the different types of flag styles, including negatedBoolean, a pattern defined near the start of the parse method diff --git a/package.json b/package.json index 1b3f7b09..2850e2ca 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,10 @@ "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { + "fix": "standard --fix", "test": "c8 --reporter=text --reporter=html mocha test/*.js", "posttest": "standard", - "coverage": "c8 report --reporter=text-lcov | coveralls", - "release": "standard-version" + "coverage": "c8 report --reporter=text-lcov | coveralls" }, "repository": { "url": "git@github.com:yargs/yargs-parser.git" @@ -30,8 +30,7 @@ "chai": "^4.2.0", "coveralls": "^3.0.2", "mocha": "^5.2.0", - "standard": "^12.0.1", - "standard-version": "^6.0.0" + "standard": "^12.0.1" }, "dependencies": { "camelcase": "^5.0.0", diff --git a/test/yargs-parser.js b/test/yargs-parser.js index ebe0c89c..608482da 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -3011,6 +3011,22 @@ describe('yargs-parser', function () { }) }) }) + + // See: https://github.com/yargs/yargs-parser/issues/231 + it('should collect unknown options terminated with digit', function () { + const argv = parser('--known-arg=1 --num2', { + alias: { 'num': ['n'] }, + number: ['known-arg'], + configuration: { + 'unknown-options-as-args': true + } + }) + argv.should.deep.equal({ + _: ['--num2'], + 'known-arg': 1, + 'knownArg': 1 + }) + }) }) // addresses: https://github.com/yargs/yargs-parser/issues/41 From 11ea8d19e88b22f0a6e7cf9858591baea37796d5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 9 Feb 2020 14:16:22 -0800 Subject: [PATCH 072/206] chore(deps): update dependency mocha to v7 (#241) Co-authored-by: WhiteSource Renovate --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2850e2ca..c9cdda90 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "c8": "^7.0.1", "chai": "^4.2.0", "coveralls": "^3.0.2", - "mocha": "^5.2.0", + "mocha": "^7.0.0", "standard": "^12.0.1" }, "dependencies": { From 34c4e19bae4e7af63e3cb6fa654a97ed476e5eb5 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sun, 9 Feb 2020 17:45:50 -0800 Subject: [PATCH 073/206] feat!: boolean arguments will not be collected into an implicit array (#236) BREAKING CHANGE: this reverts parsing behavior of booleans to that of yargs@14 --- index.js | 6 +++++- test/yargs-parser.js | 17 +++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 1693fe86..658b679b 100644 --- a/index.js +++ b/index.js @@ -713,7 +713,11 @@ function parse (args, opts) { } } else if (o[key] === undefined && isTypeArray) { o[key] = isValueArray ? value : [value] - } else if (duplicate && !(o[key] === undefined || checkAllAliases(key, flags.counts))) { + } else if (duplicate && !( + o[key] === undefined || + checkAllAliases(key, flags.counts) || + checkAllAliases(key, flags.bools) + )) { o[key] = [ o[key], value ] } else { o[key] = value diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 608482da..30a0c6fe 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -2557,7 +2557,8 @@ describe('yargs-parser', function () { }) }) describe('type=boolean', function () { - it('[-x true -x true -x false] => [true, true, false]', function () { + // in the casse of boolean arguments, only the last argument is used: + it('[-x true -x true -x false] => false', function () { var parsed = parser('-x true -x true -x false', { boolean: ['x'], configuration: { @@ -2565,7 +2566,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': true } }) - parsed['x'].should.deep.equal([true, true, false]) + parsed['x'].should.deep.equal(false) }) }) }) @@ -2605,7 +2606,7 @@ describe('yargs-parser', function () { }) }) describe('type=boolean', function () { - it('[-x true -x true -x false] => [true, true, false]', function () { + it('[-x true -x true -x false] => false', function () { var parsed = parser('-x true -x true -x false', { boolean: ['x'], configuration: { @@ -2613,7 +2614,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': false } }) - parsed['x'].should.deep.equal([true, true, false]) + parsed['x'].should.deep.equal(false) }) }) }) @@ -3530,4 +3531,12 @@ describe('yargs-parser', function () { parse.toString.should.equal(66) }) }) + + // See: https://github.com/facebook/jest/issues/9517 + it('does not collect arguments configured as booleans into implicit array', () => { + var parse = parser(['--infinite', 'true', '--infinite', 'true', '--no-infinite'], { + boolean: 'infinite' + }) + parse.infinite.should.equal(false) + }) }) From 4cbc188b7abb2249529a19c090338debdad2fe6c Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sun, 9 Feb 2020 18:10:45 -0800 Subject: [PATCH 074/206] fix: array should take precedence over nargs, but enforce nargs (#243) --- index.js | 53 +++++++++++++++++++++++++------------------- test/yargs-parser.js | 24 ++++++++++++++++++++ 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/index.js b/index.js index 658b679b..c7f728ae 100644 --- a/index.js +++ b/index.js @@ -163,14 +163,14 @@ function parse (args, opts) { // http://stackoverflow.com/a/1068308/13216 m = arg.match(/^--?([^=]+)=([\s\S]*)$/) - // nargs format = '--f=monkey washing cat' - if (checkAllAliases(m[1], flags.nargs)) { - args.splice(i + 1, 0, m[2]) - i = eatNargs(i, m[1], args) // arrays format = '--f=a b c' - } else if (checkAllAliases(m[1], flags.arrays)) { + if (checkAllAliases(m[1], flags.arrays)) { args.splice(i + 1, 0, m[2]) i = eatArray(i, m[1], args) + } else if (checkAllAliases(m[1], flags.nargs)) { + // nargs format = '--f=monkey washing cat' + args.splice(i + 1, 0, m[2]) + i = eatNargs(i, m[1], args) } else { setArg(m[1], m[2]) } @@ -184,13 +184,13 @@ function parse (args, opts) { )) { key = arg.match(/^--?(.+)/)[1] - // nargs format = '--foo a b c' - // should be truthy even if: flags.nargs[key] === 0 - if (checkAllAliases(key, flags.nargs) !== false) { - i = eatNargs(i, key, args) - // array format = '--foo a b c' - } else if (checkAllAliases(key, flags.arrays)) { + if (checkAllAliases(key, flags.arrays)) { + // array format = '--foo a b c' i = eatArray(i, key, args) + } else if (checkAllAliases(key, flags.nargs) !== false) { + // nargs format = '--foo a b c' + // should be truthy even if: flags.nargs[key] === 0 + i = eatNargs(i, key, args) } else { next = args[i + 1] @@ -237,14 +237,14 @@ function parse (args, opts) { value = arg.slice(j + 3) key = letters[j] - // nargs format = '-f=monkey washing cat' - if (checkAllAliases(key, flags.nargs)) { - args.splice(i + 1, 0, value) - i = eatNargs(i, key, args) - // array format = '-f=a b c' - } else if (checkAllAliases(key, flags.arrays)) { + if (checkAllAliases(key, flags.arrays)) { + // array format = '-f=a b c' args.splice(i + 1, 0, value) i = eatArray(i, key, args) + } else if (checkAllAliases(key, flags.nargs)) { + // nargs format = '-f=monkey washing cat' + args.splice(i + 1, 0, value) + i = eatNargs(i, key, args) } else { setArg(key, value) } @@ -278,13 +278,13 @@ function parse (args, opts) { key = arg.slice(-1)[0] if (!broken && key !== '-') { - // nargs format = '-f a b c' - // should be truthy even if: flags.nargs[key] === 0 - if (checkAllAliases(key, flags.nargs) !== false) { - i = eatNargs(i, key, args) - // array format = '-f a b c' - } else if (checkAllAliases(key, flags.arrays)) { + if (checkAllAliases(key, flags.arrays)) { + // array format = '-f a b c' i = eatArray(i, key, args) + } else if (checkAllAliases(key, flags.nargs) !== false) { + // nargs format = '-f a b c' + // should be truthy even if: flags.nargs[key] === 0 + i = eatNargs(i, key, args) } else { next = args[i + 1] @@ -409,6 +409,13 @@ function parse (args, opts) { } } + // If both array and nargs are configured, create an error if less than + // nargs positionals were found: + const toEat = checkAllAliases(key, flags.nargs) + if (toEat && argsToSet.length < toEat) { + error = Error(__('Not enough arguments following: %s', key)) + } + setArg(key, argsToSet) return i } diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 30a0c6fe..dd69197c 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -3539,4 +3539,28 @@ describe('yargs-parser', function () { }) parse.infinite.should.equal(false) }) + + // See: https://github.com/yargs/yargs/issues/1098 + describe('array with nargs', () => { + it('allows array and nargs to be configured in conjunction, enforcing the nargs value', () => { + var parse = parser(['-a', 'apple', 'banana'], { + array: 'a', + narg: { + a: 1 + } + }) + parse.a.should.eql(['apple', 'banana']) + }) + + it('returns an error if not enough positionals were provided for nargs', () => { + var parse = parser.detailed(['-a'], { + array: 'a', + narg: { + a: 1 + } + }) + parse.argv.a.should.eql([]) + parse.error.message.should.equal('Not enough arguments following: a') + }) + }) }) From f2b3952f412519d7bff01b6bc91c683214e1c836 Mon Sep 17 00:00:00 2001 From: bcoe Date: Sun, 9 Feb 2020 18:21:13 -0800 Subject: [PATCH 075/206] chore: add decamelize to ignore until we can drop Node 8 --- renovate.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index f6dd45e1..5ec138e4 100644 --- a/renovate.json +++ b/renovate.json @@ -4,5 +4,6 @@ ], "pinVersions": false, "rebaseStalePrs": true, - "gitAuthor": null + "gitAuthor": null, + "ignoreDeps": ["decamelize"] } From 0dea619b28329e6358e47ed8bcc611850d950753 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sun, 9 Feb 2020 18:27:24 -0800 Subject: [PATCH 076/206] chore: update standard (#245) --- index.js | 4 +- lib/tokenize-arg-string.js | 2 +- package.json | 2 +- test/yargs-parser.js | 164 ++++++++++++++++++------------------- 4 files changed, 86 insertions(+), 86 deletions(-) diff --git a/index.js b/index.js index c7f728ae..f7a3d378 100644 --- a/index.js +++ b/index.js @@ -725,7 +725,7 @@ function parse (args, opts) { checkAllAliases(key, flags.counts) || checkAllAliases(key, flags.bools) )) { - o[key] = [ o[key], value ] + o[key] = [o[key], value] } else { o[key] = value } @@ -774,7 +774,7 @@ function parse (args, opts) { function checkAllAliases (key, flag) { const toCheck = [].concat(flags.aliases[key] || [], key) const keys = Object.keys(flag) - let setAlias = toCheck.find(key => keys.includes(key)) + const setAlias = toCheck.find(key => keys.includes(key)) return setAlias ? flag[setAlias] : false } diff --git a/lib/tokenize-arg-string.js b/lib/tokenize-arg-string.js index 5bd5bad5..260c67c1 100644 --- a/lib/tokenize-arg-string.js +++ b/lib/tokenize-arg-string.js @@ -10,7 +10,7 @@ module.exports = function (argString) { let prevC = null let c = null let opening = null - let args = [] + const args = [] for (let ii = 0; ii < argString.length; ii++) { prevC = c diff --git a/package.json b/package.json index c9cdda90..1fbff6b6 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "chai": "^4.2.0", "coveralls": "^3.0.2", "mocha": "^7.0.0", - "standard": "^12.0.1" + "standard": "^14.3.1" }, "dependencies": { "camelcase": "^5.0.0", diff --git a/test/yargs-parser.js b/test/yargs-parser.js index dd69197c..60068165 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -146,7 +146,7 @@ describe('yargs-parser', function () { '--other', '-99', '-220' ], { array: 'bounds', - narg: { 'other': 2 } + narg: { other: 2 } }) argv._.should.deep.equal([-33, -177, 33]) @@ -166,7 +166,7 @@ describe('yargs-parser', function () { '--other', '.9', '-.5' ], { array: 'bounds', - narg: { 'other': 2 } + narg: { other: 2 } }) argv._.should.deep.equal([-0.1, -1.1, -0.5, -0.1, 0.1, 0.5]) @@ -441,7 +441,7 @@ describe('yargs-parser', function () { parse.should.have.property('_').with.length(0) }) - let jsonPath = path.resolve(__dirname, './fixtures/config.json') + const jsonPath = path.resolve(__dirname, './fixtures/config.json') describe('config', function () { // See: https://github.com/chevex/yargs/issues/12 it('should load options and values from default config if specified', function () { @@ -1221,7 +1221,7 @@ describe('yargs-parser', function () { default: { foo: 'abc', 'bar.prop': 33, baz: 'x' }, configObjects: [{ baz: 'xyz' }] }) - expect(parsed.argv).to.eql({ '_': [], baz: 'xyz', foo: 'abc', bar: { prop: 33 } }) + expect(parsed.argv).to.eql({ _: [], baz: 'xyz', foo: 'abc', bar: { prop: 33 } }) parsed.defaulted.should.deep.equal({ foo: true, 'bar.prop': true }) }) @@ -1229,7 +1229,7 @@ describe('yargs-parser', function () { var parsed = parser.detailed('--foo --bar.prop', { default: { foo: 'abc', 'bar.prop': 33 } }) - parsed.argv.should.deep.equal({ '_': [], foo: 'abc', bar: { prop: 33 } }) + parsed.argv.should.deep.equal({ _: [], foo: 'abc', bar: { prop: 33 } }) parsed.defaulted.should.deep.equal({}) }) @@ -1238,7 +1238,7 @@ describe('yargs-parser', function () { default: { kaa: 'abc' }, alias: { foo: 'kaa' } }) - parsed.argv.should.deep.equal({ '_': [], kaa: 'abc', foo: 'abc' }) + parsed.argv.should.deep.equal({ _: [], kaa: 'abc', foo: 'abc' }) parsed.defaulted.should.deep.equal({ kaa: true }) }) @@ -1247,7 +1247,7 @@ describe('yargs-parser', function () { default: { kaa: 'abc' }, alias: { foo: 'kaa' } }) - parsed.argv.should.deep.equal({ '_': [], kaa: 'abc', foo: 'abc' }) + parsed.argv.should.deep.equal({ _: [], kaa: 'abc', foo: 'abc' }) parsed.defaulted.should.deep.equal({}) }) }) @@ -1536,7 +1536,7 @@ describe('yargs-parser', function () { it('should add an error if counter is also set as narg', function () { var argv = parser.detailed(['--counter', 'foo', 'bar'], { count: ['counter'], - narg: { 'counter': 2 } + narg: { counter: 2 } }) argv.error.message.should.equal('Invalid configuration: counter, opts.count excludes opts.narg.') @@ -1572,7 +1572,7 @@ describe('yargs-parser', function () { it('should default argument to empty array if no value given', function () { var result = parser(['-b', '--tag'], { array: ['b', 'tag'], - default: { 'tag': [] } + default: { tag: [] } }) result.b.should.deep.equal([]) result.tag.should.deep.equal([]) @@ -1581,7 +1581,7 @@ describe('yargs-parser', function () { it('should place default of argument in array, when default provided', function () { var result = parser(['-b', '--tag'], { array: ['b', 'tag'], - default: { 'b': 33, 'tag': ['foo'] } + default: { b: 33, tag: ['foo'] } }) result.b.should.deep.equal([33]) result.tag.should.deep.equal(['foo']) @@ -1701,8 +1701,8 @@ describe('yargs-parser', function () { var result = parser(['--some-option', '1', '2'], { array: ['someOption'] }) - Array.isArray(result['someOption']).should.equal(true) - result['someOption'].should.deep.equal([1, 2]) + Array.isArray(result.someOption).should.equal(true) + result.someOption.should.deep.equal([1, 2]) }) // see https://github.com/yargs/yargs-parser/issues/6 @@ -1946,7 +1946,7 @@ describe('yargs-parser', function () { var result = parser([], { envPrefix: 'AIRFORCE', alias: { - '1': ['one', 'uno'] + 1: ['one', 'uno'] } }) @@ -2191,7 +2191,7 @@ describe('yargs-parser', function () { it('does not expand alias of first element of dot notation arguments', function () { var parsed = parser(['--foo.bar', 'banana'], { alias: { - 'foo': ['f'] + foo: ['f'] }, configuration: { 'dot-notation': false @@ -2205,7 +2205,7 @@ describe('yargs-parser', function () { it('does not append nested-object keys from config to top-level key', function () { var parsed = parser([], { alias: { - 'foo': ['f'] + foo: ['f'] }, configuration: { 'dot-notation': false @@ -2231,14 +2231,14 @@ describe('yargs-parser', function () { it('does not coerce defaults into numbers', function () { var parsed = parser([], { default: { - 'foo': '5' + foo: '5' }, configuration: { 'parse-numbers': false } }) - expect(parsed['foo']).to.equal('5') + expect(parsed.foo).to.equal('5') }) it('does not coerce arguments into numbers', function () { @@ -2247,7 +2247,7 @@ describe('yargs-parser', function () { 'parse-numbers': false } }) - expect(parsed['foo']).to.equal('5') + expect(parsed.foo).to.equal('5') }) it('does not coerce positional arguments into numbers', function () { @@ -2263,15 +2263,15 @@ describe('yargs-parser', function () { var parsed = parser(['--foo', '5', '--bar', '6', '--baz', '7'], { number: ['bar', 'baz'], coerce: { - 'baz': val => val + baz: val => val }, configuration: { 'parse-numbers': false } }) - expect(parsed['foo']).to.equal('5') - expect(parsed['bar']).to.equal(6) - expect(parsed['baz']).to.equal(7) + expect(parsed.foo).to.equal('5') + expect(parsed.bar).to.equal(6) + expect(parsed.baz).to.equal(7) }) it('should coerce elements of number typed arrays to numbers', function () { @@ -2284,7 +2284,7 @@ describe('yargs-parser', function () { } }) - expect(parsed['foo']).to.deep.equal([[4], [5, 2], [1, 2, 3]]) + expect(parsed.foo).to.deep.equal([[4], [5, 2], [1, 2, 3]]) }) }) @@ -2307,7 +2307,7 @@ describe('yargs-parser', function () { } }) - expect(parsed['dice']).to.equal(false) + expect(parsed.dice).to.equal(false) }) }) @@ -2319,7 +2319,7 @@ describe('yargs-parser', function () { } }) - parsed['x'].should.deep.equal(['a', 'b']) + parsed.x.should.deep.equal(['a', 'b']) }) it('keeps only last argument', function () { var parsed = parser('-x a -x b', { @@ -2328,7 +2328,7 @@ describe('yargs-parser', function () { } }) - parsed['x'].should.equal('b') + parsed.x.should.equal('b') }) it('does not interfere with nargs', function () { var parsed = parser('-x a b c -x o p q', { @@ -2338,7 +2338,7 @@ describe('yargs-parser', function () { } }) - parsed['x'].should.deep.equal(['o', 'p', 'q']) + parsed.x.should.deep.equal(['o', 'p', 'q']) }) }) @@ -2351,7 +2351,7 @@ describe('yargs-parser', function () { } }) - parsed['x'].should.deep.equal(['a', 'b', 'c', 'd']) + parsed.x.should.deep.equal(['a', 'b', 'c', 'd']) }) it('nests duplicate array types', function () { var parsed = parser('-x a b -x c d', { @@ -2361,7 +2361,7 @@ describe('yargs-parser', function () { } }) - parsed['x'].should.deep.equal([['a', 'b'], ['c', 'd']]) + parsed.x.should.deep.equal([['a', 'b'], ['c', 'd']]) }) it('nests duplicate array types of more than 2', function () { var parsed = parser('-x a b -x c d -x e f -x g h', { @@ -2371,7 +2371,7 @@ describe('yargs-parser', function () { } }) - parsed['x'].should.deep.equal([['a', 'b'], ['c', 'd'], ['e', 'f'], ['g', 'h']]) + parsed.x.should.deep.equal([['a', 'b'], ['c', 'd'], ['e', 'f'], ['g', 'h']]) }) it('doesn\'t nests single arrays', function () { var parsed = parser('-x a b', { @@ -2381,7 +2381,7 @@ describe('yargs-parser', function () { } }) - parsed['x'].should.deep.equal(['a', 'b']) + parsed.x.should.deep.equal(['a', 'b']) }) it('flattens duplicate array type, when argument uses dot notation', function () { var parsed = parser('-x.foo a -x.foo b', { @@ -2391,7 +2391,7 @@ describe('yargs-parser', function () { } }) - parsed['x'].should.deep.equal({ foo: ['a', 'b'] }) + parsed.x.should.deep.equal({ foo: ['a', 'b'] }) }) }) @@ -2435,7 +2435,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': false } }) - parsed['x'].should.deep.equal([1, 2, 3]) + parsed.x.should.deep.equal([1, 2, 3]) }) it('[-x 1 2 3 -x 2 3 4] => [2, 3, 4]', function () { var parsed = parser('-x 1 2 3 -x 2 3 4', { @@ -2445,7 +2445,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': false } }) - parsed['x'].should.deep.equal([2, 3, 4]) + parsed.x.should.deep.equal([2, 3, 4]) }) }) describe('type=number', function () { @@ -2457,7 +2457,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': false } }) - parsed['x'].should.deep.equal(3) + parsed.x.should.deep.equal(3) }) }) describe('type=boolean', function () { @@ -2469,7 +2469,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': false } }) - parsed['x'].should.deep.equal(false) + parsed.x.should.deep.equal(false) }) }) }) @@ -2483,7 +2483,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': true } }) - parsed['x'].should.deep.equal([1, 2, 3]) + parsed.x.should.deep.equal([1, 2, 3]) }) it('[-x 1 2 3 -x 2 3 4] => [2, 3, 4]', function () { var parsed = parser('-x 1 2 3 -x 2 3 4', { @@ -2493,7 +2493,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': true } }) - parsed['x'].should.deep.equal([2, 3, 4]) + parsed.x.should.deep.equal([2, 3, 4]) }) }) describe('type=number', function () { @@ -2505,7 +2505,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': true } }) - parsed['x'].should.deep.equal(3) + parsed.x.should.deep.equal(3) }) }) describe('type=boolean', function () { @@ -2517,7 +2517,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': true } }) - parsed['x'].should.deep.equal(false) + parsed.x.should.deep.equal(false) }) }) }) @@ -2531,7 +2531,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': true } }) - parsed['x'].should.deep.equal([1, 2, 3]) + parsed.x.should.deep.equal([1, 2, 3]) }) it('[-x 1 2 3 -x 2 3 4] => [1, 2, 3, 2, 3, 4]', function () { var parsed = parser('-x 1 2 3 -x 2 3 4', { @@ -2541,7 +2541,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': true } }) - parsed['x'].should.deep.equal([1, 2, 3, 2, 3, 4]) + parsed.x.should.deep.equal([1, 2, 3, 2, 3, 4]) }) }) describe('type=number', function () { @@ -2553,7 +2553,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': true } }) - parsed['x'].should.deep.equal([1, 2, 3]) + parsed.x.should.deep.equal([1, 2, 3]) }) }) describe('type=boolean', function () { @@ -2566,7 +2566,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': true } }) - parsed['x'].should.deep.equal(false) + parsed.x.should.deep.equal(false) }) }) }) @@ -2580,7 +2580,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': false } }) - parsed['x'].should.deep.equal([[1], [2], [3]]) + parsed.x.should.deep.equal([[1], [2], [3]]) }) it('[-x 1 2 3 -x 2 3 4] => [[1, 2, 3], [ 2, 3, 4]]', function () { var parsed = parser('-x 1 2 3 -x 2 3 4', { @@ -2590,7 +2590,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': false } }) - parsed['x'].should.deep.equal([[1, 2, 3], [2, 3, 4]]) + parsed.x.should.deep.equal([[1, 2, 3], [2, 3, 4]]) }) }) describe('type=number', function () { @@ -2602,7 +2602,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': false } }) - parsed['x'].should.deep.equal([1, 2, 3]) + parsed.x.should.deep.equal([1, 2, 3]) }) }) describe('type=boolean', function () { @@ -2614,7 +2614,7 @@ describe('yargs-parser', function () { 'flatten-duplicate-arrays': false } }) - parsed['x'].should.deep.equal(false) + parsed.x.should.deep.equal(false) }) }) }) @@ -2638,7 +2638,7 @@ describe('yargs-parser', function () { 'short-option-groups': false } }) - result.argv.should.deep.equal({ '_': ['foo'] }) + result.argv.should.deep.equal({ _: ['foo'] }) result.argv.should.not.have.property('--') result.newAliases.should.deep.equal({}) }) @@ -2774,7 +2774,7 @@ describe('yargs-parser', function () { argv.should.deep.equal({ _: ['--unknown-arg=2'], 'known-arg': 1, - 'knownArg': 1 + knownArg: 1 }) }) it('should ignore unknown options in boolean negations', function () { @@ -2787,7 +2787,7 @@ describe('yargs-parser', function () { argv.should.deep.equal({ _: ['--no-unknown-arg'], 'known-arg': false, - 'knownArg': false + knownArg: false }) }) it('should ignore unknown options in long format separated by space', function () { @@ -2800,7 +2800,7 @@ describe('yargs-parser', function () { argv.should.deep.equal({ _: ['--unknown-arg', 'b'], 'known-arg': 'a', - 'knownArg': 'a' + knownArg: 'a' }) }) it('should ignore unknown options in short dot format separated by equals', function () { @@ -2812,8 +2812,8 @@ describe('yargs-parser', function () { }) argv.should.deep.equal({ _: ['-u.arg=b'], - 'k': { - 'arg': 'a' + k: { + arg: 'a' } }) }) @@ -2826,8 +2826,8 @@ describe('yargs-parser', function () { }) argv.should.deep.equal({ _: ['-u.arg', '2'], - 'k': { - 'arg': 1 + k: { + arg: 1 } }) }) @@ -2840,7 +2840,7 @@ describe('yargs-parser', function () { }) argv.should.deep.equal({ _: ['-u=b'], - 'k': 'a' + k: 'a' }) }) it('should ignore unknown options in short format followed by hyphen', function () { @@ -2852,7 +2852,7 @@ describe('yargs-parser', function () { }) argv.should.deep.equal({ _: ['-u-'], - 'k': '-' + k: '-' }) }) it('should ignore unknown options in short format separated by space', function () { @@ -2864,20 +2864,20 @@ describe('yargs-parser', function () { }) argv.should.deep.equal({ _: ['-u', '2'], - 'k': 1 + k: 1 }) }) it('should allow an unknown arg to be used as the value of another flag in short form', function () { const argv = parser('-k -u', { string: ['k'], - narg: { 'k': 1 }, + narg: { k: 1 }, configuration: { 'unknown-options-as-args': true } }) argv.should.deep.equal({ _: [], - 'k': '-u' + k: '-u' }) }) it('should allow an unknown arg to be used as the value of another flag in long form', function () { @@ -2890,7 +2890,7 @@ describe('yargs-parser', function () { }) argv.should.deep.equal({ _: [], - 'knownArg': '--unknown-arg', + knownArg: '--unknown-arg', 'known-arg': '--unknown-arg' }) }) @@ -2903,7 +2903,7 @@ describe('yargs-parser', function () { }) argv.should.deep.equal({ _: [], - 'knownArg': ['--unknown-arg1', '--unknown-arg2'], + knownArg: ['--unknown-arg1', '--unknown-arg2'], 'known-arg': ['--unknown-arg1', '--unknown-arg2'] }) }) @@ -2916,7 +2916,7 @@ describe('yargs-parser', function () { }) argv.should.deep.equal({ _: ['-u2'], - 'k': 1 + k: 1 }) }) it('should ignore unknown options in short format followed by a non-word character', function () { @@ -2928,7 +2928,7 @@ describe('yargs-parser', function () { }) argv.should.deep.equal({ _: ['-u/2/'], - 'k': '/1/' + k: '/1/' }) }) it('should ignore unknown options in short format with multiple flags in one argument where an unknown flag is before the end', function () { @@ -3008,7 +3008,7 @@ describe('yargs-parser', function () { argv.should.deep.equal({ _: ['--hasOwnProperty=33'], 'known-arg': 1, - 'knownArg': 1 + knownArg: 1 }) }) }) @@ -3016,7 +3016,7 @@ describe('yargs-parser', function () { // See: https://github.com/yargs/yargs-parser/issues/231 it('should collect unknown options terminated with digit', function () { const argv = parser('--known-arg=1 --num2', { - alias: { 'num': ['n'] }, + alias: { num: ['n'] }, number: ['known-arg'], configuration: { 'unknown-options-as-args': true @@ -3025,7 +3025,7 @@ describe('yargs-parser', function () { argv.should.deep.equal({ _: ['--num2'], 'known-arg': 1, - 'knownArg': 1 + knownArg: 1 }) }) }) @@ -3033,28 +3033,28 @@ describe('yargs-parser', function () { // addresses: https://github.com/yargs/yargs-parser/issues/41 it('defaults to empty array if array option is provided no values', function () { var parsed = parser(['-f'], { - 'alias': { - 'f': 'files' + alias: { + f: 'files' }, - 'array': ['files'] + array: ['files'] }) parsed.f.should.deep.equal([]) parsed.files.should.deep.equal([]) parsed = parser(['--files'], { - 'alias': { - 'f': 'files' + alias: { + f: 'files' }, - 'array': ['files'] + array: ['files'] }) parsed.f.should.deep.equal([]) parsed.files.should.deep.equal([]) parsed = parser(['-f', '-y'], { - 'alias': { - 'f': 'files' + alias: { + f: 'files' }, - 'array': ['files'] + array: ['files'] }) parsed.f.should.deep.equal([]) parsed.files.should.deep.equal([]) @@ -3225,7 +3225,7 @@ describe('yargs-parser', function () { var argv = parser(['--foo', 'bar'], { array: ['a'], normalize: ['a'], - configObjects: [{ 'a': ['bin/../a.txt', 'bin/../b.txt'] }] + configObjects: [{ a: ['bin/../a.txt', 'bin/../b.txt'] }] }) argv.a.should.deep.equal(['a.txt', 'b.txt']) }) @@ -3355,8 +3355,8 @@ describe('yargs-parser', function () { }) argv.should.deep.equal({ _: [], - 'testValue': 1, - 'altTest': 1 + testValue: 1, + altTest: 1 }) }) @@ -3373,7 +3373,7 @@ describe('yargs-parser', function () { argv.should.deep.equal({ _: [], 'test-value': 1, - 'testValue': 1 + testValue: 1 }) }) @@ -3390,7 +3390,7 @@ describe('yargs-parser', function () { }) argv.should.deep.equal({ _: [], - 'testValue': 1 + testValue: 1 }) }) From d50822ac10e1b05f2e9643671ca131ac251b6732 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sun, 9 Feb 2020 19:30:47 -0800 Subject: [PATCH 077/206] feat: introduce nargs-eats-options config option (#246) --- README.md | 7 +++++++ index.js | 22 ++++++++++++++-------- test/yargs-parser.js | 15 +++++++++++++++ 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b8596727..1678664d 100644 --- a/README.md +++ b/README.md @@ -265,6 +265,13 @@ node example.js -x 1 2 -x 3 4 { _: [], x: [[1, 2], [3, 4]] } ``` +### nargs eats options + +* default: `false` +* key: `nargs-eats-options` + +Should nargs consume dash options as well as positional arguments. + ### negation prefix * default: `no-` diff --git a/index.js b/index.js index f7a3d378..0c5ad4e9 100644 --- a/index.js +++ b/index.js @@ -27,7 +27,8 @@ function parse (args, opts) { 'halt-at-non-option': false, 'strip-aliased': false, 'strip-dashed': false, - 'unknown-options-as-args': false + 'unknown-options-as-args': false, + 'nargs-eats-options': false }, opts.configuration) const defaults = Object.assign(Object.create(null), opts.default) const configObjects = opts.configObjects || [] @@ -366,16 +367,21 @@ function parse (args, opts) { return i } - // nargs will not consume flag arguments, e.g., -abc, --foo, - // and terminates when one is observed. let available = 0 - for (ii = i + 1; ii < args.length; ii++) { - if (!args[ii].match(/^-[^0-9]/) || args[ii].match(negative) || isUnknownOptionAsArg(args[ii])) available++ - else break + if (configuration['nargs-eats-options']) { + // classic behavior, yargs eats positional and dash arguments. + if (args.length - (i + 1) < toEat) error = Error(__('Not enough arguments following: %s', key)) + available = toEat + } else { + // nargs will not consume flag arguments, e.g., -abc, --foo, + // and terminates when one is observed. + for (ii = i + 1; ii < args.length; ii++) { + if (!args[ii].match(/^-[^0-9]/) || args[ii].match(negative) || isUnknownOptionAsArg(args[ii])) available++ + else break + } + if (available < toEat) error = Error(__('Not enough arguments following: %s', key)) } - if (available < toEat) error = Error(__('Not enough arguments following: %s', key)) - const consumed = Math.min(available, toEat) for (ii = i + 1; ii < (consumed + i + 1); ii++) { setArg(key, args[ii]) diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 60068165..0fd4c6f0 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -1903,6 +1903,21 @@ describe('yargs-parser', function () { result.error.message.should.equal('Not enough arguments following: foo') }) + // See: https://github.com/yargs/yargs-parser/issues/232 + it('should treat flag arguments as satisfying narg requirements, if nargs-eats-options=true', function () { + var result = parser.detailed(['--foo', '--bar', '99', '--batman', 'robin'], { + narg: { + foo: 2 + }, + configuration: { + 'nargs-eats-options': true + } + }) + + result.argv.foo.should.eql(['--bar', 99]) + result.argv.batman.should.eql('robin') + }) + it('should not consume more than configured nargs', function () { var result = parser(['--foo', 'a', 'b'], { narg: { From 485d2817ba77aa1a930964d03b8ea4ddb265b686 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Sun, 9 Feb 2020 19:44:44 -0800 Subject: [PATCH 078/206] chore: release 17.0.0 (#235) * updated CHANGELOG.md [ci skip] * updated package.json [ci skip] --- CHANGELOG.md | 23 +++++++++++++++++++++++ package.json | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75cc3c19..eec2cc15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [17.0.0](https://www.github.com/yargs/yargs-parser/compare/v16.1.0...v17.0.0) (2020-02-10) + + +### ⚠ BREAKING CHANGES + +* this reverts parsing behavior of booleans to that of yargs@14 +* objects used during parsing are now created with a null +prototype. There may be some scenarios where this change in behavior +leaks externally. + +### Features + +* boolean arguments will not be collected into an implicit array ([#236](https://www.github.com/yargs/yargs-parser/issues/236)) ([34c4e19](https://www.github.com/yargs/yargs-parser/commit/34c4e19bae4e7af63e3cb6fa654a97ed476e5eb5)) +* introduce nargs-eats-options config option ([#246](https://www.github.com/yargs/yargs-parser/issues/246)) ([d50822a](https://www.github.com/yargs/yargs-parser/commit/d50822ac10e1b05f2e9643671ca131ac251b6732)) + + +### Bug Fixes + +* address bugs with "uknown-options-as-args" ([bc023e3](https://www.github.com/yargs/yargs-parser/commit/bc023e3b13e20a118353f9507d1c999bf388a346)) +* array should take precedence over nargs, but enforce nargs ([#243](https://www.github.com/yargs/yargs-parser/issues/243)) ([4cbc188](https://www.github.com/yargs/yargs-parser/commit/4cbc188b7abb2249529a19c090338debdad2fe6c)) +* support keys that collide with object prototypes ([#234](https://www.github.com/yargs/yargs-parser/issues/234)) ([1587b6d](https://www.github.com/yargs/yargs-parser/commit/1587b6d91db853a9109f1be6b209077993fee4de)) +* unknown options terminated with digits now handled by unknown-options-as-args ([#238](https://www.github.com/yargs/yargs-parser/issues/238)) ([d36cdfa](https://www.github.com/yargs/yargs-parser/commit/d36cdfa854254d7c7e0fe1d583818332ac46c2a5)) + ## [16.1.0](https://www.github.com/yargs/yargs-parser/compare/v16.0.0...v16.1.0) (2019-11-01) diff --git a/package.json b/package.json index 1fbff6b6..171131b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "16.1.0", + "version": "17.0.0", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From 57119f9f17cf27499bd95e61c2f72d18314f11ba Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sat, 29 Feb 2020 12:32:38 -0800 Subject: [PATCH 079/206] fix: normalized keys were not enumerable (#247) --- index.js | 1 + test/yargs-parser.js | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/index.js b/index.js index 0c5ad4e9..0e7448c2 100644 --- a/index.js +++ b/index.js @@ -465,6 +465,7 @@ function parse (args, opts) { const keys = [key].concat(flags.aliases[key] || []) keys.forEach(function (key) { Object.defineProperty(argvReturn, key, { + enumerable: true, get () { return val }, diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 0fd4c6f0..6b22360a 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -342,6 +342,16 @@ describe('yargs-parser', function () { a.should.have.property('s').and.deep.equal(expected) a.should.have.property('save').and.deep.equal(expected) }) + + it('should allow normalized keys to be enumerated', () => { + var a = parser(['-s', ['', 'tmp', '..', ''].join(path.sep)], { + alias: { + s: ['save'] + }, + normalize: 's' + }) + Object.keys(a).should.include('s') + }) }) describe('alias', function () { From b064ea9882ba662d5a4d1de5c040a4f7de114c76 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Sat, 29 Feb 2020 12:41:19 -0800 Subject: [PATCH 080/206] chore: release 17.0.1 (#248) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eec2cc15..8da2beab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [17.0.1](https://www.github.com/yargs/yargs-parser/compare/v17.0.0...v17.0.1) (2020-02-29) + + +### Bug Fixes + +* normalized keys were not enumerable ([#247](https://www.github.com/yargs/yargs-parser/issues/247)) ([57119f9](https://www.github.com/yargs/yargs-parser/commit/57119f9f17cf27499bd95e61c2f72d18314f11ba)) + ## [17.0.0](https://www.github.com/yargs/yargs-parser/compare/v16.1.0...v17.0.0) (2020-02-10) diff --git a/package.json b/package.json index 171131b9..b247e63a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "17.0.0", + "version": "17.0.1", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From 185816ab96ab640800e5013ece5cfee8323e1546 Mon Sep 17 00:00:00 2001 From: bcoe Date: Sat, 29 Feb 2020 12:52:24 -0800 Subject: [PATCH 081/206] build: configuring wombat dressing room for publication --- .github/publish.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/publish.yml diff --git a/.github/publish.yml b/.github/publish.yml new file mode 100644 index 00000000..e933b632 --- /dev/null +++ b/.github/publish.yml @@ -0,0 +1,2 @@ +project: oss-automation +secretId: node-tooling From b0d65c62a6658b98f5c0947e95206b57dd56b808 Mon Sep 17 00:00:00 2001 From: bcoe Date: Sat, 29 Feb 2020 13:01:21 -0800 Subject: [PATCH 082/206] build: use repository format that works with wombat dressing room --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index b247e63a..59c1e8be 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "coverage": "c8 report --reporter=text-lcov | coveralls" }, "repository": { - "url": "git@github.com:yargs/yargs-parser.git" + "type": "git", + "url": "https://github.com/yargs/yargs-parser.git" }, "keywords": [ "argument", From 60e880a837046314d89fa4725f923837fd33a9eb Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sat, 29 Feb 2020 17:31:00 -0800 Subject: [PATCH 083/206] feat: introduce greedy-arrays config, for specifying whether arrays consume multiple positionals (#249) --- .github/workflows/ci.yaml | 42 +++++++++++++++++++++++++++++++++++++++ .travis.yml | 17 ---------------- README.md | 21 ++++++++++++++++++++ index.js | 18 +++++++++-------- test/yargs-parser.js | 36 +++++++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/ci.yaml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..00dea345 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,42 @@ +on: + push: + branches: + - master + pull_request: +name: ci +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + node: [8, 10, 12, 13] + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node }} + - run: node --version + - run: npm install + - run: npm test + windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12 + - run: npm install + - run: npm test + coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: 12 + - run: npm install + - run: npm test + - run: npm run coverage + env: + COVERALLS_REPO_TOKEN: "${{ secrets.COVERALLS_REPO_TOKEN }}" + COVERALLS_GIT_BRANCH: "${{ github.ref }}" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 73a542d9..00000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: node_js -os: - - linux - - windows -node_js: - - "8" - - "10" - - "12" - - "13" - -jobs: - include: - - stage: coverage - node_js: "13" - script: - - npm t - - npm run coverage diff --git a/README.md b/README.md index 1678664d..6e9ea796 100644 --- a/README.md +++ b/README.md @@ -265,6 +265,27 @@ node example.js -x 1 2 -x 3 4 { _: [], x: [[1, 2], [3, 4]] } ``` +### greedy arrays + +* default: `true` +* key: `greedy-arrays` + +Should arrays consume more than one positional argument following their flag. + +```sh +node example --arr 1 2 +{ _[], arr: [1, 2] } +``` + +_if disabled:_ + +```sh +node example --arr 1 2 +{ _[2], arr: [1] } +``` + +**Note: in `v18.0.0` we are considering defaulting greedy arrays to `false`.** + ### nargs eats options * default: `false` diff --git a/index.js b/index.js index 0e7448c2..542f6812 100644 --- a/index.js +++ b/index.js @@ -13,22 +13,23 @@ function parse (args, opts) { // aliases might have transitive relationships, normalize this. const aliases = combineAliases(Object.assign(Object.create(null), opts.alias)) const configuration = Object.assign({ - 'short-option-groups': true, + 'boolean-negation': true, 'camel-case-expansion': true, + 'combine-arrays': false, 'dot-notation': true, - 'parse-numbers': true, - 'boolean-negation': true, - 'negation-prefix': 'no-', 'duplicate-arguments-array': true, 'flatten-duplicate-arrays': true, + 'greedy-arrays': true, + 'halt-at-non-option': false, + 'nargs-eats-options': false, + 'negation-prefix': 'no-', + 'parse-numbers': true, 'populate--': false, - 'combine-arrays': false, 'set-placeholder-key': false, - 'halt-at-non-option': false, + 'short-option-groups': true, 'strip-aliased': false, 'strip-dashed': false, - 'unknown-options-as-args': false, - 'nargs-eats-options': false + 'unknown-options-as-args': false }, opts.configuration) const defaults = Object.assign(Object.create(null), opts.default) const configObjects = opts.configObjects || [] @@ -412,6 +413,7 @@ function parse (args, opts) { if (/^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next)) break i = ii argsToSet.push(processValue(key, next)) + if (!configuration['greedy-arrays']) break } } diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 6b22360a..81605629 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -3588,4 +3588,40 @@ describe('yargs-parser', function () { parse.error.message.should.equal('Not enough arguments following: a') }) }) + + describe('greedy-arrays=false', () => { + it('does not consume more than one argument after array option', () => { + const argv = parser(['--arr', 'foo', 'bar'], { + array: 'arr', + configuration: { + 'greedy-arrays': false + } + }) + argv.arr.should.eql(['foo']) + argv._.should.eql(['bar']) + }) + + it('places argument into array when specified multiple times', () => { + const argv = parser(['--arr', 99, 'foo', '--arr', 'hello', 'bar'], { + array: 'arr', + configuration: { + 'greedy-arrays': false + } + }) + argv.arr.should.eql([99, 'hello']) + argv._.should.eql(['foo', 'bar']) + }) + + it('places boolean arguments into array when specified multiple times', () => { + const argv = parser(['--arr', 101, '--arr', 102, '--arr', 'false'], { + array: 'arr', + boolean: 'arr', + configuration: { + 'greedy-arrays': false + } + }) + argv.arr.should.eql([true, true, false]) + argv._.should.eql([101, 102]) + }) + }) }) From fa42eaec17fe0f8226335623bc774ac64d8f5c7e Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Sat, 29 Feb 2020 17:34:19 -0800 Subject: [PATCH 084/206] chore: release 17.1.0 (#250) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8da2beab..a45686c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [17.1.0](https://www.github.com/yargs/yargs-parser/compare/v17.0.1...v17.1.0) (2020-03-01) + + +### Features + +* introduce greedy-arrays config, for specifying whether arrays consume multiple positionals ([#249](https://www.github.com/yargs/yargs-parser/issues/249)) ([60e880a](https://www.github.com/yargs/yargs-parser/commit/60e880a837046314d89fa4725f923837fd33a9eb)) + ### [17.0.1](https://www.github.com/yargs/yargs-parser/compare/v17.0.0...v17.0.1) (2020-02-29) diff --git a/package.json b/package.json index 59c1e8be..be32725a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "17.0.1", + "version": "17.1.0", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From 9db4be81417a2c7097128db34d86fe70ef4af70c Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sun, 1 Mar 2020 21:39:41 -0800 Subject: [PATCH 085/206] feat!: NaN can now be provided as a value for nargs, indicating "at least" one value is expected for array (#251) BREAKING CHANGE: the narg count is now enforced when parsing arrays. --- index.js | 17 ++++++++++----- test/yargs-parser.js | 49 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 55 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index 542f6812..a0367bd3 100644 --- a/index.js +++ b/index.js @@ -361,7 +361,10 @@ function parse (args, opts) { // on the nargs option? function eatNargs (i, key, args) { let ii - const toEat = checkAllAliases(key, flags.nargs) + let toEat = checkAllAliases(key, flags.nargs) + // NaN has a special meaning for the array type, indicating that one or + // more values are expected. + toEat = isNaN(toEat) ? 1 : toEat if (toEat === 0) { setArg(key, defaultValue(key)) @@ -397,6 +400,8 @@ function parse (args, opts) { function eatArray (i, key, args) { let argsToSet = [] let next = args[i + 1] + // If both array and nargs are configured, enforce the nargs count: + const nargsCount = checkAllAliases(key, flags.nargs) if (checkAllAliases(key, flags.bools) && !(/^(true|false)$/.test(next))) { argsToSet.push(true) @@ -413,14 +418,16 @@ function parse (args, opts) { if (/^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next)) break i = ii argsToSet.push(processValue(key, next)) - if (!configuration['greedy-arrays']) break + if (!configuration['greedy-arrays'] || + (nargsCount && argsToSet.length >= nargsCount)) break } } // If both array and nargs are configured, create an error if less than - // nargs positionals were found: - const toEat = checkAllAliases(key, flags.nargs) - if (toEat && argsToSet.length < toEat) { + // nargs positionals were found. NaN has special meaning, indicating + // that at least one value is required (more are okay). + if ((nargsCount && argsToSet.length < nargsCount) || + (isNaN(nargsCount) && argsToSet.length === 0)) { error = Error(__('Not enough arguments following: %s', key)) } diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 81605629..4a16cacc 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -3565,28 +3565,65 @@ describe('yargs-parser', function () { parse.infinite.should.equal(false) }) - // See: https://github.com/yargs/yargs/issues/1098 + // See: https://github.com/yargs/yargs/issues/1098, + // https://github.com/yargs/yargs/issues/1570 describe('array with nargs', () => { it('allows array and nargs to be configured in conjunction, enforcing the nargs value', () => { - var parse = parser(['-a', 'apple', 'banana'], { + var parse = parser.detailed(['-a', 'apple', 'banana'], { array: 'a', narg: { a: 1 } }) - parse.a.should.eql(['apple', 'banana']) + expect(parse.error).to.be.null // eslint-disable-line + parse.argv.a.should.eql(['apple']) + parse.argv._.should.eql(['banana']) }) - it('returns an error if not enough positionals were provided for nargs', () => { + // see; https://github.com/yargs/yargs/issues/1098 + it('allows special NaN count to be provided to narg, to indicate one or more array values', () => { + var parse = parser.detailed(['-a', 'apple', 'banana'], { + array: 'a', + narg: { + a: NaN + } + }) + expect(parse.error).to.be.null // eslint-disable-line + parse.argv.a.should.eql(['apple', 'banana']) + }) + + it('throws error if at least one value not provided for NaN', () => { var parse = parser.detailed(['-a'], { array: 'a', narg: { - a: 1 + a: NaN + } + }) + parse.error.message.should.match(/Not enough arguments/) + }) + + it('returns an error if not enough positionals were provided for nargs', () => { + var parse = parser.detailed(['-a', '33'], { + array: 'a', + narg: { + a: 2 } }) - parse.argv.a.should.eql([]) + parse.argv.a.should.eql([33]) parse.error.message.should.equal('Not enough arguments following: a') }) + + it('does not raise error if no arguments are provided for boolean option', () => { + var parse = parser.detailed(['-a'], { + array: 'a', + boolean: 'a', + narg: { + a: NaN + } + }) + expect(parse.error).to.be.null // eslint-disable-line + parse.argv.a.should.eql([true]) + }) }) describe('greedy-arrays=false', () => { From d9284ec1466d9934b7be8039db27d73dcb4de8f8 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Sun, 1 Mar 2020 21:46:56 -0800 Subject: [PATCH 086/206] chore: release 18.0.0 (#252) --- CHANGELOG.md | 11 +++++++++++ package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a45686c5..59b990eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [18.0.0](https://www.github.com/yargs/yargs-parser/compare/v17.1.0...v18.0.0) (2020-03-02) + + +### ⚠ BREAKING CHANGES + +* the narg count is now enforced when parsing arrays. + +### Features + +* NaN can now be provided as a value for nargs, indicating "at least" one value is expected for array ([#251](https://www.github.com/yargs/yargs-parser/issues/251)) ([9db4be8](https://www.github.com/yargs/yargs-parser/commit/9db4be81417a2c7097128db34d86fe70ef4af70c)) + ## [17.1.0](https://www.github.com/yargs/yargs-parser/compare/v17.0.1...v17.1.0) (2020-03-01) diff --git a/package.json b/package.json index be32725a..5a491651 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "17.1.0", + "version": "18.0.0", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From 88f36c34e87262fcc5f15f0bc3f32e44a4c0ac22 Mon Sep 17 00:00:00 2001 From: bcoe Date: Sun, 1 Mar 2020 21:58:39 -0800 Subject: [PATCH 087/206] force release From 87e0a2136156fb45a0bd8102472915a75697a2e0 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sat, 7 Mar 2020 11:26:59 -0800 Subject: [PATCH 088/206] test: use bin to enforce coverage thresholds (#256) --- .github/workflows/ci.yaml | 5 +---- README.md | 1 - package.json | 3 +-- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 00dea345..82416464 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,10 +33,7 @@ jobs: - uses: actions/checkout@v1 - uses: actions/setup-node@v1 with: - node-version: 12 + node-version: 13 - run: npm install - run: npm test - run: npm run coverage - env: - COVERALLS_REPO_TOKEN: "${{ secrets.COVERALLS_REPO_TOKEN }}" - COVERALLS_GIT_BRANCH: "${{ github.ref }}" diff --git a/README.md b/README.md index 6e9ea796..bae61c2a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # yargs-parser [![Build Status](https://travis-ci.org/yargs/yargs-parser.svg)](https://travis-ci.org/yargs/yargs-parser) -[![Coverage Status](https://coveralls.io/repos/yargs/yargs-parser/badge.svg?branch=)](https://coveralls.io/r/yargs/yargs-parser?branch=master) [![NPM version](https://img.shields.io/npm/v/yargs-parser.svg)](https://www.npmjs.com/package/yargs-parser) [![Standard Version](https://img.shields.io/badge/release-standard%20version-brightgreen.svg)](https://github.com/conventional-changelog/standard-version) diff --git a/package.json b/package.json index 5a491651..74ba716b 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "fix": "standard --fix", "test": "c8 --reporter=text --reporter=html mocha test/*.js", "posttest": "standard", - "coverage": "c8 report --reporter=text-lcov | coveralls" + "coverage": "c8 report --check-coverage check-coverage --lines=100 --branches=97 --statements=100" }, "repository": { "type": "git", @@ -29,7 +29,6 @@ "devDependencies": { "c8": "^7.0.1", "chai": "^4.2.0", - "coveralls": "^3.0.2", "mocha": "^7.0.0", "standard": "^14.3.1" }, From 9c60265fd7a03cb98e6df3e32c8c5e7508d9f56f Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Sat, 7 Mar 2020 14:33:25 -0500 Subject: [PATCH 089/206] feat: introduce single-digit boolean aliases (#255) --- index.js | 7 ++++++- test/yargs-parser.js | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index a0367bd3..5c6547d9 100644 --- a/index.js +++ b/index.js @@ -304,6 +304,12 @@ function parse (args, opts) { } } } + } else if (arg.match(/^-[0-9]$/) && + arg.match(negative) && + checkAllAliases(arg.slice(1), flags.bools)) { + // single-digit boolean alias, e.g: xargs -0 + key = arg.slice(1) + setArg(key, defaultValue(key)) } else if (arg === '--') { notFlags = args.slice(i + 1) break @@ -347,7 +353,6 @@ function parse (args, opts) { } if (configuration['strip-aliased']) { - // XXX Switch to [].concat(...Object.values(aliases)) once node.js 6 is dropped ;[].concat(...Object.keys(aliases).map(k => aliases[k])).forEach(alias => { if (configuration['camel-case-expansion']) { delete argv[alias.split('.').map(prop => camelCase(prop)).join('.')] diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 4a16cacc..3e138998 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -438,6 +438,47 @@ describe('yargs-parser', function () { argv.should.have.property('zm', 55) argv.should.have.property('f', 11) }) + + it('should set single-digit boolean alias', function () { + var argv = parser(['-f', '11', '-0'], { + alias: { + 0: 'print0' + }, + boolean: [ + 'print0' + ] + }) + argv.should.have.property('print0', true) + argv.should.have.property('f', 11) + }) + + it('should not set single-digit alias if no alias defined', function () { + var argv = parser(['-f', '11', '-0', '-1']) + argv.should.have.property('f', 11) + argv._.should.deep.equal([-0, -1]) + }) + + it('should not set single-digit boolean alias if no boolean defined', function () { + var argv = parser(['-f', '11', '-9'], { + alias: { + 0: 'print0' + } + }) + argv.should.have.property('f', 11) + argv._.should.deep.equal([-9]) + }) + + it('should be able to negate set single-digit boolean alias', function () { + var argv = parser(['--no-9'], { + alias: { + 9: 'max' + }, + boolean: [ + 'max' + ] + }) + argv.should.have.property('max', false) + }) }) it('should assign data after forward slash to the option before the slash', function () { From 48b6d9ccfa4bb3f74a7f374d8f400491127a8477 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Sat, 7 Mar 2020 11:41:26 -0800 Subject: [PATCH 090/206] chore: release 18.1.0 (#257) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59b990eb..43896b7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [18.1.0](https://www.github.com/yargs/yargs-parser/compare/v18.0.0...v18.1.0) (2020-03-07) + + +### Features + +* introduce single-digit boolean aliases ([#255](https://www.github.com/yargs/yargs-parser/issues/255)) ([9c60265](https://www.github.com/yargs/yargs-parser/commit/9c60265fd7a03cb98e6df3e32c8c5e7508d9f56f)) + ## [18.0.0](https://www.github.com/yargs/yargs-parser/compare/v17.1.0...v18.0.0) (2020-03-02) diff --git a/package.json b/package.json index 74ba716b..da51adfe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "18.0.0", + "version": "18.1.0", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From 63810ca1ae1a24b08293a4d971e70e058c7a41e2 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Mon, 16 Mar 2020 00:08:00 -0700 Subject: [PATCH 091/206] fix: __proto__ will now be replaced with ___proto___ in parse (#258) --- index.js | 16 +++++++++++++- test/fixtures/config.json | 10 ++++++++- test/yargs-parser.js | 46 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 5c6547d9..b7a18391 100644 --- a/index.js +++ b/index.js @@ -697,6 +697,10 @@ function parse (args, opts) { if (!configuration['dot-notation']) keys = [keys.join('.')] keys.slice(0, -1).forEach(function (key, index) { + // TODO(bcoe): in the next major version of yargs, switch to + // Object.create(null) for dot notation: + key = sanitizeKey(key) + if (typeof o === 'object' && o[key] === undefined) { o[key] = {} } @@ -716,7 +720,10 @@ function parse (args, opts) { } }) - const key = keys[keys.length - 1] + // TODO(bcoe): in the next major version of yargs, switch to + // Object.create(null) for dot notation: + const key = sanitizeKey(keys[keys.length - 1]) + const isTypeArray = checkAllAliases(keys.join('.'), flags.arrays) const isValueArray = Array.isArray(value) let duplicate = configuration['duplicate-arguments-array'] @@ -1001,4 +1008,11 @@ Parser.detailed = function (args, opts) { return parse(args.slice(), opts) } +// TODO(bcoe): in the next major version of yargs, switch to +// Object.create(null) for dot notation: +function sanitizeKey (key) { + if (key === '__proto__') return '___proto___' + return key +} + module.exports = Parser diff --git a/test/fixtures/config.json b/test/fixtures/config.json index 1f571f24..7065ae61 100644 --- a/test/fixtures/config.json +++ b/test/fixtures/config.json @@ -4,5 +4,13 @@ "foo": "baz", "version": "1.0.2", "truthy": true, - "toString": "method name" + "toString": "method name", + "__proto__": { + "aaa": 99 + }, + "bar": { + "__proto__": { + "bbb": 100 + } + } } diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 3e138998..217f188f 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -727,6 +727,25 @@ describe('yargs-parser', function () { argv.error.message.should.equal('someone set us up the bomb') }) + + it('should not pollute the prototype', function () { + const argv = parser(['--foo', 'bar'], { + alias: { + z: 'zoom' + }, + default: { + settings: jsonPath + }, + config: 'settings' + }) + + argv.should.have.property('herp', 'derp') + argv.should.have.property('zoom', 55) + argv.should.have.property('foo').and.deep.equal('bar') + + expect({}.bbb).to.equal(undefined) + expect({}.aaa).to.equal(undefined) + }) }) describe('config objects', function () { @@ -974,6 +993,13 @@ describe('yargs-parser', function () { argv.f.foo.should.eql(99) argv.f.bar.should.eql(true) }) + + it('should not pollute the prototype', function () { + parser(['-f.__proto__.foo', '99', '-x.y.__proto__.bar', '100', '--__proto__', '200']) + Object.keys({}.__proto__).length.should.equal(0) // eslint-disable-line + expect({}.foo).to.equal(undefined) + expect({}.bar).to.equal(undefined) + }) }) it('should set boolean and alias using explicit true', function () { @@ -3702,4 +3728,24 @@ describe('yargs-parser', function () { argv._.should.eql([101, 102]) }) }) + + it('should replace the key __proto__ with the key ___proto___', function () { + const argv = parser(['-f.__proto__.foo', '99', '-x.y.__proto__.bar', '100', '--__proto__', '200']) + argv.should.eql({ + _: [], + ___proto___: 200, + f: { + ___proto___: { + foo: 99 + } + }, + x: { + y: { + ___proto___: { + bar: 100 + } + } + } + }) + }) }) From b96b989680be8259ed530876c1e6cb69608e19ef Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2020 00:17:41 -0700 Subject: [PATCH 092/206] chore: release 18.1.1 (#259) --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43896b7d..b5236a11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [18.1.1](https://www.github.com/yargs/yargs-parser/compare/v18.1.0...v18.1.1) (2020-03-16) + + +### Bug Fixes + +* \_\_proto\_\_ will now be replaced with \_\_\_proto\_\_\_ in parse ([#258](https://www.github.com/yargs/yargs-parser/issues/258)), patching a potential +prototype pollution vulnerability. This was reported by the Snyk Security Research Team.([63810ca](https://www.github.com/yargs/yargs-parser/commit/63810ca1ae1a24b08293a4d971e70e058c7a41e2)) + ## [18.1.0](https://www.github.com/yargs/yargs-parser/compare/v18.0.0...v18.1.0) (2020-03-07) diff --git a/package.json b/package.json index da51adfe..778f1ef0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "18.1.0", + "version": "18.1.1", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From 41d3f8139e116706b28de9b0de3433feb08d2f13 Mon Sep 17 00:00:00 2001 From: Mael Le Guen Date: Thu, 26 Mar 2020 18:07:28 +0100 Subject: [PATCH 093/206] fix(array, nargs): support -o=--value and --option=--value format (#262) --- index.js | 48 ++++++++++++++++++++-------------- test/yargs-parser.js | 61 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 19 deletions(-) diff --git a/index.js b/index.js index b7a18391..4fe26632 100644 --- a/index.js +++ b/index.js @@ -167,12 +167,10 @@ function parse (args, opts) { // arrays format = '--f=a b c' if (checkAllAliases(m[1], flags.arrays)) { - args.splice(i + 1, 0, m[2]) - i = eatArray(i, m[1], args) - } else if (checkAllAliases(m[1], flags.nargs)) { + i = eatArray(i, m[1], args, m[2]) + } else if (checkAllAliases(m[1], flags.nargs) !== false) { // nargs format = '--f=monkey washing cat' - args.splice(i + 1, 0, m[2]) - i = eatNargs(i, m[1], args) + i = eatNargs(i, m[1], args, m[2]) } else { setArg(m[1], m[2]) } @@ -241,12 +239,10 @@ function parse (args, opts) { if (checkAllAliases(key, flags.arrays)) { // array format = '-f=a b c' - args.splice(i + 1, 0, value) - i = eatArray(i, key, args) - } else if (checkAllAliases(key, flags.nargs)) { + i = eatArray(i, key, args, value) + } else if (checkAllAliases(key, flags.nargs) !== false) { // nargs format = '-f=monkey washing cat' - args.splice(i + 1, 0, value) - i = eatNargs(i, key, args) + i = eatNargs(i, key, args, value) } else { setArg(key, value) } @@ -364,7 +360,7 @@ function parse (args, opts) { // how many arguments should we consume, based // on the nargs option? - function eatNargs (i, key, args) { + function eatNargs (i, key, args, argAfterEqualSign) { let ii let toEat = checkAllAliases(key, flags.nargs) // NaN has a special meaning for the array type, indicating that one or @@ -372,14 +368,19 @@ function parse (args, opts) { toEat = isNaN(toEat) ? 1 : toEat if (toEat === 0) { + if (!isUndefined(argAfterEqualSign)) { + error = Error(__('Argument unexpected for: %s', key)) + } setArg(key, defaultValue(key)) return i } - let available = 0 + let available = isUndefined(argAfterEqualSign) ? 0 : 1 if (configuration['nargs-eats-options']) { // classic behavior, yargs eats positional and dash arguments. - if (args.length - (i + 1) < toEat) error = Error(__('Not enough arguments following: %s', key)) + if (args.length - (i + 1) + available < toEat) { + error = Error(__('Not enough arguments following: %s', key)) + } available = toEat } else { // nargs will not consume flag arguments, e.g., -abc, --foo, @@ -391,7 +392,11 @@ function parse (args, opts) { if (available < toEat) error = Error(__('Not enough arguments following: %s', key)) } - const consumed = Math.min(available, toEat) + let consumed = Math.min(available, toEat) + if (!isUndefined(argAfterEqualSign) && consumed > 0) { + setArg(key, argAfterEqualSign) + consumed-- + } for (ii = i + 1; ii < (consumed + i + 1); ii++) { setArg(key, args[ii]) } @@ -402,15 +407,16 @@ function parse (args, opts) { // if an option is an array, eat all non-hyphenated arguments // following it... YUM! // e.g., --foo apple banana cat becomes ["apple", "banana", "cat"] - function eatArray (i, key, args) { + function eatArray (i, key, args, argAfterEqualSign) { let argsToSet = [] - let next = args[i + 1] + let next = argAfterEqualSign || args[i + 1] // If both array and nargs are configured, enforce the nargs count: const nargsCount = checkAllAliases(key, flags.nargs) if (checkAllAliases(key, flags.bools) && !(/^(true|false)$/.test(next))) { argsToSet.push(true) - } else if (isUndefined(next) || (/^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next))) { + } else if (isUndefined(next) || + (isUndefined(argAfterEqualSign) && /^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next))) { // for keys without value ==> argsToSet remains an empty [] // set user default value, if available if (defaults[key] !== undefined) { @@ -418,13 +424,17 @@ function parse (args, opts) { argsToSet = Array.isArray(defVal) ? defVal : [defVal] } } else { + // value in --option=value is eaten as is + if (!isUndefined(argAfterEqualSign)) { + argsToSet.push(processValue(key, argAfterEqualSign)) + } for (let ii = i + 1; ii < args.length; ii++) { + if ((!configuration['greedy-arrays'] && argsToSet.length > 0) || + (nargsCount && argsToSet.length >= nargsCount)) break next = args[ii] if (/^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next)) break i = ii argsToSet.push(processValue(key, next)) - if (!configuration['greedy-arrays'] || - (nargsCount && argsToSet.length >= nargsCount)) break } } diff --git a/test/yargs-parser.js b/test/yargs-parser.js index 217f188f..aa7399ee 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -1748,6 +1748,20 @@ describe('yargs-parser', function () { result['1'][1].should.equal('b') }) + it('should support array for -f= and --bar= format when the value is dashed', function () { + var result = parser(['-f=--dog', 'cat', '--bar=--red', 'green'], { + array: ['f', 'bar'] + }) + + Array.isArray(result.f).should.equal(true) + result.f[0].should.equal('--dog') + result.f[1].should.equal('cat') + + Array.isArray(result.bar).should.equal(true) + result.bar[0].should.equal('--red') + result.bar[1].should.equal('green') + }) + it('should create an array when passing an argument twice with same value', function () { var result = parser(['-x', 'val1', '-x', 'val1']) result.should.have.property('x').that.is.an('array').and.to.deep.equal(['val1', 'val1']) @@ -1869,6 +1883,24 @@ describe('yargs-parser', function () { result.foo[1].should.equal('bar') }) + it('should raise an exception if -f== format is used for a key with no expected argument', function () { + var argv = parser.detailed('-f=apple', { + narg: { + f: 0 + } + }) + argv.error.message.should.equal('Argument unexpected for: f') + }) + + it('should raise an exception if --bar== format is used for a key with no expected argument', function () { + var argv = parser.detailed('--bar=apple', { + narg: { + bar: 0 + } + }) + argv.error.message.should.equal('Argument unexpected for: bar') + }) + it('should raise an exception if there are not enough arguments following key', function () { var argv = parser.detailed('--foo apple', { narg: { @@ -1921,6 +1953,23 @@ describe('yargs-parser', function () { result._[1].should.equal('cat') }) + it('should support nargs for -f= and --bar= format arguments with dashed values', function () { + var result = parser(['-f=--apple', 'bar', 'blerg', '--bar=-monkey', 'washing', 'cat'], { + narg: { + f: 2, + bar: 2 + } + }) + + result.f[0].should.equal('--apple') + result.f[1].should.equal('bar') + result._[0].should.equal('blerg') + + result.bar[0].should.equal('-monkey') + result.bar[1].should.equal('washing') + result._[1].should.equal('cat') + }) + it('should not modify the input args if an = was used', function () { var expected = ['-f=apple', 'bar', 'blerg', '--bar=monkey', 'washing', 'cat'] var args = expected.slice() @@ -3680,6 +3729,18 @@ describe('yargs-parser', function () { parse.error.message.should.equal('Not enough arguments following: a') }) + it('returns an error if not enough positionals were provided for nargs even with nargs-eats-options', () => { + var parse = parser.detailed(['-a', '33', '--cat'], { + narg: { + a: 3 + }, + configuration: { + 'nargs-eats-options': true + } + }) + parse.error.message.should.equal('Not enough arguments following: a') + }) + it('does not raise error if no arguments are provided for boolean option', () => { var parse = parser.detailed(['-a'], { array: 'a', From 78014fc693ed9fc0c0069750b40bf36a60648ab9 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2020 10:13:25 -0700 Subject: [PATCH 094/206] chore: release 18.1.2 (#263) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5236a11..99ab0cd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [18.1.2](https://www.github.com/yargs/yargs-parser/compare/v18.1.1...v18.1.2) (2020-03-26) + + +### Bug Fixes + +* **array, nargs:** support -o=--value and --option=--value format ([#262](https://www.github.com/yargs/yargs-parser/issues/262)) ([41d3f81](https://www.github.com/yargs/yargs-parser/commit/41d3f8139e116706b28de9b0de3433feb08d2f13)) + ### [18.1.1](https://www.github.com/yargs/yargs-parser/compare/v18.1.0...v18.1.1) (2020-03-16) diff --git a/package.json b/package.json index 778f1ef0..0f88bf46 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "18.1.1", + "version": "18.1.2", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From f7e15b9800900b9856acac1a830a5f35847be73e Mon Sep 17 00:00:00 2001 From: Mael Le Guen Date: Thu, 16 Apr 2020 21:42:27 +0200 Subject: [PATCH 095/206] fix(setArg): options using camel-case and dot-notation populated twice (#268) --- index.js | 6 +++++- test/yargs-parser.js | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 4fe26632..c14c1fc7 100644 --- a/index.js +++ b/index.js @@ -480,7 +480,11 @@ function parse (args, opts) { a.shift() // nuke the old key. x = x.concat(a) - setKey(argv, x, value) + // populate alias only if is not already an alias of the full key + // (already populated above) + if (!(flags.aliases[key] || []).includes(x.join('.'))) { + setKey(argv, x, value) + } }) } diff --git a/test/yargs-parser.js b/test/yargs-parser.js index aa7399ee..f5cbc183 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -959,6 +959,20 @@ describe('yargs-parser', function () { }) argv.f.bar.should.eql(99) + argv.foo.bar.should.eql(99) + }) + + // see #267 + it('should populate aliases when dot notation is used on camel-cased option', function () { + var argv = parser(['--foo-baz.bar', '99'], { + alias: { + 'foo-baz': ['f'] + } + }) + + argv.f.bar.should.eql(99) + argv['foo-baz'].bar.should.eql(99) + argv.fooBaz.bar.should.eql(99) }) it('should populate aliases when nested dot notation is used', function () { From d301a5645627a30cc1721de647a6cc65bb89a426 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2020 13:12:08 -0700 Subject: [PATCH 096/206] chore: release 18.1.3 (#269) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99ab0cd2..d91dc516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [18.1.3](https://www.github.com/yargs/yargs-parser/compare/v18.1.2...v18.1.3) (2020-04-16) + + +### Bug Fixes + +* **setArg:** options using camel-case and dot-notation populated twice ([#268](https://www.github.com/yargs/yargs-parser/issues/268)) ([f7e15b9](https://www.github.com/yargs/yargs-parser/commit/f7e15b9800900b9856acac1a830a5f35847be73e)) + ### [18.1.2](https://www.github.com/yargs/yargs-parser/compare/v18.1.1...v18.1.2) (2020-03-26) diff --git a/package.json b/package.json index 0f88bf46..636ff176 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yargs-parser", - "version": "18.1.2", + "version": "18.1.3", "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { From 4d98698bc6767e84ec54a0842908191739be73b7 Mon Sep 17 00:00:00 2001 From: QmarkC Date: Wed, 3 Jun 2020 18:50:32 -0500 Subject: [PATCH 097/206] fix(deps): update dependency decamelize to v3 (#274) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 636ff176..7bf3236a 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ }, "dependencies": { "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "decamelize": "^3.2.0" }, "files": [ "lib", From 9014ed722a32768b96b829e65a31705db5c1458a Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sat, 6 Jun 2020 10:11:10 -0700 Subject: [PATCH 098/206] build!: drops Node 6. begin following Node.js LTS schedule (#278) --- .github/workflows/ci.yaml | 2 +- README.md | 6 ++++++ index.js | 13 ++++++++++++- package.json | 2 +- test/yargs-parser.js | 9 +++++++++ 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 82416464..0213d80f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [8, 10, 12, 13] + node: [10, 12, 14] steps: - uses: actions/checkout@v1 - uses: actions/setup-node@v1 diff --git a/README.md b/README.md index bae61c2a..eff195c3 100644 --- a/README.md +++ b/README.md @@ -439,6 +439,12 @@ node example.js --unknown-option --known-option 2 --string-option --unknown-opti { _: ['--unknown-option'], knownOption: 2, stringOption: '--unknown-option2' } ``` +## Supported Node.js Versions + +Libraries in this ecosystem make a best effort to track +[Node.js' release schedule](https://nodejs.org/en/about/releases/). Here's [a +post on why we think this is important](https://medium.com/the-node-js-collection/maintainers-should-consider-following-node-js-release-schedule-ab08ed4de71a). + ## Special Thanks The yargs project evolves from optimist and minimist. It owes its diff --git a/index.js b/index.js index c14c1fc7..24962d66 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,17 @@ const path = require('path') const tokenizeArgString = require('./lib/tokenize-arg-string') const util = require('util') +// See https://github.com/yargs/yargs-parser#supported-nodejs-versions for our +// version support policy. The YARGS_MIN_NODE_VERSION is used for testing only. +const minNodeVersion = (process && process.env && process.env.YARGS_MIN_NODE_VERSION) + ? Number(process.env.YARGS_MIN_NODE_VERSION) : 10 +if (process && process.version) { + const major = Number(process.version.match(/v([^.]+)/)[1]) + if (major < minNodeVersion) { + throw Error(`yargs parser supports a minimum Node.js version of ${minNodeVersion}. Read our version support policy: https://github.com/yargs/yargs-parser#supported-nodejs-versions`) + } +} + function parse (args, opts) { opts = Object.assign(Object.create(null), opts) // allow a string argument to be passed in rather @@ -626,8 +637,8 @@ function parse (args, opts) { } function applyEnvVars (argv, configOnly) { + if (!process) return if (typeof envPrefix === 'undefined') return - const prefix = typeof envPrefix === 'string' ? envPrefix : '' Object.keys(process.env).forEach(function (envVar) { if (prefix === '' || envVar.lastIndexOf(prefix, 0) === 0) { diff --git a/package.json b/package.json index 7bf3236a..1acaabc0 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,6 @@ "index.js" ], "engines": { - "node": ">=6" + "node": ">=10" } } diff --git a/test/yargs-parser.js b/test/yargs-parser.js index f5cbc183..e017a08b 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -3823,4 +3823,13 @@ describe('yargs-parser', function () { } }) }) + + it('throws error for unsupported Node.js versions', () => { + process.env.YARGS_MIN_NODE_VERSION = '55' + delete require.cache[require.resolve('../')] + expect(() => { + require('../') + }).to.throw(/yargs parser supports a minimum Node.js version of 55/) + delete process.env.YARGS_MIN_NODE_VERSION + }) }) From f0c1032db46ac4a477bdc786bf5c4c5175cc143c Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sat, 6 Jun 2020 10:29:04 -0700 Subject: [PATCH 099/206] build: switch to release-please action (#280) --- .github/release-please.yml | 2 -- .github/workflows/release-please.yml | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) delete mode 100644 .github/release-please.yml create mode 100644 .github/workflows/release-please.yml diff --git a/.github/release-please.yml b/.github/release-please.yml deleted file mode 100644 index 3c065997..00000000 --- a/.github/release-please.yml +++ /dev/null @@ -1,2 +0,0 @@ -releaseType: node -handleGHRelease: true diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 00000000..70660547 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,14 @@ +on: + push: + branches: + - master +name: release-please +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: bcoe/release-please-action@v1.2.1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + release-type: node + package-name: yargs-parser From 8931ab08f686cc55286f33a95a83537da2be5516 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sat, 6 Jun 2020 10:38:05 -0700 Subject: [PATCH 100/206] feat(deps): update to latest camelcase/decamelize (#281) --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 1acaabc0..2f3a8068 100644 --- a/package.json +++ b/package.json @@ -27,14 +27,14 @@ "author": "Ben Coe ", "license": "ISC", "devDependencies": { - "c8": "^7.0.1", + "c8": "^7.1.2", "chai": "^4.2.0", - "mocha": "^7.0.0", - "standard": "^14.3.1" + "mocha": "^7.2.0", + "standard": "^14.3.4" }, "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^3.2.0" + "camelcase": "^6.0.0", + "decamelize": "^4.0.0" }, "files": [ "lib", From 5c17398632979220937f6b580bf6a3c04a325349 Mon Sep 17 00:00:00 2001 From: QmarkC Date: Wed, 10 Jun 2020 22:36:38 -0500 Subject: [PATCH 101/206] refactor(ts): init typescript conf + tsify tokenize-arg-string (#272) --- .editorconfig | 12 ++ .eslintrc | 21 +++ .gitignore | 1 + .mocharc.json | 6 + .nycrc | 13 ++ index.js | 2 +- ...e-arg-string.js => tokenize-arg-string.ts} | 10 +- package.json | 36 ++++- test/tokenize-arg-string.js | 70 --------- test/tokenize-arg-string.ts | 133 ++++++++++++++++++ tsconfig.json | 11 ++ tsconfig.test.json | 10 ++ 12 files changed, 242 insertions(+), 83 deletions(-) create mode 100644 .editorconfig create mode 100644 .eslintrc create mode 100644 .mocharc.json create mode 100644 .nycrc rename lib/{tokenize-arg-string.js => tokenize-arg-string.ts} (78%) delete mode 100644 test/tokenize-arg-string.js create mode 100644 test/tokenize-arg-string.ts create mode 100644 tsconfig.json create mode 100644 tsconfig.test.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..4039ff11 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..ac6ca880 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,21 @@ +{ + "overrides": [ + { + "files": "*.ts", + "parser": "@typescript-eslint/parser", + "rules": { + "no-unused-vars": "off", + "no-useless-constructor": "off", + "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/no-useless-constructor": "error" + } + } + ], + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint/eslint-plugin" + ] +} diff --git a/.gitignore b/.gitignore index a7502d1d..791cb484 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea +build/ .nyc_output node_modules .DS_Store diff --git a/.mocharc.json b/.mocharc.json new file mode 100644 index 00000000..ef1c3645 --- /dev/null +++ b/.mocharc.json @@ -0,0 +1,6 @@ +{ + "spec": [ + "build/test", + "test" + ] +} diff --git a/.nycrc b/.nycrc new file mode 100644 index 00000000..89f6c3a0 --- /dev/null +++ b/.nycrc @@ -0,0 +1,13 @@ +{ + "exclude": [ + "build/test/**", + "test/**" + ], + "reporter": [ + "html", + "text" + ], + "lines": 100, + "branches": "97", + "statements": "100" +} diff --git a/index.js b/index.js index 24962d66..4522fcc1 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ const camelCase = require('camelcase') const decamelize = require('decamelize') const path = require('path') -const tokenizeArgString = require('./lib/tokenize-arg-string') +const { tokenizeArgString } = require('./build/lib/tokenize-arg-string') const util = require('util') // See https://github.com/yargs/yargs-parser#supported-nodejs-versions for our diff --git a/lib/tokenize-arg-string.js b/lib/tokenize-arg-string.ts similarity index 78% rename from lib/tokenize-arg-string.js rename to lib/tokenize-arg-string.ts index 260c67c1..52827080 100644 --- a/lib/tokenize-arg-string.js +++ b/lib/tokenize-arg-string.ts @@ -1,5 +1,5 @@ // take an un-split argv string and tokenize it. -module.exports = function (argString) { +export function tokenizeArgString (argString: string | any[]): string[] { if (Array.isArray(argString)) { return argString.map(e => typeof e !== 'string' ? e + '' : e) } @@ -7,10 +7,10 @@ module.exports = function (argString) { argString = argString.trim() let i = 0 - let prevC = null - let c = null - let opening = null - const args = [] + let prevC: string | null = null + let c: string | null = null + let opening: string | null = null + const args: string[] = [] for (let ii = 0; ii < argString.length; ii++) { prevC = c diff --git a/package.json b/package.json index 2f3a8068..1b44cd1b 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,14 @@ "description": "the mighty option parser used by yargs", "main": "index.js", "scripts": { - "fix": "standard --fix", - "test": "c8 --reporter=text --reporter=html mocha test/*.js", - "posttest": "standard", - "coverage": "c8 report --check-coverage check-coverage --lines=100 --branches=97 --statements=100" + "fix": "standardx --fix && standardx --fix **/*.ts", + "pretest": "npm run compile -- -p tsconfig.test.json", + "test": "c8 --reporter=text --reporter=html mocha test/*.js", + "posttest": "npm run check", + "coverage": "c8 report --check-coverage", + "check": "standardx && standardx **/*.ts", + "compile": "rimraf build && tsc", + "prepare": "npm run compile" }, "repository": { "type": "git", @@ -27,20 +31,38 @@ "author": "Ben Coe ", "license": "ISC", "devDependencies": { + "@types/chai": "^4.2.11", + "@types/mocha": "^7.0.2", + "@types/node": "^10.0.3", + "@typescript-eslint/eslint-plugin": "^2.25.0", + "@typescript-eslint/parser": "^2.25.0", "c8": "^7.1.2", "chai": "^4.2.0", + "eslint": "^7.0.0", + "eslint-plugin-import": "^2.20.1", + "eslint-plugin-node": "^11.0.0", + "gts": "^2.0.0-alpha.4", "mocha": "^7.2.0", - "standard": "^14.3.4" + "rimraf": "^3.0.2", + "standardx": "^5.0.0", + "typescript": "^3.7.0" }, "dependencies": { "camelcase": "^6.0.0", "decamelize": "^4.0.0" }, "files": [ - "lib", - "index.js" + "index.js", + "build", + "lib/**/*.js" ], "engines": { "node": ">=10" + }, + "standardx": { + "ignore": [ + "build", + "example.js" + ] } } diff --git a/test/tokenize-arg-string.js b/test/tokenize-arg-string.js deleted file mode 100644 index 2f35895c..00000000 --- a/test/tokenize-arg-string.js +++ /dev/null @@ -1,70 +0,0 @@ -/* global describe, it */ - -const tokenizeArgString = require('../lib/tokenize-arg-string') - -require('chai').should() -const expect = require('chai').expect - -describe('TokenizeArgString', function () { - it('handles unquoted string', function () { - const args = tokenizeArgString('--foo 99') - args[0].should.equal('--foo') - args[1].should.equal('99') - }) - - it('handles unquoted numbers', function () { - const args = tokenizeArgString(['--foo', 9]) - args[0].should.equal('--foo') - args[1].should.equal('9') - }) - - it('handles quoted string with no spaces', function () { - const args = tokenizeArgString("--foo 'hello'") - args[0].should.equal('--foo') - args[1].should.equal("'hello'") - }) - - it('handles single quoted string with spaces', function () { - const args = tokenizeArgString("--foo 'hello world' --bar='foo bar'") - args[0].should.equal('--foo') - args[1].should.equal("'hello world'") - args[2].should.equal("--bar='foo bar'") - }) - - it('handles double quoted string with spaces', function () { - const args = tokenizeArgString('--foo "hello world" --bar="foo bar"') - args[0].should.equal('--foo') - args[1].should.equal('"hello world"') - args[2].should.equal('--bar="foo bar"') - }) - - it('handles single quoted empty string', function () { - const args = tokenizeArgString('--foo \'\' --bar=\'\'') - args[0].should.equal('--foo') - args[1].should.equal("''") - args[2].should.equal("--bar=''") - }) - - it('handles double quoted empty string', function () { - const args = tokenizeArgString('--foo "" --bar=""') - args[0].should.equal('--foo') - args[1].should.equal('""') - args[2].should.equal('--bar=""') - }) - - it('handles quoted string with embedded quotes', function () { - var args = tokenizeArgString('--foo "hello \'world\'" --bar=\'foo "bar"\'') - args[0].should.equal('--foo') - args[1].should.equal('"hello \'world\'"') - args[2].should.equal('--bar=\'foo "bar"\'') - }) - - // https://github.com/yargs/yargs-parser/pull/100 - // https://github.com/yargs/yargs-parser/pull/106 - it('ignores unneeded spaces', function () { - const args = tokenizeArgString(' foo bar "foo bar" ') - args[0].should.equal('foo') - expect(args[1]).equal('bar') - expect(args[2]).equal('"foo bar"') - }) -}) diff --git a/test/tokenize-arg-string.ts b/test/tokenize-arg-string.ts new file mode 100644 index 00000000..7621d45c --- /dev/null +++ b/test/tokenize-arg-string.ts @@ -0,0 +1,133 @@ +/* global describe, it */ +import { expect, should } from 'chai' +import { tokenizeArgString } from '../lib/tokenize-arg-string' + +should() + +describe('TokenizeArgString', function () { + it('handles unquoted string', function () { + const args = tokenizeArgString('--foo 99') + args[0].should.equal('--foo') + args[1].should.equal('99') + }) + + it('handles unquoted numbers', function () { + const args = tokenizeArgString(['--foo', 9]) + args[0].should.equal('--foo') + args[1].should.equal('9') + }) + + it('handles quoted string with no spaces', function () { + const args = tokenizeArgString("--foo 'hello'") + args[0].should.equal('--foo') + args[1].should.equal("'hello'") + }) + + it('handles single quoted string with spaces', function () { + const args = tokenizeArgString("--foo 'hello world' --bar='foo bar'") + args[0].should.equal('--foo') + args[1].should.equal("'hello world'") + args[2].should.equal("--bar='foo bar'") + }) + + it('handles double quoted string with spaces', function () { + const args = tokenizeArgString('--foo "hello world" --bar="foo bar"') + args[0].should.equal('--foo') + args[1].should.equal('"hello world"') + args[2].should.equal('--bar="foo bar"') + }) + + it('handles single quoted empty string', function () { + const args = tokenizeArgString('--foo \'\' --bar=\'\'') + args[0].should.equal('--foo') + args[1].should.equal("''") + args[2].should.equal("--bar=''") + }) + + it('handles double quoted empty string', function () { + const args = tokenizeArgString('--foo "" --bar=""') + args[0].should.equal('--foo') + args[1].should.equal('""') + args[2].should.equal('--bar=""') + }) + + it('handles quoted string with embedded quotes', function () { + var args = tokenizeArgString('--foo "hello \'world\'" --bar=\'foo "bar"\'') + args[0].should.equal('--foo') + args[1].should.equal('"hello \'world\'"') + args[2].should.equal('--bar=\'foo "bar"\'') + }) + + // https://github.com/yargs/yargs-parser/pull/100 + // https://github.com/yargs/yargs-parser/pull/106 + it('ignores unneeded spaces', function () { + const args = tokenizeArgString(' foo bar "foo bar" ') + args[0].should.equal('foo') + expect(args[1]).equal('bar') + expect(args[2]).equal('"foo bar"') + }) + + it('handles boolean options', function () { + const args = tokenizeArgString('--foo -bar') + expect(args[0]).to.equal(('--foo')) + expect(args[1]).to.equal(('-bar')) + }) + + it('handles empty string', function () { + const args = tokenizeArgString('') + expect(args.length).to.equal(0) + }) + + it('handles array with unquoted string', function () { + const args = tokenizeArgString(['--foo', '99']) + args[0].should.equal('--foo') + args[1].should.equal('99') + }) + + it('handles array with quoted string with no spaces', function () { + const args = tokenizeArgString(['--foo', "'hello'"]) + args[0].should.equal('--foo') + args[1].should.equal("'hello'") + }) + + it('handles array with single quoted string with spaces', function () { + const args = tokenizeArgString(['--foo', "'hello world'", "--bar='foo bar'"]) + args[0].should.equal('--foo') + args[1].should.equal("'hello world'") + args[2].should.equal("--bar='foo bar'") + }) + + it('handles array with double quoted string with spaces', function () { + const args = tokenizeArgString(['--foo', '"hello world"', '--bar="foo bar"']) + args[0].should.equal('--foo') + args[1].should.equal('"hello world"') + args[2].should.equal('--bar="foo bar"') + }) + + it('handles array with single quoted empty string', function () { + const args = tokenizeArgString(['--foo', "''", "--bar=''"]) + args[0].should.equal('--foo') + args[1].should.equal("''") + args[2].should.equal("--bar=''") + }) + + it('handles array with double quoted empty string', function () { + const args = tokenizeArgString(['--foo', '""', '--bar=""']) + args[0].should.equal('--foo') + args[1].should.equal('""') + args[2].should.equal('--bar=""') + }) + + it('handles array with quoted string with embedded quotes', function () { + var args = tokenizeArgString(['--foo', '"hello \'world\'"', '--bar=\'foo "bar"\'']) + args[0].should.equal('--foo') + args[1].should.equal('"hello \'world\'"') + args[2].should.equal('--bar=\'foo "bar"\'') + }) + + it('handles array with boolean options', function () { + const args = tokenizeArgString(['--foo', '-bar']) + expect(args[0]).to.equal('--foo') + expect(args[1]).to.equal('-bar') + }) +}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..c28e1966 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "./node_modules/gts/tsconfig-google.json", + "compilerOptions": { + "outDir": "build", + "rootDir": ".", + "sourceMap": false + }, + "include": [ + "lib/**/*.ts" + ] +} diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 00000000..75a19fe0 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "sourceMap": true + }, + "include": [ + "lib/**/*.ts", + "test/**/*.ts" + ] +} From bb95ea8be5f63b0b3c8f2005f54c7c62fdb965b6 Mon Sep 17 00:00:00 2001 From: QmarkC Date: Mon, 15 Jun 2020 19:07:17 -0500 Subject: [PATCH 102/206] chore(deps): update devDependency eslint to 6.8 (#286) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1b44cd1b..05391482 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@typescript-eslint/parser": "^2.25.0", "c8": "^7.1.2", "chai": "^4.2.0", - "eslint": "^7.0.0", + "eslint": "^6.8.0", "eslint-plugin-import": "^2.20.1", "eslint-plugin-node": "^11.0.0", "gts": "^2.0.0-alpha.4", From 94af47ea384099abcb91a4b25bc18adb5c3e754f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2020 22:12:01 -0700 Subject: [PATCH 103/206] chore(deps): update dependency eslint to v7 (#287) Co-authored-by: Renovate Bot --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 05391482..1b44cd1b 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@typescript-eslint/parser": "^2.25.0", "c8": "^7.1.2", "chai": "^4.2.0", - "eslint": "^6.8.0", + "eslint": "^7.0.0", "eslint-plugin-import": "^2.20.1", "eslint-plugin-node": "^11.0.0", "gts": "^2.0.0-alpha.4", From 7b62b73fa6b549418934d38d72504b630f59a861 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2020 14:18:54 -0700 Subject: [PATCH 104/206] chore(deps): update dependency mocha to v8 (#282) Co-authored-by: Renovate Bot --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1b44cd1b..c8e3c83f 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "eslint-plugin-import": "^2.20.1", "eslint-plugin-node": "^11.0.0", "gts": "^2.0.0-alpha.4", - "mocha": "^7.2.0", + "mocha": "^8.0.0", "rimraf": "^3.0.2", "standardx": "^5.0.0", "typescript": "^3.7.0" From 5e748902f0347433b8cd8772a3c6510d7384fed1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jul 2020 17:31:16 -0700 Subject: [PATCH 105/206] chore(deps): update typescript-eslint monorepo to v3 (#284) Co-authored-by: Renovate Bot --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c8e3c83f..26268129 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,8 @@ "@types/chai": "^4.2.11", "@types/mocha": "^7.0.2", "@types/node": "^10.0.3", - "@typescript-eslint/eslint-plugin": "^2.25.0", - "@typescript-eslint/parser": "^2.25.0", + "@typescript-eslint/eslint-plugin": "^3.0.0", + "@typescript-eslint/parser": "^3.0.0", "c8": "^7.1.2", "chai": "^4.2.0", "eslint": "^7.0.0", From 596184f95bf334a880a237a0bc3c37f95a8eff7e Mon Sep 17 00:00:00 2001 From: QmarkC Date: Sat, 18 Jul 2020 17:08:21 -0500 Subject: [PATCH 106/206] refactor(ts): initial conversion to TypeScript (#285) --- index.js | 1033 +--------------------------------- lib/common-types.ts | 18 + lib/index.ts | 1114 +++++++++++++++++++++++++++++++++++++ lib/yargs-parser-types.ts | 159 ++++++ test/yargs-parser.js | 55 ++ 5 files changed, 1348 insertions(+), 1031 deletions(-) create mode 100644 lib/common-types.ts create mode 100644 lib/index.ts create mode 100644 lib/yargs-parser-types.ts diff --git a/index.js b/index.js index 4522fcc1..d6e1bb04 100644 --- a/index.js +++ b/index.js @@ -1,8 +1,4 @@ -const camelCase = require('camelcase') -const decamelize = require('decamelize') -const path = require('path') -const { tokenizeArgString } = require('./build/lib/tokenize-arg-string') -const util = require('util') +const { yargsParser } = require('./build/lib') // See https://github.com/yargs/yargs-parser#supported-nodejs-versions for our // version support policy. The YARGS_MIN_NODE_VERSION is used for testing only. @@ -15,1029 +11,4 @@ if (process && process.version) { } } -function parse (args, opts) { - opts = Object.assign(Object.create(null), opts) - // allow a string argument to be passed in rather - // than an argv array. - args = tokenizeArgString(args) - - // aliases might have transitive relationships, normalize this. - const aliases = combineAliases(Object.assign(Object.create(null), opts.alias)) - const configuration = Object.assign({ - 'boolean-negation': true, - 'camel-case-expansion': true, - 'combine-arrays': false, - 'dot-notation': true, - 'duplicate-arguments-array': true, - 'flatten-duplicate-arrays': true, - 'greedy-arrays': true, - 'halt-at-non-option': false, - 'nargs-eats-options': false, - 'negation-prefix': 'no-', - 'parse-numbers': true, - 'populate--': false, - 'set-placeholder-key': false, - 'short-option-groups': true, - 'strip-aliased': false, - 'strip-dashed': false, - 'unknown-options-as-args': false - }, opts.configuration) - const defaults = Object.assign(Object.create(null), opts.default) - const configObjects = opts.configObjects || [] - const envPrefix = opts.envPrefix - const notFlagsOption = configuration['populate--'] - const notFlagsArgv = notFlagsOption ? '--' : '_' - const newAliases = Object.create(null) - const defaulted = Object.create(null) - // allow a i18n handler to be passed in, default to a fake one (util.format). - const __ = opts.__ || util.format - const flags = { - aliases: Object.create(null), - arrays: Object.create(null), - bools: Object.create(null), - strings: Object.create(null), - numbers: Object.create(null), - counts: Object.create(null), - normalize: Object.create(null), - configs: Object.create(null), - nargs: Object.create(null), - coercions: Object.create(null), - keys: [] - } - const negative = /^-([0-9]+(\.[0-9]+)?|\.[0-9]+)$/ - const negatedBoolean = new RegExp('^--' + configuration['negation-prefix'] + '(.+)') - - ;[].concat(opts.array).filter(Boolean).forEach(function (opt) { - const key = opt.key || opt - - // assign to flags[bools|strings|numbers] - const assignment = Object.keys(opt).map(function (key) { - return ({ - boolean: 'bools', - string: 'strings', - number: 'numbers' - })[key] - }).filter(Boolean).pop() - - // assign key to be coerced - if (assignment) { - flags[assignment][key] = true - } - - flags.arrays[key] = true - flags.keys.push(key) - }) - - ;[].concat(opts.boolean).filter(Boolean).forEach(function (key) { - flags.bools[key] = true - flags.keys.push(key) - }) - - ;[].concat(opts.string).filter(Boolean).forEach(function (key) { - flags.strings[key] = true - flags.keys.push(key) - }) - - ;[].concat(opts.number).filter(Boolean).forEach(function (key) { - flags.numbers[key] = true - flags.keys.push(key) - }) - - ;[].concat(opts.count).filter(Boolean).forEach(function (key) { - flags.counts[key] = true - flags.keys.push(key) - }) - - ;[].concat(opts.normalize).filter(Boolean).forEach(function (key) { - flags.normalize[key] = true - flags.keys.push(key) - }) - - Object.keys(opts.narg || {}).forEach(function (k) { - flags.nargs[k] = opts.narg[k] - flags.keys.push(k) - }) - - Object.keys(opts.coerce || {}).forEach(function (k) { - flags.coercions[k] = opts.coerce[k] - flags.keys.push(k) - }) - - if (Array.isArray(opts.config) || typeof opts.config === 'string') { - ;[].concat(opts.config).filter(Boolean).forEach(function (key) { - flags.configs[key] = true - }) - } else { - Object.keys(opts.config || {}).forEach(function (k) { - flags.configs[k] = opts.config[k] - }) - } - - // create a lookup table that takes into account all - // combinations of aliases: {f: ['foo'], foo: ['f']} - extendAliases(opts.key, aliases, opts.default, flags.arrays) - - // apply default values to all aliases. - Object.keys(defaults).forEach(function (key) { - (flags.aliases[key] || []).forEach(function (alias) { - defaults[alias] = defaults[key] - }) - }) - - let error = null - checkConfiguration() - - let notFlags = [] - - const argv = Object.assign(Object.create(null), { _: [] }) - // TODO(bcoe): for the first pass at removing object prototype we didn't - // remove all prototypes from objects returned by this API, we might want - // to gradually move towards doing so. - const argvReturn = {} - - for (let i = 0; i < args.length; i++) { - const arg = args[i] - let broken - let key - let letters - let m - let next - let value - - // any unknown option (except for end-of-options, "--") - if (arg !== '--' && isUnknownOptionAsArg(arg)) { - argv._.push(arg) - // -- separated by = - } else if (arg.match(/^--.+=/) || ( - !configuration['short-option-groups'] && arg.match(/^-.+=/) - )) { - // Using [\s\S] instead of . because js doesn't support the - // 'dotall' regex modifier. See: - // http://stackoverflow.com/a/1068308/13216 - m = arg.match(/^--?([^=]+)=([\s\S]*)$/) - - // arrays format = '--f=a b c' - if (checkAllAliases(m[1], flags.arrays)) { - i = eatArray(i, m[1], args, m[2]) - } else if (checkAllAliases(m[1], flags.nargs) !== false) { - // nargs format = '--f=monkey washing cat' - i = eatNargs(i, m[1], args, m[2]) - } else { - setArg(m[1], m[2]) - } - } else if (arg.match(negatedBoolean) && configuration['boolean-negation']) { - key = arg.match(negatedBoolean)[1] - setArg(key, checkAllAliases(key, flags.arrays) ? [false] : false) - - // -- separated by space. - } else if (arg.match(/^--.+/) || ( - !configuration['short-option-groups'] && arg.match(/^-[^-]+/) - )) { - key = arg.match(/^--?(.+)/)[1] - - if (checkAllAliases(key, flags.arrays)) { - // array format = '--foo a b c' - i = eatArray(i, key, args) - } else if (checkAllAliases(key, flags.nargs) !== false) { - // nargs format = '--foo a b c' - // should be truthy even if: flags.nargs[key] === 0 - i = eatNargs(i, key, args) - } else { - next = args[i + 1] - - if (next !== undefined && (!next.match(/^-/) || - next.match(negative)) && - !checkAllAliases(key, flags.bools) && - !checkAllAliases(key, flags.counts)) { - setArg(key, next) - i++ - } else if (/^(true|false)$/.test(next)) { - setArg(key, next) - i++ - } else { - setArg(key, defaultValue(key)) - } - } - - // dot-notation flag separated by '='. - } else if (arg.match(/^-.\..+=/)) { - m = arg.match(/^-([^=]+)=([\s\S]*)$/) - setArg(m[1], m[2]) - - // dot-notation flag separated by space. - } else if (arg.match(/^-.\..+/) && !arg.match(negative)) { - next = args[i + 1] - key = arg.match(/^-(.\..+)/)[1] - - if (next !== undefined && !next.match(/^-/) && - !checkAllAliases(key, flags.bools) && - !checkAllAliases(key, flags.counts)) { - setArg(key, next) - i++ - } else { - setArg(key, defaultValue(key)) - } - } else if (arg.match(/^-[^-]+/) && !arg.match(negative)) { - letters = arg.slice(1, -1).split('') - broken = false - - for (let j = 0; j < letters.length; j++) { - next = arg.slice(j + 2) - - if (letters[j + 1] && letters[j + 1] === '=') { - value = arg.slice(j + 3) - key = letters[j] - - if (checkAllAliases(key, flags.arrays)) { - // array format = '-f=a b c' - i = eatArray(i, key, args, value) - } else if (checkAllAliases(key, flags.nargs) !== false) { - // nargs format = '-f=monkey washing cat' - i = eatNargs(i, key, args, value) - } else { - setArg(key, value) - } - - broken = true - break - } - - if (next === '-') { - setArg(letters[j], next) - continue - } - - // current letter is an alphabetic character and next value is a number - if (/[A-Za-z]/.test(letters[j]) && - /^-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) { - setArg(letters[j], next) - broken = true - break - } - - if (letters[j + 1] && letters[j + 1].match(/\W/)) { - setArg(letters[j], next) - broken = true - break - } else { - setArg(letters[j], defaultValue(letters[j])) - } - } - - key = arg.slice(-1)[0] - - if (!broken && key !== '-') { - if (checkAllAliases(key, flags.arrays)) { - // array format = '-f a b c' - i = eatArray(i, key, args) - } else if (checkAllAliases(key, flags.nargs) !== false) { - // nargs format = '-f a b c' - // should be truthy even if: flags.nargs[key] === 0 - i = eatNargs(i, key, args) - } else { - next = args[i + 1] - - if (next !== undefined && (!/^(-|--)[^-]/.test(next) || - next.match(negative)) && - !checkAllAliases(key, flags.bools) && - !checkAllAliases(key, flags.counts)) { - setArg(key, next) - i++ - } else if (/^(true|false)$/.test(next)) { - setArg(key, next) - i++ - } else { - setArg(key, defaultValue(key)) - } - } - } - } else if (arg.match(/^-[0-9]$/) && - arg.match(negative) && - checkAllAliases(arg.slice(1), flags.bools)) { - // single-digit boolean alias, e.g: xargs -0 - key = arg.slice(1) - setArg(key, defaultValue(key)) - } else if (arg === '--') { - notFlags = args.slice(i + 1) - break - } else if (configuration['halt-at-non-option']) { - notFlags = args.slice(i) - break - } else { - argv._.push(maybeCoerceNumber('_', arg)) - } - } - - // order of precedence: - // 1. command line arg - // 2. value from env var - // 3. value from config file - // 4. value from config objects - // 5. configured default value - applyEnvVars(argv, true) // special case: check env vars that point to config file - applyEnvVars(argv, false) - setConfig(argv) - setConfigObjects() - applyDefaultsAndAliases(argv, flags.aliases, defaults, true) - applyCoercions(argv) - if (configuration['set-placeholder-key']) setPlaceholderKeys(argv) - - // for any counts either not in args or without an explicit default, set to 0 - Object.keys(flags.counts).forEach(function (key) { - if (!hasKey(argv, key.split('.'))) setArg(key, 0) - }) - - // '--' defaults to undefined. - if (notFlagsOption && notFlags.length) argv[notFlagsArgv] = [] - notFlags.forEach(function (key) { - argv[notFlagsArgv].push(key) - }) - - if (configuration['camel-case-expansion'] && configuration['strip-dashed']) { - Object.keys(argv).filter(key => key !== '--' && key.includes('-')).forEach(key => { - delete argv[key] - }) - } - - if (configuration['strip-aliased']) { - ;[].concat(...Object.keys(aliases).map(k => aliases[k])).forEach(alias => { - if (configuration['camel-case-expansion']) { - delete argv[alias.split('.').map(prop => camelCase(prop)).join('.')] - } - - delete argv[alias] - }) - } - - // how many arguments should we consume, based - // on the nargs option? - function eatNargs (i, key, args, argAfterEqualSign) { - let ii - let toEat = checkAllAliases(key, flags.nargs) - // NaN has a special meaning for the array type, indicating that one or - // more values are expected. - toEat = isNaN(toEat) ? 1 : toEat - - if (toEat === 0) { - if (!isUndefined(argAfterEqualSign)) { - error = Error(__('Argument unexpected for: %s', key)) - } - setArg(key, defaultValue(key)) - return i - } - - let available = isUndefined(argAfterEqualSign) ? 0 : 1 - if (configuration['nargs-eats-options']) { - // classic behavior, yargs eats positional and dash arguments. - if (args.length - (i + 1) + available < toEat) { - error = Error(__('Not enough arguments following: %s', key)) - } - available = toEat - } else { - // nargs will not consume flag arguments, e.g., -abc, --foo, - // and terminates when one is observed. - for (ii = i + 1; ii < args.length; ii++) { - if (!args[ii].match(/^-[^0-9]/) || args[ii].match(negative) || isUnknownOptionAsArg(args[ii])) available++ - else break - } - if (available < toEat) error = Error(__('Not enough arguments following: %s', key)) - } - - let consumed = Math.min(available, toEat) - if (!isUndefined(argAfterEqualSign) && consumed > 0) { - setArg(key, argAfterEqualSign) - consumed-- - } - for (ii = i + 1; ii < (consumed + i + 1); ii++) { - setArg(key, args[ii]) - } - - return (i + consumed) - } - - // if an option is an array, eat all non-hyphenated arguments - // following it... YUM! - // e.g., --foo apple banana cat becomes ["apple", "banana", "cat"] - function eatArray (i, key, args, argAfterEqualSign) { - let argsToSet = [] - let next = argAfterEqualSign || args[i + 1] - // If both array and nargs are configured, enforce the nargs count: - const nargsCount = checkAllAliases(key, flags.nargs) - - if (checkAllAliases(key, flags.bools) && !(/^(true|false)$/.test(next))) { - argsToSet.push(true) - } else if (isUndefined(next) || - (isUndefined(argAfterEqualSign) && /^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next))) { - // for keys without value ==> argsToSet remains an empty [] - // set user default value, if available - if (defaults[key] !== undefined) { - const defVal = defaults[key] - argsToSet = Array.isArray(defVal) ? defVal : [defVal] - } - } else { - // value in --option=value is eaten as is - if (!isUndefined(argAfterEqualSign)) { - argsToSet.push(processValue(key, argAfterEqualSign)) - } - for (let ii = i + 1; ii < args.length; ii++) { - if ((!configuration['greedy-arrays'] && argsToSet.length > 0) || - (nargsCount && argsToSet.length >= nargsCount)) break - next = args[ii] - if (/^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next)) break - i = ii - argsToSet.push(processValue(key, next)) - } - } - - // If both array and nargs are configured, create an error if less than - // nargs positionals were found. NaN has special meaning, indicating - // that at least one value is required (more are okay). - if ((nargsCount && argsToSet.length < nargsCount) || - (isNaN(nargsCount) && argsToSet.length === 0)) { - error = Error(__('Not enough arguments following: %s', key)) - } - - setArg(key, argsToSet) - return i - } - - function setArg (key, val) { - if (/-/.test(key) && configuration['camel-case-expansion']) { - const alias = key.split('.').map(function (prop) { - return camelCase(prop) - }).join('.') - addNewAlias(key, alias) - } - - const value = processValue(key, val) - const splitKey = key.split('.') - setKey(argv, splitKey, value) - - // handle populating aliases of the full key - if (flags.aliases[key]) { - flags.aliases[key].forEach(function (x) { - x = x.split('.') - setKey(argv, x, value) - }) - } - - // handle populating aliases of the first element of the dot-notation key - if (splitKey.length > 1 && configuration['dot-notation']) { - ;(flags.aliases[splitKey[0]] || []).forEach(function (x) { - x = x.split('.') - - // expand alias with nested objects in key - const a = [].concat(splitKey) - a.shift() // nuke the old key. - x = x.concat(a) - - // populate alias only if is not already an alias of the full key - // (already populated above) - if (!(flags.aliases[key] || []).includes(x.join('.'))) { - setKey(argv, x, value) - } - }) - } - - // Set normalize getter and setter when key is in 'normalize' but isn't an array - if (checkAllAliases(key, flags.normalize) && !checkAllAliases(key, flags.arrays)) { - const keys = [key].concat(flags.aliases[key] || []) - keys.forEach(function (key) { - Object.defineProperty(argvReturn, key, { - enumerable: true, - get () { - return val - }, - set (value) { - val = typeof value === 'string' ? path.normalize(value) : value - } - }) - }) - } - } - - function addNewAlias (key, alias) { - if (!(flags.aliases[key] && flags.aliases[key].length)) { - flags.aliases[key] = [alias] - newAliases[alias] = true - } - if (!(flags.aliases[alias] && flags.aliases[alias].length)) { - addNewAlias(alias, key) - } - } - - function processValue (key, val) { - // strings may be quoted, clean this up as we assign values. - if (typeof val === 'string' && - (val[0] === "'" || val[0] === '"') && - val[val.length - 1] === val[0] - ) { - val = val.substring(1, val.length - 1) - } - - // handle parsing boolean arguments --foo=true --bar false. - if (checkAllAliases(key, flags.bools) || checkAllAliases(key, flags.counts)) { - if (typeof val === 'string') val = val === 'true' - } - - let value = Array.isArray(val) - ? val.map(function (v) { return maybeCoerceNumber(key, v) }) - : maybeCoerceNumber(key, val) - - // increment a count given as arg (either no value or value parsed as boolean) - if (checkAllAliases(key, flags.counts) && (isUndefined(value) || typeof value === 'boolean')) { - value = increment - } - - // Set normalized value when key is in 'normalize' and in 'arrays' - if (checkAllAliases(key, flags.normalize) && checkAllAliases(key, flags.arrays)) { - if (Array.isArray(val)) value = val.map(path.normalize) - else value = path.normalize(val) - } - return value - } - - function maybeCoerceNumber (key, value) { - if (!checkAllAliases(key, flags.strings) && !checkAllAliases(key, flags.bools) && !Array.isArray(value)) { - const shouldCoerceNumber = isNumber(value) && configuration['parse-numbers'] && ( - Number.isSafeInteger(Math.floor(value)) - ) - if (shouldCoerceNumber || (!isUndefined(value) && checkAllAliases(key, flags.numbers))) value = Number(value) - } - return value - } - - // set args from config.json file, this should be - // applied last so that defaults can be applied. - function setConfig (argv) { - const configLookup = Object.create(null) - - // expand defaults/aliases, in-case any happen to reference - // the config.json file. - applyDefaultsAndAliases(configLookup, flags.aliases, defaults) - - Object.keys(flags.configs).forEach(function (configKey) { - const configPath = argv[configKey] || configLookup[configKey] - if (configPath) { - try { - let config = null - const resolvedConfigPath = path.resolve(process.cwd(), configPath) - - if (typeof flags.configs[configKey] === 'function') { - try { - config = flags.configs[configKey](resolvedConfigPath) - } catch (e) { - config = e - } - if (config instanceof Error) { - error = config - return - } - } else { - config = require(resolvedConfigPath) - } - - setConfigObject(config) - } catch (ex) { - if (argv[configKey]) error = Error(__('Invalid JSON config file: %s', configPath)) - } - } - }) - } - - // set args from config object. - // it recursively checks nested objects. - function setConfigObject (config, prev) { - Object.keys(config).forEach(function (key) { - const value = config[key] - const fullKey = prev ? prev + '.' + key : key - - // if the value is an inner object and we have dot-notation - // enabled, treat inner objects in config the same as - // heavily nested dot notations (foo.bar.apple). - if (typeof value === 'object' && value !== null && !Array.isArray(value) && configuration['dot-notation']) { - // if the value is an object but not an array, check nested object - setConfigObject(value, fullKey) - } else { - // setting arguments via CLI takes precedence over - // values within the config file. - if (!hasKey(argv, fullKey.split('.')) || (checkAllAliases(fullKey, flags.arrays) && configuration['combine-arrays'])) { - setArg(fullKey, value) - } - } - }) - } - - // set all config objects passed in opts - function setConfigObjects () { - if (typeof configObjects === 'undefined') return - configObjects.forEach(function (configObject) { - setConfigObject(configObject) - }) - } - - function applyEnvVars (argv, configOnly) { - if (!process) return - if (typeof envPrefix === 'undefined') return - const prefix = typeof envPrefix === 'string' ? envPrefix : '' - Object.keys(process.env).forEach(function (envVar) { - if (prefix === '' || envVar.lastIndexOf(prefix, 0) === 0) { - // get array of nested keys and convert them to camel case - const keys = envVar.split('__').map(function (key, i) { - if (i === 0) { - key = key.substring(prefix.length) - } - return camelCase(key) - }) - - if (((configOnly && flags.configs[keys.join('.')]) || !configOnly) && !hasKey(argv, keys)) { - setArg(keys.join('.'), process.env[envVar]) - } - } - }) - } - - function applyCoercions (argv) { - let coerce - const applied = new Set() - Object.keys(argv).forEach(function (key) { - if (!applied.has(key)) { // If we haven't already coerced this option via one of its aliases - coerce = checkAllAliases(key, flags.coercions) - if (typeof coerce === 'function') { - try { - const value = maybeCoerceNumber(key, coerce(argv[key])) - ;([].concat(flags.aliases[key] || [], key)).forEach(ali => { - applied.add(ali) - argv[ali] = value - }) - } catch (err) { - error = err - } - } - } - }) - } - - function setPlaceholderKeys (argv) { - flags.keys.forEach((key) => { - // don't set placeholder keys for dot notation options 'foo.bar'. - if (~key.indexOf('.')) return - if (typeof argv[key] === 'undefined') argv[key] = undefined - }) - return argv - } - - function applyDefaultsAndAliases (obj, aliases, defaults, canLog = false) { - Object.keys(defaults).forEach(function (key) { - if (!hasKey(obj, key.split('.'))) { - setKey(obj, key.split('.'), defaults[key]) - if (canLog) defaulted[key] = true - - ;(aliases[key] || []).forEach(function (x) { - if (hasKey(obj, x.split('.'))) return - setKey(obj, x.split('.'), defaults[key]) - }) - } - }) - } - - function hasKey (obj, keys) { - let o = obj - - if (!configuration['dot-notation']) keys = [keys.join('.')] - - keys.slice(0, -1).forEach(function (key) { - o = (o[key] || {}) - }) - - const key = keys[keys.length - 1] - - if (typeof o !== 'object') return false - else return key in o - } - - function setKey (obj, keys, value) { - let o = obj - - if (!configuration['dot-notation']) keys = [keys.join('.')] - - keys.slice(0, -1).forEach(function (key, index) { - // TODO(bcoe): in the next major version of yargs, switch to - // Object.create(null) for dot notation: - key = sanitizeKey(key) - - if (typeof o === 'object' && o[key] === undefined) { - o[key] = {} - } - - if (typeof o[key] !== 'object' || Array.isArray(o[key])) { - // ensure that o[key] is an array, and that the last item is an empty object. - if (Array.isArray(o[key])) { - o[key].push({}) - } else { - o[key] = [o[key], {}] - } - - // we want to update the empty object at the end of the o[key] array, so set o to that object - o = o[key][o[key].length - 1] - } else { - o = o[key] - } - }) - - // TODO(bcoe): in the next major version of yargs, switch to - // Object.create(null) for dot notation: - const key = sanitizeKey(keys[keys.length - 1]) - - const isTypeArray = checkAllAliases(keys.join('.'), flags.arrays) - const isValueArray = Array.isArray(value) - let duplicate = configuration['duplicate-arguments-array'] - - // nargs has higher priority than duplicate - if (!duplicate && checkAllAliases(key, flags.nargs)) { - duplicate = true - if ((!isUndefined(o[key]) && flags.nargs[key] === 1) || (Array.isArray(o[key]) && o[key].length === flags.nargs[key])) { - o[key] = undefined - } - } - - if (value === increment) { - o[key] = increment(o[key]) - } else if (Array.isArray(o[key])) { - if (duplicate && isTypeArray && isValueArray) { - o[key] = configuration['flatten-duplicate-arrays'] ? o[key].concat(value) : (Array.isArray(o[key][0]) ? o[key] : [o[key]]).concat([value]) - } else if (!duplicate && Boolean(isTypeArray) === Boolean(isValueArray)) { - o[key] = value - } else { - o[key] = o[key].concat([value]) - } - } else if (o[key] === undefined && isTypeArray) { - o[key] = isValueArray ? value : [value] - } else if (duplicate && !( - o[key] === undefined || - checkAllAliases(key, flags.counts) || - checkAllAliases(key, flags.bools) - )) { - o[key] = [o[key], value] - } else { - o[key] = value - } - } - - // extend the aliases list with inferred aliases. - function extendAliases (...args) { - args.forEach(function (obj) { - Object.keys(obj || {}).forEach(function (key) { - // short-circuit if we've already added a key - // to the aliases array, for example it might - // exist in both 'opts.default' and 'opts.key'. - if (flags.aliases[key]) return - - flags.aliases[key] = [].concat(aliases[key] || []) - // For "--option-name", also set argv.optionName - flags.aliases[key].concat(key).forEach(function (x) { - if (/-/.test(x) && configuration['camel-case-expansion']) { - const c = camelCase(x) - if (c !== key && flags.aliases[key].indexOf(c) === -1) { - flags.aliases[key].push(c) - newAliases[c] = true - } - } - }) - // For "--optionName", also set argv['option-name'] - flags.aliases[key].concat(key).forEach(function (x) { - if (x.length > 1 && /[A-Z]/.test(x) && configuration['camel-case-expansion']) { - const c = decamelize(x, '-') - if (c !== key && flags.aliases[key].indexOf(c) === -1) { - flags.aliases[key].push(c) - newAliases[c] = true - } - } - }) - flags.aliases[key].forEach(function (x) { - flags.aliases[x] = [key].concat(flags.aliases[key].filter(function (y) { - return x !== y - })) - }) - }) - }) - } - - // return the 1st set flag for any of a key's aliases (or false if no flag set) - function checkAllAliases (key, flag) { - const toCheck = [].concat(flags.aliases[key] || [], key) - const keys = Object.keys(flag) - const setAlias = toCheck.find(key => keys.includes(key)) - return setAlias ? flag[setAlias] : false - } - - function hasAnyFlag (key) { - const toCheck = [].concat(Object.keys(flags).map(k => flags[k])) - return toCheck.some(function (flag) { - return Array.isArray(flag) ? flag.includes(key) : flag[key] - }) - } - - function hasFlagsMatching (arg, ...patterns) { - const toCheck = [].concat(...patterns) - return toCheck.some(function (pattern) { - const match = arg.match(pattern) - return match && hasAnyFlag(match[1]) - }) - } - - // based on a simplified version of the short flag group parsing logic - function hasAllShortFlags (arg) { - // if this is a negative number, or doesn't start with a single hyphen, it's not a short flag group - if (arg.match(negative) || !arg.match(/^-[^-]+/)) { return false } - let hasAllFlags = true - let next - const letters = arg.slice(1).split('') - for (let j = 0; j < letters.length; j++) { - next = arg.slice(j + 2) - - if (!hasAnyFlag(letters[j])) { - hasAllFlags = false - break - } - - if ((letters[j + 1] && letters[j + 1] === '=') || - next === '-' || - (/[A-Za-z]/.test(letters[j]) && /^-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) || - (letters[j + 1] && letters[j + 1].match(/\W/))) { - break - } - } - return hasAllFlags - } - - function isUnknownOptionAsArg (arg) { - return configuration['unknown-options-as-args'] && isUnknownOption(arg) - } - - function isUnknownOption (arg) { - // ignore negative numbers - if (arg.match(negative)) { return false } - // if this is a short option group and all of them are configured, it isn't unknown - if (hasAllShortFlags(arg)) { return false } - // e.g. '--count=2' - const flagWithEquals = /^-+([^=]+?)=[\s\S]*$/ - // e.g. '-a' or '--arg' - const normalFlag = /^-+([^=]+?)$/ - // e.g. '-a-' - const flagEndingInHyphen = /^-+([^=]+?)-$/ - // e.g. '-abc123' - const flagEndingInDigits = /^-+([^=]+?\d+)$/ - // e.g. '-a/usr/local' - const flagEndingInNonWordCharacters = /^-+([^=]+?)\W+.*$/ - // check the different types of flag styles, including negatedBoolean, a pattern defined near the start of the parse method - return !hasFlagsMatching(arg, flagWithEquals, negatedBoolean, normalFlag, flagEndingInHyphen, flagEndingInDigits, flagEndingInNonWordCharacters) - } - - // make a best effor to pick a default value - // for an option based on name and type. - function defaultValue (key) { - if (!checkAllAliases(key, flags.bools) && - !checkAllAliases(key, flags.counts) && - `${key}` in defaults) { - return defaults[key] - } else { - return defaultForType(guessType(key)) - } - } - - // return a default value, given the type of a flag., - // e.g., key of type 'string' will default to '', rather than 'true'. - function defaultForType (type) { - const def = { - boolean: true, - string: '', - number: undefined, - array: [] - } - - return def[type] - } - - // given a flag, enforce a default type. - function guessType (key) { - let type = 'boolean' - if (checkAllAliases(key, flags.strings)) type = 'string' - else if (checkAllAliases(key, flags.numbers)) type = 'number' - else if (checkAllAliases(key, flags.bools)) type = 'boolean' - else if (checkAllAliases(key, flags.arrays)) type = 'array' - return type - } - - function isNumber (x) { - if (x === null || x === undefined) return false - // if loaded from config, may already be a number. - if (typeof x === 'number') return true - // hexadecimal. - if (/^0x[0-9a-f]+$/i.test(x)) return true - // don't treat 0123 as a number; as it drops the leading '0'. - if (x.length > 1 && x[0] === '0') return false - return /^[-]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x) - } - - function isUndefined (num) { - return num === undefined - } - - // check user configuration settings for inconsistencies - function checkConfiguration () { - // count keys should not be set as array/narg - Object.keys(flags.counts).find(key => { - if (checkAllAliases(key, flags.arrays)) { - error = Error(__('Invalid configuration: %s, opts.count excludes opts.array.', key)) - return true - } else if (checkAllAliases(key, flags.nargs)) { - error = Error(__('Invalid configuration: %s, opts.count excludes opts.narg.', key)) - return true - } - }) - } - - return { - argv: Object.assign(argvReturn, argv), - error: error, - aliases: Object.assign({}, flags.aliases), - newAliases: Object.assign({}, newAliases), - defaulted: Object.assign({}, defaulted), - configuration: configuration - } -} - -// if any aliases reference each other, we should -// merge them together. -function combineAliases (aliases) { - const aliasArrays = [] - const combined = Object.create(null) - let change = true - - // turn alias lookup hash {key: ['alias1', 'alias2']} into - // a simple array ['key', 'alias1', 'alias2'] - Object.keys(aliases).forEach(function (key) { - aliasArrays.push( - [].concat(aliases[key], key) - ) - }) - - // combine arrays until zero changes are - // made in an iteration. - while (change) { - change = false - for (let i = 0; i < aliasArrays.length; i++) { - for (let ii = i + 1; ii < aliasArrays.length; ii++) { - const intersect = aliasArrays[i].filter(function (v) { - return aliasArrays[ii].indexOf(v) !== -1 - }) - - if (intersect.length) { - aliasArrays[i] = aliasArrays[i].concat(aliasArrays[ii]) - aliasArrays.splice(ii, 1) - change = true - break - } - } - } - } - - // map arrays back to the hash-lookup (de-dupe while - // we're at it). - aliasArrays.forEach(function (aliasArray) { - aliasArray = aliasArray.filter(function (v, i, self) { - return self.indexOf(v) === i - }) - combined[aliasArray.pop()] = aliasArray - }) - - return combined -} - -// this function should only be called when a count is given as an arg -// it is NOT called to set a default value -// thus we can start the count at 1 instead of 0 -function increment (orig) { - return orig !== undefined ? orig + 1 : 1 -} - -function Parser (args, opts) { - const result = parse(args.slice(), opts) - return result.argv -} - -// parse arguments and return detailed -// meta information, aliases, etc. -Parser.detailed = function (args, opts) { - return parse(args.slice(), opts) -} - -// TODO(bcoe): in the next major version of yargs, switch to -// Object.create(null) for dot notation: -function sanitizeKey (key) { - if (key === '__proto__') return '___proto___' - return key -} - -module.exports = Parser +module.exports = yargsParser diff --git a/lib/common-types.ts b/lib/common-types.ts new file mode 100644 index 00000000..8b02498d --- /dev/null +++ b/lib/common-types.ts @@ -0,0 +1,18 @@ +/** + * An object whose all properties have the same type, where each key is a string. + */ +export interface Dictionary { + [key: string]: T; +} + +/** + * Returns the keys of T. + */ +export type KeyOf = { + [K in keyof T]: string extends K ? never : number extends K ? never : K +} extends { [_ in keyof T]: infer U } ? U : never; + +/** + * Returns the type of a Dictionary or array values. + */ +export type ValueOf = T extends (infer U)[] ? U : T[keyof T]; diff --git a/lib/index.ts b/lib/index.ts new file mode 100644 index 00000000..4166b3b2 --- /dev/null +++ b/lib/index.ts @@ -0,0 +1,1114 @@ +import * as path from 'path' +import * as util from 'util' +import { tokenizeArgString } from './tokenize-arg-string' +import type { + ArgsInput, + Arguments, + ArrayFlagsKey, + ArrayOption, + CoerceCallback, + Configuration, + DefaultValuesForType, + DefaultValuesForTypeKey, + DetailedArguments, + Flag, + Flags, + FlagsKey, + StringFlag, + BooleanFlag, + NumberFlag, + ConfigsFlag, + CoercionsFlag, + Options, + OptionsDefault, + Parser +} from './yargs-parser-types' +import type { Dictionary, ValueOf } from './common-types' +import camelCase = require('camelcase') +import decamelize = require('decamelize') + +export type { Arguments, DetailedArguments, Configuration, Options, Parser } + +function parse (argsInput: ArgsInput, options?: Partial): DetailedArguments { + const opts: Partial = Object.assign({ + alias: undefined, + array: undefined, + boolean: undefined, + config: undefined, + configObjects: undefined, + configuration: undefined, + coerce: undefined, + count: undefined, + default: undefined, + envPrefix: undefined, + narg: undefined, + normalize: undefined, + string: undefined, + number: undefined, + __: undefined, + key: undefined + }, options) + // allow a string argument to be passed in rather + // than an argv array. + const args = tokenizeArgString(argsInput) + + // aliases might have transitive relationships, normalize this. + const aliases = combineAliases(Object.assign(Object.create(null), opts.alias)) + const configuration: Configuration = Object.assign({ + 'boolean-negation': true, + 'camel-case-expansion': true, + 'combine-arrays': false, + 'dot-notation': true, + 'duplicate-arguments-array': true, + 'flatten-duplicate-arrays': true, + 'greedy-arrays': true, + 'halt-at-non-option': false, + 'nargs-eats-options': false, + 'negation-prefix': 'no-', + 'parse-numbers': true, + 'populate--': false, + 'set-placeholder-key': false, + 'short-option-groups': true, + 'strip-aliased': false, + 'strip-dashed': false, + 'unknown-options-as-args': false + }, opts.configuration) + const defaults: OptionsDefault = Object.assign(Object.create(null), opts.default) + const configObjects = opts.configObjects || [] + const envPrefix = opts.envPrefix + const notFlagsOption = configuration['populate--'] + const notFlagsArgv: string = notFlagsOption ? '--' : '_' + const newAliases: Dictionary = Object.create(null) + const defaulted: Dictionary = Object.create(null) + // allow a i18n handler to be passed in, default to a fake one (util.format). + const __ = opts.__ || util.format + const flags: Flags = { + aliases: Object.create(null), + arrays: Object.create(null), + bools: Object.create(null), + strings: Object.create(null), + numbers: Object.create(null), + counts: Object.create(null), + normalize: Object.create(null), + configs: Object.create(null), + nargs: Object.create(null), + coercions: Object.create(null), + keys: [] + } + const negative = /^-([0-9]+(\.[0-9]+)?|\.[0-9]+)$/ + const negatedBoolean = new RegExp('^--' + configuration['negation-prefix'] + '(.+)') + + ;([] as ArrayOption[]).concat(opts.array || []).filter(Boolean).forEach(function (opt) { + const key = typeof opt === 'object' ? opt.key : opt + + // assign to flags[bools|strings|numbers] + const assignment: ArrayFlagsKey | undefined = Object.keys(opt).map(function (key) { + const arrayFlagKeys: Record = { + boolean: 'bools', + string: 'strings', + number: 'numbers' + } + return arrayFlagKeys[key] + }).filter(Boolean).pop() + + // assign key to be coerced + if (assignment) { + flags[assignment][key] = true + } + + flags.arrays[key] = true + flags.keys.push(key) + }) + + ;([] as string[]).concat(opts.boolean || []).filter(Boolean).forEach(function (key) { + flags.bools[key] = true + flags.keys.push(key) + }) + + ;([] as string[]).concat(opts.string || []).filter(Boolean).forEach(function (key) { + flags.strings[key] = true + flags.keys.push(key) + }) + + ;([] as string[]).concat(opts.number || []).filter(Boolean).forEach(function (key) { + flags.numbers[key] = true + flags.keys.push(key) + }) + + ;([] as string[]).concat(opts.count || []).filter(Boolean).forEach(function (key) { + flags.counts[key] = true + flags.keys.push(key) + }) + + ;([] as string[]).concat(opts.normalize || []).filter(Boolean).forEach(function (key) { + flags.normalize[key] = true + flags.keys.push(key) + }) + + if (typeof opts.narg === 'object') { + Object.entries(opts.narg).forEach(([key, value]) => { + if (typeof value === 'number') { + flags.nargs[key] = value + flags.keys.push(key) + } + }) + } + + if (typeof opts.coerce === 'object') { + Object.entries(opts.coerce).forEach(([key, value]) => { + if (typeof value === 'function') { + flags.coercions[key] = value + flags.keys.push(key) + } + }) + } + + if (typeof opts.config !== 'undefined') { + if (Array.isArray(opts.config) || typeof opts.config === 'string') { + ;([] as string[]).concat(opts.config).filter(Boolean).forEach(function (key) { + flags.configs[key] = true + }) + } else if (typeof opts.config === 'object') { + Object.entries(opts.config).forEach(([key, value]) => { + if (typeof value === 'boolean' || typeof value === 'function') { + flags.configs[key] = value + } + }) + } + } + + // create a lookup table that takes into account all + // combinations of aliases: {f: ['foo'], foo: ['f']} + extendAliases(opts.key, aliases, opts.default, flags.arrays) + + // apply default values to all aliases. + Object.keys(defaults).forEach(function (key) { + (flags.aliases[key] || []).forEach(function (alias) { + defaults[alias] = defaults[key] + }) + }) + + let error: Error | null = null + checkConfiguration() + + let notFlags: string[] = [] + + const argv: Arguments = Object.assign(Object.create(null), { _: [] }) + // TODO(bcoe): for the first pass at removing object prototype we didn't + // remove all prototypes from objects returned by this API, we might want + // to gradually move towards doing so. + const argvReturn: { [argName: string]: any } = {} + + for (let i = 0; i < args.length; i++) { + const arg = args[i] + let broken: boolean + let key: string | undefined + let letters: string[] + let m: RegExpMatchArray | null + let next: string + let value: string + + // any unknown option (except for end-of-options, "--") + if (arg !== '--' && isUnknownOptionAsArg(arg)) { + argv._.push(arg) + // -- separated by = + } else if (arg.match(/^--.+=/) || ( + !configuration['short-option-groups'] && arg.match(/^-.+=/) + )) { + // Using [\s\S] instead of . because js doesn't support the + // 'dotall' regex modifier. See: + // http://stackoverflow.com/a/1068308/13216 + m = arg.match(/^--?([^=]+)=([\s\S]*)$/) + + // arrays format = '--f=a b c' + if (m !== null && Array.isArray(m) && m.length >= 3) { + if (checkAllAliases(m[1], flags.arrays)) { + i = eatArray(i, m[1], args, m[2]) + } else if (checkAllAliases(m[1], flags.nargs) !== false) { + // nargs format = '--f=monkey washing cat' + i = eatNargs(i, m[1], args, m[2]) + } else { + setArg(m[1], m[2]) + } + } + } else if (arg.match(negatedBoolean) && configuration['boolean-negation']) { + m = arg.match(negatedBoolean) + if (m !== null && Array.isArray(m) && m.length >= 2) { + key = m[1] + setArg(key, checkAllAliases(key, flags.arrays) ? [false] : false) + } + + // -- separated by space. + } else if (arg.match(/^--.+/) || ( + !configuration['short-option-groups'] && arg.match(/^-[^-]+/) + )) { + m = arg.match(/^--?(.+)/) + if (m !== null && Array.isArray(m) && m.length >= 2) { + key = m[1] + if (checkAllAliases(key, flags.arrays)) { + // array format = '--foo a b c' + i = eatArray(i, key, args) + } else if (checkAllAliases(key, flags.nargs) !== false) { + // nargs format = '--foo a b c' + // should be truthy even if: flags.nargs[key] === 0 + i = eatNargs(i, key, args) + } else { + next = args[i + 1] + + if (next !== undefined && (!next.match(/^-/) || + next.match(negative)) && + !checkAllAliases(key, flags.bools) && + !checkAllAliases(key, flags.counts)) { + setArg(key, next) + i++ + } else if (/^(true|false)$/.test(next)) { + setArg(key, next) + i++ + } else { + setArg(key, defaultValue(key)) + } + } + } + + // dot-notation flag separated by '='. + } else if (arg.match(/^-.\..+=/)) { + m = arg.match(/^-([^=]+)=([\s\S]*)$/) + if (m !== null && Array.isArray(m) && m.length >= 3) { + setArg(m[1], m[2]) + } + + // dot-notation flag separated by space. + } else if (arg.match(/^-.\..+/) && !arg.match(negative)) { + next = args[i + 1] + m = arg.match(/^-(.\..+)/) + if (m !== null && Array.isArray(m) && m.length >= 2) { + key = m[1] + if (next !== undefined && !next.match(/^-/) && + !checkAllAliases(key, flags.bools) && + !checkAllAliases(key, flags.counts)) { + setArg(key, next) + i++ + } else { + setArg(key, defaultValue(key)) + } + } + } else if (arg.match(/^-[^-]+/) && !arg.match(negative)) { + letters = arg.slice(1, -1).split('') + broken = false + + for (let j = 0; j < letters.length; j++) { + next = arg.slice(j + 2) + + if (letters[j + 1] && letters[j + 1] === '=') { + value = arg.slice(j + 3) + key = letters[j] + + if (checkAllAliases(key, flags.arrays)) { + // array format = '-f=a b c' + i = eatArray(i, key, args, value) + } else if (checkAllAliases(key, flags.nargs) !== false) { + // nargs format = '-f=monkey washing cat' + i = eatNargs(i, key, args, value) + } else { + setArg(key, value) + } + + broken = true + break + } + + if (next === '-') { + setArg(letters[j], next) + continue + } + + // current letter is an alphabetic character and next value is a number + if (/[A-Za-z]/.test(letters[j]) && + /^-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) { + setArg(letters[j], next) + broken = true + break + } + + if (letters[j + 1] && letters[j + 1].match(/\W/)) { + setArg(letters[j], next) + broken = true + break + } else { + setArg(letters[j], defaultValue(letters[j])) + } + } + + key = arg.slice(-1)[0] + + if (!broken && key !== '-') { + if (checkAllAliases(key, flags.arrays)) { + // array format = '-f a b c' + i = eatArray(i, key, args) + } else if (checkAllAliases(key, flags.nargs) !== false) { + // nargs format = '-f a b c' + // should be truthy even if: flags.nargs[key] === 0 + i = eatNargs(i, key, args) + } else { + next = args[i + 1] + + if (next !== undefined && (!/^(-|--)[^-]/.test(next) || + next.match(negative)) && + !checkAllAliases(key, flags.bools) && + !checkAllAliases(key, flags.counts)) { + setArg(key, next) + i++ + } else if (/^(true|false)$/.test(next)) { + setArg(key, next) + i++ + } else { + setArg(key, defaultValue(key)) + } + } + } + } else if (arg.match(/^-[0-9]$/) && + arg.match(negative) && + checkAllAliases(arg.slice(1), flags.bools)) { + // single-digit boolean alias, e.g: xargs -0 + key = arg.slice(1) + setArg(key, defaultValue(key)) + } else if (arg === '--') { + notFlags = args.slice(i + 1) + break + } else if (configuration['halt-at-non-option']) { + notFlags = args.slice(i) + break + } else { + const maybeCoercedNumber = maybeCoerceNumber('_', arg) + if (typeof maybeCoercedNumber === 'string' || typeof maybeCoercedNumber === 'number') { + argv._.push(maybeCoercedNumber) + } + } + } + + // order of precedence: + // 1. command line arg + // 2. value from env var + // 3. value from config file + // 4. value from config objects + // 5. configured default value + applyEnvVars(argv, true) // special case: check env vars that point to config file + applyEnvVars(argv, false) + setConfig(argv) + setConfigObjects() + applyDefaultsAndAliases(argv, flags.aliases, defaults, true) + applyCoercions(argv) + if (configuration['set-placeholder-key']) setPlaceholderKeys(argv) + + // for any counts either not in args or without an explicit default, set to 0 + Object.keys(flags.counts).forEach(function (key) { + if (!hasKey(argv, key.split('.'))) setArg(key, 0) + }) + + // '--' defaults to undefined. + if (notFlagsOption && notFlags.length) argv[notFlagsArgv] = [] + notFlags.forEach(function (key) { + argv[notFlagsArgv].push(key) + }) + + if (configuration['camel-case-expansion'] && configuration['strip-dashed']) { + Object.keys(argv).filter(key => key !== '--' && key.includes('-')).forEach(key => { + delete argv[key] + }) + } + + if (configuration['strip-aliased']) { + ;([] as string[]).concat(...Object.keys(aliases).map(k => aliases[k])).forEach(alias => { + if (configuration['camel-case-expansion']) { + delete argv[alias.split('.').map(prop => camelCase(prop)).join('.')] + } + + delete argv[alias] + }) + } + + // how many arguments should we consume, based + // on the nargs option? + function eatNargs (i: number, key: string, args: string[], argAfterEqualSign?: string): number { + let ii + let toEat = checkAllAliases(key, flags.nargs) + // NaN has a special meaning for the array type, indicating that one or + // more values are expected. + toEat = typeof toEat !== 'number' || isNaN(toEat) ? 1 : toEat + + if (toEat === 0) { + if (!isUndefined(argAfterEqualSign)) { + error = Error(__('Argument unexpected for: %s', key)) + } + setArg(key, defaultValue(key)) + return i + } + + let available = isUndefined(argAfterEqualSign) ? 0 : 1 + if (configuration['nargs-eats-options']) { + // classic behavior, yargs eats positional and dash arguments. + if (args.length - (i + 1) + available < toEat) { + error = Error(__('Not enough arguments following: %s', key)) + } + available = toEat + } else { + // nargs will not consume flag arguments, e.g., -abc, --foo, + // and terminates when one is observed. + for (ii = i + 1; ii < args.length; ii++) { + if (!args[ii].match(/^-[^0-9]/) || args[ii].match(negative) || isUnknownOptionAsArg(args[ii])) available++ + else break + } + if (available < toEat) error = Error(__('Not enough arguments following: %s', key)) + } + + let consumed = Math.min(available, toEat) + if (!isUndefined(argAfterEqualSign) && consumed > 0) { + setArg(key, argAfterEqualSign) + consumed-- + } + for (ii = i + 1; ii < (consumed + i + 1); ii++) { + setArg(key, args[ii]) + } + + return (i + consumed) + } + + // if an option is an array, eat all non-hyphenated arguments + // following it... YUM! + // e.g., --foo apple banana cat becomes ["apple", "banana", "cat"] + function eatArray (i: number, key: string, args: string[], argAfterEqualSign?: string): number { + let argsToSet = [] + let next = argAfterEqualSign || args[i + 1] + // If both array and nargs are configured, enforce the nargs count: + const nargsCount = checkAllAliases(key, flags.nargs) + + if (checkAllAliases(key, flags.bools) && !(/^(true|false)$/.test(next))) { + argsToSet.push(true) + } else if (isUndefined(next) || + (isUndefined(argAfterEqualSign) && /^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next))) { + // for keys without value ==> argsToSet remains an empty [] + // set user default value, if available + if (defaults[key] !== undefined) { + const defVal = defaults[key] + argsToSet = Array.isArray(defVal) ? defVal : [defVal] + } + } else { + // value in --option=value is eaten as is + if (!isUndefined(argAfterEqualSign)) { + argsToSet.push(processValue(key, argAfterEqualSign)) + } + for (let ii = i + 1; ii < args.length; ii++) { + if ((!configuration['greedy-arrays'] && argsToSet.length > 0) || + (nargsCount && typeof nargsCount === 'number' && argsToSet.length >= nargsCount)) break + next = args[ii] + if (/^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next)) break + i = ii + argsToSet.push(processValue(key, next)) + } + } + + // If both array and nargs are configured, create an error if less than + // nargs positionals were found. NaN has special meaning, indicating + // that at least one value is required (more are okay). + if (typeof nargsCount === 'number' && ((nargsCount && argsToSet.length < nargsCount) || + (isNaN(nargsCount) && argsToSet.length === 0))) { + error = Error(__('Not enough arguments following: %s', key)) + } + + setArg(key, argsToSet) + return i + } + + function setArg (key: string, val: any): void { + if (/-/.test(key) && configuration['camel-case-expansion']) { + const alias = key.split('.').map(function (prop) { + return camelCase(prop) + }).join('.') + addNewAlias(key, alias) + } + + const value = processValue(key, val) + const splitKey = key.split('.') + setKey(argv, splitKey, value) + + // handle populating aliases of the full key + if (flags.aliases[key]) { + flags.aliases[key].forEach(function (x) { + const keyProperties = x.split('.') + setKey(argv, keyProperties, value) + }) + } + + // handle populating aliases of the first element of the dot-notation key + if (splitKey.length > 1 && configuration['dot-notation']) { + ;(flags.aliases[splitKey[0]] || []).forEach(function (x) { + let keyProperties = x.split('.') + + // expand alias with nested objects in key + const a = ([] as string[]).concat(splitKey) + a.shift() // nuke the old key. + keyProperties = keyProperties.concat(a) + + // populate alias only if is not already an alias of the full key + // (already populated above) + if (!(flags.aliases[key] || []).includes(keyProperties.join('.'))) { + setKey(argv, keyProperties, value) + } + }) + } + + // Set normalize getter and setter when key is in 'normalize' but isn't an array + if (checkAllAliases(key, flags.normalize) && !checkAllAliases(key, flags.arrays)) { + const keys = [key].concat(flags.aliases[key] || []) + keys.forEach(function (key) { + Object.defineProperty(argvReturn, key, { + enumerable: true, + get () { + return val + }, + set (value) { + val = typeof value === 'string' ? path.normalize(value) : value + } + }) + }) + } + } + + function addNewAlias (key: string, alias: string): void { + if (!(flags.aliases[key] && flags.aliases[key].length)) { + flags.aliases[key] = [alias] + newAliases[alias] = true + } + if (!(flags.aliases[alias] && flags.aliases[alias].length)) { + addNewAlias(alias, key) + } + } + + function processValue (key: string, val: any) { + // strings may be quoted, clean this up as we assign values. + if (typeof val === 'string' && + (val[0] === "'" || val[0] === '"') && + val[val.length - 1] === val[0] + ) { + val = val.substring(1, val.length - 1) + } + + // handle parsing boolean arguments --foo=true --bar false. + if (checkAllAliases(key, flags.bools) || checkAllAliases(key, flags.counts)) { + if (typeof val === 'string') val = val === 'true' + } + + let value = Array.isArray(val) + ? val.map(function (v) { return maybeCoerceNumber(key, v) }) + : maybeCoerceNumber(key, val) + + // increment a count given as arg (either no value or value parsed as boolean) + if (checkAllAliases(key, flags.counts) && (isUndefined(value) || typeof value === 'boolean')) { + value = increment() + } + + // Set normalized value when key is in 'normalize' and in 'arrays' + if (checkAllAliases(key, flags.normalize) && checkAllAliases(key, flags.arrays)) { + if (Array.isArray(val)) value = val.map(path.normalize) + else value = path.normalize(val) + } + return value + } + + function maybeCoerceNumber (key: string, value: string | number | null | undefined) { + if (!checkAllAliases(key, flags.strings) && !checkAllAliases(key, flags.bools) && !Array.isArray(value)) { + const shouldCoerceNumber = isNumber(value) && configuration['parse-numbers'] && ( + Number.isSafeInteger(Math.floor(parseFloat(`${value}`))) + ) + if (shouldCoerceNumber || (!isUndefined(value) && checkAllAliases(key, flags.numbers))) value = Number(value) + } + return value + } + + // set args from config.json file, this should be + // applied last so that defaults can be applied. + function setConfig (argv: Arguments): void { + const configLookup = Object.create(null) + + // expand defaults/aliases, in-case any happen to reference + // the config.json file. + applyDefaultsAndAliases(configLookup, flags.aliases, defaults) + + Object.keys(flags.configs).forEach(function (configKey) { + const configPath = argv[configKey] || configLookup[configKey] + if (configPath) { + try { + let config = null + const resolvedConfigPath = path.resolve(process.cwd(), configPath) + const resolveConfig = flags.configs[configKey] + + if (typeof resolveConfig === 'function') { + try { + config = resolveConfig(resolvedConfigPath) + } catch (e) { + config = e + } + if (config instanceof Error) { + error = config + return + } + } else { + config = require(resolvedConfigPath) + } + + setConfigObject(config) + } catch (ex) { + if (argv[configKey]) error = Error(__('Invalid JSON config file: %s', configPath)) + } + } + }) + } + + // set args from config object. + // it recursively checks nested objects. + function setConfigObject (config: { [key: string]: any }, prev?: string): void { + Object.keys(config).forEach(function (key) { + const value = config[key] + const fullKey = prev ? prev + '.' + key : key + + // if the value is an inner object and we have dot-notation + // enabled, treat inner objects in config the same as + // heavily nested dot notations (foo.bar.apple). + if (typeof value === 'object' && value !== null && !Array.isArray(value) && configuration['dot-notation']) { + // if the value is an object but not an array, check nested object + setConfigObject(value, fullKey) + } else { + // setting arguments via CLI takes precedence over + // values within the config file. + if (!hasKey(argv, fullKey.split('.')) || (checkAllAliases(fullKey, flags.arrays) && configuration['combine-arrays'])) { + setArg(fullKey, value) + } + } + }) + } + + // set all config objects passed in opts + function setConfigObjects (): void { + if (typeof configObjects !== 'undefined') { + configObjects.forEach(function (configObject) { + setConfigObject(configObject) + }) + } + } + + function applyEnvVars (argv: Arguments, configOnly: boolean): void { + if (process) { + if (typeof envPrefix === 'undefined') return + + const prefix = typeof envPrefix === 'string' ? envPrefix : '' + Object.keys(process.env).forEach(function (envVar) { + if (prefix === '' || envVar.lastIndexOf(prefix, 0) === 0) { + // get array of nested keys and convert them to camel case + const keys = envVar.split('__').map(function (key, i) { + if (i === 0) { + key = key.substring(prefix.length) + } + return camelCase(key) + }) + + if (((configOnly && flags.configs[keys.join('.')]) || !configOnly) && !hasKey(argv, keys)) { + setArg(keys.join('.'), process.env[envVar]) + } + } + }) + } + } + + function applyCoercions (argv: Arguments): void { + let coerce: false | CoerceCallback + const applied: Set = new Set() + Object.keys(argv).forEach(function (key) { + if (!applied.has(key)) { // If we haven't already coerced this option via one of its aliases + coerce = checkAllAliases(key, flags.coercions) + if (typeof coerce === 'function') { + try { + const value = maybeCoerceNumber(key, coerce(argv[key])) + ;(([] as string[]).concat(flags.aliases[key] || [], key)).forEach(ali => { + applied.add(ali) + argv[ali] = value + }) + } catch (err) { + error = err + } + } + } + }) + } + + function setPlaceholderKeys (argv: Arguments): Arguments { + flags.keys.forEach((key) => { + // don't set placeholder keys for dot notation options 'foo.bar'. + if (~key.indexOf('.')) return + if (typeof argv[key] === 'undefined') argv[key] = undefined + }) + return argv + } + + function applyDefaultsAndAliases (obj: { [key: string]: any }, aliases: { [key: string]: string[] }, defaults: { [key: string]: any }, canLog: boolean = false): void { + Object.keys(defaults).forEach(function (key) { + if (!hasKey(obj, key.split('.'))) { + setKey(obj, key.split('.'), defaults[key]) + if (canLog) defaulted[key] = true + + ;(aliases[key] || []).forEach(function (x) { + if (hasKey(obj, x.split('.'))) return + setKey(obj, x.split('.'), defaults[key]) + }) + } + }) + } + + function hasKey (obj: { [key: string]: any }, keys: string[]): boolean { + let o = obj + + if (!configuration['dot-notation']) keys = [keys.join('.')] + + keys.slice(0, -1).forEach(function (key) { + o = (o[key] || {}) + }) + + const key = keys[keys.length - 1] + + if (typeof o !== 'object') return false + else return key in o + } + + function setKey (obj: { [key: string]: any }, keys: string[], value: any): void { + let o = obj + + if (!configuration['dot-notation']) keys = [keys.join('.')] + + keys.slice(0, -1).forEach(function (key) { + // TODO(bcoe): in the next major version of yargs, switch to + // Object.create(null) for dot notation: + key = sanitizeKey(key) + + if (typeof o === 'object' && o[key] === undefined) { + o[key] = {} + } + + if (typeof o[key] !== 'object' || Array.isArray(o[key])) { + // ensure that o[key] is an array, and that the last item is an empty object. + if (Array.isArray(o[key])) { + o[key].push({}) + } else { + o[key] = [o[key], {}] + } + + // we want to update the empty object at the end of the o[key] array, so set o to that object + o = o[key][o[key].length - 1] + } else { + o = o[key] + } + }) + + // TODO(bcoe): in the next major version of yargs, switch to + // Object.create(null) for dot notation: + const key = sanitizeKey(keys[keys.length - 1]) + + const isTypeArray = checkAllAliases(keys.join('.'), flags.arrays) + const isValueArray = Array.isArray(value) + let duplicate = configuration['duplicate-arguments-array'] + + // nargs has higher priority than duplicate + if (!duplicate && checkAllAliases(key, flags.nargs)) { + duplicate = true + if ((!isUndefined(o[key]) && flags.nargs[key] === 1) || (Array.isArray(o[key]) && o[key].length === flags.nargs[key])) { + o[key] = undefined + } + } + + if (value === increment()) { + o[key] = increment(o[key]) + } else if (Array.isArray(o[key])) { + if (duplicate && isTypeArray && isValueArray) { + o[key] = configuration['flatten-duplicate-arrays'] ? o[key].concat(value) : (Array.isArray(o[key][0]) ? o[key] : [o[key]]).concat([value]) + } else if (!duplicate && Boolean(isTypeArray) === Boolean(isValueArray)) { + o[key] = value + } else { + o[key] = o[key].concat([value]) + } + } else if (o[key] === undefined && isTypeArray) { + o[key] = isValueArray ? value : [value] + } else if (duplicate && !( + o[key] === undefined || + checkAllAliases(key, flags.counts) || + checkAllAliases(key, flags.bools) + )) { + o[key] = [o[key], value] + } else { + o[key] = value + } + } + + // extend the aliases list with inferred aliases. + function extendAliases (...args: Array<{ [key: string]: any } | undefined>) { + args.forEach(function (obj) { + Object.keys(obj || {}).forEach(function (key) { + // short-circuit if we've already added a key + // to the aliases array, for example it might + // exist in both 'opts.default' and 'opts.key'. + if (flags.aliases[key]) return + + flags.aliases[key] = ([] as string[]).concat(aliases[key] || []) + // For "--option-name", also set argv.optionName + flags.aliases[key].concat(key).forEach(function (x) { + if (/-/.test(x) && configuration['camel-case-expansion']) { + const c = camelCase(x) + if (c !== key && flags.aliases[key].indexOf(c) === -1) { + flags.aliases[key].push(c) + newAliases[c] = true + } + } + }) + // For "--optionName", also set argv['option-name'] + flags.aliases[key].concat(key).forEach(function (x) { + if (x.length > 1 && /[A-Z]/.test(x) && configuration['camel-case-expansion']) { + const c = decamelize(x, '-') + if (c !== key && flags.aliases[key].indexOf(c) === -1) { + flags.aliases[key].push(c) + newAliases[c] = true + } + } + }) + flags.aliases[key].forEach(function (x) { + flags.aliases[x] = [key].concat(flags.aliases[key].filter(function (y) { + return x !== y + })) + }) + }) + }) + } + + // return the 1st set flag for any of a key's aliases (or false if no flag set) + function checkAllAliases (key: string, flag: StringFlag): ValueOf | false + function checkAllAliases (key: string, flag: BooleanFlag): ValueOf | false + function checkAllAliases (key: string, flag: NumberFlag): ValueOf | false + function checkAllAliases (key: string, flag: ConfigsFlag): ValueOf | false + function checkAllAliases (key: string, flag: CoercionsFlag): ValueOf | false + function checkAllAliases (key: string, flag: Flag): ValueOf | false { + const toCheck = ([] as string[]).concat(flags.aliases[key] || [], key) + const keys = Object.keys(flag) + const setAlias = toCheck.find(key => keys.includes(key)) + return setAlias ? flag[setAlias] : false + } + + function hasAnyFlag (key: string): boolean { + const flagsKeys = Object.keys(flags) as FlagsKey[] + const toCheck = ([] as Array<{ [key: string]: any } | string[]>).concat(flagsKeys.map(k => flags[k])) + return toCheck.some(function (flag) { + return Array.isArray(flag) ? flag.includes(key) : flag[key] + }) + } + + function hasFlagsMatching (arg: string, ...patterns: RegExp[]): boolean { + const toCheck = ([] as RegExp[]).concat(...patterns) + return toCheck.some(function (pattern) { + const match = arg.match(pattern) + return match && hasAnyFlag(match[1]) + }) + } + + // based on a simplified version of the short flag group parsing logic + function hasAllShortFlags (arg: string): boolean { + // if this is a negative number, or doesn't start with a single hyphen, it's not a short flag group + if (arg.match(negative) || !arg.match(/^-[^-]+/)) { return false } + let hasAllFlags = true + let next: string + const letters = arg.slice(1).split('') + for (let j = 0; j < letters.length; j++) { + next = arg.slice(j + 2) + + if (!hasAnyFlag(letters[j])) { + hasAllFlags = false + break + } + + if ((letters[j + 1] && letters[j + 1] === '=') || + next === '-' || + (/[A-Za-z]/.test(letters[j]) && /^-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) || + (letters[j + 1] && letters[j + 1].match(/\W/))) { + break + } + } + return hasAllFlags + } + + function isUnknownOptionAsArg (arg: string): boolean { + return configuration['unknown-options-as-args'] && isUnknownOption(arg) + } + + function isUnknownOption (arg: string): boolean { + // ignore negative numbers + if (arg.match(negative)) { return false } + // if this is a short option group and all of them are configured, it isn't unknown + if (hasAllShortFlags(arg)) { return false } + // e.g. '--count=2' + const flagWithEquals = /^-+([^=]+?)=[\s\S]*$/ + // e.g. '-a' or '--arg' + const normalFlag = /^-+([^=]+?)$/ + // e.g. '-a-' + const flagEndingInHyphen = /^-+([^=]+?)-$/ + // e.g. '-abc123' + const flagEndingInDigits = /^-+([^=]+?\d+)$/ + // e.g. '-a/usr/local' + const flagEndingInNonWordCharacters = /^-+([^=]+?)\W+.*$/ + // check the different types of flag styles, including negatedBoolean, a pattern defined near the start of the parse method + return !hasFlagsMatching(arg, flagWithEquals, negatedBoolean, normalFlag, flagEndingInHyphen, flagEndingInDigits, flagEndingInNonWordCharacters) + } + + // make a best effort to pick a default value + // for an option based on name and type. + function defaultValue (key: string) { + if (!checkAllAliases(key, flags.bools) && + !checkAllAliases(key, flags.counts) && + `${key}` in defaults) { + return defaults[key] + } else { + return defaultForType(guessType(key)) + } + } + + // return a default value, given the type of a flag., + function defaultForType (type: K): DefaultValuesForType[K] { + const def: DefaultValuesForType = { + boolean: true, + string: '', + number: undefined, + array: [] + } + + return def[type] + } + + // given a flag, enforce a default type. + function guessType (key: string): DefaultValuesForTypeKey { + let type: DefaultValuesForTypeKey = 'boolean' + if (checkAllAliases(key, flags.strings)) type = 'string' + else if (checkAllAliases(key, flags.numbers)) type = 'number' + else if (checkAllAliases(key, flags.bools)) type = 'boolean' + else if (checkAllAliases(key, flags.arrays)) type = 'array' + return type + } + + function isNumber (x: null | undefined | number | string): boolean { + if (x === null || x === undefined) return false + // if loaded from config, may already be a number. + if (typeof x === 'number') return true + // hexadecimal. + if (/^0x[0-9a-f]+$/i.test(x)) return true + // don't treat 0123 as a number; as it drops the leading '0'. + if (x.length > 1 && x[0] === '0') return false + return /^[-]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x) + } + + function isUndefined (num: any): num is undefined { + return num === undefined + } + + // check user configuration settings for inconsistencies + function checkConfiguration (): void { + // count keys should not be set as array/narg + Object.keys(flags.counts).find(key => { + if (checkAllAliases(key, flags.arrays)) { + error = Error(__('Invalid configuration: %s, opts.count excludes opts.array.', key)) + return true + } else if (checkAllAliases(key, flags.nargs)) { + error = Error(__('Invalid configuration: %s, opts.count excludes opts.narg.', key)) + return true + } + return false + }) + } + + return { + argv: Object.assign(argvReturn, argv), + error: error, + aliases: Object.assign({}, flags.aliases), + newAliases: Object.assign({}, newAliases), + defaulted: Object.assign({}, defaulted), + configuration: configuration + } +} + +// if any aliases reference each other, we should +// merge them together. +function combineAliases (aliases: Dictionary): Dictionary { + const aliasArrays: Array = [] + const combined: Dictionary = Object.create(null) + let change = true + + // turn alias lookup hash {key: ['alias1', 'alias2']} into + // a simple array ['key', 'alias1', 'alias2'] + Object.keys(aliases).forEach(function (key) { + aliasArrays.push( + ([] as string[]).concat(aliases[key], key) + ) + }) + + // combine arrays until zero changes are + // made in an iteration. + while (change) { + change = false + for (let i = 0; i < aliasArrays.length; i++) { + for (let ii = i + 1; ii < aliasArrays.length; ii++) { + const intersect = aliasArrays[i].filter(function (v) { + return aliasArrays[ii].indexOf(v) !== -1 + }) + + if (intersect.length) { + aliasArrays[i] = aliasArrays[i].concat(aliasArrays[ii]) + aliasArrays.splice(ii, 1) + change = true + break + } + } + } + } + + // map arrays back to the hash-lookup (de-dupe while + // we're at it). + aliasArrays.forEach(function (aliasArray) { + aliasArray = aliasArray.filter(function (v, i, self) { + return self.indexOf(v) === i + }) + const lastAlias = aliasArray.pop() + if (lastAlias !== undefined && typeof lastAlias === 'string') { + combined[lastAlias] = aliasArray + } + }) + + return combined +} + +// this function should only be called when a count is given as an arg +// it is NOT called to set a default value +// thus we can start the count at 1 instead of 0 +function increment (orig?: number | undefined): number { + return orig !== undefined ? orig + 1 : 1 +} + +// TODO(bcoe): in the next major version of yargs, switch to +// Object.create(null) for dot notation: +function sanitizeKey (key: string): string { + if (key === '__proto__') return '___proto___' + return key +} + +const yargsParser: Parser = function Parser (args: ArgsInput, opts?: Partial): Arguments { + const result = parse(args.slice(), opts) + return result.argv +} + +// parse arguments and return detailed +// meta information, aliases, etc. +yargsParser.detailed = function (args: ArgsInput, opts?: Partial): DetailedArguments { + return parse(args.slice(), opts) +} + +export { yargsParser } diff --git a/lib/yargs-parser-types.ts b/lib/yargs-parser-types.ts new file mode 100644 index 00000000..6e218948 --- /dev/null +++ b/lib/yargs-parser-types.ts @@ -0,0 +1,159 @@ +import type { Dictionary, KeyOf, ValueOf } from './common-types' + +export type ArgsInput = string | any[]; + +export type ArgsOutput = (string | number)[]; + +export interface Arguments { + /** Non-option arguments */ + _: ArgsOutput; + /** Arguments after the end-of-options flag `--` */ + '--'?: ArgsOutput; + /** All remaining options */ + [argName: string]: any; +} + +export interface DetailedArguments { + /** An object representing the parsed value of `args` */ + argv: Arguments; + /** Populated with an error object if an exception occurred during parsing. */ + error: Error | null; + /** The inferred list of aliases built by combining lists in opts.alias. */ + aliases: Dictionary; + /** Any new aliases added via camel-case expansion. */ + newAliases: Dictionary; + /** Any new argument created by opts.default, no aliases included. */ + defaulted: Dictionary; + /** The configuration loaded from the yargs stanza in package.json. */ + configuration: Configuration; +} + +export interface Configuration { + /** Should variables prefixed with --no be treated as negations? Default is `true` */ + 'boolean-negation': boolean; + /** Should hyphenated arguments be expanded into camel-case aliases? Default is `true` */ + 'camel-case-expansion': boolean; + /** Should arrays be combined when provided by both command line arguments and a configuration file? Default is `false` */ + 'combine-arrays': boolean; + /** Should keys that contain `.` be treated as objects? Default is `true` */ + 'dot-notation': boolean; + /** Should arguments be coerced into an array when duplicated? Default is `true` */ + 'duplicate-arguments-array': boolean; + /** Should array arguments be coerced into a single array when duplicated? Default is `true` */ + 'flatten-duplicate-arrays': boolean; + /** Should arrays consume more than one positional argument following their flag? Default is `true` */ + 'greedy-arrays': boolean; + /** Should parsing stop at the first text argument? This is similar to how e.g. ssh parses its command line. Default is `false` */ + 'halt-at-non-option': boolean; + /** Should nargs consume dash options as well as positional arguments? Default is `false` */ + 'nargs-eats-options': boolean; + /** The prefix to use for negated boolean variables. Default is `'no-'` */ + 'negation-prefix': string; + /** Should keys that look like numbers be treated as such? Default is `true` */ + 'parse-numbers': boolean; + /** Should unparsed flags be stored in -- or _? Default is `false` */ + 'populate--': boolean; + /** Should a placeholder be added for keys not set via the corresponding CLI argument? Default is `false` */ + 'set-placeholder-key': boolean; + /** Should a group of short-options be treated as boolean flags? Default is `true` */ + 'short-option-groups': boolean; + /** Should aliases be removed before returning results? Default is `false` */ + 'strip-aliased': boolean; + /** Should dashed keys be removed before returning results? This option has no effect if camel-case-expansion is disabled. Default is `false` */ + 'strip-dashed': boolean; + /** Should unknown options be treated like regular arguments? An unknown option is one that is not configured in opts. Default is `false` */ + 'unknown-options-as-args': boolean; +} + +export type ArrayOption = string | { key: string; boolean?: boolean, string?: boolean, number?: boolean, integer?: boolean }; + +export type CoerceCallback = (arg: any) => any; + +export type ConfigCallback = (configPath: string) => { [key: string]: any } | Error; + +export interface Options { + /** An object representing the set of aliases for a key: `{ alias: { foo: ['f']} }`. */ + alias: Dictionary; + /** + * Indicate that keys should be parsed as an array: `{ array: ['foo', 'bar'] }`. + * Indicate that keys should be parsed as an array and coerced to booleans / numbers: + * { array: [ { key: 'foo', boolean: true }, {key: 'bar', number: true} ] }`. + */ + array: ArrayOption | ArrayOption[]; + /** Arguments should be parsed as booleans: `{ boolean: ['x', 'y'] }`. */ + boolean: string | string[]; + /** Indicate a key that represents a path to a configuration file (this file will be loaded and parsed). */ + config: string | string[] | Dictionary; + /** configuration objects to parse, their properties will be set as arguments */ + configObjects: Dictionary[]; + /** Provide configuration options to the yargs-parser. */ + configuration: Partial; + /** + * Provide a custom synchronous function that returns a coerced value from the argument provided (or throws an error), e.g. + * `{ coerce: { foo: function (arg) { return modifiedArg } } }`. + */ + coerce: Dictionary; + /** Indicate a key that should be used as a counter, e.g., `-vvv = {v: 3}`. */ + count: string | string[]; + /** Provide default values for keys: `{ default: { x: 33, y: 'hello world!' } }`. */ + default: Dictionary; + /** Environment variables (`process.env`) with the prefix provided should be parsed. */ + envPrefix: string; + /** Specify that a key requires n arguments: `{ narg: {x: 2} }`. */ + narg: Dictionary; + /** `path.normalize()` will be applied to values set to this key. */ + normalize: string | string[]; + /** Keys should be treated as strings (even if they resemble a number `-x 33`). */ + string: string | string[]; + /** Keys should be treated as numbers. */ + number: string | string[]; + /** i18n handler, defaults to util.format */ + __: (format: any, ...param: any[]) => string; + /** alias lookup table defaults */ + key: Dictionary; +} + +export type OptionsDefault = ValueOf, 'default'>>; + +export interface Parser { + (args: ArgsInput, opts?: Options): Arguments; + detailed(args: ArgsInput, opts?: Options): DetailedArguments; +} + +export type StringFlag = Dictionary; +export type BooleanFlag = Dictionary; +export type NumberFlag = Dictionary; +export type ConfigsFlag = Dictionary; +export type CoercionsFlag = Dictionary; +export type KeysFlag = string[]; + +export interface Flags { + aliases: StringFlag; + arrays: BooleanFlag; + bools: BooleanFlag; + strings: BooleanFlag; + numbers: BooleanFlag; + counts: BooleanFlag; + normalize: BooleanFlag; + configs: ConfigsFlag; + nargs: NumberFlag; + coercions: CoercionsFlag; + keys: KeysFlag; +} + +export type Flag = ValueOf>; + +export type FlagValue = ValueOf; + +export type FlagsKey = KeyOf>; + +export type ArrayFlagsKey = Extract; + +export interface DefaultValuesForType { + boolean: boolean; + string: string; + number: undefined; + array: any[]; +} + +export type DefaultValuesForTypeKey = KeyOf; diff --git a/test/yargs-parser.js b/test/yargs-parser.js index e017a08b..0705a389 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.js @@ -2067,6 +2067,26 @@ describe('yargs-parser', function () { result.foo.should.eql('a') }) + + it('should ignore undefined configured nargs', function () { + var result = parser(['--foo', 'a', 'b'], { + narg: { + foo: undefined + } + }) + + result.foo.should.eql('a') + }) + + it('should default to 1 if configured narg is NaN', function () { + var result = parser(['--foo', 'a', 'b'], { + narg: { + foo: NaN + } + }) + + result.foo.should.eql('a') + }) }) describe('env vars', function () { @@ -2081,6 +2101,29 @@ describe('yargs-parser', function () { result.redFish.should.equal('bluefish') }) + // envPrefix falls back to empty string if not a string + it('should apply all env vars if prefix is not a string', function () { + process.env.ONE_FISH = 'twofish' + process.env.RED_FISH = 'bluefish' + var result = parser([], { + envPrefix: null + }) + + result.oneFish.should.equal('twofish') + result.redFish.should.equal('bluefish') + }) + + it('should not apply all env vars if prefix is undefined', function () { + process.env.ONE_FISH = 'twofish' + process.env.RED_FISH = 'bluefish' + var result = parser([], { + envPrefix: undefined + }) + + expect(result).to.not.have.property('oneFish') + expect(result).to.not.have.property('redFish') + }) + it('should apply only env vars matching prefix if prefix is valid string', function () { process.env.ONE_FISH = 'twofish' process.env.RED_FISH = 'bluefish' @@ -2883,6 +2926,18 @@ describe('yargs-parser', function () { parsed.should.not.have.property('b') parsed.should.not.have.property('a.b') }) + + it('should not set placeholder key with dot notation when `set-placeholder-key` is `true`', function () { + var parsed = parser([], { + string: ['a.b'], + configuration: { + 'set-placeholder-key': true + } + }) + parsed.should.not.have.property('a') + parsed.should.not.have.property('b') + parsed.should.not.have.property('a.b') + }) }) describe('halt-at-non-option', function () { From 5634c67dc6cc90cae6a3b118110b331089e4057f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 18 Jul 2020 15:11:46 -0700 Subject: [PATCH 107/206] chore(deps): update dependency @types/mocha to v8 (#290) Co-authored-by: Renovate Bot Co-authored-by: Benjamin E. Coe --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 26268129..08c358c5 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "license": "ISC", "devDependencies": { "@types/chai": "^4.2.11", - "@types/mocha": "^7.0.2", + "@types/mocha": "^8.0.0", "@types/node": "^10.0.3", "@typescript-eslint/eslint-plugin": "^3.0.0", "@typescript-eslint/parser": "^3.0.0", From f78d2b97567ac4828624406e420b4047c710b789 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sat, 18 Jul 2020 16:57:36 -0700 Subject: [PATCH 108/206] refactor(ts)!: move index.js to TypeScript (#292) BREAKING CHANGE: projects using `@types/yargs-parser` may see variations in type definitions. --- index.js | 14 -------------- lib/index.ts => index.ts | 27 ++++++++++++++++++--------- lib/yargs-parser-types.ts | 32 ++++++++++++++++---------------- package.json | 6 +++--- test/types.ts | 13 +++++++++++++ tsconfig.json | 1 + tsconfig.test.json | 1 + 7 files changed, 52 insertions(+), 42 deletions(-) delete mode 100644 index.js rename lib/index.ts => index.ts (97%) create mode 100644 test/types.ts diff --git a/index.js b/index.js deleted file mode 100644 index d6e1bb04..00000000 --- a/index.js +++ /dev/null @@ -1,14 +0,0 @@ -const { yargsParser } = require('./build/lib') - -// See https://github.com/yargs/yargs-parser#supported-nodejs-versions for our -// version support policy. The YARGS_MIN_NODE_VERSION is used for testing only. -const minNodeVersion = (process && process.env && process.env.YARGS_MIN_NODE_VERSION) - ? Number(process.env.YARGS_MIN_NODE_VERSION) : 10 -if (process && process.version) { - const major = Number(process.version.match(/v([^.]+)/)[1]) - if (major < minNodeVersion) { - throw Error(`yargs parser supports a minimum Node.js version of ${minNodeVersion}. Read our version support policy: https://github.com/yargs/yargs-parser#supported-nodejs-versions`) - } -} - -module.exports = yargsParser diff --git a/lib/index.ts b/index.ts similarity index 97% rename from lib/index.ts rename to index.ts index 4166b3b2..558dcf71 100644 --- a/lib/index.ts +++ b/index.ts @@ -1,6 +1,6 @@ import * as path from 'path' import * as util from 'util' -import { tokenizeArgString } from './tokenize-arg-string' +import { tokenizeArgString } from './lib/tokenize-arg-string' import type { ArgsInput, Arguments, @@ -22,14 +22,23 @@ import type { Options, OptionsDefault, Parser -} from './yargs-parser-types' -import type { Dictionary, ValueOf } from './common-types' +} from './lib/yargs-parser-types' +import type { Dictionary, ValueOf } from './lib/common-types' + +// See https://github.com/yargs/yargs-parser#supported-nodejs-versions for our +// version support policy. The YARGS_MIN_NODE_VERSION is used for testing only. +const minNodeVersion = (process && process.env && process.env.YARGS_MIN_NODE_VERSION) + ? Number(process.env.YARGS_MIN_NODE_VERSION) : 10 +if (process && process.version) { + const major = Number(process.version.match(/v([^.]+)/)![1]) + if (major < minNodeVersion) { + throw Error(`yargs parser supports a minimum Node.js version of ${minNodeVersion}. Read our version support policy: https://github.com/yargs/yargs-parser#supported-nodejs-versions`) + } +} import camelCase = require('camelcase') import decamelize = require('decamelize') -export type { Arguments, DetailedArguments, Configuration, Options, Parser } - -function parse (argsInput: ArgsInput, options?: Partial): DetailedArguments { +function parse (argsInput: ArgsInput, options?: Options): DetailedArguments { const opts: Partial = Object.assign({ alias: undefined, array: undefined, @@ -1100,15 +1109,15 @@ function sanitizeKey (key: string): string { return key } -const yargsParser: Parser = function Parser (args: ArgsInput, opts?: Partial): Arguments { +const yargsParser: Parser = function Parser (args: ArgsInput, opts?: Options): Arguments { const result = parse(args.slice(), opts) return result.argv } // parse arguments and return detailed // meta information, aliases, etc. -yargsParser.detailed = function (args: ArgsInput, opts?: Partial): DetailedArguments { +yargsParser.detailed = function (args: ArgsInput, opts?: Options): DetailedArguments { return parse(args.slice(), opts) } -export { yargsParser } +export = yargsParser diff --git a/lib/yargs-parser-types.ts b/lib/yargs-parser-types.ts index 6e218948..8edf6837 100644 --- a/lib/yargs-parser-types.ts +++ b/lib/yargs-parser-types.ts @@ -73,44 +73,44 @@ export type ConfigCallback = (configPath: string) => { [key: string]: any } | Er export interface Options { /** An object representing the set of aliases for a key: `{ alias: { foo: ['f']} }`. */ - alias: Dictionary; + alias?: Dictionary; /** * Indicate that keys should be parsed as an array: `{ array: ['foo', 'bar'] }`. * Indicate that keys should be parsed as an array and coerced to booleans / numbers: * { array: [ { key: 'foo', boolean: true }, {key: 'bar', number: true} ] }`. */ - array: ArrayOption | ArrayOption[]; + array?: ArrayOption | ArrayOption[]; /** Arguments should be parsed as booleans: `{ boolean: ['x', 'y'] }`. */ - boolean: string | string[]; + boolean?: string | string[]; /** Indicate a key that represents a path to a configuration file (this file will be loaded and parsed). */ - config: string | string[] | Dictionary; + config?: string | string[] | Dictionary; /** configuration objects to parse, their properties will be set as arguments */ - configObjects: Dictionary[]; + configObjects?: Dictionary[]; /** Provide configuration options to the yargs-parser. */ - configuration: Partial; + configuration?: Partial; /** * Provide a custom synchronous function that returns a coerced value from the argument provided (or throws an error), e.g. * `{ coerce: { foo: function (arg) { return modifiedArg } } }`. */ - coerce: Dictionary; + coerce?: Dictionary; /** Indicate a key that should be used as a counter, e.g., `-vvv = {v: 3}`. */ - count: string | string[]; + count?: string | string[]; /** Provide default values for keys: `{ default: { x: 33, y: 'hello world!' } }`. */ - default: Dictionary; + default?: Dictionary; /** Environment variables (`process.env`) with the prefix provided should be parsed. */ - envPrefix: string; + envPrefix?: string; /** Specify that a key requires n arguments: `{ narg: {x: 2} }`. */ - narg: Dictionary; + narg?: Dictionary; /** `path.normalize()` will be applied to values set to this key. */ - normalize: string | string[]; + normalize?: string | string[]; /** Keys should be treated as strings (even if they resemble a number `-x 33`). */ - string: string | string[]; + string?: string | string[]; /** Keys should be treated as numbers. */ - number: string | string[]; + number?: string | string[]; /** i18n handler, defaults to util.format */ - __: (format: any, ...param: any[]) => string; + __?: (format: any, ...param: any[]) => string; /** alias lookup table defaults */ - key: Dictionary; + key?: Dictionary; } export type OptionsDefault = ValueOf, 'default'>>; diff --git a/package.json b/package.json index 08c358c5..7dd03ccc 100644 --- a/package.json +++ b/package.json @@ -2,14 +2,14 @@ "name": "yargs-parser", "version": "18.1.3", "description": "the mighty option parser used by yargs", - "main": "index.js", + "main": "build/index.js", "scripts": { - "fix": "standardx --fix && standardx --fix **/*.ts", + "fix": "standardx --fix ./*.ts && standardx --fix **/*.ts", "pretest": "npm run compile -- -p tsconfig.test.json", "test": "c8 --reporter=text --reporter=html mocha test/*.js", "posttest": "npm run check", "coverage": "c8 report --check-coverage", - "check": "standardx && standardx **/*.ts", + "check": "standardx ./*.ts && standardx **/*.ts", "compile": "rimraf build && tsc", "prepare": "npm run compile" }, diff --git a/test/types.ts b/test/types.ts new file mode 100644 index 00000000..e29ff542 --- /dev/null +++ b/test/types.ts @@ -0,0 +1,13 @@ +/* global describe, it */ + +import * as yargsParser from '../' +import * as assert from 'assert' + +describe('types', function () { + it('allows a partial options object to be provided', () => { + const argv = yargsParser('--foo 99', { + string: 'foo' + }) + assert.strictEqual(argv.foo, '99') + }) +}) diff --git a/tsconfig.json b/tsconfig.json index c28e1966..8e20a300 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "sourceMap": false }, "include": [ + "./*.ts", "lib/**/*.ts" ] } diff --git a/tsconfig.test.json b/tsconfig.test.json index 75a19fe0..62fec111 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -4,6 +4,7 @@ "sourceMap": true }, "include": [ + "./*.ts", "lib/**/*.ts", "test/**/*.ts" ] From bdc80ba59fa13bc3025ce0a85e8bad9f9da24ea7 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sat, 18 Jul 2020 19:12:52 -0700 Subject: [PATCH 109/206] fix(types): switch back to using Partial types (#293) --- index.ts | 6 +++--- lib/yargs-parser-types.ts | 36 ++++++++++++++++++------------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/index.ts b/index.ts index 558dcf71..f751c7b9 100644 --- a/index.ts +++ b/index.ts @@ -38,7 +38,7 @@ if (process && process.version) { import camelCase = require('camelcase') import decamelize = require('decamelize') -function parse (argsInput: ArgsInput, options?: Options): DetailedArguments { +function parse (argsInput: ArgsInput, options?: Partial): DetailedArguments { const opts: Partial = Object.assign({ alias: undefined, array: undefined, @@ -1109,14 +1109,14 @@ function sanitizeKey (key: string): string { return key } -const yargsParser: Parser = function Parser (args: ArgsInput, opts?: Options): Arguments { +const yargsParser: Parser = function Parser (args: ArgsInput, opts?: Partial): Arguments { const result = parse(args.slice(), opts) return result.argv } // parse arguments and return detailed // meta information, aliases, etc. -yargsParser.detailed = function (args: ArgsInput, opts?: Options): DetailedArguments { +yargsParser.detailed = function (args: ArgsInput, opts?: Partial): DetailedArguments { return parse(args.slice(), opts) } diff --git a/lib/yargs-parser-types.ts b/lib/yargs-parser-types.ts index 8edf6837..9d4b38b8 100644 --- a/lib/yargs-parser-types.ts +++ b/lib/yargs-parser-types.ts @@ -73,51 +73,51 @@ export type ConfigCallback = (configPath: string) => { [key: string]: any } | Er export interface Options { /** An object representing the set of aliases for a key: `{ alias: { foo: ['f']} }`. */ - alias?: Dictionary; + alias: Dictionary; /** * Indicate that keys should be parsed as an array: `{ array: ['foo', 'bar'] }`. * Indicate that keys should be parsed as an array and coerced to booleans / numbers: * { array: [ { key: 'foo', boolean: true }, {key: 'bar', number: true} ] }`. */ - array?: ArrayOption | ArrayOption[]; + array: ArrayOption | ArrayOption[]; /** Arguments should be parsed as booleans: `{ boolean: ['x', 'y'] }`. */ - boolean?: string | string[]; + boolean: string | string[]; /** Indicate a key that represents a path to a configuration file (this file will be loaded and parsed). */ - config?: string | string[] | Dictionary; + config: string | string[] | Dictionary; /** configuration objects to parse, their properties will be set as arguments */ - configObjects?: Dictionary[]; + configObjects: Dictionary[]; /** Provide configuration options to the yargs-parser. */ - configuration?: Partial; + configuration: Partial; /** * Provide a custom synchronous function that returns a coerced value from the argument provided (or throws an error), e.g. * `{ coerce: { foo: function (arg) { return modifiedArg } } }`. */ - coerce?: Dictionary; + coerce: Dictionary; /** Indicate a key that should be used as a counter, e.g., `-vvv = {v: 3}`. */ - count?: string | string[]; + count: string | string[]; /** Provide default values for keys: `{ default: { x: 33, y: 'hello world!' } }`. */ - default?: Dictionary; + default: Dictionary; /** Environment variables (`process.env`) with the prefix provided should be parsed. */ - envPrefix?: string; + envPrefix: string; /** Specify that a key requires n arguments: `{ narg: {x: 2} }`. */ - narg?: Dictionary; + narg: Dictionary; /** `path.normalize()` will be applied to values set to this key. */ - normalize?: string | string[]; + normalize: string | string[]; /** Keys should be treated as strings (even if they resemble a number `-x 33`). */ - string?: string | string[]; + string: string | string[]; /** Keys should be treated as numbers. */ - number?: string | string[]; + number: string | string[]; /** i18n handler, defaults to util.format */ - __?: (format: any, ...param: any[]) => string; + __: (format: any, ...param: any[]) => string; /** alias lookup table defaults */ - key?: Dictionary; + key: Dictionary; } export type OptionsDefault = ValueOf, 'default'>>; export interface Parser { - (args: ArgsInput, opts?: Options): Arguments; - detailed(args: ArgsInput, opts?: Options): DetailedArguments; + (args: ArgsInput, opts?: Partial): Arguments; + detailed(args: ArgsInput, opts?: Partial): DetailedArguments; } export type StringFlag = Dictionary; From 195bc4a7f20c2a8f8e33fbb6ba96ef6e9a0120a1 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Mon, 3 Aug 2020 22:58:55 -0700 Subject: [PATCH 110/206] feat!: adds support for ESM and Deno (#295) --- .editorconfig | 12 - .github/workflows/ci.yaml | 45 +- .github/workflows/release-please.yml | 22 +- .gitignore | 2 +- .mocharc.json | 6 - .nycrc | 6 +- CHANGELOG.md | 504 -------- README.md | 50 +- browser.js | 26 + deno.ts | 35 + docs/CHANGELOG-full.md | 503 ++++++++ example.js | 3 - index.ts | 1123 ------------------ lib/index.ts | 49 + lib/string-utils.ts | 40 + lib/yargs-parser-types.ts | 11 +- lib/yargs-parser.ts | 1102 +++++++++++++++++ package.json | 46 +- renovate.json | 3 +- rollup.config.js | 17 + scripts/replace-legacy-export.cjs | 11 + test/browser/yargs-test.cjs | 55 + test/browser/yargs-test.html | 21 + test/deno/yargs-test.ts | 46 + test/fixtures/{settings.js => settings.cjs} | 0 test/{ => typescript}/tokenize-arg-string.ts | 22 +- test/{ => typescript}/types.ts | 2 +- test/{yargs-parser.js => yargs-parser.cjs} | 5 +- tsconfig.json | 10 +- tsconfig.test.json | 6 +- 30 files changed, 2082 insertions(+), 1701 deletions(-) delete mode 100644 .editorconfig delete mode 100644 .mocharc.json create mode 100644 browser.js create mode 100644 deno.ts create mode 100644 docs/CHANGELOG-full.md delete mode 100755 example.js delete mode 100644 index.ts create mode 100644 lib/index.ts create mode 100644 lib/string-utils.ts create mode 100644 lib/yargs-parser.ts create mode 100644 rollup.config.js create mode 100755 scripts/replace-legacy-export.cjs create mode 100644 test/browser/yargs-test.cjs create mode 100644 test/browser/yargs-test.html create mode 100644 test/deno/yargs-test.ts rename test/fixtures/{settings.js => settings.cjs} (100%) rename test/{ => typescript}/tokenize-arg-string.ts (92%) rename test/{ => typescript}/types.ts (85%) rename test/{yargs-parser.js => yargs-parser.cjs} (99%) diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 4039ff11..00000000 --- a/.editorconfig +++ /dev/null @@ -1,12 +0,0 @@ -root = true - -[*] -charset = utf-8 -end_of_line = lf -indent_size = 2 -indent_style = space -insert_final_newline = true -trim_trailing_whitespace = true - -[*.md] -trim_trailing_whitespace = false diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0213d80f..67e7de55 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,6 +17,8 @@ jobs: node-version: ${{ matrix.node }} - run: node --version - run: npm install + env: + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true - run: npm test windows: runs-on: windows-latest @@ -26,14 +28,53 @@ jobs: with: node-version: 12 - run: npm install + env: + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true - run: npm test coverage: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 13 + node-version: 14 - run: npm install + env: + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true - run: npm test - run: npm run coverage + deno: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 14 + - run: npm install + env: + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true + - run: npm run compile + - uses: denolib/setup-deno@v2 + with: + deno-version: v1.x + - run: | + deno --version + deno test --allow-read test/deno/yargs-test.ts + browser: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 14 + - run: npm install + - run: npm run test:browser + typescript: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 14 + - run: npm install + - run: npm run test:typescript diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 70660547..86f5a9db 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -7,8 +7,28 @@ jobs: release-please: runs-on: ubuntu-latest steps: - - uses: bcoe/release-please-action@v1.2.1 + - uses: bcoe/release-please-action@v1.6.3 with: token: ${{ secrets.GITHUB_TOKEN }} release-type: node package-name: yargs-parser + release-deno: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 14 + - run: npm install + env: + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true + - run: npm run compile + - name: push Deno release + run: | + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git remote add gh-token "https://$%7B%7B%20secrets.GITHUB_TOKEN%7D%7D@github.com/yargs/yargs-parser.git" + git checkout -b deno + git add -f build + git commit -a -m 'build: deploy latest Deno build' + git push origin +deno diff --git a/.gitignore b/.gitignore index 791cb484..3059a185 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ .idea -build/ .nyc_output node_modules .DS_Store package-lock.json ./test/fixtures/package.json coverage +build diff --git a/.mocharc.json b/.mocharc.json deleted file mode 100644 index ef1c3645..00000000 --- a/.mocharc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "spec": [ - "build/test", - "test" - ] -} diff --git a/.nycrc b/.nycrc index 89f6c3a0..3b315160 100644 --- a/.nycrc +++ b/.nycrc @@ -7,7 +7,7 @@ "html", "text" ], - "lines": 100, - "branches": "97", - "statements": "100" + "lines": 99.5, + "branches": "98", + "statements": "99.5" } diff --git a/CHANGELOG.md b/CHANGELOG.md index d91dc516..fe6e61d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,507 +95,3 @@ leaks externally. ### Reverts * revert 16.0.0 CHANGELOG entry ([920320a](https://www.github.com/yargs/yargs-parser/commit/920320ad9861bbfd58eda39221ae211540fc1daf)) - -## [15.0.0](https://github.com/yargs/yargs-parser/compare/v14.0.0...v15.0.0) (2019-10-07) - - -### Features - -* rework `collect-unknown-options` into `unknown-options-as-args`, providing more comprehensive functionality ([ef771ca](https://github.com/yargs/yargs-parser/commit/ef771ca)) - - -### BREAKING CHANGES - -* rework `collect-unknown-options` into `unknown-options-as-args`, providing more comprehensive functionality - - - -## [14.0.0](https://github.com/yargs/yargs-parser/compare/v13.1.1...v14.0.0) (2019-09-06) - - -### Bug Fixes - -* boolean arrays with default values ([#185](https://github.com/yargs/yargs-parser/issues/185)) ([7d42572](https://github.com/yargs/yargs-parser/commit/7d42572)) -* boolean now behaves the same as other array types ([#184](https://github.com/yargs/yargs-parser/issues/184)) ([17ca3bd](https://github.com/yargs/yargs-parser/commit/17ca3bd)) -* eatNargs() for 'opt.narg === 0' and boolean typed options ([#188](https://github.com/yargs/yargs-parser/issues/188)) ([c5a1db0](https://github.com/yargs/yargs-parser/commit/c5a1db0)) -* maybeCoerceNumber now takes precedence over coerce return value ([#182](https://github.com/yargs/yargs-parser/issues/182)) ([2f26436](https://github.com/yargs/yargs-parser/commit/2f26436)) -* take into account aliases when appending arrays from config object ([#199](https://github.com/yargs/yargs-parser/issues/199)) ([f8a2d3f](https://github.com/yargs/yargs-parser/commit/f8a2d3f)) - - -### Features - -* add configuration option to "collect-unknown-options" ([#181](https://github.com/yargs/yargs-parser/issues/181)) ([7909cc4](https://github.com/yargs/yargs-parser/commit/7909cc4)) -* maybeCoerceNumber() now takes into account arrays ([#187](https://github.com/yargs/yargs-parser/issues/187)) ([31c204b](https://github.com/yargs/yargs-parser/commit/31c204b)) - - -### BREAKING CHANGES - -* unless "parse-numbers" is set to "false", arrays of numeric strings are now parsed as numbers, rather than strings. -* we have dropped the broken "defaulted" functionality; we would like to revisit adding this in the future. -* maybeCoerceNumber now takes precedence over coerce return value (#182) - - - -### [13.1.1](https://www.github.com/yargs/yargs-parser/compare/v13.1.0...v13.1.1) (2019-06-10) - - -### Bug Fixes - -* convert values to strings when tokenizing ([#167](https://www.github.com/yargs/yargs-parser/issues/167)) ([57b7883](https://www.github.com/yargs/yargs-parser/commit/57b7883)) -* nargs should allow duplicates when duplicate-arguments-array=false ([#164](https://www.github.com/yargs/yargs-parser/issues/164)) ([47ccb0b](https://www.github.com/yargs/yargs-parser/commit/47ccb0b)) -* should populate "_" when given config with "short-option-groups" false ([#179](https://www.github.com/yargs/yargs-parser/issues/179)) ([6055974](https://www.github.com/yargs/yargs-parser/commit/6055974)) - -## [13.1.0](https://github.com/yargs/yargs-parser/compare/v13.0.0...v13.1.0) (2019-05-05) - - -### Features - -* add `strip-aliased` and `strip-dashed` configuration options. ([#172](https://github.com/yargs/yargs-parser/issues/172)) ([a3936aa](https://github.com/yargs/yargs-parser/commit/a3936aa)) -* support boolean which do not consume next argument. ([#171](https://github.com/yargs/yargs-parser/issues/171)) ([0ae7fcb](https://github.com/yargs/yargs-parser/commit/0ae7fcb)) - - - - -# [13.0.0](https://github.com/yargs/yargs-parser/compare/v12.0.0...v13.0.0) (2019-02-02) - - -### Features - -* don't coerce number from string with leading '0' or '+' ([#158](https://github.com/yargs/yargs-parser/issues/158)) ([18d0fd5](https://github.com/yargs/yargs-parser/commit/18d0fd5)) - - -### BREAKING CHANGES - -* options with leading '+' or '0' now parse as strings - - - - -# [12.0.0](https://github.com/yargs/yargs-parser/compare/v11.1.1...v12.0.0) (2019-01-29) - - -### Bug Fixes - -* better handling of quoted strings ([#153](https://github.com/yargs/yargs-parser/issues/153)) ([2fb71b2](https://github.com/yargs/yargs-parser/commit/2fb71b2)) - - -### Features - -* default value is now used if no right-hand value provided for numbers/strings ([#156](https://github.com/yargs/yargs-parser/issues/156)) ([5a7c46a](https://github.com/yargs/yargs-parser/commit/5a7c46a)) - - -### BREAKING CHANGES - -* a flag with no right-hand value no longer populates defaulted options with `undefined`. -* quotes at beginning and endings of strings are not removed during parsing. - - - - -## [11.1.1](https://github.com/yargs/yargs-parser/compare/v11.1.0...v11.1.1) (2018-11-19) - - -### Bug Fixes - -* ensure empty string is added into argv._ ([#140](https://github.com/yargs/yargs-parser/issues/140)) ([79cda98](https://github.com/yargs/yargs-parser/commit/79cda98)) - - -### Reverts - -* make requiresArg work in conjunction with arrays ([#136](https://github.com/yargs/yargs-parser/issues/136)) ([f4a3063](https://github.com/yargs/yargs-parser/commit/f4a3063)) - - - - -# [11.1.0](https://github.com/yargs/yargs-parser/compare/v11.0.0...v11.1.0) (2018-11-10) - - -### Bug Fixes - -* handling of one char alias ([#139](https://github.com/yargs/yargs-parser/issues/139)) ([ee56e31](https://github.com/yargs/yargs-parser/commit/ee56e31)) - - -### Features - -* add halt-at-non-option configuration option ([#130](https://github.com/yargs/yargs-parser/issues/130)) ([a849fce](https://github.com/yargs/yargs-parser/commit/a849fce)) - - - - -# [11.0.0](https://github.com/yargs/yargs-parser/compare/v10.1.0...v11.0.0) (2018-10-06) - - -### Bug Fixes - -* flatten-duplicate-arrays:false for more than 2 arrays ([#128](https://github.com/yargs/yargs-parser/issues/128)) ([2bc395f](https://github.com/yargs/yargs-parser/commit/2bc395f)) -* hyphenated flags combined with dot notation broke parsing ([#131](https://github.com/yargs/yargs-parser/issues/131)) ([dc788da](https://github.com/yargs/yargs-parser/commit/dc788da)) -* make requiresArg work in conjunction with arrays ([#136](https://github.com/yargs/yargs-parser/issues/136)) ([77ae1d4](https://github.com/yargs/yargs-parser/commit/77ae1d4)) - - -### Chores - -* update dependencies ([6dc42a1](https://github.com/yargs/yargs-parser/commit/6dc42a1)) - - -### Features - -* also add camelCase array options ([#125](https://github.com/yargs/yargs-parser/issues/125)) ([08c0117](https://github.com/yargs/yargs-parser/commit/08c0117)) -* array.type can now be provided, supporting coercion ([#132](https://github.com/yargs/yargs-parser/issues/132)) ([4b8cfce](https://github.com/yargs/yargs-parser/commit/4b8cfce)) - - -### BREAKING CHANGES - -* drops Node 4 support -* the argv object is now populated differently (correctly) when hyphens and dot notation are used in conjunction. - - - - -# [10.1.0](https://github.com/yargs/yargs-parser/compare/v10.0.0...v10.1.0) (2018-06-29) - - -### Features - -* add `set-placeholder-key` configuration ([#123](https://github.com/yargs/yargs-parser/issues/123)) ([19386ee](https://github.com/yargs/yargs-parser/commit/19386ee)) - - - - -# [10.0.0](https://github.com/yargs/yargs-parser/compare/v9.0.2...v10.0.0) (2018-04-04) - - -### Bug Fixes - -* do not set boolean flags if not defined in `argv` ([#119](https://github.com/yargs/yargs-parser/issues/119)) ([f6e6599](https://github.com/yargs/yargs-parser/commit/f6e6599)) - - -### BREAKING CHANGES - -* `boolean` flags defined without a `default` value will now behave like other option type and won't be set in the parsed results when the user doesn't set the corresponding CLI arg. - -Previous behavior: -```js -var parse = require('yargs-parser'); - -parse('--flag', {boolean: ['flag']}); -// => { _: [], flag: true } - -parse('--no-flag', {boolean: ['flag']}); -// => { _: [], flag: false } - -parse('', {boolean: ['flag']}); -// => { _: [], flag: false } -``` - -New behavior: -```js -var parse = require('yargs-parser'); - -parse('--flag', {boolean: ['flag']}); -// => { _: [], flag: true } - -parse('--no-flag', {boolean: ['flag']}); -// => { _: [], flag: false } - -parse('', {boolean: ['flag']}); -// => { _: [] } => flag not set similarly to other option type -``` - - - - -## [9.0.2](https://github.com/yargs/yargs-parser/compare/v9.0.1...v9.0.2) (2018-01-20) - - -### Bug Fixes - -* nargs was still aggressively consuming too many arguments ([9b28aad](https://github.com/yargs/yargs-parser/commit/9b28aad)) - - - - -## [9.0.1](https://github.com/yargs/yargs-parser/compare/v9.0.0...v9.0.1) (2018-01-20) - - -### Bug Fixes - -* nargs was consuming too many arguments ([4fef206](https://github.com/yargs/yargs-parser/commit/4fef206)) - - - - -# [9.0.0](https://github.com/yargs/yargs-parser/compare/v8.1.0...v9.0.0) (2018-01-20) - - -### Features - -* narg arguments no longer consume flag arguments ([#114](https://github.com/yargs/yargs-parser/issues/114)) ([60bb9b3](https://github.com/yargs/yargs-parser/commit/60bb9b3)) - - -### BREAKING CHANGES - -* arguments of form --foo, -abc, will no longer be consumed by nargs - - - - -# [8.1.0](https://github.com/yargs/yargs-parser/compare/v8.0.0...v8.1.0) (2017-12-20) - - -### Bug Fixes - -* allow null config values ([#108](https://github.com/yargs/yargs-parser/issues/108)) ([d8b14f9](https://github.com/yargs/yargs-parser/commit/d8b14f9)) -* ensure consistent parsing of dot-notation arguments ([#102](https://github.com/yargs/yargs-parser/issues/102)) ([c9bd79c](https://github.com/yargs/yargs-parser/commit/c9bd79c)) -* implement [@antoniom](https://github.com/antoniom)'s fix for camel-case expansion ([3087e1d](https://github.com/yargs/yargs-parser/commit/3087e1d)) -* only run coercion functions once, despite aliases. ([#76](https://github.com/yargs/yargs-parser/issues/76)) ([#103](https://github.com/yargs/yargs-parser/issues/103)) ([507aaef](https://github.com/yargs/yargs-parser/commit/507aaef)) -* scientific notation circumvented bounds check ([#110](https://github.com/yargs/yargs-parser/issues/110)) ([3571f57](https://github.com/yargs/yargs-parser/commit/3571f57)) -* tokenizer should ignore spaces at the beginning of the argString ([#106](https://github.com/yargs/yargs-parser/issues/106)) ([f34ead9](https://github.com/yargs/yargs-parser/commit/f34ead9)) - - -### Features - -* make combining arrays a configurable option ([#111](https://github.com/yargs/yargs-parser/issues/111)) ([c8bf536](https://github.com/yargs/yargs-parser/commit/c8bf536)) -* merge array from arguments with array from config ([#83](https://github.com/yargs/yargs-parser/issues/83)) ([806ddd6](https://github.com/yargs/yargs-parser/commit/806ddd6)) - - - - -# [8.0.0](https://github.com/yargs/yargs-parser/compare/v7.0.0...v8.0.0) (2017-10-05) - - -### Bug Fixes - -* Ignore multiple spaces between arguments. ([#100](https://github.com/yargs/yargs-parser/issues/100)) ([d137227](https://github.com/yargs/yargs-parser/commit/d137227)) - - -### Features - -* allow configuration of prefix for boolean negation ([#94](https://github.com/yargs/yargs-parser/issues/94)) ([00bde7d](https://github.com/yargs/yargs-parser/commit/00bde7d)) -* reworking how numbers are parsed ([#104](https://github.com/yargs/yargs-parser/issues/104)) ([fba00eb](https://github.com/yargs/yargs-parser/commit/fba00eb)) - - -### BREAKING CHANGES - -* strings that fail `Number.isSafeInteger()` are no longer coerced into numbers. - - - - -# [7.0.0](https://github.com/yargs/yargs-parser/compare/v6.0.1...v7.0.0) (2017-05-02) - - -### Chores - -* revert populate-- logic ([#91](https://github.com/yargs/yargs-parser/issues/91)) ([6003e6d](https://github.com/yargs/yargs-parser/commit/6003e6d)) - - -### BREAKING CHANGES - -* populate-- now defaults to false. - - - - -## [6.0.1](https://github.com/yargs/yargs-parser/compare/v6.0.0...v6.0.1) (2017-05-01) - - -### Bug Fixes - -* default '--' to undefined when not provided; this is closer to the array API ([#90](https://github.com/yargs/yargs-parser/issues/90)) ([4e739cc](https://github.com/yargs/yargs-parser/commit/4e739cc)) - - - - -# [6.0.0](https://github.com/yargs/yargs-parser/compare/v4.2.1...v6.0.0) (2017-05-01) - - -### Bug Fixes - -* environment variables should take precedence over config file ([#81](https://github.com/yargs/yargs-parser/issues/81)) ([76cee1f](https://github.com/yargs/yargs-parser/commit/76cee1f)) -* parsing hints should apply for dot notation keys ([#86](https://github.com/yargs/yargs-parser/issues/86)) ([3e47d62](https://github.com/yargs/yargs-parser/commit/3e47d62)) - - -### Chores - -* upgrade to newest version of camelcase ([#87](https://github.com/yargs/yargs-parser/issues/87)) ([f1903aa](https://github.com/yargs/yargs-parser/commit/f1903aa)) - - -### Features - -* add -- option which allows arguments after the -- flag to be returned separated from positional arguments ([#84](https://github.com/yargs/yargs-parser/issues/84)) ([2572ca8](https://github.com/yargs/yargs-parser/commit/2572ca8)) -* when parsing stops, we now populate "--" by default ([#88](https://github.com/yargs/yargs-parser/issues/88)) ([cd666db](https://github.com/yargs/yargs-parser/commit/cd666db)) - - -### BREAKING CHANGES - -* rather than placing arguments in "_", when parsing is stopped via "--"; we now populate an array called "--" by default. -* camelcase now requires Node 4+. -* environment variables will now override config files (args, env, config-file, config-object) - - - - -# [5.0.0](https://github.com/yargs/yargs-parser/compare/v4.2.1...v5.0.0) (2017-02-18) - - -### Bug Fixes - -* environment variables should take precedence over config file ([#81](https://github.com/yargs/yargs-parser/issues/81)) ([76cee1f](https://github.com/yargs/yargs-parser/commit/76cee1f)) - - -### BREAKING CHANGES - -* environment variables will now override config files (args, env, config-file, config-object) - - - - -## [4.2.1](https://github.com/yargs/yargs-parser/compare/v4.2.0...v4.2.1) (2017-01-02) - - -### Bug Fixes - -* flatten/duplicate regression ([#75](https://github.com/yargs/yargs-parser/issues/75)) ([68d68a0](https://github.com/yargs/yargs-parser/commit/68d68a0)) - - - - -# [4.2.0](https://github.com/yargs/yargs-parser/compare/v4.1.0...v4.2.0) (2016-12-01) - - -### Bug Fixes - -* inner objects in configs had their keys appended to top-level key when dot-notation was disabled ([#72](https://github.com/yargs/yargs-parser/issues/72)) ([0b1b5f9](https://github.com/yargs/yargs-parser/commit/0b1b5f9)) - - -### Features - -* allow multiple arrays to be provided, rather than always combining ([#71](https://github.com/yargs/yargs-parser/issues/71)) ([0f0fb2d](https://github.com/yargs/yargs-parser/commit/0f0fb2d)) - - - - -# [4.1.0](https://github.com/yargs/yargs-parser/compare/v4.0.2...v4.1.0) (2016-11-07) - - -### Features - -* apply coercions to default options ([#65](https://github.com/yargs/yargs-parser/issues/65)) ([c79052b](https://github.com/yargs/yargs-parser/commit/c79052b)) -* handle dot notation boolean options ([#63](https://github.com/yargs/yargs-parser/issues/63)) ([02c3545](https://github.com/yargs/yargs-parser/commit/02c3545)) - - - - -## [4.0.2](https://github.com/yargs/yargs-parser/compare/v4.0.1...v4.0.2) (2016-09-30) - - -### Bug Fixes - -* whoops, let's make the assign not change the Object key order ([29d069a](https://github.com/yargs/yargs-parser/commit/29d069a)) - - - - -## [4.0.1](https://github.com/yargs/yargs-parser/compare/v4.0.0...v4.0.1) (2016-09-30) - - -### Bug Fixes - -* lodash.assign was deprecated ([#59](https://github.com/yargs/yargs-parser/issues/59)) ([5e7eb11](https://github.com/yargs/yargs-parser/commit/5e7eb11)) - - - - -# [4.0.0](https://github.com/yargs/yargs-parser/compare/v3.2.0...v4.0.0) (2016-09-26) - - -### Bug Fixes - -* coerce should be applied to the final objects and arrays created ([#57](https://github.com/yargs/yargs-parser/issues/57)) ([4ca69da](https://github.com/yargs/yargs-parser/commit/4ca69da)) - - -### BREAKING CHANGES - -* coerce is no longer applied to individual arguments in an implicit array. - - - - -# [3.2.0](https://github.com/yargs/yargs-parser/compare/v3.1.0...v3.2.0) (2016-08-13) - - -### Features - -* coerce full array instead of each element ([#51](https://github.com/yargs/yargs-parser/issues/51)) ([cc4dc56](https://github.com/yargs/yargs-parser/commit/cc4dc56)) - - - - -# [3.1.0](https://github.com/yargs/yargs-parser/compare/v3.0.0...v3.1.0) (2016-08-09) - - -### Bug Fixes - -* address pkgConf parsing bug outlined in [#37](https://github.com/yargs/yargs-parser/issues/37) ([#45](https://github.com/yargs/yargs-parser/issues/45)) ([be76ee6](https://github.com/yargs/yargs-parser/commit/be76ee6)) -* better parsing of negative values ([#44](https://github.com/yargs/yargs-parser/issues/44)) ([2e43692](https://github.com/yargs/yargs-parser/commit/2e43692)) -* check aliases when guessing defaults for arguments fixes [#41](https://github.com/yargs/yargs-parser/issues/41) ([#43](https://github.com/yargs/yargs-parser/issues/43)) ([f3e4616](https://github.com/yargs/yargs-parser/commit/f3e4616)) - - -### Features - -* added coerce option, for providing specialized argument parsing ([#42](https://github.com/yargs/yargs-parser/issues/42)) ([7b49cd2](https://github.com/yargs/yargs-parser/commit/7b49cd2)) - - - - -# [3.0.0](https://github.com/yargs/yargs-parser/compare/v2.4.1...v3.0.0) (2016-08-07) - - -### Bug Fixes - -* parsing issue with numeric character in group of options ([#19](https://github.com/yargs/yargs-parser/issues/19)) ([f743236](https://github.com/yargs/yargs-parser/commit/f743236)) -* upgraded lodash.assign ([5d7fdf4](https://github.com/yargs/yargs-parser/commit/5d7fdf4)) - -### BREAKING CHANGES - -* subtle change to how values are parsed in a group of single-character arguments. -* _first released in 3.1.0, better handling of negative values should be considered a breaking change._ - - - - -## [2.4.1](https://github.com/yargs/yargs-parser/compare/v2.4.0...v2.4.1) (2016-07-16) - - -### Bug Fixes - -* **count:** do not increment a default value ([#39](https://github.com/yargs/yargs-parser/issues/39)) ([b04a189](https://github.com/yargs/yargs-parser/commit/b04a189)) - - - - -# [2.4.0](https://github.com/yargs/yargs-parser/compare/v2.3.0...v2.4.0) (2016-04-11) - - -### Features - -* **environment:** Support nested options in environment variables ([#26](https://github.com/yargs/yargs-parser/issues/26)) thanks [@elas7](https://github.com/elas7) \o/ ([020778b](https://github.com/yargs/yargs-parser/commit/020778b)) - - - - -# [2.3.0](https://github.com/yargs/yargs-parser/compare/v2.2.0...v2.3.0) (2016-04-09) - - -### Bug Fixes - -* **boolean:** fix for boolean options with non boolean defaults (#20) ([2dbe86b](https://github.com/yargs/yargs-parser/commit/2dbe86b)), closes [(#20](https://github.com/(/issues/20) -* **package:** remove tests from tarball ([0353c0d](https://github.com/yargs/yargs-parser/commit/0353c0d)) -* **parsing:** handle calling short option with an empty string as the next value. ([a867165](https://github.com/yargs/yargs-parser/commit/a867165)) -* boolean flag when next value contains the strings 'true' or 'false'. ([69941a6](https://github.com/yargs/yargs-parser/commit/69941a6)) -* update dependencies; add standard-version bin for next release (#24) ([822d9d5](https://github.com/yargs/yargs-parser/commit/822d9d5)) - -### Features - -* **configuration:** Allow to pass configuration objects to yargs-parser ([0780900](https://github.com/yargs/yargs-parser/commit/0780900)) -* **normalize:** allow normalize to work with arrays ([e0eaa1a](https://github.com/yargs/yargs-parser/commit/e0eaa1a)) diff --git a/README.md b/README.md index eff195c3..c3c346ee 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ npm i yargs-parser --save ``` ```js -var argv = require('yargs-parser')(process.argv.slice(2)) +const argv = require('yargs-parser')(process.argv.slice(2)) console.log(argv) ``` @@ -30,7 +30,7 @@ node example.js --foo=33 --bar hello _or parse a string!_ ```js -var argv = require('yargs-parser')('--foo=99 --bar=33') +const argv = require('yargs-parser')('--foo=99 --bar=33') console.log(argv) ``` @@ -41,11 +41,55 @@ console.log(argv) Convert an array of mixed types before passing to `yargs-parser`: ```js -var parse = require('yargs-parser') +const parse = require('yargs-parser') parse(['-f', 11, '--zoom', 55].join(' ')) // <-- array to string parse(['-f', 11, '--zoom', 55].map(String)) // <-- array of strings ``` +## Deno Example + +As of `v19` `yargs-parser` supports [Deno](https://github.com/denoland/deno): + +```typescript +import parser from "https://github.com/yargs/yargs-parser/raw/deno/deno.ts"; + +const argv = parser('--foo=99 --bar=9987930', { + string: ['bar'] +}) +console.log(argv) +``` + +## ESM Example + +As of `v19` `yargs-parser` supports ESM (_both in Node.js and in the browser_): + +**Node.js:** + +```js +import parser from 'yargs-parser' + +const argv = parser('--foo=99 --bar=9987930', { + string: ['bar'] +}) +console.log(argv) +``` + +**Browsers:** + +```html + + + + +``` + ## API ### require('yargs-parser')(args, opts={}) diff --git a/browser.js b/browser.js new file mode 100644 index 00000000..934edee1 --- /dev/null +++ b/browser.js @@ -0,0 +1,26 @@ +// Main entrypoint for ESM web browser environments. Avoids using Node.js +// specific libraries, such as "path". +// +// TODO: figure out reasonable web equivalents for "resolve", "normalize", etc. +import { YargsParser } from './build/lib/yargs-parser.js' +const parser = new YargsParser({ + cwd: () => { return '' }, + format: (str, arg) => { return str.replace('%s', arg) }, + normalize: (str) => { return str }, + resolve: (str) => { return str }, + require: () => { + throw Error('loading config from files not currently supported in browser') + }, + env: () => {} +}) + +const yargsParser = function Parser (args, opts) { + const result = parser.parse(args.slice(), opts) + return result.argv +} + +yargsParser.detailed = function (args, opts) { + return parser.parse(args.slice(), opts) +} + +export default yargsParser diff --git a/deno.ts b/deno.ts new file mode 100644 index 00000000..f60b7f7c --- /dev/null +++ b/deno.ts @@ -0,0 +1,35 @@ +/* global Deno */ +// Main entrypoint for Deno. +// +// TODO: find reasonable replacement for require logic. +import * as path from 'https://deno.land/std/path/mod.ts' +import { YargsParser } from './build/lib/yargs-parser.js' +import { Arguments, ArgsInput, Parser, Options, DetailedArguments } from './build/lib/yargs-parser-types.d.ts' + +const parser = new YargsParser({ + cwd: Deno.cwd, + env: () => { + Deno.env.toObject() + }, + format: (str: string, arg: string) => { return str.replace('%s', arg) }, + normalize: path.posix.normalize, + resolve: path.posix.resolve, + require: (path: string) => { + if (!path.match(/\.json$/)) { + throw Error('only .json config files are supported in Deno') + } else { + return JSON.parse(Deno.readTextFileSync(path)) + } + } +}) + +const yargsParser: Parser = function Parser (args: ArgsInput, opts?: Partial): Arguments { + const result = parser.parse(args.slice(), opts) + return result.argv +} + +yargsParser.detailed = function (args: ArgsInput, opts?: Partial): DetailedArguments { + return parser.parse(args.slice(), opts) +} + +export default yargsParser diff --git a/docs/CHANGELOG-full.md b/docs/CHANGELOG-full.md new file mode 100644 index 00000000..330089ed --- /dev/null +++ b/docs/CHANGELOG-full.md @@ -0,0 +1,503 @@ +## [15.0.0](https://github.com/yargs/yargs-parser/compare/v14.0.0...v15.0.0) (2019-10-07) + + +### Features + +* rework `collect-unknown-options` into `unknown-options-as-args`, providing more comprehensive functionality ([ef771ca](https://github.com/yargs/yargs-parser/commit/ef771ca)) + + +### BREAKING CHANGES + +* rework `collect-unknown-options` into `unknown-options-as-args`, providing more comprehensive functionality + + + +## [14.0.0](https://github.com/yargs/yargs-parser/compare/v13.1.1...v14.0.0) (2019-09-06) + + +### Bug Fixes + +* boolean arrays with default values ([#185](https://github.com/yargs/yargs-parser/issues/185)) ([7d42572](https://github.com/yargs/yargs-parser/commit/7d42572)) +* boolean now behaves the same as other array types ([#184](https://github.com/yargs/yargs-parser/issues/184)) ([17ca3bd](https://github.com/yargs/yargs-parser/commit/17ca3bd)) +* eatNargs() for 'opt.narg === 0' and boolean typed options ([#188](https://github.com/yargs/yargs-parser/issues/188)) ([c5a1db0](https://github.com/yargs/yargs-parser/commit/c5a1db0)) +* maybeCoerceNumber now takes precedence over coerce return value ([#182](https://github.com/yargs/yargs-parser/issues/182)) ([2f26436](https://github.com/yargs/yargs-parser/commit/2f26436)) +* take into account aliases when appending arrays from config object ([#199](https://github.com/yargs/yargs-parser/issues/199)) ([f8a2d3f](https://github.com/yargs/yargs-parser/commit/f8a2d3f)) + + +### Features + +* add configuration option to "collect-unknown-options" ([#181](https://github.com/yargs/yargs-parser/issues/181)) ([7909cc4](https://github.com/yargs/yargs-parser/commit/7909cc4)) +* maybeCoerceNumber() now takes into account arrays ([#187](https://github.com/yargs/yargs-parser/issues/187)) ([31c204b](https://github.com/yargs/yargs-parser/commit/31c204b)) + + +### BREAKING CHANGES + +* unless "parse-numbers" is set to "false", arrays of numeric strings are now parsed as numbers, rather than strings. +* we have dropped the broken "defaulted" functionality; we would like to revisit adding this in the future. +* maybeCoerceNumber now takes precedence over coerce return value (#182) + + + +### [13.1.1](https://www.github.com/yargs/yargs-parser/compare/v13.1.0...v13.1.1) (2019-06-10) + + +### Bug Fixes + +* convert values to strings when tokenizing ([#167](https://www.github.com/yargs/yargs-parser/issues/167)) ([57b7883](https://www.github.com/yargs/yargs-parser/commit/57b7883)) +* nargs should allow duplicates when duplicate-arguments-array=false ([#164](https://www.github.com/yargs/yargs-parser/issues/164)) ([47ccb0b](https://www.github.com/yargs/yargs-parser/commit/47ccb0b)) +* should populate "_" when given config with "short-option-groups" false ([#179](https://www.github.com/yargs/yargs-parser/issues/179)) ([6055974](https://www.github.com/yargs/yargs-parser/commit/6055974)) + +## [13.1.0](https://github.com/yargs/yargs-parser/compare/v13.0.0...v13.1.0) (2019-05-05) + + +### Features + +* add `strip-aliased` and `strip-dashed` configuration options. ([#172](https://github.com/yargs/yargs-parser/issues/172)) ([a3936aa](https://github.com/yargs/yargs-parser/commit/a3936aa)) +* support boolean which do not consume next argument. ([#171](https://github.com/yargs/yargs-parser/issues/171)) ([0ae7fcb](https://github.com/yargs/yargs-parser/commit/0ae7fcb)) + + + + +# [13.0.0](https://github.com/yargs/yargs-parser/compare/v12.0.0...v13.0.0) (2019-02-02) + + +### Features + +* don't coerce number from string with leading '0' or '+' ([#158](https://github.com/yargs/yargs-parser/issues/158)) ([18d0fd5](https://github.com/yargs/yargs-parser/commit/18d0fd5)) + + +### BREAKING CHANGES + +* options with leading '+' or '0' now parse as strings + + + + +# [12.0.0](https://github.com/yargs/yargs-parser/compare/v11.1.1...v12.0.0) (2019-01-29) + + +### Bug Fixes + +* better handling of quoted strings ([#153](https://github.com/yargs/yargs-parser/issues/153)) ([2fb71b2](https://github.com/yargs/yargs-parser/commit/2fb71b2)) + + +### Features + +* default value is now used if no right-hand value provided for numbers/strings ([#156](https://github.com/yargs/yargs-parser/issues/156)) ([5a7c46a](https://github.com/yargs/yargs-parser/commit/5a7c46a)) + + +### BREAKING CHANGES + +* a flag with no right-hand value no longer populates defaulted options with `undefined`. +* quotes at beginning and endings of strings are not removed during parsing. + + + + +## [11.1.1](https://github.com/yargs/yargs-parser/compare/v11.1.0...v11.1.1) (2018-11-19) + + +### Bug Fixes + +* ensure empty string is added into argv._ ([#140](https://github.com/yargs/yargs-parser/issues/140)) ([79cda98](https://github.com/yargs/yargs-parser/commit/79cda98)) + + +### Reverts + +* make requiresArg work in conjunction with arrays ([#136](https://github.com/yargs/yargs-parser/issues/136)) ([f4a3063](https://github.com/yargs/yargs-parser/commit/f4a3063)) + + + + +# [11.1.0](https://github.com/yargs/yargs-parser/compare/v11.0.0...v11.1.0) (2018-11-10) + + +### Bug Fixes + +* handling of one char alias ([#139](https://github.com/yargs/yargs-parser/issues/139)) ([ee56e31](https://github.com/yargs/yargs-parser/commit/ee56e31)) + + +### Features + +* add halt-at-non-option configuration option ([#130](https://github.com/yargs/yargs-parser/issues/130)) ([a849fce](https://github.com/yargs/yargs-parser/commit/a849fce)) + + + + +# [11.0.0](https://github.com/yargs/yargs-parser/compare/v10.1.0...v11.0.0) (2018-10-06) + + +### Bug Fixes + +* flatten-duplicate-arrays:false for more than 2 arrays ([#128](https://github.com/yargs/yargs-parser/issues/128)) ([2bc395f](https://github.com/yargs/yargs-parser/commit/2bc395f)) +* hyphenated flags combined with dot notation broke parsing ([#131](https://github.com/yargs/yargs-parser/issues/131)) ([dc788da](https://github.com/yargs/yargs-parser/commit/dc788da)) +* make requiresArg work in conjunction with arrays ([#136](https://github.com/yargs/yargs-parser/issues/136)) ([77ae1d4](https://github.com/yargs/yargs-parser/commit/77ae1d4)) + + +### Chores + +* update dependencies ([6dc42a1](https://github.com/yargs/yargs-parser/commit/6dc42a1)) + + +### Features + +* also add camelCase array options ([#125](https://github.com/yargs/yargs-parser/issues/125)) ([08c0117](https://github.com/yargs/yargs-parser/commit/08c0117)) +* array.type can now be provided, supporting coercion ([#132](https://github.com/yargs/yargs-parser/issues/132)) ([4b8cfce](https://github.com/yargs/yargs-parser/commit/4b8cfce)) + + +### BREAKING CHANGES + +* drops Node 4 support +* the argv object is now populated differently (correctly) when hyphens and dot notation are used in conjunction. + + + + +# [10.1.0](https://github.com/yargs/yargs-parser/compare/v10.0.0...v10.1.0) (2018-06-29) + + +### Features + +* add `set-placeholder-key` configuration ([#123](https://github.com/yargs/yargs-parser/issues/123)) ([19386ee](https://github.com/yargs/yargs-parser/commit/19386ee)) + + + + +# [10.0.0](https://github.com/yargs/yargs-parser/compare/v9.0.2...v10.0.0) (2018-04-04) + + +### Bug Fixes + +* do not set boolean flags if not defined in `argv` ([#119](https://github.com/yargs/yargs-parser/issues/119)) ([f6e6599](https://github.com/yargs/yargs-parser/commit/f6e6599)) + + +### BREAKING CHANGES + +* `boolean` flags defined without a `default` value will now behave like other option type and won't be set in the parsed results when the user doesn't set the corresponding CLI arg. + +Previous behavior: +```js +var parse = require('yargs-parser'); + +parse('--flag', {boolean: ['flag']}); +// => { _: [], flag: true } + +parse('--no-flag', {boolean: ['flag']}); +// => { _: [], flag: false } + +parse('', {boolean: ['flag']}); +// => { _: [], flag: false } +``` + +New behavior: +```js +var parse = require('yargs-parser'); + +parse('--flag', {boolean: ['flag']}); +// => { _: [], flag: true } + +parse('--no-flag', {boolean: ['flag']}); +// => { _: [], flag: false } + +parse('', {boolean: ['flag']}); +// => { _: [] } => flag not set similarly to other option type +``` + + + + +## [9.0.2](https://github.com/yargs/yargs-parser/compare/v9.0.1...v9.0.2) (2018-01-20) + + +### Bug Fixes + +* nargs was still aggressively consuming too many arguments ([9b28aad](https://github.com/yargs/yargs-parser/commit/9b28aad)) + + + + +## [9.0.1](https://github.com/yargs/yargs-parser/compare/v9.0.0...v9.0.1) (2018-01-20) + + +### Bug Fixes + +* nargs was consuming too many arguments ([4fef206](https://github.com/yargs/yargs-parser/commit/4fef206)) + + + + +# [9.0.0](https://github.com/yargs/yargs-parser/compare/v8.1.0...v9.0.0) (2018-01-20) + + +### Features + +* narg arguments no longer consume flag arguments ([#114](https://github.com/yargs/yargs-parser/issues/114)) ([60bb9b3](https://github.com/yargs/yargs-parser/commit/60bb9b3)) + + +### BREAKING CHANGES + +* arguments of form --foo, -abc, will no longer be consumed by nargs + + + + +# [8.1.0](https://github.com/yargs/yargs-parser/compare/v8.0.0...v8.1.0) (2017-12-20) + + +### Bug Fixes + +* allow null config values ([#108](https://github.com/yargs/yargs-parser/issues/108)) ([d8b14f9](https://github.com/yargs/yargs-parser/commit/d8b14f9)) +* ensure consistent parsing of dot-notation arguments ([#102](https://github.com/yargs/yargs-parser/issues/102)) ([c9bd79c](https://github.com/yargs/yargs-parser/commit/c9bd79c)) +* implement [@antoniom](https://github.com/antoniom)'s fix for camel-case expansion ([3087e1d](https://github.com/yargs/yargs-parser/commit/3087e1d)) +* only run coercion functions once, despite aliases. ([#76](https://github.com/yargs/yargs-parser/issues/76)) ([#103](https://github.com/yargs/yargs-parser/issues/103)) ([507aaef](https://github.com/yargs/yargs-parser/commit/507aaef)) +* scientific notation circumvented bounds check ([#110](https://github.com/yargs/yargs-parser/issues/110)) ([3571f57](https://github.com/yargs/yargs-parser/commit/3571f57)) +* tokenizer should ignore spaces at the beginning of the argString ([#106](https://github.com/yargs/yargs-parser/issues/106)) ([f34ead9](https://github.com/yargs/yargs-parser/commit/f34ead9)) + + +### Features + +* make combining arrays a configurable option ([#111](https://github.com/yargs/yargs-parser/issues/111)) ([c8bf536](https://github.com/yargs/yargs-parser/commit/c8bf536)) +* merge array from arguments with array from config ([#83](https://github.com/yargs/yargs-parser/issues/83)) ([806ddd6](https://github.com/yargs/yargs-parser/commit/806ddd6)) + + + + +# [8.0.0](https://github.com/yargs/yargs-parser/compare/v7.0.0...v8.0.0) (2017-10-05) + + +### Bug Fixes + +* Ignore multiple spaces between arguments. ([#100](https://github.com/yargs/yargs-parser/issues/100)) ([d137227](https://github.com/yargs/yargs-parser/commit/d137227)) + + +### Features + +* allow configuration of prefix for boolean negation ([#94](https://github.com/yargs/yargs-parser/issues/94)) ([00bde7d](https://github.com/yargs/yargs-parser/commit/00bde7d)) +* reworking how numbers are parsed ([#104](https://github.com/yargs/yargs-parser/issues/104)) ([fba00eb](https://github.com/yargs/yargs-parser/commit/fba00eb)) + + +### BREAKING CHANGES + +* strings that fail `Number.isSafeInteger()` are no longer coerced into numbers. + + + + +# [7.0.0](https://github.com/yargs/yargs-parser/compare/v6.0.1...v7.0.0) (2017-05-02) + + +### Chores + +* revert populate-- logic ([#91](https://github.com/yargs/yargs-parser/issues/91)) ([6003e6d](https://github.com/yargs/yargs-parser/commit/6003e6d)) + + +### BREAKING CHANGES + +* populate-- now defaults to false. + + + + +## [6.0.1](https://github.com/yargs/yargs-parser/compare/v6.0.0...v6.0.1) (2017-05-01) + + +### Bug Fixes + +* default '--' to undefined when not provided; this is closer to the array API ([#90](https://github.com/yargs/yargs-parser/issues/90)) ([4e739cc](https://github.com/yargs/yargs-parser/commit/4e739cc)) + + + + +# [6.0.0](https://github.com/yargs/yargs-parser/compare/v4.2.1...v6.0.0) (2017-05-01) + + +### Bug Fixes + +* environment variables should take precedence over config file ([#81](https://github.com/yargs/yargs-parser/issues/81)) ([76cee1f](https://github.com/yargs/yargs-parser/commit/76cee1f)) +* parsing hints should apply for dot notation keys ([#86](https://github.com/yargs/yargs-parser/issues/86)) ([3e47d62](https://github.com/yargs/yargs-parser/commit/3e47d62)) + + +### Chores + +* upgrade to newest version of camelcase ([#87](https://github.com/yargs/yargs-parser/issues/87)) ([f1903aa](https://github.com/yargs/yargs-parser/commit/f1903aa)) + + +### Features + +* add -- option which allows arguments after the -- flag to be returned separated from positional arguments ([#84](https://github.com/yargs/yargs-parser/issues/84)) ([2572ca8](https://github.com/yargs/yargs-parser/commit/2572ca8)) +* when parsing stops, we now populate "--" by default ([#88](https://github.com/yargs/yargs-parser/issues/88)) ([cd666db](https://github.com/yargs/yargs-parser/commit/cd666db)) + + +### BREAKING CHANGES + +* rather than placing arguments in "_", when parsing is stopped via "--"; we now populate an array called "--" by default. +* camelcase now requires Node 4+. +* environment variables will now override config files (args, env, config-file, config-object) + + + + +# [5.0.0](https://github.com/yargs/yargs-parser/compare/v4.2.1...v5.0.0) (2017-02-18) + + +### Bug Fixes + +* environment variables should take precedence over config file ([#81](https://github.com/yargs/yargs-parser/issues/81)) ([76cee1f](https://github.com/yargs/yargs-parser/commit/76cee1f)) + + +### BREAKING CHANGES + +* environment variables will now override config files (args, env, config-file, config-object) + + + + +## [4.2.1](https://github.com/yargs/yargs-parser/compare/v4.2.0...v4.2.1) (2017-01-02) + + +### Bug Fixes + +* flatten/duplicate regression ([#75](https://github.com/yargs/yargs-parser/issues/75)) ([68d68a0](https://github.com/yargs/yargs-parser/commit/68d68a0)) + + + + +# [4.2.0](https://github.com/yargs/yargs-parser/compare/v4.1.0...v4.2.0) (2016-12-01) + + +### Bug Fixes + +* inner objects in configs had their keys appended to top-level key when dot-notation was disabled ([#72](https://github.com/yargs/yargs-parser/issues/72)) ([0b1b5f9](https://github.com/yargs/yargs-parser/commit/0b1b5f9)) + + +### Features + +* allow multiple arrays to be provided, rather than always combining ([#71](https://github.com/yargs/yargs-parser/issues/71)) ([0f0fb2d](https://github.com/yargs/yargs-parser/commit/0f0fb2d)) + + + + +# [4.1.0](https://github.com/yargs/yargs-parser/compare/v4.0.2...v4.1.0) (2016-11-07) + + +### Features + +* apply coercions to default options ([#65](https://github.com/yargs/yargs-parser/issues/65)) ([c79052b](https://github.com/yargs/yargs-parser/commit/c79052b)) +* handle dot notation boolean options ([#63](https://github.com/yargs/yargs-parser/issues/63)) ([02c3545](https://github.com/yargs/yargs-parser/commit/02c3545)) + + + + +## [4.0.2](https://github.com/yargs/yargs-parser/compare/v4.0.1...v4.0.2) (2016-09-30) + + +### Bug Fixes + +* whoops, let's make the assign not change the Object key order ([29d069a](https://github.com/yargs/yargs-parser/commit/29d069a)) + + + + +## [4.0.1](https://github.com/yargs/yargs-parser/compare/v4.0.0...v4.0.1) (2016-09-30) + + +### Bug Fixes + +* lodash.assign was deprecated ([#59](https://github.com/yargs/yargs-parser/issues/59)) ([5e7eb11](https://github.com/yargs/yargs-parser/commit/5e7eb11)) + + + + +# [4.0.0](https://github.com/yargs/yargs-parser/compare/v3.2.0...v4.0.0) (2016-09-26) + + +### Bug Fixes + +* coerce should be applied to the final objects and arrays created ([#57](https://github.com/yargs/yargs-parser/issues/57)) ([4ca69da](https://github.com/yargs/yargs-parser/commit/4ca69da)) + + +### BREAKING CHANGES + +* coerce is no longer applied to individual arguments in an implicit array. + + + + +# [3.2.0](https://github.com/yargs/yargs-parser/compare/v3.1.0...v3.2.0) (2016-08-13) + + +### Features + +* coerce full array instead of each element ([#51](https://github.com/yargs/yargs-parser/issues/51)) ([cc4dc56](https://github.com/yargs/yargs-parser/commit/cc4dc56)) + + + + +# [3.1.0](https://github.com/yargs/yargs-parser/compare/v3.0.0...v3.1.0) (2016-08-09) + + +### Bug Fixes + +* address pkgConf parsing bug outlined in [#37](https://github.com/yargs/yargs-parser/issues/37) ([#45](https://github.com/yargs/yargs-parser/issues/45)) ([be76ee6](https://github.com/yargs/yargs-parser/commit/be76ee6)) +* better parsing of negative values ([#44](https://github.com/yargs/yargs-parser/issues/44)) ([2e43692](https://github.com/yargs/yargs-parser/commit/2e43692)) +* check aliases when guessing defaults for arguments fixes [#41](https://github.com/yargs/yargs-parser/issues/41) ([#43](https://github.com/yargs/yargs-parser/issues/43)) ([f3e4616](https://github.com/yargs/yargs-parser/commit/f3e4616)) + + +### Features + +* added coerce option, for providing specialized argument parsing ([#42](https://github.com/yargs/yargs-parser/issues/42)) ([7b49cd2](https://github.com/yargs/yargs-parser/commit/7b49cd2)) + + + + +# [3.0.0](https://github.com/yargs/yargs-parser/compare/v2.4.1...v3.0.0) (2016-08-07) + + +### Bug Fixes + +* parsing issue with numeric character in group of options ([#19](https://github.com/yargs/yargs-parser/issues/19)) ([f743236](https://github.com/yargs/yargs-parser/commit/f743236)) +* upgraded lodash.assign ([5d7fdf4](https://github.com/yargs/yargs-parser/commit/5d7fdf4)) + +### BREAKING CHANGES + +* subtle change to how values are parsed in a group of single-character arguments. +* _first released in 3.1.0, better handling of negative values should be considered a breaking change._ + + + + +## [2.4.1](https://github.com/yargs/yargs-parser/compare/v2.4.0...v2.4.1) (2016-07-16) + + +### Bug Fixes + +* **count:** do not increment a default value ([#39](https://github.com/yargs/yargs-parser/issues/39)) ([b04a189](https://github.com/yargs/yargs-parser/commit/b04a189)) + + + + +# [2.4.0](https://github.com/yargs/yargs-parser/compare/v2.3.0...v2.4.0) (2016-04-11) + + +### Features + +* **environment:** Support nested options in environment variables ([#26](https://github.com/yargs/yargs-parser/issues/26)) thanks [@elas7](https://github.com/elas7) \o/ ([020778b](https://github.com/yargs/yargs-parser/commit/020778b)) + + + + +# [2.3.0](https://github.com/yargs/yargs-parser/compare/v2.2.0...v2.3.0) (2016-04-09) + + +### Bug Fixes + +* **boolean:** fix for boolean options with non boolean defaults (#20) ([2dbe86b](https://github.com/yargs/yargs-parser/commit/2dbe86b)), closes [(#20](https://github.com/(/issues/20) +* **package:** remove tests from tarball ([0353c0d](https://github.com/yargs/yargs-parser/commit/0353c0d)) +* **parsing:** handle calling short option with an empty string as the next value. ([a867165](https://github.com/yargs/yargs-parser/commit/a867165)) +* boolean flag when next value contains the strings 'true' or 'false'. ([69941a6](https://github.com/yargs/yargs-parser/commit/69941a6)) +* update dependencies; add standard-version bin for next release (#24) ([822d9d5](https://github.com/yargs/yargs-parser/commit/822d9d5)) + +### Features + +* **configuration:** Allow to pass configuration objects to yargs-parser ([0780900](https://github.com/yargs/yargs-parser/commit/0780900)) +* **normalize:** allow normalize to work with arrays ([e0eaa1a](https://github.com/yargs/yargs-parser/commit/e0eaa1a)) diff --git a/example.js b/example.js deleted file mode 100755 index cbfe167d..00000000 --- a/example.js +++ /dev/null @@ -1,3 +0,0 @@ -var parser = require('./') -var parse = parser('--foo "-bar"') -console.log(parse) diff --git a/index.ts b/index.ts deleted file mode 100644 index f751c7b9..00000000 --- a/index.ts +++ /dev/null @@ -1,1123 +0,0 @@ -import * as path from 'path' -import * as util from 'util' -import { tokenizeArgString } from './lib/tokenize-arg-string' -import type { - ArgsInput, - Arguments, - ArrayFlagsKey, - ArrayOption, - CoerceCallback, - Configuration, - DefaultValuesForType, - DefaultValuesForTypeKey, - DetailedArguments, - Flag, - Flags, - FlagsKey, - StringFlag, - BooleanFlag, - NumberFlag, - ConfigsFlag, - CoercionsFlag, - Options, - OptionsDefault, - Parser -} from './lib/yargs-parser-types' -import type { Dictionary, ValueOf } from './lib/common-types' - -// See https://github.com/yargs/yargs-parser#supported-nodejs-versions for our -// version support policy. The YARGS_MIN_NODE_VERSION is used for testing only. -const minNodeVersion = (process && process.env && process.env.YARGS_MIN_NODE_VERSION) - ? Number(process.env.YARGS_MIN_NODE_VERSION) : 10 -if (process && process.version) { - const major = Number(process.version.match(/v([^.]+)/)![1]) - if (major < minNodeVersion) { - throw Error(`yargs parser supports a minimum Node.js version of ${minNodeVersion}. Read our version support policy: https://github.com/yargs/yargs-parser#supported-nodejs-versions`) - } -} -import camelCase = require('camelcase') -import decamelize = require('decamelize') - -function parse (argsInput: ArgsInput, options?: Partial): DetailedArguments { - const opts: Partial = Object.assign({ - alias: undefined, - array: undefined, - boolean: undefined, - config: undefined, - configObjects: undefined, - configuration: undefined, - coerce: undefined, - count: undefined, - default: undefined, - envPrefix: undefined, - narg: undefined, - normalize: undefined, - string: undefined, - number: undefined, - __: undefined, - key: undefined - }, options) - // allow a string argument to be passed in rather - // than an argv array. - const args = tokenizeArgString(argsInput) - - // aliases might have transitive relationships, normalize this. - const aliases = combineAliases(Object.assign(Object.create(null), opts.alias)) - const configuration: Configuration = Object.assign({ - 'boolean-negation': true, - 'camel-case-expansion': true, - 'combine-arrays': false, - 'dot-notation': true, - 'duplicate-arguments-array': true, - 'flatten-duplicate-arrays': true, - 'greedy-arrays': true, - 'halt-at-non-option': false, - 'nargs-eats-options': false, - 'negation-prefix': 'no-', - 'parse-numbers': true, - 'populate--': false, - 'set-placeholder-key': false, - 'short-option-groups': true, - 'strip-aliased': false, - 'strip-dashed': false, - 'unknown-options-as-args': false - }, opts.configuration) - const defaults: OptionsDefault = Object.assign(Object.create(null), opts.default) - const configObjects = opts.configObjects || [] - const envPrefix = opts.envPrefix - const notFlagsOption = configuration['populate--'] - const notFlagsArgv: string = notFlagsOption ? '--' : '_' - const newAliases: Dictionary = Object.create(null) - const defaulted: Dictionary = Object.create(null) - // allow a i18n handler to be passed in, default to a fake one (util.format). - const __ = opts.__ || util.format - const flags: Flags = { - aliases: Object.create(null), - arrays: Object.create(null), - bools: Object.create(null), - strings: Object.create(null), - numbers: Object.create(null), - counts: Object.create(null), - normalize: Object.create(null), - configs: Object.create(null), - nargs: Object.create(null), - coercions: Object.create(null), - keys: [] - } - const negative = /^-([0-9]+(\.[0-9]+)?|\.[0-9]+)$/ - const negatedBoolean = new RegExp('^--' + configuration['negation-prefix'] + '(.+)') - - ;([] as ArrayOption[]).concat(opts.array || []).filter(Boolean).forEach(function (opt) { - const key = typeof opt === 'object' ? opt.key : opt - - // assign to flags[bools|strings|numbers] - const assignment: ArrayFlagsKey | undefined = Object.keys(opt).map(function (key) { - const arrayFlagKeys: Record = { - boolean: 'bools', - string: 'strings', - number: 'numbers' - } - return arrayFlagKeys[key] - }).filter(Boolean).pop() - - // assign key to be coerced - if (assignment) { - flags[assignment][key] = true - } - - flags.arrays[key] = true - flags.keys.push(key) - }) - - ;([] as string[]).concat(opts.boolean || []).filter(Boolean).forEach(function (key) { - flags.bools[key] = true - flags.keys.push(key) - }) - - ;([] as string[]).concat(opts.string || []).filter(Boolean).forEach(function (key) { - flags.strings[key] = true - flags.keys.push(key) - }) - - ;([] as string[]).concat(opts.number || []).filter(Boolean).forEach(function (key) { - flags.numbers[key] = true - flags.keys.push(key) - }) - - ;([] as string[]).concat(opts.count || []).filter(Boolean).forEach(function (key) { - flags.counts[key] = true - flags.keys.push(key) - }) - - ;([] as string[]).concat(opts.normalize || []).filter(Boolean).forEach(function (key) { - flags.normalize[key] = true - flags.keys.push(key) - }) - - if (typeof opts.narg === 'object') { - Object.entries(opts.narg).forEach(([key, value]) => { - if (typeof value === 'number') { - flags.nargs[key] = value - flags.keys.push(key) - } - }) - } - - if (typeof opts.coerce === 'object') { - Object.entries(opts.coerce).forEach(([key, value]) => { - if (typeof value === 'function') { - flags.coercions[key] = value - flags.keys.push(key) - } - }) - } - - if (typeof opts.config !== 'undefined') { - if (Array.isArray(opts.config) || typeof opts.config === 'string') { - ;([] as string[]).concat(opts.config).filter(Boolean).forEach(function (key) { - flags.configs[key] = true - }) - } else if (typeof opts.config === 'object') { - Object.entries(opts.config).forEach(([key, value]) => { - if (typeof value === 'boolean' || typeof value === 'function') { - flags.configs[key] = value - } - }) - } - } - - // create a lookup table that takes into account all - // combinations of aliases: {f: ['foo'], foo: ['f']} - extendAliases(opts.key, aliases, opts.default, flags.arrays) - - // apply default values to all aliases. - Object.keys(defaults).forEach(function (key) { - (flags.aliases[key] || []).forEach(function (alias) { - defaults[alias] = defaults[key] - }) - }) - - let error: Error | null = null - checkConfiguration() - - let notFlags: string[] = [] - - const argv: Arguments = Object.assign(Object.create(null), { _: [] }) - // TODO(bcoe): for the first pass at removing object prototype we didn't - // remove all prototypes from objects returned by this API, we might want - // to gradually move towards doing so. - const argvReturn: { [argName: string]: any } = {} - - for (let i = 0; i < args.length; i++) { - const arg = args[i] - let broken: boolean - let key: string | undefined - let letters: string[] - let m: RegExpMatchArray | null - let next: string - let value: string - - // any unknown option (except for end-of-options, "--") - if (arg !== '--' && isUnknownOptionAsArg(arg)) { - argv._.push(arg) - // -- separated by = - } else if (arg.match(/^--.+=/) || ( - !configuration['short-option-groups'] && arg.match(/^-.+=/) - )) { - // Using [\s\S] instead of . because js doesn't support the - // 'dotall' regex modifier. See: - // http://stackoverflow.com/a/1068308/13216 - m = arg.match(/^--?([^=]+)=([\s\S]*)$/) - - // arrays format = '--f=a b c' - if (m !== null && Array.isArray(m) && m.length >= 3) { - if (checkAllAliases(m[1], flags.arrays)) { - i = eatArray(i, m[1], args, m[2]) - } else if (checkAllAliases(m[1], flags.nargs) !== false) { - // nargs format = '--f=monkey washing cat' - i = eatNargs(i, m[1], args, m[2]) - } else { - setArg(m[1], m[2]) - } - } - } else if (arg.match(negatedBoolean) && configuration['boolean-negation']) { - m = arg.match(negatedBoolean) - if (m !== null && Array.isArray(m) && m.length >= 2) { - key = m[1] - setArg(key, checkAllAliases(key, flags.arrays) ? [false] : false) - } - - // -- separated by space. - } else if (arg.match(/^--.+/) || ( - !configuration['short-option-groups'] && arg.match(/^-[^-]+/) - )) { - m = arg.match(/^--?(.+)/) - if (m !== null && Array.isArray(m) && m.length >= 2) { - key = m[1] - if (checkAllAliases(key, flags.arrays)) { - // array format = '--foo a b c' - i = eatArray(i, key, args) - } else if (checkAllAliases(key, flags.nargs) !== false) { - // nargs format = '--foo a b c' - // should be truthy even if: flags.nargs[key] === 0 - i = eatNargs(i, key, args) - } else { - next = args[i + 1] - - if (next !== undefined && (!next.match(/^-/) || - next.match(negative)) && - !checkAllAliases(key, flags.bools) && - !checkAllAliases(key, flags.counts)) { - setArg(key, next) - i++ - } else if (/^(true|false)$/.test(next)) { - setArg(key, next) - i++ - } else { - setArg(key, defaultValue(key)) - } - } - } - - // dot-notation flag separated by '='. - } else if (arg.match(/^-.\..+=/)) { - m = arg.match(/^-([^=]+)=([\s\S]*)$/) - if (m !== null && Array.isArray(m) && m.length >= 3) { - setArg(m[1], m[2]) - } - - // dot-notation flag separated by space. - } else if (arg.match(/^-.\..+/) && !arg.match(negative)) { - next = args[i + 1] - m = arg.match(/^-(.\..+)/) - if (m !== null && Array.isArray(m) && m.length >= 2) { - key = m[1] - if (next !== undefined && !next.match(/^-/) && - !checkAllAliases(key, flags.bools) && - !checkAllAliases(key, flags.counts)) { - setArg(key, next) - i++ - } else { - setArg(key, defaultValue(key)) - } - } - } else if (arg.match(/^-[^-]+/) && !arg.match(negative)) { - letters = arg.slice(1, -1).split('') - broken = false - - for (let j = 0; j < letters.length; j++) { - next = arg.slice(j + 2) - - if (letters[j + 1] && letters[j + 1] === '=') { - value = arg.slice(j + 3) - key = letters[j] - - if (checkAllAliases(key, flags.arrays)) { - // array format = '-f=a b c' - i = eatArray(i, key, args, value) - } else if (checkAllAliases(key, flags.nargs) !== false) { - // nargs format = '-f=monkey washing cat' - i = eatNargs(i, key, args, value) - } else { - setArg(key, value) - } - - broken = true - break - } - - if (next === '-') { - setArg(letters[j], next) - continue - } - - // current letter is an alphabetic character and next value is a number - if (/[A-Za-z]/.test(letters[j]) && - /^-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) { - setArg(letters[j], next) - broken = true - break - } - - if (letters[j + 1] && letters[j + 1].match(/\W/)) { - setArg(letters[j], next) - broken = true - break - } else { - setArg(letters[j], defaultValue(letters[j])) - } - } - - key = arg.slice(-1)[0] - - if (!broken && key !== '-') { - if (checkAllAliases(key, flags.arrays)) { - // array format = '-f a b c' - i = eatArray(i, key, args) - } else if (checkAllAliases(key, flags.nargs) !== false) { - // nargs format = '-f a b c' - // should be truthy even if: flags.nargs[key] === 0 - i = eatNargs(i, key, args) - } else { - next = args[i + 1] - - if (next !== undefined && (!/^(-|--)[^-]/.test(next) || - next.match(negative)) && - !checkAllAliases(key, flags.bools) && - !checkAllAliases(key, flags.counts)) { - setArg(key, next) - i++ - } else if (/^(true|false)$/.test(next)) { - setArg(key, next) - i++ - } else { - setArg(key, defaultValue(key)) - } - } - } - } else if (arg.match(/^-[0-9]$/) && - arg.match(negative) && - checkAllAliases(arg.slice(1), flags.bools)) { - // single-digit boolean alias, e.g: xargs -0 - key = arg.slice(1) - setArg(key, defaultValue(key)) - } else if (arg === '--') { - notFlags = args.slice(i + 1) - break - } else if (configuration['halt-at-non-option']) { - notFlags = args.slice(i) - break - } else { - const maybeCoercedNumber = maybeCoerceNumber('_', arg) - if (typeof maybeCoercedNumber === 'string' || typeof maybeCoercedNumber === 'number') { - argv._.push(maybeCoercedNumber) - } - } - } - - // order of precedence: - // 1. command line arg - // 2. value from env var - // 3. value from config file - // 4. value from config objects - // 5. configured default value - applyEnvVars(argv, true) // special case: check env vars that point to config file - applyEnvVars(argv, false) - setConfig(argv) - setConfigObjects() - applyDefaultsAndAliases(argv, flags.aliases, defaults, true) - applyCoercions(argv) - if (configuration['set-placeholder-key']) setPlaceholderKeys(argv) - - // for any counts either not in args or without an explicit default, set to 0 - Object.keys(flags.counts).forEach(function (key) { - if (!hasKey(argv, key.split('.'))) setArg(key, 0) - }) - - // '--' defaults to undefined. - if (notFlagsOption && notFlags.length) argv[notFlagsArgv] = [] - notFlags.forEach(function (key) { - argv[notFlagsArgv].push(key) - }) - - if (configuration['camel-case-expansion'] && configuration['strip-dashed']) { - Object.keys(argv).filter(key => key !== '--' && key.includes('-')).forEach(key => { - delete argv[key] - }) - } - - if (configuration['strip-aliased']) { - ;([] as string[]).concat(...Object.keys(aliases).map(k => aliases[k])).forEach(alias => { - if (configuration['camel-case-expansion']) { - delete argv[alias.split('.').map(prop => camelCase(prop)).join('.')] - } - - delete argv[alias] - }) - } - - // how many arguments should we consume, based - // on the nargs option? - function eatNargs (i: number, key: string, args: string[], argAfterEqualSign?: string): number { - let ii - let toEat = checkAllAliases(key, flags.nargs) - // NaN has a special meaning for the array type, indicating that one or - // more values are expected. - toEat = typeof toEat !== 'number' || isNaN(toEat) ? 1 : toEat - - if (toEat === 0) { - if (!isUndefined(argAfterEqualSign)) { - error = Error(__('Argument unexpected for: %s', key)) - } - setArg(key, defaultValue(key)) - return i - } - - let available = isUndefined(argAfterEqualSign) ? 0 : 1 - if (configuration['nargs-eats-options']) { - // classic behavior, yargs eats positional and dash arguments. - if (args.length - (i + 1) + available < toEat) { - error = Error(__('Not enough arguments following: %s', key)) - } - available = toEat - } else { - // nargs will not consume flag arguments, e.g., -abc, --foo, - // and terminates when one is observed. - for (ii = i + 1; ii < args.length; ii++) { - if (!args[ii].match(/^-[^0-9]/) || args[ii].match(negative) || isUnknownOptionAsArg(args[ii])) available++ - else break - } - if (available < toEat) error = Error(__('Not enough arguments following: %s', key)) - } - - let consumed = Math.min(available, toEat) - if (!isUndefined(argAfterEqualSign) && consumed > 0) { - setArg(key, argAfterEqualSign) - consumed-- - } - for (ii = i + 1; ii < (consumed + i + 1); ii++) { - setArg(key, args[ii]) - } - - return (i + consumed) - } - - // if an option is an array, eat all non-hyphenated arguments - // following it... YUM! - // e.g., --foo apple banana cat becomes ["apple", "banana", "cat"] - function eatArray (i: number, key: string, args: string[], argAfterEqualSign?: string): number { - let argsToSet = [] - let next = argAfterEqualSign || args[i + 1] - // If both array and nargs are configured, enforce the nargs count: - const nargsCount = checkAllAliases(key, flags.nargs) - - if (checkAllAliases(key, flags.bools) && !(/^(true|false)$/.test(next))) { - argsToSet.push(true) - } else if (isUndefined(next) || - (isUndefined(argAfterEqualSign) && /^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next))) { - // for keys without value ==> argsToSet remains an empty [] - // set user default value, if available - if (defaults[key] !== undefined) { - const defVal = defaults[key] - argsToSet = Array.isArray(defVal) ? defVal : [defVal] - } - } else { - // value in --option=value is eaten as is - if (!isUndefined(argAfterEqualSign)) { - argsToSet.push(processValue(key, argAfterEqualSign)) - } - for (let ii = i + 1; ii < args.length; ii++) { - if ((!configuration['greedy-arrays'] && argsToSet.length > 0) || - (nargsCount && typeof nargsCount === 'number' && argsToSet.length >= nargsCount)) break - next = args[ii] - if (/^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next)) break - i = ii - argsToSet.push(processValue(key, next)) - } - } - - // If both array and nargs are configured, create an error if less than - // nargs positionals were found. NaN has special meaning, indicating - // that at least one value is required (more are okay). - if (typeof nargsCount === 'number' && ((nargsCount && argsToSet.length < nargsCount) || - (isNaN(nargsCount) && argsToSet.length === 0))) { - error = Error(__('Not enough arguments following: %s', key)) - } - - setArg(key, argsToSet) - return i - } - - function setArg (key: string, val: any): void { - if (/-/.test(key) && configuration['camel-case-expansion']) { - const alias = key.split('.').map(function (prop) { - return camelCase(prop) - }).join('.') - addNewAlias(key, alias) - } - - const value = processValue(key, val) - const splitKey = key.split('.') - setKey(argv, splitKey, value) - - // handle populating aliases of the full key - if (flags.aliases[key]) { - flags.aliases[key].forEach(function (x) { - const keyProperties = x.split('.') - setKey(argv, keyProperties, value) - }) - } - - // handle populating aliases of the first element of the dot-notation key - if (splitKey.length > 1 && configuration['dot-notation']) { - ;(flags.aliases[splitKey[0]] || []).forEach(function (x) { - let keyProperties = x.split('.') - - // expand alias with nested objects in key - const a = ([] as string[]).concat(splitKey) - a.shift() // nuke the old key. - keyProperties = keyProperties.concat(a) - - // populate alias only if is not already an alias of the full key - // (already populated above) - if (!(flags.aliases[key] || []).includes(keyProperties.join('.'))) { - setKey(argv, keyProperties, value) - } - }) - } - - // Set normalize getter and setter when key is in 'normalize' but isn't an array - if (checkAllAliases(key, flags.normalize) && !checkAllAliases(key, flags.arrays)) { - const keys = [key].concat(flags.aliases[key] || []) - keys.forEach(function (key) { - Object.defineProperty(argvReturn, key, { - enumerable: true, - get () { - return val - }, - set (value) { - val = typeof value === 'string' ? path.normalize(value) : value - } - }) - }) - } - } - - function addNewAlias (key: string, alias: string): void { - if (!(flags.aliases[key] && flags.aliases[key].length)) { - flags.aliases[key] = [alias] - newAliases[alias] = true - } - if (!(flags.aliases[alias] && flags.aliases[alias].length)) { - addNewAlias(alias, key) - } - } - - function processValue (key: string, val: any) { - // strings may be quoted, clean this up as we assign values. - if (typeof val === 'string' && - (val[0] === "'" || val[0] === '"') && - val[val.length - 1] === val[0] - ) { - val = val.substring(1, val.length - 1) - } - - // handle parsing boolean arguments --foo=true --bar false. - if (checkAllAliases(key, flags.bools) || checkAllAliases(key, flags.counts)) { - if (typeof val === 'string') val = val === 'true' - } - - let value = Array.isArray(val) - ? val.map(function (v) { return maybeCoerceNumber(key, v) }) - : maybeCoerceNumber(key, val) - - // increment a count given as arg (either no value or value parsed as boolean) - if (checkAllAliases(key, flags.counts) && (isUndefined(value) || typeof value === 'boolean')) { - value = increment() - } - - // Set normalized value when key is in 'normalize' and in 'arrays' - if (checkAllAliases(key, flags.normalize) && checkAllAliases(key, flags.arrays)) { - if (Array.isArray(val)) value = val.map(path.normalize) - else value = path.normalize(val) - } - return value - } - - function maybeCoerceNumber (key: string, value: string | number | null | undefined) { - if (!checkAllAliases(key, flags.strings) && !checkAllAliases(key, flags.bools) && !Array.isArray(value)) { - const shouldCoerceNumber = isNumber(value) && configuration['parse-numbers'] && ( - Number.isSafeInteger(Math.floor(parseFloat(`${value}`))) - ) - if (shouldCoerceNumber || (!isUndefined(value) && checkAllAliases(key, flags.numbers))) value = Number(value) - } - return value - } - - // set args from config.json file, this should be - // applied last so that defaults can be applied. - function setConfig (argv: Arguments): void { - const configLookup = Object.create(null) - - // expand defaults/aliases, in-case any happen to reference - // the config.json file. - applyDefaultsAndAliases(configLookup, flags.aliases, defaults) - - Object.keys(flags.configs).forEach(function (configKey) { - const configPath = argv[configKey] || configLookup[configKey] - if (configPath) { - try { - let config = null - const resolvedConfigPath = path.resolve(process.cwd(), configPath) - const resolveConfig = flags.configs[configKey] - - if (typeof resolveConfig === 'function') { - try { - config = resolveConfig(resolvedConfigPath) - } catch (e) { - config = e - } - if (config instanceof Error) { - error = config - return - } - } else { - config = require(resolvedConfigPath) - } - - setConfigObject(config) - } catch (ex) { - if (argv[configKey]) error = Error(__('Invalid JSON config file: %s', configPath)) - } - } - }) - } - - // set args from config object. - // it recursively checks nested objects. - function setConfigObject (config: { [key: string]: any }, prev?: string): void { - Object.keys(config).forEach(function (key) { - const value = config[key] - const fullKey = prev ? prev + '.' + key : key - - // if the value is an inner object and we have dot-notation - // enabled, treat inner objects in config the same as - // heavily nested dot notations (foo.bar.apple). - if (typeof value === 'object' && value !== null && !Array.isArray(value) && configuration['dot-notation']) { - // if the value is an object but not an array, check nested object - setConfigObject(value, fullKey) - } else { - // setting arguments via CLI takes precedence over - // values within the config file. - if (!hasKey(argv, fullKey.split('.')) || (checkAllAliases(fullKey, flags.arrays) && configuration['combine-arrays'])) { - setArg(fullKey, value) - } - } - }) - } - - // set all config objects passed in opts - function setConfigObjects (): void { - if (typeof configObjects !== 'undefined') { - configObjects.forEach(function (configObject) { - setConfigObject(configObject) - }) - } - } - - function applyEnvVars (argv: Arguments, configOnly: boolean): void { - if (process) { - if (typeof envPrefix === 'undefined') return - - const prefix = typeof envPrefix === 'string' ? envPrefix : '' - Object.keys(process.env).forEach(function (envVar) { - if (prefix === '' || envVar.lastIndexOf(prefix, 0) === 0) { - // get array of nested keys and convert them to camel case - const keys = envVar.split('__').map(function (key, i) { - if (i === 0) { - key = key.substring(prefix.length) - } - return camelCase(key) - }) - - if (((configOnly && flags.configs[keys.join('.')]) || !configOnly) && !hasKey(argv, keys)) { - setArg(keys.join('.'), process.env[envVar]) - } - } - }) - } - } - - function applyCoercions (argv: Arguments): void { - let coerce: false | CoerceCallback - const applied: Set = new Set() - Object.keys(argv).forEach(function (key) { - if (!applied.has(key)) { // If we haven't already coerced this option via one of its aliases - coerce = checkAllAliases(key, flags.coercions) - if (typeof coerce === 'function') { - try { - const value = maybeCoerceNumber(key, coerce(argv[key])) - ;(([] as string[]).concat(flags.aliases[key] || [], key)).forEach(ali => { - applied.add(ali) - argv[ali] = value - }) - } catch (err) { - error = err - } - } - } - }) - } - - function setPlaceholderKeys (argv: Arguments): Arguments { - flags.keys.forEach((key) => { - // don't set placeholder keys for dot notation options 'foo.bar'. - if (~key.indexOf('.')) return - if (typeof argv[key] === 'undefined') argv[key] = undefined - }) - return argv - } - - function applyDefaultsAndAliases (obj: { [key: string]: any }, aliases: { [key: string]: string[] }, defaults: { [key: string]: any }, canLog: boolean = false): void { - Object.keys(defaults).forEach(function (key) { - if (!hasKey(obj, key.split('.'))) { - setKey(obj, key.split('.'), defaults[key]) - if (canLog) defaulted[key] = true - - ;(aliases[key] || []).forEach(function (x) { - if (hasKey(obj, x.split('.'))) return - setKey(obj, x.split('.'), defaults[key]) - }) - } - }) - } - - function hasKey (obj: { [key: string]: any }, keys: string[]): boolean { - let o = obj - - if (!configuration['dot-notation']) keys = [keys.join('.')] - - keys.slice(0, -1).forEach(function (key) { - o = (o[key] || {}) - }) - - const key = keys[keys.length - 1] - - if (typeof o !== 'object') return false - else return key in o - } - - function setKey (obj: { [key: string]: any }, keys: string[], value: any): void { - let o = obj - - if (!configuration['dot-notation']) keys = [keys.join('.')] - - keys.slice(0, -1).forEach(function (key) { - // TODO(bcoe): in the next major version of yargs, switch to - // Object.create(null) for dot notation: - key = sanitizeKey(key) - - if (typeof o === 'object' && o[key] === undefined) { - o[key] = {} - } - - if (typeof o[key] !== 'object' || Array.isArray(o[key])) { - // ensure that o[key] is an array, and that the last item is an empty object. - if (Array.isArray(o[key])) { - o[key].push({}) - } else { - o[key] = [o[key], {}] - } - - // we want to update the empty object at the end of the o[key] array, so set o to that object - o = o[key][o[key].length - 1] - } else { - o = o[key] - } - }) - - // TODO(bcoe): in the next major version of yargs, switch to - // Object.create(null) for dot notation: - const key = sanitizeKey(keys[keys.length - 1]) - - const isTypeArray = checkAllAliases(keys.join('.'), flags.arrays) - const isValueArray = Array.isArray(value) - let duplicate = configuration['duplicate-arguments-array'] - - // nargs has higher priority than duplicate - if (!duplicate && checkAllAliases(key, flags.nargs)) { - duplicate = true - if ((!isUndefined(o[key]) && flags.nargs[key] === 1) || (Array.isArray(o[key]) && o[key].length === flags.nargs[key])) { - o[key] = undefined - } - } - - if (value === increment()) { - o[key] = increment(o[key]) - } else if (Array.isArray(o[key])) { - if (duplicate && isTypeArray && isValueArray) { - o[key] = configuration['flatten-duplicate-arrays'] ? o[key].concat(value) : (Array.isArray(o[key][0]) ? o[key] : [o[key]]).concat([value]) - } else if (!duplicate && Boolean(isTypeArray) === Boolean(isValueArray)) { - o[key] = value - } else { - o[key] = o[key].concat([value]) - } - } else if (o[key] === undefined && isTypeArray) { - o[key] = isValueArray ? value : [value] - } else if (duplicate && !( - o[key] === undefined || - checkAllAliases(key, flags.counts) || - checkAllAliases(key, flags.bools) - )) { - o[key] = [o[key], value] - } else { - o[key] = value - } - } - - // extend the aliases list with inferred aliases. - function extendAliases (...args: Array<{ [key: string]: any } | undefined>) { - args.forEach(function (obj) { - Object.keys(obj || {}).forEach(function (key) { - // short-circuit if we've already added a key - // to the aliases array, for example it might - // exist in both 'opts.default' and 'opts.key'. - if (flags.aliases[key]) return - - flags.aliases[key] = ([] as string[]).concat(aliases[key] || []) - // For "--option-name", also set argv.optionName - flags.aliases[key].concat(key).forEach(function (x) { - if (/-/.test(x) && configuration['camel-case-expansion']) { - const c = camelCase(x) - if (c !== key && flags.aliases[key].indexOf(c) === -1) { - flags.aliases[key].push(c) - newAliases[c] = true - } - } - }) - // For "--optionName", also set argv['option-name'] - flags.aliases[key].concat(key).forEach(function (x) { - if (x.length > 1 && /[A-Z]/.test(x) && configuration['camel-case-expansion']) { - const c = decamelize(x, '-') - if (c !== key && flags.aliases[key].indexOf(c) === -1) { - flags.aliases[key].push(c) - newAliases[c] = true - } - } - }) - flags.aliases[key].forEach(function (x) { - flags.aliases[x] = [key].concat(flags.aliases[key].filter(function (y) { - return x !== y - })) - }) - }) - }) - } - - // return the 1st set flag for any of a key's aliases (or false if no flag set) - function checkAllAliases (key: string, flag: StringFlag): ValueOf | false - function checkAllAliases (key: string, flag: BooleanFlag): ValueOf | false - function checkAllAliases (key: string, flag: NumberFlag): ValueOf | false - function checkAllAliases (key: string, flag: ConfigsFlag): ValueOf | false - function checkAllAliases (key: string, flag: CoercionsFlag): ValueOf | false - function checkAllAliases (key: string, flag: Flag): ValueOf | false { - const toCheck = ([] as string[]).concat(flags.aliases[key] || [], key) - const keys = Object.keys(flag) - const setAlias = toCheck.find(key => keys.includes(key)) - return setAlias ? flag[setAlias] : false - } - - function hasAnyFlag (key: string): boolean { - const flagsKeys = Object.keys(flags) as FlagsKey[] - const toCheck = ([] as Array<{ [key: string]: any } | string[]>).concat(flagsKeys.map(k => flags[k])) - return toCheck.some(function (flag) { - return Array.isArray(flag) ? flag.includes(key) : flag[key] - }) - } - - function hasFlagsMatching (arg: string, ...patterns: RegExp[]): boolean { - const toCheck = ([] as RegExp[]).concat(...patterns) - return toCheck.some(function (pattern) { - const match = arg.match(pattern) - return match && hasAnyFlag(match[1]) - }) - } - - // based on a simplified version of the short flag group parsing logic - function hasAllShortFlags (arg: string): boolean { - // if this is a negative number, or doesn't start with a single hyphen, it's not a short flag group - if (arg.match(negative) || !arg.match(/^-[^-]+/)) { return false } - let hasAllFlags = true - let next: string - const letters = arg.slice(1).split('') - for (let j = 0; j < letters.length; j++) { - next = arg.slice(j + 2) - - if (!hasAnyFlag(letters[j])) { - hasAllFlags = false - break - } - - if ((letters[j + 1] && letters[j + 1] === '=') || - next === '-' || - (/[A-Za-z]/.test(letters[j]) && /^-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) || - (letters[j + 1] && letters[j + 1].match(/\W/))) { - break - } - } - return hasAllFlags - } - - function isUnknownOptionAsArg (arg: string): boolean { - return configuration['unknown-options-as-args'] && isUnknownOption(arg) - } - - function isUnknownOption (arg: string): boolean { - // ignore negative numbers - if (arg.match(negative)) { return false } - // if this is a short option group and all of them are configured, it isn't unknown - if (hasAllShortFlags(arg)) { return false } - // e.g. '--count=2' - const flagWithEquals = /^-+([^=]+?)=[\s\S]*$/ - // e.g. '-a' or '--arg' - const normalFlag = /^-+([^=]+?)$/ - // e.g. '-a-' - const flagEndingInHyphen = /^-+([^=]+?)-$/ - // e.g. '-abc123' - const flagEndingInDigits = /^-+([^=]+?\d+)$/ - // e.g. '-a/usr/local' - const flagEndingInNonWordCharacters = /^-+([^=]+?)\W+.*$/ - // check the different types of flag styles, including negatedBoolean, a pattern defined near the start of the parse method - return !hasFlagsMatching(arg, flagWithEquals, negatedBoolean, normalFlag, flagEndingInHyphen, flagEndingInDigits, flagEndingInNonWordCharacters) - } - - // make a best effort to pick a default value - // for an option based on name and type. - function defaultValue (key: string) { - if (!checkAllAliases(key, flags.bools) && - !checkAllAliases(key, flags.counts) && - `${key}` in defaults) { - return defaults[key] - } else { - return defaultForType(guessType(key)) - } - } - - // return a default value, given the type of a flag., - function defaultForType (type: K): DefaultValuesForType[K] { - const def: DefaultValuesForType = { - boolean: true, - string: '', - number: undefined, - array: [] - } - - return def[type] - } - - // given a flag, enforce a default type. - function guessType (key: string): DefaultValuesForTypeKey { - let type: DefaultValuesForTypeKey = 'boolean' - if (checkAllAliases(key, flags.strings)) type = 'string' - else if (checkAllAliases(key, flags.numbers)) type = 'number' - else if (checkAllAliases(key, flags.bools)) type = 'boolean' - else if (checkAllAliases(key, flags.arrays)) type = 'array' - return type - } - - function isNumber (x: null | undefined | number | string): boolean { - if (x === null || x === undefined) return false - // if loaded from config, may already be a number. - if (typeof x === 'number') return true - // hexadecimal. - if (/^0x[0-9a-f]+$/i.test(x)) return true - // don't treat 0123 as a number; as it drops the leading '0'. - if (x.length > 1 && x[0] === '0') return false - return /^[-]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x) - } - - function isUndefined (num: any): num is undefined { - return num === undefined - } - - // check user configuration settings for inconsistencies - function checkConfiguration (): void { - // count keys should not be set as array/narg - Object.keys(flags.counts).find(key => { - if (checkAllAliases(key, flags.arrays)) { - error = Error(__('Invalid configuration: %s, opts.count excludes opts.array.', key)) - return true - } else if (checkAllAliases(key, flags.nargs)) { - error = Error(__('Invalid configuration: %s, opts.count excludes opts.narg.', key)) - return true - } - return false - }) - } - - return { - argv: Object.assign(argvReturn, argv), - error: error, - aliases: Object.assign({}, flags.aliases), - newAliases: Object.assign({}, newAliases), - defaulted: Object.assign({}, defaulted), - configuration: configuration - } -} - -// if any aliases reference each other, we should -// merge them together. -function combineAliases (aliases: Dictionary): Dictionary { - const aliasArrays: Array = [] - const combined: Dictionary = Object.create(null) - let change = true - - // turn alias lookup hash {key: ['alias1', 'alias2']} into - // a simple array ['key', 'alias1', 'alias2'] - Object.keys(aliases).forEach(function (key) { - aliasArrays.push( - ([] as string[]).concat(aliases[key], key) - ) - }) - - // combine arrays until zero changes are - // made in an iteration. - while (change) { - change = false - for (let i = 0; i < aliasArrays.length; i++) { - for (let ii = i + 1; ii < aliasArrays.length; ii++) { - const intersect = aliasArrays[i].filter(function (v) { - return aliasArrays[ii].indexOf(v) !== -1 - }) - - if (intersect.length) { - aliasArrays[i] = aliasArrays[i].concat(aliasArrays[ii]) - aliasArrays.splice(ii, 1) - change = true - break - } - } - } - } - - // map arrays back to the hash-lookup (de-dupe while - // we're at it). - aliasArrays.forEach(function (aliasArray) { - aliasArray = aliasArray.filter(function (v, i, self) { - return self.indexOf(v) === i - }) - const lastAlias = aliasArray.pop() - if (lastAlias !== undefined && typeof lastAlias === 'string') { - combined[lastAlias] = aliasArray - } - }) - - return combined -} - -// this function should only be called when a count is given as an arg -// it is NOT called to set a default value -// thus we can start the count at 1 instead of 0 -function increment (orig?: number | undefined): number { - return orig !== undefined ? orig + 1 : 1 -} - -// TODO(bcoe): in the next major version of yargs, switch to -// Object.create(null) for dot notation: -function sanitizeKey (key: string): string { - if (key === '__proto__') return '___proto___' - return key -} - -const yargsParser: Parser = function Parser (args: ArgsInput, opts?: Partial): Arguments { - const result = parse(args.slice(), opts) - return result.argv -} - -// parse arguments and return detailed -// meta information, aliases, etc. -yargsParser.detailed = function (args: ArgsInput, opts?: Partial): DetailedArguments { - return parse(args.slice(), opts) -} - -export = yargsParser diff --git a/lib/index.ts b/lib/index.ts new file mode 100644 index 00000000..d903020d --- /dev/null +++ b/lib/index.ts @@ -0,0 +1,49 @@ +// Main entrypoint for libraries using yargs-parser in Node.js +// CJS and ESM environments: +import { format } from 'util' +import { readFileSync } from 'fs' +import { normalize, resolve } from 'path' +import { ArgsInput, Arguments, Parser, Options, DetailedArguments } from './yargs-parser-types.js' +import { YargsParser } from './yargs-parser.js' + +// See https://github.com/yargs/yargs-parser#supported-nodejs-versions for our +// version support policy. The YARGS_MIN_NODE_VERSION is used for testing only. +const minNodeVersion = (process && process.env && process.env.YARGS_MIN_NODE_VERSION) + ? Number(process.env.YARGS_MIN_NODE_VERSION) : 10 +if (process && process.version) { + const major = Number(process.version.match(/v([^.]+)/)![1]) + if (major < minNodeVersion) { + throw Error(`yargs parser supports a minimum Node.js version of ${minNodeVersion}. Read our version support policy: https://github.com/yargs/yargs-parser#supported-nodejs-versions`) + } +} + +// Creates a yargs-parser instance using Node.js standard libraries: +const env = process ? process.env as { [key: string]: string } : {} +const parser = new YargsParser({ + cwd: process.cwd, + env: () => { + return env + }, + format, + normalize, + resolve, + // TODO: figure out a way to combine ESM and CJS coverage, such that + // we can exercise all the lines below: + require: (path: string) => { + if (typeof require !== 'undefined') { + return require(path) + } else if (path.match(/\.json$/)) { + return readFileSync(path, 'utf8') + } else { + throw Error('only .json config files are supported in ESM') + } + } +}) +const yargsParser: Parser = function Parser (args: ArgsInput, opts?: Partial): Arguments { + const result = parser.parse(args.slice(), opts) + return result.argv +} +yargsParser.detailed = function (args: ArgsInput, opts?: Partial): DetailedArguments { + return parser.parse(args.slice(), opts) +} +export default yargsParser diff --git a/lib/string-utils.ts b/lib/string-utils.ts new file mode 100644 index 00000000..214dd82c --- /dev/null +++ b/lib/string-utils.ts @@ -0,0 +1,40 @@ +export function camelCase (str: string): string { + str = str.toLocaleLowerCase() + if (str.indexOf('-') === -1 && str.indexOf('_') === -1) { + return str + } else { + let camelcase = '' + let nextChrUpper = false + const leadingHyphens = str.match(/^-+/) + for (let i = leadingHyphens ? leadingHyphens[0].length : 0; i < str.length; i++) { + let chr = str.charAt(i) + if (nextChrUpper) { + nextChrUpper = false + chr = chr.toLocaleUpperCase() + } + if (i !== 0 && (chr === '-' || chr === '_')) { + nextChrUpper = true + continue + } else if (chr !== '-' && chr !== '_') { + camelcase += chr + } + } + return camelcase + } +} + +export function decamelize (str: string, joinString?: string): string { + const lowercase = str.toLocaleLowerCase() + joinString = joinString || '-' + let notCamelcase = '' + for (let i = 0; i < str.length; i++) { + const chrLower = lowercase.charAt(i) + const chrString = str.charAt(i) + if (chrLower !== chrString) { + notCamelcase += `${joinString}${lowercase.charAt(i)}` + } else { + notCamelcase += chrString + } + } + return notCamelcase +} diff --git a/lib/yargs-parser-types.ts b/lib/yargs-parser-types.ts index 9d4b38b8..35136277 100644 --- a/lib/yargs-parser-types.ts +++ b/lib/yargs-parser-types.ts @@ -1,4 +1,4 @@ -import type { Dictionary, KeyOf, ValueOf } from './common-types' +import type { Dictionary, KeyOf, ValueOf } from './common-types.js' export type ArgsInput = string | any[]; @@ -113,6 +113,15 @@ export interface Options { key: Dictionary; } +export interface YargsParserMixin { + cwd: Function; + format: Function; + normalize: Function; + require: Function; + resolve: Function; + env: Function; +} + export type OptionsDefault = ValueOf, 'default'>>; export interface Parser { diff --git a/lib/yargs-parser.ts b/lib/yargs-parser.ts new file mode 100644 index 00000000..47ad99dd --- /dev/null +++ b/lib/yargs-parser.ts @@ -0,0 +1,1102 @@ +import { tokenizeArgString } from './tokenize-arg-string.js' +import type { + ArgsInput, + Arguments, + ArrayFlagsKey, + ArrayOption, + CoerceCallback, + Configuration, + DefaultValuesForType, + DefaultValuesForTypeKey, + DetailedArguments, + Flag, + Flags, + FlagsKey, + StringFlag, + BooleanFlag, + NumberFlag, + ConfigsFlag, + CoercionsFlag, + Options, + OptionsDefault, + YargsParserMixin +} from './yargs-parser-types.js' +import type { Dictionary, ValueOf } from './common-types.js' +import { camelCase, decamelize } from './string-utils.js' + +let mixin: YargsParserMixin +export class YargsParser { + constructor (_mixin: YargsParserMixin) { + mixin = _mixin + } + + parse (argsInput: ArgsInput, options?: Partial): DetailedArguments { + const opts: Partial = Object.assign({ + alias: undefined, + array: undefined, + boolean: undefined, + config: undefined, + configObjects: undefined, + configuration: undefined, + coerce: undefined, + count: undefined, + default: undefined, + envPrefix: undefined, + narg: undefined, + normalize: undefined, + string: undefined, + number: undefined, + __: undefined, + key: undefined + }, options) + // allow a string argument to be passed in rather + // than an argv array. + const args = tokenizeArgString(argsInput) + + // aliases might have transitive relationships, normalize this. + const aliases = combineAliases(Object.assign(Object.create(null), opts.alias)) + const configuration: Configuration = Object.assign({ + 'boolean-negation': true, + 'camel-case-expansion': true, + 'combine-arrays': false, + 'dot-notation': true, + 'duplicate-arguments-array': true, + 'flatten-duplicate-arrays': true, + 'greedy-arrays': true, + 'halt-at-non-option': false, + 'nargs-eats-options': false, + 'negation-prefix': 'no-', + 'parse-numbers': true, + 'populate--': false, + 'set-placeholder-key': false, + 'short-option-groups': true, + 'strip-aliased': false, + 'strip-dashed': false, + 'unknown-options-as-args': false + }, opts.configuration) + const defaults: OptionsDefault = Object.assign(Object.create(null), opts.default) + const configObjects = opts.configObjects || [] + const envPrefix = opts.envPrefix + const notFlagsOption = configuration['populate--'] + const notFlagsArgv: string = notFlagsOption ? '--' : '_' + const newAliases: Dictionary = Object.create(null) + const defaulted: Dictionary = Object.create(null) + // allow a i18n handler to be passed in, default to a fake one (util.format). + const __ = opts.__ || mixin.format + const flags: Flags = { + aliases: Object.create(null), + arrays: Object.create(null), + bools: Object.create(null), + strings: Object.create(null), + numbers: Object.create(null), + counts: Object.create(null), + normalize: Object.create(null), + configs: Object.create(null), + nargs: Object.create(null), + coercions: Object.create(null), + keys: [] + } + const negative = /^-([0-9]+(\.[0-9]+)?|\.[0-9]+)$/ + const negatedBoolean = new RegExp('^--' + configuration['negation-prefix'] + '(.+)') + + ;([] as ArrayOption[]).concat(opts.array || []).filter(Boolean).forEach(function (opt) { + const key = typeof opt === 'object' ? opt.key : opt + + // assign to flags[bools|strings|numbers] + const assignment: ArrayFlagsKey | undefined = Object.keys(opt).map(function (key) { + const arrayFlagKeys: Record = { + boolean: 'bools', + string: 'strings', + number: 'numbers' + } + return arrayFlagKeys[key] + }).filter(Boolean).pop() + + // assign key to be coerced + if (assignment) { + flags[assignment][key] = true + } + + flags.arrays[key] = true + flags.keys.push(key) + }) + + ;([] as string[]).concat(opts.boolean || []).filter(Boolean).forEach(function (key) { + flags.bools[key] = true + flags.keys.push(key) + }) + + ;([] as string[]).concat(opts.string || []).filter(Boolean).forEach(function (key) { + flags.strings[key] = true + flags.keys.push(key) + }) + + ;([] as string[]).concat(opts.number || []).filter(Boolean).forEach(function (key) { + flags.numbers[key] = true + flags.keys.push(key) + }) + + ;([] as string[]).concat(opts.count || []).filter(Boolean).forEach(function (key) { + flags.counts[key] = true + flags.keys.push(key) + }) + + ;([] as string[]).concat(opts.normalize || []).filter(Boolean).forEach(function (key) { + flags.normalize[key] = true + flags.keys.push(key) + }) + + if (typeof opts.narg === 'object') { + Object.entries(opts.narg).forEach(([key, value]) => { + if (typeof value === 'number') { + flags.nargs[key] = value + flags.keys.push(key) + } + }) + } + + if (typeof opts.coerce === 'object') { + Object.entries(opts.coerce).forEach(([key, value]) => { + if (typeof value === 'function') { + flags.coercions[key] = value + flags.keys.push(key) + } + }) + } + + if (typeof opts.config !== 'undefined') { + if (Array.isArray(opts.config) || typeof opts.config === 'string') { + ;([] as string[]).concat(opts.config).filter(Boolean).forEach(function (key) { + flags.configs[key] = true + }) + } else if (typeof opts.config === 'object') { + Object.entries(opts.config).forEach(([key, value]) => { + if (typeof value === 'boolean' || typeof value === 'function') { + flags.configs[key] = value + } + }) + } + } + + // create a lookup table that takes into account all + // combinations of aliases: {f: ['foo'], foo: ['f']} + extendAliases(opts.key, aliases, opts.default, flags.arrays) + + // apply default values to all aliases. + Object.keys(defaults).forEach(function (key) { + (flags.aliases[key] || []).forEach(function (alias) { + defaults[alias] = defaults[key] + }) + }) + + let error: Error | null = null + checkConfiguration() + + let notFlags: string[] = [] + + const argv: Arguments = Object.assign(Object.create(null), { _: [] }) + // TODO(bcoe): for the first pass at removing object prototype we didn't + // remove all prototypes from objects returned by this API, we might want + // to gradually move towards doing so. + const argvReturn: { [argName: string]: any } = {} + + for (let i = 0; i < args.length; i++) { + const arg = args[i] + let broken: boolean + let key: string | undefined + let letters: string[] + let m: RegExpMatchArray | null + let next: string + let value: string + + // any unknown option (except for end-of-options, "--") + if (arg !== '--' && isUnknownOptionAsArg(arg)) { + argv._.push(arg) + // -- separated by = + } else if (arg.match(/^--.+=/) || ( + !configuration['short-option-groups'] && arg.match(/^-.+=/) + )) { + // Using [\s\S] instead of . because js doesn't support the + // 'dotall' regex modifier. See: + // http://stackoverflow.com/a/1068308/13216 + m = arg.match(/^--?([^=]+)=([\s\S]*)$/) + + // arrays format = '--f=a b c' + if (m !== null && Array.isArray(m) && m.length >= 3) { + if (checkAllAliases(m[1], flags.arrays)) { + i = eatArray(i, m[1], args, m[2]) + } else if (checkAllAliases(m[1], flags.nargs) !== false) { + // nargs format = '--f=monkey washing cat' + i = eatNargs(i, m[1], args, m[2]) + } else { + setArg(m[1], m[2]) + } + } + } else if (arg.match(negatedBoolean) && configuration['boolean-negation']) { + m = arg.match(negatedBoolean) + if (m !== null && Array.isArray(m) && m.length >= 2) { + key = m[1] + setArg(key, checkAllAliases(key, flags.arrays) ? [false] : false) + } + + // -- separated by space. + } else if (arg.match(/^--.+/) || ( + !configuration['short-option-groups'] && arg.match(/^-[^-]+/) + )) { + m = arg.match(/^--?(.+)/) + if (m !== null && Array.isArray(m) && m.length >= 2) { + key = m[1] + if (checkAllAliases(key, flags.arrays)) { + // array format = '--foo a b c' + i = eatArray(i, key, args) + } else if (checkAllAliases(key, flags.nargs) !== false) { + // nargs format = '--foo a b c' + // should be truthy even if: flags.nargs[key] === 0 + i = eatNargs(i, key, args) + } else { + next = args[i + 1] + + if (next !== undefined && (!next.match(/^-/) || + next.match(negative)) && + !checkAllAliases(key, flags.bools) && + !checkAllAliases(key, flags.counts)) { + setArg(key, next) + i++ + } else if (/^(true|false)$/.test(next)) { + setArg(key, next) + i++ + } else { + setArg(key, defaultValue(key)) + } + } + } + + // dot-notation flag separated by '='. + } else if (arg.match(/^-.\..+=/)) { + m = arg.match(/^-([^=]+)=([\s\S]*)$/) + if (m !== null && Array.isArray(m) && m.length >= 3) { + setArg(m[1], m[2]) + } + + // dot-notation flag separated by space. + } else if (arg.match(/^-.\..+/) && !arg.match(negative)) { + next = args[i + 1] + m = arg.match(/^-(.\..+)/) + if (m !== null && Array.isArray(m) && m.length >= 2) { + key = m[1] + if (next !== undefined && !next.match(/^-/) && + !checkAllAliases(key, flags.bools) && + !checkAllAliases(key, flags.counts)) { + setArg(key, next) + i++ + } else { + setArg(key, defaultValue(key)) + } + } + } else if (arg.match(/^-[^-]+/) && !arg.match(negative)) { + letters = arg.slice(1, -1).split('') + broken = false + + for (let j = 0; j < letters.length; j++) { + next = arg.slice(j + 2) + + if (letters[j + 1] && letters[j + 1] === '=') { + value = arg.slice(j + 3) + key = letters[j] + + if (checkAllAliases(key, flags.arrays)) { + // array format = '-f=a b c' + i = eatArray(i, key, args, value) + } else if (checkAllAliases(key, flags.nargs) !== false) { + // nargs format = '-f=monkey washing cat' + i = eatNargs(i, key, args, value) + } else { + setArg(key, value) + } + + broken = true + break + } + + if (next === '-') { + setArg(letters[j], next) + continue + } + + // current letter is an alphabetic character and next value is a number + if (/[A-Za-z]/.test(letters[j]) && + /^-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) { + setArg(letters[j], next) + broken = true + break + } + + if (letters[j + 1] && letters[j + 1].match(/\W/)) { + setArg(letters[j], next) + broken = true + break + } else { + setArg(letters[j], defaultValue(letters[j])) + } + } + + key = arg.slice(-1)[0] + + if (!broken && key !== '-') { + if (checkAllAliases(key, flags.arrays)) { + // array format = '-f a b c' + i = eatArray(i, key, args) + } else if (checkAllAliases(key, flags.nargs) !== false) { + // nargs format = '-f a b c' + // should be truthy even if: flags.nargs[key] === 0 + i = eatNargs(i, key, args) + } else { + next = args[i + 1] + + if (next !== undefined && (!/^(-|--)[^-]/.test(next) || + next.match(negative)) && + !checkAllAliases(key, flags.bools) && + !checkAllAliases(key, flags.counts)) { + setArg(key, next) + i++ + } else if (/^(true|false)$/.test(next)) { + setArg(key, next) + i++ + } else { + setArg(key, defaultValue(key)) + } + } + } + } else if (arg.match(/^-[0-9]$/) && + arg.match(negative) && + checkAllAliases(arg.slice(1), flags.bools)) { + // single-digit boolean alias, e.g: xargs -0 + key = arg.slice(1) + setArg(key, defaultValue(key)) + } else if (arg === '--') { + notFlags = args.slice(i + 1) + break + } else if (configuration['halt-at-non-option']) { + notFlags = args.slice(i) + break + } else { + const maybeCoercedNumber = maybeCoerceNumber('_', arg) + if (typeof maybeCoercedNumber === 'string' || typeof maybeCoercedNumber === 'number') { + argv._.push(maybeCoercedNumber) + } + } + } + + // order of precedence: + // 1. command line arg + // 2. value from env var + // 3. value from config file + // 4. value from config objects + // 5. configured default value + applyEnvVars(argv, true) // special case: check env vars that point to config file + applyEnvVars(argv, false) + setConfig(argv) + setConfigObjects() + applyDefaultsAndAliases(argv, flags.aliases, defaults, true) + applyCoercions(argv) + if (configuration['set-placeholder-key']) setPlaceholderKeys(argv) + + // for any counts either not in args or without an explicit default, set to 0 + Object.keys(flags.counts).forEach(function (key) { + if (!hasKey(argv, key.split('.'))) setArg(key, 0) + }) + + // '--' defaults to undefined. + if (notFlagsOption && notFlags.length) argv[notFlagsArgv] = [] + notFlags.forEach(function (key) { + argv[notFlagsArgv].push(key) + }) + + if (configuration['camel-case-expansion'] && configuration['strip-dashed']) { + Object.keys(argv).filter(key => key !== '--' && key.includes('-')).forEach(key => { + delete argv[key] + }) + } + + if (configuration['strip-aliased']) { + ;([] as string[]).concat(...Object.keys(aliases).map(k => aliases[k])).forEach(alias => { + if (configuration['camel-case-expansion']) { + delete argv[alias.split('.').map(prop => camelCase(prop)).join('.')] + } + + delete argv[alias] + }) + } + + // how many arguments should we consume, based + // on the nargs option? + function eatNargs (i: number, key: string, args: string[], argAfterEqualSign?: string): number { + let ii + let toEat = checkAllAliases(key, flags.nargs) + // NaN has a special meaning for the array type, indicating that one or + // more values are expected. + toEat = typeof toEat !== 'number' || isNaN(toEat) ? 1 : toEat + + if (toEat === 0) { + if (!isUndefined(argAfterEqualSign)) { + error = Error(__('Argument unexpected for: %s', key)) + } + setArg(key, defaultValue(key)) + return i + } + + let available = isUndefined(argAfterEqualSign) ? 0 : 1 + if (configuration['nargs-eats-options']) { + // classic behavior, yargs eats positional and dash arguments. + if (args.length - (i + 1) + available < toEat) { + error = Error(__('Not enough arguments following: %s', key)) + } + available = toEat + } else { + // nargs will not consume flag arguments, e.g., -abc, --foo, + // and terminates when one is observed. + for (ii = i + 1; ii < args.length; ii++) { + if (!args[ii].match(/^-[^0-9]/) || args[ii].match(negative) || isUnknownOptionAsArg(args[ii])) available++ + else break + } + if (available < toEat) error = Error(__('Not enough arguments following: %s', key)) + } + + let consumed = Math.min(available, toEat) + if (!isUndefined(argAfterEqualSign) && consumed > 0) { + setArg(key, argAfterEqualSign) + consumed-- + } + for (ii = i + 1; ii < (consumed + i + 1); ii++) { + setArg(key, args[ii]) + } + + return (i + consumed) + } + + // if an option is an array, eat all non-hyphenated arguments + // following it... YUM! + // e.g., --foo apple banana cat becomes ["apple", "banana", "cat"] + function eatArray (i: number, key: string, args: string[], argAfterEqualSign?: string): number { + let argsToSet = [] + let next = argAfterEqualSign || args[i + 1] + // If both array and nargs are configured, enforce the nargs count: + const nargsCount = checkAllAliases(key, flags.nargs) + + if (checkAllAliases(key, flags.bools) && !(/^(true|false)$/.test(next))) { + argsToSet.push(true) + } else if (isUndefined(next) || + (isUndefined(argAfterEqualSign) && /^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next))) { + // for keys without value ==> argsToSet remains an empty [] + // set user default value, if available + if (defaults[key] !== undefined) { + const defVal = defaults[key] + argsToSet = Array.isArray(defVal) ? defVal : [defVal] + } + } else { + // value in --option=value is eaten as is + if (!isUndefined(argAfterEqualSign)) { + argsToSet.push(processValue(key, argAfterEqualSign)) + } + for (let ii = i + 1; ii < args.length; ii++) { + if ((!configuration['greedy-arrays'] && argsToSet.length > 0) || + (nargsCount && typeof nargsCount === 'number' && argsToSet.length >= nargsCount)) break + next = args[ii] + if (/^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next)) break + i = ii + argsToSet.push(processValue(key, next)) + } + } + + // If both array and nargs are configured, create an error if less than + // nargs positionals were found. NaN has special meaning, indicating + // that at least one value is required (more are okay). + if (typeof nargsCount === 'number' && ((nargsCount && argsToSet.length < nargsCount) || + (isNaN(nargsCount) && argsToSet.length === 0))) { + error = Error(__('Not enough arguments following: %s', key)) + } + + setArg(key, argsToSet) + return i + } + + function setArg (key: string, val: any): void { + if (/-/.test(key) && configuration['camel-case-expansion']) { + const alias = key.split('.').map(function (prop) { + return camelCase(prop) + }).join('.') + addNewAlias(key, alias) + } + + const value = processValue(key, val) + const splitKey = key.split('.') + setKey(argv, splitKey, value) + + // handle populating aliases of the full key + if (flags.aliases[key]) { + flags.aliases[key].forEach(function (x) { + const keyProperties = x.split('.') + setKey(argv, keyProperties, value) + }) + } + + // handle populating aliases of the first element of the dot-notation key + if (splitKey.length > 1 && configuration['dot-notation']) { + ;(flags.aliases[splitKey[0]] || []).forEach(function (x) { + let keyProperties = x.split('.') + + // expand alias with nested objects in key + const a = ([] as string[]).concat(splitKey) + a.shift() // nuke the old key. + keyProperties = keyProperties.concat(a) + + // populate alias only if is not already an alias of the full key + // (already populated above) + if (!(flags.aliases[key] || []).includes(keyProperties.join('.'))) { + setKey(argv, keyProperties, value) + } + }) + } + + // Set normalize getter and setter when key is in 'normalize' but isn't an array + if (checkAllAliases(key, flags.normalize) && !checkAllAliases(key, flags.arrays)) { + const keys = [key].concat(flags.aliases[key] || []) + keys.forEach(function (key) { + Object.defineProperty(argvReturn, key, { + enumerable: true, + get () { + return val + }, + set (value) { + val = typeof value === 'string' ? mixin.normalize(value) : value + } + }) + }) + } + } + + function addNewAlias (key: string, alias: string): void { + if (!(flags.aliases[key] && flags.aliases[key].length)) { + flags.aliases[key] = [alias] + newAliases[alias] = true + } + if (!(flags.aliases[alias] && flags.aliases[alias].length)) { + addNewAlias(alias, key) + } + } + + function processValue (key: string, val: any) { + // strings may be quoted, clean this up as we assign values. + if (typeof val === 'string' && + (val[0] === "'" || val[0] === '"') && + val[val.length - 1] === val[0] + ) { + val = val.substring(1, val.length - 1) + } + + // handle parsing boolean arguments --foo=true --bar false. + if (checkAllAliases(key, flags.bools) || checkAllAliases(key, flags.counts)) { + if (typeof val === 'string') val = val === 'true' + } + + let value = Array.isArray(val) + ? val.map(function (v) { return maybeCoerceNumber(key, v) }) + : maybeCoerceNumber(key, val) + + // increment a count given as arg (either no value or value parsed as boolean) + if (checkAllAliases(key, flags.counts) && (isUndefined(value) || typeof value === 'boolean')) { + value = increment() + } + + // Set normalized value when key is in 'normalize' and in 'arrays' + if (checkAllAliases(key, flags.normalize) && checkAllAliases(key, flags.arrays)) { + if (Array.isArray(val)) value = val.map((val) => { return mixin.normalize(val) }) + else value = mixin.normalize(val) + } + return value + } + + function maybeCoerceNumber (key: string, value: string | number | null | undefined) { + if (!checkAllAliases(key, flags.strings) && !checkAllAliases(key, flags.bools) && !Array.isArray(value)) { + const shouldCoerceNumber = isNumber(value) && configuration['parse-numbers'] && ( + Number.isSafeInteger(Math.floor(parseFloat(`${value}`))) + ) + if (shouldCoerceNumber || (!isUndefined(value) && checkAllAliases(key, flags.numbers))) value = Number(value) + } + return value + } + + // set args from config.json file, this should be + // applied last so that defaults can be applied. + function setConfig (argv: Arguments): void { + const configLookup = Object.create(null) + + // expand defaults/aliases, in-case any happen to reference + // the config.json file. + applyDefaultsAndAliases(configLookup, flags.aliases, defaults) + + Object.keys(flags.configs).forEach(function (configKey) { + const configPath = argv[configKey] || configLookup[configKey] + if (configPath) { + try { + let config = null + const resolvedConfigPath = mixin.resolve(mixin.cwd(), configPath) + const resolveConfig = flags.configs[configKey] + + if (typeof resolveConfig === 'function') { + try { + config = resolveConfig(resolvedConfigPath) + } catch (e) { + config = e + } + if (config instanceof Error) { + error = config + return + } + } else { + config = mixin.require(resolvedConfigPath) + } + + setConfigObject(config) + } catch (ex) { + if (argv[configKey]) error = Error(__('Invalid JSON config file: %s', configPath)) + } + } + }) + } + + // set args from config object. + // it recursively checks nested objects. + function setConfigObject (config: { [key: string]: any }, prev?: string): void { + Object.keys(config).forEach(function (key) { + const value = config[key] + const fullKey = prev ? prev + '.' + key : key + + // if the value is an inner object and we have dot-notation + // enabled, treat inner objects in config the same as + // heavily nested dot notations (foo.bar.apple). + if (typeof value === 'object' && value !== null && !Array.isArray(value) && configuration['dot-notation']) { + // if the value is an object but not an array, check nested object + setConfigObject(value, fullKey) + } else { + // setting arguments via CLI takes precedence over + // values within the config file. + if (!hasKey(argv, fullKey.split('.')) || (checkAllAliases(fullKey, flags.arrays) && configuration['combine-arrays'])) { + setArg(fullKey, value) + } + } + }) + } + + // set all config objects passed in opts + function setConfigObjects (): void { + if (typeof configObjects !== 'undefined') { + configObjects.forEach(function (configObject) { + setConfigObject(configObject) + }) + } + } + + function applyEnvVars (argv: Arguments, configOnly: boolean): void { + if (typeof envPrefix === 'undefined') return + + const prefix = typeof envPrefix === 'string' ? envPrefix : '' + const env = mixin.env() + Object.keys(env).forEach(function (envVar) { + if (prefix === '' || envVar.lastIndexOf(prefix, 0) === 0) { + // get array of nested keys and convert them to camel case + const keys = envVar.split('__').map(function (key, i) { + if (i === 0) { + key = key.substring(prefix.length) + } + return camelCase(key) + }) + + if (((configOnly && flags.configs[keys.join('.')]) || !configOnly) && !hasKey(argv, keys)) { + setArg(keys.join('.'), env[envVar]) + } + } + }) + } + + function applyCoercions (argv: Arguments): void { + let coerce: false | CoerceCallback + const applied: Set = new Set() + Object.keys(argv).forEach(function (key) { + if (!applied.has(key)) { // If we haven't already coerced this option via one of its aliases + coerce = checkAllAliases(key, flags.coercions) + if (typeof coerce === 'function') { + try { + const value = maybeCoerceNumber(key, coerce(argv[key])) + ;(([] as string[]).concat(flags.aliases[key] || [], key)).forEach(ali => { + applied.add(ali) + argv[ali] = value + }) + } catch (err) { + error = err + } + } + } + }) + } + + function setPlaceholderKeys (argv: Arguments): Arguments { + flags.keys.forEach((key) => { + // don't set placeholder keys for dot notation options 'foo.bar'. + if (~key.indexOf('.')) return + if (typeof argv[key] === 'undefined') argv[key] = undefined + }) + return argv + } + + function applyDefaultsAndAliases (obj: { [key: string]: any }, aliases: { [key: string]: string[] }, defaults: { [key: string]: any }, canLog: boolean = false): void { + Object.keys(defaults).forEach(function (key) { + if (!hasKey(obj, key.split('.'))) { + setKey(obj, key.split('.'), defaults[key]) + if (canLog) defaulted[key] = true + + ;(aliases[key] || []).forEach(function (x) { + if (hasKey(obj, x.split('.'))) return + setKey(obj, x.split('.'), defaults[key]) + }) + } + }) + } + + function hasKey (obj: { [key: string]: any }, keys: string[]): boolean { + let o = obj + + if (!configuration['dot-notation']) keys = [keys.join('.')] + + keys.slice(0, -1).forEach(function (key) { + o = (o[key] || {}) + }) + + const key = keys[keys.length - 1] + + if (typeof o !== 'object') return false + else return key in o + } + + function setKey (obj: { [key: string]: any }, keys: string[], value: any): void { + let o = obj + + if (!configuration['dot-notation']) keys = [keys.join('.')] + + keys.slice(0, -1).forEach(function (key) { + // TODO(bcoe): in the next major version of yargs, switch to + // Object.create(null) for dot notation: + key = sanitizeKey(key) + + if (typeof o === 'object' && o[key] === undefined) { + o[key] = {} + } + + if (typeof o[key] !== 'object' || Array.isArray(o[key])) { + // ensure that o[key] is an array, and that the last item is an empty object. + if (Array.isArray(o[key])) { + o[key].push({}) + } else { + o[key] = [o[key], {}] + } + + // we want to update the empty object at the end of the o[key] array, so set o to that object + o = o[key][o[key].length - 1] + } else { + o = o[key] + } + }) + + // TODO(bcoe): in the next major version of yargs, switch to + // Object.create(null) for dot notation: + const key = sanitizeKey(keys[keys.length - 1]) + + const isTypeArray = checkAllAliases(keys.join('.'), flags.arrays) + const isValueArray = Array.isArray(value) + let duplicate = configuration['duplicate-arguments-array'] + + // nargs has higher priority than duplicate + if (!duplicate && checkAllAliases(key, flags.nargs)) { + duplicate = true + if ((!isUndefined(o[key]) && flags.nargs[key] === 1) || (Array.isArray(o[key]) && o[key].length === flags.nargs[key])) { + o[key] = undefined + } + } + + if (value === increment()) { + o[key] = increment(o[key]) + } else if (Array.isArray(o[key])) { + if (duplicate && isTypeArray && isValueArray) { + o[key] = configuration['flatten-duplicate-arrays'] ? o[key].concat(value) : (Array.isArray(o[key][0]) ? o[key] : [o[key]]).concat([value]) + } else if (!duplicate && Boolean(isTypeArray) === Boolean(isValueArray)) { + o[key] = value + } else { + o[key] = o[key].concat([value]) + } + } else if (o[key] === undefined && isTypeArray) { + o[key] = isValueArray ? value : [value] + } else if (duplicate && !( + o[key] === undefined || + checkAllAliases(key, flags.counts) || + checkAllAliases(key, flags.bools) + )) { + o[key] = [o[key], value] + } else { + o[key] = value + } + } + + // extend the aliases list with inferred aliases. + function extendAliases (...args: Array<{ [key: string]: any } | undefined>) { + args.forEach(function (obj) { + Object.keys(obj || {}).forEach(function (key) { + // short-circuit if we've already added a key + // to the aliases array, for example it might + // exist in both 'opts.default' and 'opts.key'. + if (flags.aliases[key]) return + + flags.aliases[key] = ([] as string[]).concat(aliases[key] || []) + // For "--option-name", also set argv.optionName + flags.aliases[key].concat(key).forEach(function (x) { + if (/-/.test(x) && configuration['camel-case-expansion']) { + const c = camelCase(x) + if (c !== key && flags.aliases[key].indexOf(c) === -1) { + flags.aliases[key].push(c) + newAliases[c] = true + } + } + }) + // For "--optionName", also set argv['option-name'] + flags.aliases[key].concat(key).forEach(function (x) { + if (x.length > 1 && /[A-Z]/.test(x) && configuration['camel-case-expansion']) { + const c = decamelize(x, '-') + if (c !== key && flags.aliases[key].indexOf(c) === -1) { + flags.aliases[key].push(c) + newAliases[c] = true + } + } + }) + flags.aliases[key].forEach(function (x) { + flags.aliases[x] = [key].concat(flags.aliases[key].filter(function (y) { + return x !== y + })) + }) + }) + }) + } + + // return the 1st set flag for any of a key's aliases (or false if no flag set) + function checkAllAliases (key: string, flag: StringFlag): ValueOf | false + function checkAllAliases (key: string, flag: BooleanFlag): ValueOf | false + function checkAllAliases (key: string, flag: NumberFlag): ValueOf | false + function checkAllAliases (key: string, flag: ConfigsFlag): ValueOf | false + function checkAllAliases (key: string, flag: CoercionsFlag): ValueOf | false + function checkAllAliases (key: string, flag: Flag): ValueOf | false { + const toCheck = ([] as string[]).concat(flags.aliases[key] || [], key) + const keys = Object.keys(flag) + const setAlias = toCheck.find(key => keys.includes(key)) + return setAlias ? flag[setAlias] : false + } + + function hasAnyFlag (key: string): boolean { + const flagsKeys = Object.keys(flags) as FlagsKey[] + const toCheck = ([] as Array<{ [key: string]: any } | string[]>).concat(flagsKeys.map(k => flags[k])) + return toCheck.some(function (flag) { + return Array.isArray(flag) ? flag.includes(key) : flag[key] + }) + } + + function hasFlagsMatching (arg: string, ...patterns: RegExp[]): boolean { + const toCheck = ([] as RegExp[]).concat(...patterns) + return toCheck.some(function (pattern) { + const match = arg.match(pattern) + return match && hasAnyFlag(match[1]) + }) + } + + // based on a simplified version of the short flag group parsing logic + function hasAllShortFlags (arg: string): boolean { + // if this is a negative number, or doesn't start with a single hyphen, it's not a short flag group + if (arg.match(negative) || !arg.match(/^-[^-]+/)) { return false } + let hasAllFlags = true + let next: string + const letters = arg.slice(1).split('') + for (let j = 0; j < letters.length; j++) { + next = arg.slice(j + 2) + + if (!hasAnyFlag(letters[j])) { + hasAllFlags = false + break + } + + if ((letters[j + 1] && letters[j + 1] === '=') || + next === '-' || + (/[A-Za-z]/.test(letters[j]) && /^-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) || + (letters[j + 1] && letters[j + 1].match(/\W/))) { + break + } + } + return hasAllFlags + } + + function isUnknownOptionAsArg (arg: string): boolean { + return configuration['unknown-options-as-args'] && isUnknownOption(arg) + } + + function isUnknownOption (arg: string): boolean { + // ignore negative numbers + if (arg.match(negative)) { return false } + // if this is a short option group and all of them are configured, it isn't unknown + if (hasAllShortFlags(arg)) { return false } + // e.g. '--count=2' + const flagWithEquals = /^-+([^=]+?)=[\s\S]*$/ + // e.g. '-a' or '--arg' + const normalFlag = /^-+([^=]+?)$/ + // e.g. '-a-' + const flagEndingInHyphen = /^-+([^=]+?)-$/ + // e.g. '-abc123' + const flagEndingInDigits = /^-+([^=]+?\d+)$/ + // e.g. '-a/usr/local' + const flagEndingInNonWordCharacters = /^-+([^=]+?)\W+.*$/ + // check the different types of flag styles, including negatedBoolean, a pattern defined near the start of the parse method + return !hasFlagsMatching(arg, flagWithEquals, negatedBoolean, normalFlag, flagEndingInHyphen, flagEndingInDigits, flagEndingInNonWordCharacters) + } + + // make a best effort to pick a default value + // for an option based on name and type. + function defaultValue (key: string) { + if (!checkAllAliases(key, flags.bools) && + !checkAllAliases(key, flags.counts) && + `${key}` in defaults) { + return defaults[key] + } else { + return defaultForType(guessType(key)) + } + } + + // return a default value, given the type of a flag., + function defaultForType (type: K): DefaultValuesForType[K] { + const def: DefaultValuesForType = { + boolean: true, + string: '', + number: undefined, + array: [] + } + + return def[type] + } + + // given a flag, enforce a default type. + function guessType (key: string): DefaultValuesForTypeKey { + let type: DefaultValuesForTypeKey = 'boolean' + if (checkAllAliases(key, flags.strings)) type = 'string' + else if (checkAllAliases(key, flags.numbers)) type = 'number' + else if (checkAllAliases(key, flags.bools)) type = 'boolean' + else if (checkAllAliases(key, flags.arrays)) type = 'array' + return type + } + + function isNumber (x: null | undefined | number | string): boolean { + if (x === null || x === undefined) return false + // if loaded from config, may already be a number. + if (typeof x === 'number') return true + // hexadecimal. + if (/^0x[0-9a-f]+$/i.test(x)) return true + // don't treat 0123 as a number; as it drops the leading '0'. + if (x.length > 1 && x[0] === '0') return false + return /^[-]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x) + } + + function isUndefined (num: any): num is undefined { + return num === undefined + } + + // check user configuration settings for inconsistencies + function checkConfiguration (): void { + // count keys should not be set as array/narg + Object.keys(flags.counts).find(key => { + if (checkAllAliases(key, flags.arrays)) { + error = Error(__('Invalid configuration: %s, opts.count excludes opts.array.', key)) + return true + } else if (checkAllAliases(key, flags.nargs)) { + error = Error(__('Invalid configuration: %s, opts.count excludes opts.narg.', key)) + return true + } + return false + }) + } + + return { + argv: Object.assign(argvReturn, argv), + error: error, + aliases: Object.assign({}, flags.aliases), + newAliases: Object.assign({}, newAliases), + defaulted: Object.assign({}, defaulted), + configuration: configuration + } + } +} + +// if any aliases reference each other, we should +// merge them together. +function combineAliases (aliases: Dictionary): Dictionary { + const aliasArrays: Array = [] + const combined: Dictionary = Object.create(null) + let change = true + + // turn alias lookup hash {key: ['alias1', 'alias2']} into + // a simple array ['key', 'alias1', 'alias2'] + Object.keys(aliases).forEach(function (key) { + aliasArrays.push( + ([] as string[]).concat(aliases[key], key) + ) + }) + + // combine arrays until zero changes are + // made in an iteration. + while (change) { + change = false + for (let i = 0; i < aliasArrays.length; i++) { + for (let ii = i + 1; ii < aliasArrays.length; ii++) { + const intersect = aliasArrays[i].filter(function (v) { + return aliasArrays[ii].indexOf(v) !== -1 + }) + + if (intersect.length) { + aliasArrays[i] = aliasArrays[i].concat(aliasArrays[ii]) + aliasArrays.splice(ii, 1) + change = true + break + } + } + } + } + + // map arrays back to the hash-lookup (de-dupe while + // we're at it). + aliasArrays.forEach(function (aliasArray) { + aliasArray = aliasArray.filter(function (v, i, self) { + return self.indexOf(v) === i + }) + const lastAlias = aliasArray.pop() + if (lastAlias !== undefined && typeof lastAlias === 'string') { + combined[lastAlias] = aliasArray + } + }) + + return combined +} + +// this function should only be called when a count is given as an arg +// it is NOT called to set a default value +// thus we can start the count at 1 instead of 0 +function increment (orig?: number | undefined): number { + return orig !== undefined ? orig + 1 : 1 +} + +// TODO(bcoe): in the next major version of yargs, switch to +// Object.create(null) for dot notation: +function sanitizeKey (key: string): string { + if (key === '__proto__') return '___proto___' + return key +} diff --git a/package.json b/package.json index 7dd03ccc..96a25a60 100644 --- a/package.json +++ b/package.json @@ -2,15 +2,29 @@ "name": "yargs-parser", "version": "18.1.3", "description": "the mighty option parser used by yargs", - "main": "build/index.js", + "main": "build/index.cjs", + "exports": { + "import": "./build/lib/index.js", + "require": "./build/index.cjs" + }, + "type": "module", + "module": "./build/lib/index.js", + "types": "./build/index.cjs.d.ts", "scripts": { - "fix": "standardx --fix ./*.ts && standardx --fix **/*.ts", - "pretest": "npm run compile -- -p tsconfig.test.json", - "test": "c8 --reporter=text --reporter=html mocha test/*.js", + "check": "standardx '**/*.ts' && standardx '**/*.js' && standardx '**/*.cjs'", + "fix": "standardx --fix '**/*.ts' && standardx --fix '**/*.js' && standardx --fix '**/*.cjs'", + "pretest": "rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs", + "test": "c8 --reporter=text --reporter=html mocha test/*.cjs", + "test:browser": "start-server-and-test 'serve ./ -p 8080' http://127.0.0.1:8080/package.json 'node ./test/browser/yargs-test.cjs'", + "pretest:typescript": "npm run pretest", + "test:typescript": "c8 mocha ./build/test/typescript/*.js", "posttest": "npm run check", "coverage": "c8 report --check-coverage", - "check": "standardx ./*.ts && standardx **/*.ts", - "compile": "rimraf build && tsc", + "precompile": "rimraf build", + "compile": "tsc", + "postcompile": "npm run build:cjs", + "build:cjs": "rollup -c", + "postbuild:cjs": "node scripts/replace-legacy-export.cjs", "prepare": "npm run compile" }, "repository": { @@ -36,33 +50,33 @@ "@types/node": "^10.0.3", "@typescript-eslint/eslint-plugin": "^3.0.0", "@typescript-eslint/parser": "^3.0.0", - "c8": "^7.1.2", + "@wessberg/rollup-plugin-ts": "^1.2.28", + "c8": "^7.3.0", "chai": "^4.2.0", + "cross-env": "^7.0.2", "eslint": "^7.0.0", "eslint-plugin-import": "^2.20.1", "eslint-plugin-node": "^11.0.0", "gts": "^2.0.0-alpha.4", "mocha": "^8.0.0", + "puppeteer": "^5.2.1", "rimraf": "^3.0.2", + "rollup": "^2.22.1", + "serve": "^11.3.2", "standardx": "^5.0.0", + "start-server-and-test": "^1.11.2", "typescript": "^3.7.0" }, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0" - }, "files": [ - "index.js", - "build", - "lib/**/*.js" + "browser.js", + "build" ], "engines": { "node": ">=10" }, "standardx": { "ignore": [ - "build", - "example.js" + "build" ] } } diff --git a/renovate.json b/renovate.json index 5ec138e4..f6dd45e1 100644 --- a/renovate.json +++ b/renovate.json @@ -4,6 +4,5 @@ ], "pinVersions": false, "rebaseStalePrs": true, - "gitAuthor": null, - "ignoreDeps": ["decamelize"] + "gitAuthor": null } diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 00000000..935ebda1 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,17 @@ +import ts from '@wessberg/rollup-plugin-ts' + +const output = { + format: 'cjs', + file: './build/index.cjs', + exports: 'default' +} + +if (process.env.NODE_ENV === 'test') output.sourcemap = true + +export default { + input: './lib/index.ts', + output, + plugins: [ + ts({ /* options */ }) + ] +} diff --git a/scripts/replace-legacy-export.cjs b/scripts/replace-legacy-export.cjs new file mode 100755 index 00000000..c4af84bd --- /dev/null +++ b/scripts/replace-legacy-export.cjs @@ -0,0 +1,11 @@ +#!/usr/bin/env node +'use strict' + +const { readFileSync, writeFileSync } = require('fs') + +// Cleanup the export statement in CJS typings file generated by rollup: +const legacyTypings = require('path').resolve(__dirname, '../build/index.cjs.d.ts') +const contents = readFileSync(legacyTypings, 'utf8').replace( + 'export { yargsParser as default };', 'export = yargsParser;' +) +writeFileSync(legacyTypings, contents, 'utf8') diff --git a/test/browser/yargs-test.cjs b/test/browser/yargs-test.cjs new file mode 100644 index 00000000..8be641d8 --- /dev/null +++ b/test/browser/yargs-test.cjs @@ -0,0 +1,55 @@ +const { deepStrictEqual } = require('assert') +const puppeteer = require('puppeteer') + +// Runs a browser window with a given argv string and options: +let browser +async function parse (argv, opts) { + if (!browser) { + browser = await puppeteer.launch() + } + const page = await browser.newPage() + opts = encodeURIComponent(JSON.stringify(opts)) + await page.goto(`http://127.0.0.1:8080/test/browser/yargs-test?argv=${encodeURIComponent(argv)}&opts=${opts}`) + const element = await page.$('#output') + return JSON.parse(await page.evaluate(element => element.textContent, element)) +} + +// The actual tests: +async function tests () { + { + const output = await parse('--hello world --x 102') + deepStrictEqual(output, { + _: [], + hello: 'world', + x: 102 + }) + console.info('✅ parse simple string') + } + + { + const output = await parse('--hello world --x 102', { + alias: { + hello: ['goodbye'], + x: ['example'] + } + }) + deepStrictEqual(output, { + _: [], + hello: 'world', + x: 102, + example: 102, + goodbye: 'world' + }) + console.info('✅ parse with aliases') + } +} + +tests().then(() => { + console.info('👌all tests finished') + browser.close() +}).catch((err) => { + console.error(err.stack) + console.error('❌some tests failed') + process.exitCode = 1 + browser.close() +}) diff --git a/test/browser/yargs-test.html b/test/browser/yargs-test.html new file mode 100644 index 00000000..71a7ff98 --- /dev/null +++ b/test/browser/yargs-test.html @@ -0,0 +1,21 @@ + + +
+ + diff --git a/test/deno/yargs-test.ts b/test/deno/yargs-test.ts new file mode 100644 index 00000000..b766aa52 --- /dev/null +++ b/test/deno/yargs-test.ts @@ -0,0 +1,46 @@ +/* global Deno */ + +import { + assertEquals +} from 'https://deno.land/std/testing/asserts.ts' +import parser from '../../deno.ts' + +Deno.test('parse string', () => { + const parsed = parser('--foo --bar 99') + assertEquals(parsed.foo, true) + assertEquals(parsed.bar, 99) +}) + +Deno.test('parse array', () => { + const parsed = parser(['--foo', '--bar', '99']) + assertEquals(parsed.foo, true) + assertEquals(parsed.bar, 99) +}) + +Deno.test('aliases', () => { + const parsed = parser(['--bar', '99'], { + alias: { + bar: ['foo'], + foo: ['f'] + } + }) + assertEquals(parsed.bar, 99) + assertEquals(parsed.foo, 99) + assertEquals(parsed.f, 99) +}) + +const jsonPath = './test/fixtures/config.json' +Deno.test('should load options and values from default config if specified', () => { + var argv = parser(['--foo', 'bar'], { + alias: { + z: 'zoom' + }, + default: { + settings: jsonPath + }, + config: 'settings' + }) + assertEquals(argv.herp, 'derp') + assertEquals(argv.zoom, 55) + assertEquals(argv.zoom, 55) +}) diff --git a/test/fixtures/settings.js b/test/fixtures/settings.cjs similarity index 100% rename from test/fixtures/settings.js rename to test/fixtures/settings.cjs diff --git a/test/tokenize-arg-string.ts b/test/typescript/tokenize-arg-string.ts similarity index 92% rename from test/tokenize-arg-string.ts rename to test/typescript/tokenize-arg-string.ts index 7621d45c..052aa939 100644 --- a/test/tokenize-arg-string.ts +++ b/test/typescript/tokenize-arg-string.ts @@ -1,28 +1,26 @@ /* global describe, it */ -import { expect, should } from 'chai' -import { tokenizeArgString } from '../lib/tokenize-arg-string' - -should() +import { strictEqual } from 'assert' +import { tokenizeArgString } from '../../lib/tokenize-arg-string.js' describe('TokenizeArgString', function () { it('handles unquoted string', function () { const args = tokenizeArgString('--foo 99') - args[0].should.equal('--foo') - args[1].should.equal('99') + strictEqual(args[0], '--foo') + strictEqual(args[1], '99') }) it('handles unquoted numbers', function () { const args = tokenizeArgString(['--foo', 9]) - args[0].should.equal('--foo') - args[1].should.equal('9') + strictEqual(args[0], '--foo') + strictEqual(args[1], '9') }) it('handles quoted string with no spaces', function () { const args = tokenizeArgString("--foo 'hello'") - args[0].should.equal('--foo') - args[1].should.equal("'hello'") + strictEqual(args[0], '--foo') + strictEqual(args[1], "'hello'") }) - +/* it('handles single quoted string with spaces', function () { const args = tokenizeArgString("--foo 'hello world' --bar='foo bar'") args[0].should.equal('--foo') @@ -129,5 +127,5 @@ describe('TokenizeArgString', function () { const args = tokenizeArgString(['--foo', '-bar']) expect(args[0]).to.equal('--foo') expect(args[1]).to.equal('-bar') - }) + }) */ }) diff --git a/test/types.ts b/test/typescript/types.ts similarity index 85% rename from test/types.ts rename to test/typescript/types.ts index e29ff542..39631057 100644 --- a/test/types.ts +++ b/test/typescript/types.ts @@ -1,6 +1,6 @@ /* global describe, it */ -import * as yargsParser from '../' +import yargsParser from '../../lib/index.js' import * as assert from 'assert' describe('types', function () { diff --git a/test/yargs-parser.js b/test/yargs-parser.cjs similarity index 99% rename from test/yargs-parser.js rename to test/yargs-parser.cjs index 0705a389..24935a26 100644 --- a/test/yargs-parser.js +++ b/test/yargs-parser.cjs @@ -4,7 +4,7 @@ require('chai').should() const { expect } = require('chai') const fs = require('fs') -const parser = require('../') +const parser = require('../build/index.cjs') const path = require('path') describe('yargs-parser', function () { @@ -598,7 +598,7 @@ describe('yargs-parser', function () { }) it('should load options and values from a JS file when config has .js extention', function () { - var jsPath = path.resolve(__dirname, './fixtures/settings.js') + var jsPath = path.resolve(__dirname, './fixtures/settings.cjs') var argv = parser(['--settings', jsPath, '--foo', 'bar'], { config: ['settings'] }) @@ -2096,7 +2096,6 @@ describe('yargs-parser', function () { var result = parser([], { envPrefix: '' }) - result.oneFish.should.equal('twofish') result.redFish.should.equal('bluefish') }) diff --git a/tsconfig.json b/tsconfig.json index 8e20a300..9cf3eed3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,10 +3,12 @@ "compilerOptions": { "outDir": "build", "rootDir": ".", - "sourceMap": false - }, + "sourceMap": false, + "target": "es2017", + "moduleResolution": "node", + "module": "es2015" + }, "include": [ - "./*.ts", "lib/**/*.ts" ] -} +} \ No newline at end of file diff --git a/tsconfig.test.json b/tsconfig.test.json index 62fec111..0a884940 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -4,8 +4,6 @@ "sourceMap": true }, "include": [ - "./*.ts", - "lib/**/*.ts", - "test/**/*.ts" + "test/typescript/*.ts" ] -} +} \ No newline at end of file From 99cdf5db942645896cba7a066158658043427c5b Mon Sep 17 00:00:00 2001 From: bcoe Date: Tue, 4 Aug 2020 20:54:00 -0700 Subject: [PATCH 111/206] build: automate tagging process for deno --- .github/workflows/release-please.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 86f5a9db..ce89cea9 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -8,13 +8,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: bcoe/release-please-action@v1.6.3 + id: release with: token: ${{ secrets.GITHUB_TOKEN }} release-type: node package-name: yargs-parser - release-deno: - runs-on: ubuntu-latest - steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: @@ -30,5 +28,7 @@ jobs: git remote add gh-token "https://$%7B%7B%20secrets.GITHUB_TOKEN%7D%7D@github.com/yargs/yargs-parser.git" git checkout -b deno git add -f build - git commit -a -m 'build: deploy latest Deno build' + git commit -a -m 'chore: ${{ steps.release.outputs.tag_name }} release' git push origin +deno + git tag -a ${{ steps.release.outputs.tag_name }}-deno -m 'chore: ${{ steps.release.outputs.tag_name }} release' + if: ${{ steps.release.outputs.release_created }} From f600082c959e092076caf420bbbc9d7a231e2418 Mon Sep 17 00:00:00 2001 From: QmarkC Date: Tue, 4 Aug 2020 23:04:14 -0500 Subject: [PATCH 112/206] fix: boolean numeric short option (#294) --- lib/yargs-parser.ts | 3 ++- test/yargs-parser.cjs | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/yargs-parser.ts b/lib/yargs-parser.ts index 47ad99dd..90078178 100644 --- a/lib/yargs-parser.ts +++ b/lib/yargs-parser.ts @@ -325,7 +325,8 @@ export class YargsParser { // current letter is an alphabetic character and next value is a number if (/[A-Za-z]/.test(letters[j]) && - /^-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) { + /^-?\d+(\.\d*)?(e-?\d+)?$/.test(next) && + checkAllAliases(next, flags.bools) === false) { setArg(letters[j], next) broken = true break diff --git a/test/yargs-parser.cjs b/test/yargs-parser.cjs index 24935a26..159e1dc7 100644 --- a/test/yargs-parser.cjs +++ b/test/yargs-parser.cjs @@ -1128,6 +1128,26 @@ describe('yargs-parser', function () { result.should.have.property('b').that.is.a('boolean').and.is.true // eslint-disable-line result.should.have.property('_').and.deep.equal([123]) }) + + // Fixes: https://github.com/yargs/yargs-parser/issues/283 + it('should set boolean numeric option, with numeric option at the end of a group', function () { + const result = parser(['-x1'], { boolean: ['x', '1'] }) + expect(result).to.have.property('x', true) + expect(result).to.have.property('1', true) + }) + + it('should set boolean numeric option, with numeric option at the start of a group', function () { + const result = parser(['-1x'], { boolean: ['x', '1'] }) + expect(result).to.have.property('x', true) + expect(result).to.have.property('1', true) + }) + + it('should set boolean numeric option, with numeric option as part of a group', function () { + const result = parser(['-x1b'], { boolean: ['x', '1', 'b'] }) + expect(result).to.have.property('x', true) + expect(result).to.have.property('1', true) + expect(result).to.have.property('b', true) + }) }) describe('defaults', function () { From 7cc4a91fdcc976ee226c9d0d9cf677c7d501eae2 Mon Sep 17 00:00:00 2001 From: bcoe Date: Tue, 4 Aug 2020 21:14:29 -0700 Subject: [PATCH 113/206] chore: fix spacing on conditional --- .github/workflows/release-please.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index ce89cea9..d2339535 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -31,4 +31,4 @@ jobs: git commit -a -m 'chore: ${{ steps.release.outputs.tag_name }} release' git push origin +deno git tag -a ${{ steps.release.outputs.tag_name }}-deno -m 'chore: ${{ steps.release.outputs.tag_name }} release' - if: ${{ steps.release.outputs.release_created }} + if: ${{ steps.release.outputs.release_created }} From fd91dde2dc5f86c1db29dcf05a224e1746803e73 Mon Sep 17 00:00:00 2001 From: bcoe Date: Tue, 4 Aug 2020 21:18:26 -0700 Subject: [PATCH 114/206] docs: reflect next release in docs --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c3c346ee..3db70b39 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ parse(['-f', 11, '--zoom', 55].map(String)) // <-- array of strings As of `v19` `yargs-parser` supports [Deno](https://github.com/denoland/deno): ```typescript -import parser from "https://github.com/yargs/yargs-parser/raw/deno/deno.ts"; +import parser from "https://deno.land/x/yargs_parser/deno.ts"; const argv = parser('--foo=99 --bar=9987930', { string: ['bar'] @@ -80,7 +80,7 @@ console.log(argv)