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/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 00000000..9af2578d
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,91 @@
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ types: [ assigned, opened, synchronize, reopened, labeled ]
+name: ci
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ node: [10, 12, 14]
+ steps:
+ - uses: actions/checkout@v1
+ - uses: actions/setup-node@v1
+ with:
+ node-version: ${{ matrix.node }}
+ - run: node --version
+ - run: npm install
+ env:
+ PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true
+ - run: npm test
+ - run: npm run check
+ windows:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v1
+ 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@v2
+ - uses: actions/setup-node@v1
+ with:
+ 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
+ optimize:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v1
+ with:
+ node-version: 14
+ - run: cd test/tscc && npm install && npx @tscc/tscc
+ - run: cd test/tscc && node out.js
diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
new file mode 100644
index 00000000..7ab9a096
--- /dev/null
+++ b/.github/workflows/release-please.yml
@@ -0,0 +1,46 @@
+on:
+ push:
+ branches:
+ - main
+name: release-please
+jobs:
+ release-please:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: google-github-actions/release-please-action@v2
+ id: release
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ command: manifest
+ default-branch: main
+ - 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://${{ secrets.GITHUB_TOKEN}}@github.com/yargs/yargs-parser.git"
+ git checkout -b deno
+ git add -f 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'
+ git push origin ${{ steps.release.outputs.tag_name }}-deno
+ if: ${{ steps.release.outputs.releases_created }}
+ - uses: actions/setup-node@v1
+ with:
+ node-version: 14
+ registry-url: 'https://external-dot-oss-automation.appspot.com/'
+ if: ${{ steps.release.outputs.releases_created }}
+ - run: npm install
+ if: ${{ steps.release.outputs.releases_created }}
+ - run: npm publish
+ env:
+ NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
+ if: ${{ steps.release.outputs.releases_created }}
diff --git a/.gitignore b/.gitignore
index 24546300..245f7bd3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,9 @@
+.idea
.nyc_output
node_modules
.DS_Store
+package-lock.json
./test/fixtures/package.json
+coverage
+build
+example.*
diff --git a/.nycrc b/.nycrc
new file mode 100644
index 00000000..3b315160
--- /dev/null
+++ b/.nycrc
@@ -0,0 +1,13 @@
+{
+ "exclude": [
+ "build/test/**",
+ "test/**"
+ ],
+ "reporter": [
+ "html",
+ "text"
+ ],
+ "lines": 99.5,
+ "branches": "98",
+ "statements": "99.5"
+}
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
new file mode 100644
index 00000000..6f365a22
--- /dev/null
+++ b/.release-please-manifest.json
@@ -0,0 +1,3 @@
+{
+ ".": "20.2.9"
+}
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index f3760a19..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-language: node_js
-os:
- - linux
-node_js:
- - "0.10"
- - "0.12"
- - "4.1"
- - "node"
-after_script: npm run coverage
-deploy:
- provider: npm
- email: ben@npmjs.com
- api_key: $NPM_TOKEN
- on:
- tags: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cd060f3c..fa838940 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,169 +1,264 @@
-# 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.
-
-# [5.0.0](https://github.com/yargs/yargs-parser/compare/v4.2.1...v5.0.0) (2017-02-18)
+### [20.2.9](https://www.github.com/yargs/yargs-parser/compare/yargs-parser-v20.2.8...yargs-parser-v20.2.9) (2021-06-20)
### 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))
+* **build:** fixed automated release pipeline ([1fe9135](https://www.github.com/yargs/yargs-parser/commit/1fe9135884790a083615419b2861683e2597dac3))
+### [20.2.8](https://www.github.com/yargs/yargs-parser/compare/yargs-parser-v20.2.7...yargs-parser-v20.2.8) (2021-06-20)
-### BREAKING CHANGES
-* environment variables will now override config files (args, env, config-file, config-object)
+### Bug Fixes
+
+* **deno:** force relese for Deno ([6687c97](https://www.github.com/yargs/yargs-parser/commit/6687c972d0f3ca7865a97908dde3080b05f8b026))
+* **locale:** Turkish camelize and decamelize issues with toLocaleLowerCase/toLocaleUpperCase ([2617303](https://www.github.com/yargs/yargs-parser/commit/261730383e02448562f737b94bbd1f164aed5143))
+* **perf:** address slow parse when using unknown-options-as-args ([#394](https://www.github.com/yargs/yargs-parser/issues/394)) ([441f059](https://www.github.com/yargs/yargs-parser/commit/441f059d585d446551068ad213db79ac91daf83a))
+* **string-utils:** detect [0,1] ranged values as numbers ([#388](https://www.github.com/yargs/yargs-parser/issues/388)) ([efcc32c](https://www.github.com/yargs/yargs-parser/commit/efcc32c2d6b09aba31abfa2db9bd947befe5586b))
+
+### [20.2.7](https://www.github.com/yargs/yargs-parser/compare/v20.2.6...v20.2.7) (2021-03-10)
+
+
+### Bug Fixes
+
+* **deno:** force relese for Deno ([6687c97](https://www.github.com/yargs/yargs-parser/commit/6687c972d0f3ca7865a97908dde3080b05f8b026))
+
+### [20.2.6](https://www.github.com/yargs/yargs-parser/compare/v20.2.5...v20.2.6) (2021-02-22)
+
+
+### Bug Fixes
+
+* **populate--:** -- should always be array ([#354](https://www.github.com/yargs/yargs-parser/issues/354)) ([585ae8f](https://www.github.com/yargs/yargs-parser/commit/585ae8ffad74cc02974f92d788e750137fd65146))
+
+### [20.2.5](https://www.github.com/yargs/yargs-parser/compare/v20.2.4...v20.2.5) (2021-02-13)
+
+
+### Bug Fixes
+* do not lowercase camel cased string ([#348](https://www.github.com/yargs/yargs-parser/issues/348)) ([5f4da1f](https://www.github.com/yargs/yargs-parser/commit/5f4da1f17d9d50542d2aaa206c9806ce3e320335))
+### [20.2.4](https://www.github.com/yargs/yargs-parser/compare/v20.2.3...v20.2.4) (2020-11-09)
-
-## [4.2.1](https://github.com/yargs/yargs-parser/compare/v4.2.0...v4.2.1) (2017-01-02)
+
+### Bug Fixes
+
+* **deno:** address import issues in Deno ([#339](https://www.github.com/yargs/yargs-parser/issues/339)) ([3b54e5e](https://www.github.com/yargs/yargs-parser/commit/3b54e5eef6e9a7b7c6eec7c12bab3ba3b8ba8306))
+
+### [20.2.3](https://www.github.com/yargs/yargs-parser/compare/v20.2.2...v20.2.3) (2020-10-16)
### Bug Fixes
-* flatten/duplicate regression ([#75](https://github.com/yargs/yargs-parser/issues/75)) ([68d68a0](https://github.com/yargs/yargs-parser/commit/68d68a0))
+* **exports:** node 13.0 and 13.1 require the dotted object form _with_ a string fallback ([#336](https://www.github.com/yargs/yargs-parser/issues/336)) ([3ae7242](https://www.github.com/yargs/yargs-parser/commit/3ae7242040ff876d28dabded60ac226e00150c88))
+### [20.2.2](https://www.github.com/yargs/yargs-parser/compare/v20.2.1...v20.2.2) (2020-10-14)
-
-# [4.2.0](https://github.com/yargs/yargs-parser/compare/v4.1.0...v4.2.0) (2016-12-01)
+### Bug Fixes
+
+* **exports:** node 13.0-13.6 require a string fallback ([#333](https://www.github.com/yargs/yargs-parser/issues/333)) ([291aeda](https://www.github.com/yargs/yargs-parser/commit/291aeda06b685b7a015d83bdf2558e180b37388d))
+
+### [20.2.1](https://www.github.com/yargs/yargs-parser/compare/v20.2.0...v20.2.1) (2020-10-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))
+* **deno:** update types for deno ^1.4.0 ([#330](https://www.github.com/yargs/yargs-parser/issues/330)) ([0ab92e5](https://www.github.com/yargs/yargs-parser/commit/0ab92e50b090f11196334c048c9c92cecaddaf56))
+
+## [20.2.0](https://www.github.com/yargs/yargs-parser/compare/v20.1.0...v20.2.0) (2020-09-21)
### 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))
+* **string-utils:** export looksLikeNumber helper ([#324](https://www.github.com/yargs/yargs-parser/issues/324)) ([c8580a2](https://www.github.com/yargs/yargs-parser/commit/c8580a2327b55f6342acecb6e72b62963d506750))
+### Bug Fixes
+
+* **unknown-options-as-args:** convert positionals that look like numbers ([#326](https://www.github.com/yargs/yargs-parser/issues/326)) ([f85ebb4](https://www.github.com/yargs/yargs-parser/commit/f85ebb4face9d4b0f56147659404cbe0002f3dad))
-
-# [4.1.0](https://github.com/yargs/yargs-parser/compare/v4.0.2...v4.1.0) (2016-11-07)
+## [20.1.0](https://www.github.com/yargs/yargs-parser/compare/v20.0.0...v20.1.0) (2020-09-20)
### 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))
+* adds parse-positional-numbers configuration ([#321](https://www.github.com/yargs/yargs-parser/issues/321)) ([9cec00a](https://www.github.com/yargs/yargs-parser/commit/9cec00a622251292ffb7dce6f78f5353afaa0d4c))
+
+
+### Bug Fixes
+
+* **build:** update release-please; make labels kick off builds ([#323](https://www.github.com/yargs/yargs-parser/issues/323)) ([09f448b](https://www.github.com/yargs/yargs-parser/commit/09f448b4cd66e25d2872544718df46dab8af062a))
+## [20.0.0](https://www.github.com/yargs/yargs-parser/compare/v19.0.4...v20.0.0) (2020-09-09)
-
-## [4.0.2](https://github.com/yargs/yargs-parser/compare/v4.0.1...v4.0.2) (2016-09-30)
+### ⚠ BREAKING CHANGES
+* do not ship type definitions (#318)
### Bug Fixes
-* whoops, let's make the assign not change the Object key order ([29d069a](https://github.com/yargs/yargs-parser/commit/29d069a))
+* only strip camel case if hyphenated ([#316](https://www.github.com/yargs/yargs-parser/issues/316)) ([95a9e78](https://www.github.com/yargs/yargs-parser/commit/95a9e785127b9bbf2d1db1f1f808ca1fb100e82a)), closes [#315](https://www.github.com/yargs/yargs-parser/issues/315)
+### Code Refactoring
-
-## [4.0.1](https://github.com/yargs/yargs-parser/compare/v4.0.0...v4.0.1) (2016-09-30)
+* do not ship type definitions ([#318](https://www.github.com/yargs/yargs-parser/issues/318)) ([8fbd56f](https://www.github.com/yargs/yargs-parser/commit/8fbd56f1d0b6c44c30fca62708812151ca0ce330))
+
+### [19.0.4](https://www.github.com/yargs/yargs-parser/compare/v19.0.3...v19.0.4) (2020-08-27)
### Bug Fixes
-* lodash.assign was deprecated ([#59](https://github.com/yargs/yargs-parser/issues/59)) ([5e7eb11](https://github.com/yargs/yargs-parser/commit/5e7eb11))
+* **build:** fixing publication ([#310](https://www.github.com/yargs/yargs-parser/issues/310)) ([5d3c6c2](https://www.github.com/yargs/yargs-parser/commit/5d3c6c29a9126248ba601920d9cf87c78e161ff5))
+
+### [19.0.3](https://www.github.com/yargs/yargs-parser/compare/v19.0.2...v19.0.3) (2020-08-27)
+### Bug Fixes
+
+* **build:** switch to action for publish ([#308](https://www.github.com/yargs/yargs-parser/issues/308)) ([5c2f305](https://www.github.com/yargs/yargs-parser/commit/5c2f30585342bcd8aaf926407c863099d256d174))
-
-# [4.0.0](https://github.com/yargs/yargs-parser/compare/v3.2.0...v4.0.0) (2016-09-26)
+### [19.0.2](https://www.github.com/yargs/yargs-parser/compare/v19.0.1...v19.0.2) (2020-08-27)
### 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))
+* **types:** envPrefix should be optional ([#305](https://www.github.com/yargs/yargs-parser/issues/305)) ([ae3f180](https://www.github.com/yargs/yargs-parser/commit/ae3f180e14df2de2fd962145f4518f9aa0e76523))
+### [19.0.1](https://www.github.com/yargs/yargs-parser/compare/v19.0.0...v19.0.1) (2020-08-09)
-### BREAKING CHANGES
-* coerce is no longer applied to individual arguments in an implicit array.
+### Bug Fixes
+
+* **build:** push tag created for deno ([2186a14](https://www.github.com/yargs/yargs-parser/commit/2186a14989749887d56189867602e39e6679f8b0))
+## [19.0.0](https://www.github.com/yargs/yargs-parser/compare/v18.1.3...v19.0.0) (2020-08-09)
-
-# [3.2.0](https://github.com/yargs/yargs-parser/compare/v3.1.0...v3.2.0) (2016-08-13)
+### ⚠ BREAKING CHANGES
+* adds support for ESM and Deno (#295)
+* **ts:** projects using `@types/yargs-parser` may see variations in type definitions.
+* drops Node 6. begin following Node.js LTS schedule (#278)
### 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))
+* adds support for ESM and Deno ([#295](https://www.github.com/yargs/yargs-parser/issues/295)) ([195bc4a](https://www.github.com/yargs/yargs-parser/commit/195bc4a7f20c2a8f8e33fbb6ba96ef6e9a0120a1))
+* expose camelCase and decamelize helpers ([#296](https://www.github.com/yargs/yargs-parser/issues/296)) ([39154ce](https://www.github.com/yargs/yargs-parser/commit/39154ceb5bdcf76b5f59a9219b34cedb79b67f26))
+* **deps:** update to latest camelcase/decamelize ([#281](https://www.github.com/yargs/yargs-parser/issues/281)) ([8931ab0](https://www.github.com/yargs/yargs-parser/commit/8931ab08f686cc55286f33a95a83537da2be5516))
+
+
+### Bug Fixes
+
+* boolean numeric short option ([#294](https://www.github.com/yargs/yargs-parser/issues/294)) ([f600082](https://www.github.com/yargs/yargs-parser/commit/f600082c959e092076caf420bbbc9d7a231e2418))
+* raise permission error for Deno if config load fails ([#298](https://www.github.com/yargs/yargs-parser/issues/298)) ([1174e2b](https://www.github.com/yargs/yargs-parser/commit/1174e2b3f0c845a1cd64e14ffc3703e730567a84))
+* **deps:** update dependency decamelize to v3 ([#274](https://www.github.com/yargs/yargs-parser/issues/274)) ([4d98698](https://www.github.com/yargs/yargs-parser/commit/4d98698bc6767e84ec54a0842908191739be73b7))
+* **types:** switch back to using Partial types ([#293](https://www.github.com/yargs/yargs-parser/issues/293)) ([bdc80ba](https://www.github.com/yargs/yargs-parser/commit/bdc80ba59fa13bc3025ce0a85e8bad9f9da24ea7))
+
+
+### Build System
+
+* drops Node 6. begin following Node.js LTS schedule ([#278](https://www.github.com/yargs/yargs-parser/issues/278)) ([9014ed7](https://www.github.com/yargs/yargs-parser/commit/9014ed722a32768b96b829e65a31705db5c1458a))
+
+
+### Code Refactoring
+
+* **ts:** move index.js to TypeScript ([#292](https://www.github.com/yargs/yargs-parser/issues/292)) ([f78d2b9](https://www.github.com/yargs/yargs-parser/commit/f78d2b97567ac4828624406e420b4047c710b789))
+
+### [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)
+### 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))
-
-# [3.1.0](https://github.com/yargs/yargs-parser/compare/v3.0.0...v3.1.0) (2016-08-09)
+### [18.1.1](https://www.github.com/yargs/yargs-parser/compare/v18.1.0...v18.1.1) (2020-03-16)
### 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))
+* \_\_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)
### 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))
+* 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)
-
-# [3.0.0](https://github.com/yargs/yargs-parser/compare/v2.4.1...v3.0.0) (2016-08-07)
+### ⚠ BREAKING CHANGES
+* the narg count is now enforced when parsing arrays.
-### Bug Fixes
+### Features
-* 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))
+* 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))
-### BREAKING CHANGES
+## [17.1.0](https://www.github.com/yargs/yargs-parser/compare/v17.0.1...v17.1.0) (2020-03-01)
-* 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._
+### 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))
-
-## [2.4.1](https://github.com/yargs/yargs-parser/compare/v2.4.0...v2.4.1) (2016-07-16)
+### [17.0.1](https://www.github.com/yargs/yargs-parser/compare/v17.0.0...v17.0.1) (2020-02-29)
### 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))
+* 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)
-
-# [2.4.0](https://github.com/yargs/yargs-parser/compare/v2.3.0...v2.4.0) (2016-04-11)
+### ⚠ 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
-* **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))
+* 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
-
-# [2.3.0](https://github.com/yargs/yargs-parser/compare/v2.2.0...v2.3.0) (2016-04-09)
+* 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)
-### 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))
+### ⚠ BREAKING CHANGES
+
+* populate error if incompatible narg/count or array/count options are used (#191)
### 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))
+* 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))
diff --git a/README.md b/README.md
index 6d5916c3..26148407 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,15 @@
# yargs-parser
-[](https://travis-ci.org/yargs/yargs-parser)
-[](https://coveralls.io/r/yargs/yargs-parser?branch=master)
+
[](https://www.npmjs.com/package/yargs-parser)
-[](https://ci.appveyor.com/project/bcoe/yargs-parser)
-[](https://github.com/conventional-changelog/standard-version)
-
+[](https://conventionalcommits.org)
+
The mighty option parser used by [yargs](https://github.com/yargs/yargs).
visit the [yargs website](http://yargs.js.org/) for more examples, and thorough usage instructions.
-
+
## Example
@@ -20,37 +18,81 @@ 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)
```
-```sh
-node example.js --foo=33 --bar hello
+```console
+$ node example.js --foo=33 --bar hello
{ _: [], foo: 33, bar: 'hello' }
```
_or parse a string!_
```js
-var argv = require('./')('--foo=99 --bar=33')
+const argv = require('yargs-parser')('--foo=99 --bar=33')
console.log(argv)
```
-```sh
+```console
{ _: [], foo: 99, bar: 33 }
```
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://deno.land/x/yargs_parser/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={})
+### parser(args, opts={})
Parses command line arguments returning a simple mapping of keys and values.
@@ -59,25 +101,31 @@ 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']}`.
+ * `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:**
* `obj`: 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 `--`.
### require('yargs-parser').detailed(args, opts={})
@@ -94,12 +142,17 @@ 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`.
+
### Configuration
The yargs-parser applies several automated transformations on the keys provided
@@ -121,15 +174,15 @@ var parsed = parser(['--no-dice'], {
Should a group of short-options be treated as boolean flags?
-```sh
-node example.js -abc
+```console
+$ node example.js -abc
{ _: [], a: true, b: true, c: true }
```
_if disabled:_
-```sh
-node example.js -abc
+```console
+$ node example.js -abc
{ _: [], abc: true }
```
@@ -140,15 +193,15 @@ node example.js -abc
Should hyphenated arguments be expanded into camel-case aliases?
-```sh
-node example.js --foo-bar
+```console
+$ node example.js --foo-bar
{ _: [], 'foo-bar': true, fooBar: true }
```
_if disabled:_
-```sh
-node example.js --foo-bar
+```console
+$ node example.js --foo-bar
{ _: [], 'foo-bar': true }
```
@@ -159,15 +212,15 @@ node example.js --foo-bar
Should keys that contain `.` be treated as objects?
-```sh
-node example.js --foo.bar
+```console
+$ node example.js --foo.bar
{ _: [], foo: { bar: true } }
```
_if disabled:_
-```sh
-node example.js --foo.bar
+```console
+$ node example.js --foo.bar
{ _: [], "foo.bar": true }
```
@@ -178,18 +231,37 @@ node example.js --foo.bar
Should keys that look like numbers be treated as such?
-```sh
-node example.js --foo=99.3
+```console
+$ node example.js --foo=99.3
{ _: [], foo: 99.3 }
```
_if disabled:_
-```sh
-node example.js --foo=99.3
+```console
+$ node example.js --foo=99.3
{ _: [], foo: "99.3" }
```
+### parse positional numbers
+
+* default: `true`
+* key: `parse-positional-numbers`
+
+Should positional keys that look like numbers be treated as such.
+
+```console
+$ node example.js 99.3
+{ _: [99.3] }
+```
+
+_if disabled:_
+
+```console
+$ node example.js 99.3
+{ _: ['99.3'] }
+```
+
### boolean negation
* default: `true`
@@ -197,18 +269,26 @@ node example.js --foo=99.3
Should variables prefixed with `--no` be treated as negations?
-```sh
-node example.js --no-foo
+```console
+$ node example.js --no-foo
{ _: [], foo: false }
```
_if disabled:_
-```sh
-node example.js --no-foo
+```console
+$ node example.js --no-foo
{ _: [], "no-foo": true }
```
+### combine arrays
+
+* default: `false`
+* key: `combine-arrays`
+
+Should arrays be combined when provided by both command line arguments and
+a configuration file.
+
### duplicate arguments array
* default: `true`
@@ -216,15 +296,15 @@ node example.js --no-foo
Should arguments be coerced into an array when duplicated:
-```sh
-node example.js -x 1 -x 2
+```console
+$ node example.js -x 1 -x 2
{ _: [], x: [1, 2] }
```
_if disabled:_
-```sh
-node example.js -x 1 -x 2
+```console
+$ node example.js -x 1 -x 2
{ _: [], x: 2 }
```
@@ -235,18 +315,199 @@ node example.js -x 1 -x 2
Should array arguments be coerced into a single array when duplicated:
-```sh
-node example.js -x 1 2 -x 3 4
+```console
+$ node example.js -x 1 2 -x 3 4
{ _: [], x: [1, 2, 3, 4] }
```
_if disabled:_
-```sh
-node example.js -x 1 2 -x 3 4
+```console
+$ 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.
+
+```console
+$ node example --arr 1 2
+{ _: [], arr: [1, 2] }
+```
+
+_if disabled:_
+
+```console
+$ 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`
+* key: `nargs-eats-options`
+
+Should nargs consume dash options as well as positional arguments.
+
+### negation prefix
+
+* default: `no-`
+* key: `negation-prefix`
+
+The prefix to use for negated boolean variables.
+
+```console
+$ node example.js --no-foo
+{ _: [], foo: false }
+```
+
+_if set to `quux`:_
+
+```console
+$ node example.js --quuxfoo
+{ _: [], foo: false }
+```
+
+### populate --
+
+* default: `false`.
+* key: `populate--`
+
+Should unparsed flags be stored in `--` or `_`.
+
+_If disabled:_
+
+```console
+$ node example.js a -b -- x y
+{ _: [ 'a', 'x', 'y' ], b: true }
+```
+
+_If enabled:_
+
+```console
+$ node example.js a -b -- x y
+{ _: [ 'a' ], '--': [ 'x', 'y' ], b: true }
+```
+
+### set placeholder key
+
+* default: `false`.
+* key: `set-placeholder-key`.
+
+Should a placeholder be added for keys not set via the corresponding CLI argument?
+
+_If disabled:_
+
+```console
+$ node example.js -a 1 -c 2
+{ _: [], a: 1, c: 2 }
+```
+
+_If enabled:_
+
+```console
+$ 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 positional argument? This is similar to how e.g. `ssh` parses its command line.
+
+_If disabled:_
+
+```console
+$ node example.js -a run b -x y
+{ _: [ 'b' ], a: 'run', x: 'y' }
+```
+
+_If enabled:_
+
+```console
+$ node example.js -a run b -x y
+{ _: [ 'b', '-x', 'y' ], a: 'run' }
+```
+
+### strip aliased
+
+* default: `false`
+* key: `strip-aliased`
+
+Should aliases be removed before returning results?
+
+_If disabled:_
+
+```console
+$ node example.js --test-field 1
+{ _: [], 'test-field': 1, testField: 1, 'test-alias': 1, testAlias: 1 }
+```
+
+_If enabled:_
+
+```console
+$ 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-expansion` is disabled.
+
+_If disabled:_
+
+```console
+$ node example.js --test-field 1
+{ _: [], 'test-field': 1, testField: 1 }
+```
+
+_If enabled:_
+
+```console
+$ node example.js --test-field 1
+{ _: [], testField: 1 }
+```
+
+### unknown options as args
+
+* default: `false`
+* key: `unknown-options-as-args`
+
+Should unknown options be treated like regular arguments? An unknown option is one that is not
+configured in `opts`.
+
+_If disabled_
+
+```console
+$ node example.js --unknown-option --known-option 2 --string-option --unknown-option2
+{ _: [], unknownOption: true, knownOption: 2, stringOption: '', unknownOption2: true }
+```
+
+_If enabled_
+
+```console
+$ node example.js --unknown-option --known-option 2 --string-option --unknown-option2
+{ _: ['--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/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 2f85f7fe..00000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-environment:
- matrix:
- - nodejs_version: '5'
- - nodejs_version: '4'
- - nodejs_version: '0.12'
-install:
- - ps: Install-Product node $env:nodejs_version
- - set CI=true
- - npm -g install npm@latest
- - 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
diff --git a/browser.js b/browser.js
new file mode 100644
index 00000000..241202c7
--- /dev/null
+++ b/browser.js
@@ -0,0 +1,29 @@
+// 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 { camelCase, decamelize, looksLikeNumber } from './build/lib/string-utils.js'
+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)
+}
+yargsParser.camelCase = camelCase
+yargsParser.decamelize = decamelize
+yargsParser.looksLikeNumber = looksLikeNumber
+
+export default yargsParser
diff --git a/deno.ts b/deno.ts
new file mode 100644
index 00000000..1074dc64
--- /dev/null
+++ b/deno.ts
@@ -0,0 +1,38 @@
+/* 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 { camelCase, decamelize, looksLikeNumber } from './build/lib/string-utils.js'
+import { YargsParser } from './build/lib/yargs-parser.js'
+import type { 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)
+}
+yargsParser.camelCase = camelCase
+yargsParser.decamelize = decamelize
+yargsParser.looksLikeNumber = looksLikeNumber
+
+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 1c1ea630..00000000
--- a/example.js
+++ /dev/null
@@ -1,3 +0,0 @@
-var parser = require('./')
-var parse = parser(['-cats', 'meow'])
-console.log(parse)
diff --git a/index.js b/index.js
deleted file mode 100644
index b71faf58..00000000
--- a/index.js
+++ /dev/null
@@ -1,759 +0,0 @@
-var camelCase = require('camelcase')
-var path = require('path')
-var tokenizeArgString = require('./lib/tokenize-arg-string')
-var util = require('util')
-
-function parse (args, opts) {
- if (!opts) 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({
- 'short-option-groups': true,
- 'camel-case-expansion': true,
- 'dot-notation': true,
- 'parse-numbers': true,
- 'boolean-negation': true,
- 'duplicate-arguments-array': true,
- 'flatten-duplicate-arrays': true
- }, opts.configuration)
- var defaults = opts.default || {}
- var configObjects = opts.configObjects || []
- var envPrefix = opts.envPrefix
- 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 error = null
- var flags = {
- aliases: {},
- arrays: {},
- bools: {},
- strings: {},
- numbers: {},
- counts: {},
- normalize: {},
- configs: {},
- defaulted: {},
- nargs: {},
- coercions: {}
- }
- var negative = /^-[0-9]+(\.[0-9]+)?/
-
- ;[].concat(opts.array).filter(Boolean).forEach(function (key) {
- flags.arrays[key] = true
- })
-
- ;[].concat(opts.boolean).filter(Boolean).forEach(function (key) {
- flags.bools[key] = true
- })
-
- ;[].concat(opts.string).filter(Boolean).forEach(function (key) {
- flags.strings[key] = true
- })
-
- ;[].concat(opts.number).filter(Boolean).forEach(function (key) {
- flags.numbers[key] = true
- })
-
- ;[].concat(opts.count).filter(Boolean).forEach(function (key) {
- flags.counts[key] = true
- })
-
- ;[].concat(opts.normalize).filter(Boolean).forEach(function (key) {
- flags.normalize[key] = true
- })
-
- Object.keys(opts.narg || {}).forEach(function (k) {
- flags.nargs[k] = opts.narg[k]
- })
-
- Object.keys(opts.coerce || {}).forEach(function (k) {
- flags.coercions[k] = opts.coerce[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]
- })
- })
-
- var argv = { _: [] }
-
- Object.keys(flags.bools).forEach(function (key) {
- setArg(key, !(key in defaults) ? false : defaults[key])
- setDefaulted(key)
- })
-
- 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]
- var broken
- var key
- var letters
- var m
- var next
- var value
-
- // -- seperated by =
- 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]*)$/)
-
- // 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) && args.length > i + 1) {
- args.splice(i + 1, 0, m[2])
- i = eatArray(i, m[1], args)
- } else {
- setArg(m[1], m[2])
- }
- } else if (arg.match(/^--no-.+/) && configuration['boolean-negation']) {
- key = arg.match(/^--no-(.+)/)[1]
- setArg(key, false)
-
- // -- seperated by space.
- } else if (arg.match(/^--.+/) || (
- !configuration['short-option-groups'] && arg.match(/^-.+/)
- )) {
- 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) {
- i = eatArray(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, defaultForType(guessType(key, flags)))
- }
- }
-
- // dot-notation flag seperated by '='.
- } else if (arg.match(/^-.\..+=/)) {
- m = arg.match(/^-([^=]+)=([\s\S]*)$/)
- setArg(m[1], m[2])
-
- // dot-notation flag seperated by space.
- } else if (arg.match(/^-.\..+/)) {
- 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, defaultForType(guessType(key, flags)))
- }
- } else if (arg.match(/^-[^-]+/) && !arg.match(negative)) {
- letters = arg.slice(1, -1).split('')
- broken = false
-
- for (var 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]
-
- // 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) && args.length > i + 1) {
- args.splice(i + 1, 0, value)
- i = eatArray(i, key, args)
- } 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], defaultForType(guessType(letters[j], flags)))
- }
- }
-
- key = arg.slice(-1)[0]
-
- if (!broken && key !== '-') {
- // nargs format = '-f a b c'
- if (checkAllAliases(key, flags.nargs)) {
- i = eatNargs(i, key, args)
- // array format = '-f a b c'
- } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) {
- i = eatArray(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, defaultForType(guessType(key, flags)))
- }
- }
- }
- } else {
- argv._.push(
- flags.strings['_'] || !isNumber(arg) ? arg : Number(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)
- applyCoercions(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)
- })
-
- notFlags.forEach(function (key) {
- argv._.push(key)
- })
-
- // how many arguments should we consume, based
- // on the nargs option?
- function eatNargs (i, key, args) {
- var toEat = checkAllAliases(key, flags.nargs)
-
- if (args.length - (i + 1) < toEat) error = Error(__('Not enough arguments following: %s', key))
-
- for (var ii = i + 1; ii < (toEat + i + 1); ii++) {
- setArg(key, args[ii])
- }
-
- return (i + toEat)
- }
-
- // 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) {
- 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
- }
- 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)
- })
- }
-
- return i
- }
-
- function setArg (key, val) {
- unsetDefaulted(key)
-
- if (/-/.test(key) && !(flags.aliases[key] && flags.aliases[key].length) && configuration['camel-case-expansion']) {
- var c = camelCase(key)
- flags.aliases[key] = [c]
- newAliases[c] = true
- }
-
- var value = processValue(key, val)
-
- var 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
- var a = [].concat(splitKey)
- a.shift() // nuke the old key.
- x = x.concat(a)
-
- 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)) {
- var 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
- })
- })
- }
- }
-
- function processValue (key, val) {
- // handle parsing boolean arguments --foo=true --bar false.
- if (checkAllAliases(key, flags.bools) || checkAllAliases(key, flags.counts)) {
- if (typeof val === 'string') val = val === 'true'
- }
-
- var value = val
- if (!checkAllAliases(key, flags.strings) && !checkAllAliases(key, flags.coercions)) {
- if (isNumber(val)) value = Number(val)
- if (!isUndefined(val) && !isNumber(val) && checkAllAliases(key, flags.numbers)) value = NaN
- }
-
- // 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
- }
-
- // set args from config.json file, this should be
- // applied last so that defaults can be applied.
- function setConfig (argv) {
- var configLookup = {}
-
- // 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]
- if (configPath) {
- try {
- var config = null
- var 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) {
- var value = config[key]
- var 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' && !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('.')) || (flags.defaulted[fullKey])) {
- 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 (typeof envPrefix === 'undefined') return
-
- var 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) {
- if (i === 0) {
- key = key.substring(prefix.length)
- }
- return camelCase(key)
- })
-
- if (((configOnly && flags.configs[keys.join('.')]) || !configOnly) && (!hasKey(argv, keys) || flags.defaulted[keys.join('.')])) {
- setArg(keys.join('.'), process.env[envVar])
- }
- }
- })
- }
-
- function applyCoercions (argv) {
- var coerce
- Object.keys(argv).forEach(function (key) {
- coerce = checkAllAliases(key, flags.coercions)
- if (typeof coerce === 'function') {
- try {
- argv[key] = coerce(argv[key])
- } catch (err) {
- error = err
- }
- }
- })
- }
-
- function applyDefaultsAndAliases (obj, aliases, defaults) {
- Object.keys(defaults).forEach(function (key) {
- if (!hasKey(obj, key.split('.'))) {
- setKey(obj, key.split('.'), defaults[key])
-
- ;(aliases[key] || []).forEach(function (x) {
- if (hasKey(obj, x.split('.'))) return
- setKey(obj, x.split('.'), defaults[key])
- })
- }
- })
- }
-
- function hasKey (obj, keys) {
- var o = obj
-
- if (!configuration['dot-notation']) keys = [keys.join('.')]
-
- keys.slice(0, -1).forEach(function (key) {
- o = (o[key] || {})
- })
-
- var key = keys[keys.length - 1]
-
- if (typeof o !== 'object') return false
- else return key in o
- }
-
- function setKey (obj, keys, value) {
- var o = obj
-
- if (!configuration['dot-notation']) keys = [keys.join('.')]
-
- keys.slice(0, -1).forEach(function (key) {
- if (o[key] === undefined) o[key] = {}
- o = o[key]
- })
-
- var key = keys[keys.length - 1]
-
- var isTypeArray = checkAllAliases(key, flags.arrays)
- var isValueArray = Array.isArray(value)
- var duplicate = configuration['duplicate-arguments-array']
-
- 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) : [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.bools) || checkAllAliases(keys.join('.'), flags.bools) || checkAllAliases(key, flags.counts))) {
- o[key] = [ o[key], value ]
- } else {
- o[key] = value
- }
- }
-
- // extend the aliases list with inferred aliases.
- function extendAliases () {
- Array.prototype.slice.call(arguments).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']) {
- var c = camelCase(x)
- 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
- }))
- })
- })
- })
- }
-
- // check if a flag is set for any of a key's aliases.
- function checkAllAliases (key, flag) {
- var isSet = false
- var toCheck = [].concat(flags.aliases[key] || [], key)
-
- toCheck.forEach(function (key) {
- if (flag[key]) isSet = flag[key]
- })
-
- 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]
- })
- }
-
- // 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 = {
- boolean: true,
- string: '',
- number: undefined,
- array: []
- }
-
- return def[type]
- }
-
- // given a flag, enforce a default type.
- function guessType (key, flags) {
- var type = 'boolean'
-
- if (checkAllAliases(key, flags.strings)) type = 'string'
- else if (checkAllAliases(key, flags.numbers)) type = 'number'
- else if (checkAllAliases(key, flags.arrays)) type = 'array'
-
- return type
- }
-
- function isNumber (x) {
- if (!configuration['parse-numbers']) return false
- if (typeof x === 'number') return true
- if (/^0x[0-9a-f]+$/i.test(x)) return true
- return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x)
- }
-
- function isUndefined (num) {
- return num === undefined
- }
-
- return {
- argv: argv,
- error: error,
- aliases: flags.aliases,
- newAliases: newAliases,
- configuration: configuration
- }
-}
-
-// if any aliases reference each other, we should
-// merge them together.
-function combineAliases (aliases) {
- var aliasArrays = []
- var change = true
- var combined = {}
-
- // 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 (var i = 0; i < aliasArrays.length; i++) {
- for (var ii = i + 1; ii < aliasArrays.length; ii++) {
- var 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
-}
-
-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
-function increment (orig) {
- return orig !== undefined ? orig + 1 : 1
-}
-
-function Parser (args, opts) {
- var 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)
-}
-
-module.exports = Parser
diff --git a/lib/index.ts b/lib/index.ts
new file mode 100644
index 00000000..c0bfac81
--- /dev/null
+++ b/lib/index.ts
@@ -0,0 +1,61 @@
+/**
+ * @fileoverview Main entrypoint for libraries using yargs-parser in Node.js
+ * CJS and ESM environments.
+ *
+ * @license
+ * Copyright (c) 2016, Contributors
+ * SPDX-License-Identifier: ISC
+ */
+
+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 { camelCase, decamelize, looksLikeNumber } from './string-utils.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)
+}
+yargsParser.camelCase = camelCase
+yargsParser.decamelize = decamelize
+yargsParser.looksLikeNumber = looksLikeNumber
+export default yargsParser
diff --git a/lib/string-utils.ts b/lib/string-utils.ts
new file mode 100644
index 00000000..5932a4c7
--- /dev/null
+++ b/lib/string-utils.ts
@@ -0,0 +1,63 @@
+/**
+ * @license
+ * Copyright (c) 2016, Contributors
+ * SPDX-License-Identifier: ISC
+ */
+
+export function camelCase (str: string): string {
+ // Handle the case where an argument is provided as camel case, e.g., fooBar.
+ // by ensuring that the string isn't already mixed case:
+ const isCamelCase = str !== str.toLowerCase() && str !== str.toUpperCase()
+
+ if (!isCamelCase) {
+ str = str.toLowerCase()
+ }
+
+ 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.toUpperCase()
+ }
+ if (i !== 0 && (chr === '-' || chr === '_')) {
+ nextChrUpper = true
+ } else if (chr !== '-' && chr !== '_') {
+ camelcase += chr
+ }
+ }
+ return camelcase
+ }
+}
+
+export function decamelize (str: string, joinString?: string): string {
+ const lowercase = str.toLowerCase()
+ 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 && i > 0) {
+ notCamelcase += `${joinString}${lowercase.charAt(i)}`
+ } else {
+ notCamelcase += chrString
+ }
+ }
+ return notCamelcase
+}
+
+export function looksLikeNumber (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 (/^0[^.]/.test(x)) return false
+ return /^[-]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x)
+}
diff --git a/lib/tokenize-arg-string.js b/lib/tokenize-arg-string.js
deleted file mode 100644
index 23d39e1f..00000000
--- a/lib/tokenize-arg-string.js
+++ /dev/null
@@ -1,34 +0,0 @@
-// take an un-split argv string and tokenize it.
-module.exports = function (argString) {
- if (Array.isArray(argString)) return argString
-
- var i = 0
- var c = null
- var opening = null
- var args = []
-
- for (var ii = 0; ii < argString.length; ii++) {
- c = argString.charAt(ii)
-
- // split on spaces unless we're in quotes.
- if (c === ' ' && !opening) {
- i++
- continue
- }
-
- // don't split the string if we're in matching
- // opening or closing single and double quotes.
- if (c === opening) {
- opening = null
- continue
- } else if ((c === "'" || c === '"') && !opening) {
- opening = c
- continue
- }
-
- if (!args[i]) args[i] = ''
- args[i] += c
- }
-
- return args
-}
diff --git a/lib/tokenize-arg-string.ts b/lib/tokenize-arg-string.ts
new file mode 100644
index 00000000..3d63e875
--- /dev/null
+++ b/lib/tokenize-arg-string.ts
@@ -0,0 +1,46 @@
+/**
+ * @license
+ * Copyright (c) 2016, Contributors
+ * SPDX-License-Identifier: ISC
+ */
+
+// take an un-split argv string and tokenize it.
+export function tokenizeArgString (argString: string | any[]): string[] {
+ if (Array.isArray(argString)) {
+ return argString.map(e => typeof e !== 'string' ? e + '' : e)
+ }
+
+ argString = argString.trim()
+
+ let i = 0
+ 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
+ c = argString.charAt(ii)
+
+ // split on spaces unless we're in quotes.
+ if (c === ' ' && !opening) {
+ if (!(prevC === ' ')) {
+ i++
+ }
+ continue
+ }
+
+ // don't split the string if we're in matching
+ // opening or closing single and double quotes.
+ if (c === opening) {
+ opening = null
+ } else if ((c === "'" || c === '"') && !opening) {
+ opening = c
+ }
+
+ if (!args[i]) args[i] = ''
+ args[i] += c
+ }
+
+ return args
+}
diff --git a/lib/yargs-parser-types.ts b/lib/yargs-parser-types.ts
new file mode 100644
index 00000000..9e03ff2c
--- /dev/null
+++ b/lib/yargs-parser-types.ts
@@ -0,0 +1,201 @@
+/**
+ * @license
+ * Copyright (c) 2016, Contributors
+ * SPDX-License-Identifier: ISC
+ */
+
+/**
+ * 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];
+
+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 positional values that look like numbers be parsed? Default is `true` */
+ 'parse-positional-numbers': boolean;
+ /** 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 interface YargsParserMixin {
+ cwd: Function;
+ format: Function;
+ normalize: Function;
+ require: Function;
+ resolve: Function;
+ env: Function;
+}
+
+export type OptionsDefault = ValueOf, 'default'>>;
+
+export interface Parser {
+ (args: ArgsInput, opts?: Partial): Arguments;
+ detailed(args: ArgsInput, opts?: Partial): DetailedArguments;
+ camelCase(str: string): string;
+ decamelize(str: string, joinString?: string): string;
+ looksLikeNumber(x: null | undefined | number | string): boolean;
+}
+
+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 enum DefaultValuesForTypeKey {
+ BOOLEAN = 'boolean',
+ STRING = 'string',
+ NUMBER = 'number',
+ ARRAY = 'array',
+}
+
+export interface DefaultValuesForType {
+ [DefaultValuesForTypeKey.BOOLEAN]: boolean;
+ [DefaultValuesForTypeKey.STRING]: string;
+ [DefaultValuesForTypeKey.NUMBER]: undefined;
+ [DefaultValuesForTypeKey.ARRAY]: any[];
+}
diff --git a/lib/yargs-parser.ts b/lib/yargs-parser.ts
new file mode 100644
index 00000000..1499fc60
--- /dev/null
+++ b/lib/yargs-parser.ts
@@ -0,0 +1,1118 @@
+/**
+ * @license
+ * Copyright (c) 2016, Contributors
+ * SPDX-License-Identifier: ISC
+ */
+
+import { tokenizeArgString } from './tokenize-arg-string.js'
+import type {
+ ArgsInput,
+ Arguments,
+ ArrayFlagsKey,
+ ArrayOption,
+ CoerceCallback,
+ Configuration,
+ DefaultValuesForType,
+ DetailedArguments,
+ Dictionary,
+ Flag,
+ Flags,
+ FlagsKey,
+ StringFlag,
+ BooleanFlag,
+ NumberFlag,
+ ConfigsFlag,
+ CoercionsFlag,
+ Options,
+ OptionsDefault,
+ ValueOf,
+ YargsParserMixin
+} from './yargs-parser-types.js'
+import { DefaultValuesForTypeKey } from './yargs-parser-types.js'
+import { camelCase, decamelize, looksLikeNumber } 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,
+ 'parse-positional-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]
+ const truncatedArg = arg.replace(/^-{3,}/, '---')
+ 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)) {
+ pushPositional(arg)
+ // ---, ---=, ----, etc,
+ } else if (truncatedArg.match(/---+(=|$)/)) {
+ // options without key name are invalid.
+ pushPositional(arg)
+ continue
+ // -- 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) &&
+ checkAllAliases(next, flags.bools) === false) {
+ 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 {
+ pushPositional(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']) {
+ ;([] as string[]).concat(...Object.keys(aliases).map(k => aliases[k])).forEach(alias => {
+ if (configuration['camel-case-expansion'] && alias.includes('-')) {
+ delete argv[alias.split('.').map(prop => camelCase(prop)).join('.')]
+ }
+
+ delete argv[alias]
+ })
+ }
+
+ // Push argument into positional array, applying numeric coercion:
+ function pushPositional (arg: string) {
+ const maybeCoercedNumber = maybeCoerceNumber('_', arg)
+ if (typeof maybeCoercedNumber === 'string' || typeof maybeCoercedNumber === 'number') {
+ argv._.push(maybeCoercedNumber)
+ }
+ }
+
+ // 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 (!configuration['parse-positional-numbers'] && key === '_') return value
+ if (!checkAllAliases(key, flags.strings) && !checkAllAliases(key, flags.bools) && !Array.isArray(value)) {
+ const shouldCoerceNumber = looksLikeNumber(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) {
+ // Deno will receive a PermissionDenied error if an attempt is
+ // made to load config without the --allow-read flag:
+ if (ex.name === 'PermissionDenied') error = ex
+ else 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 {
+ arg = arg.replace(/^-{3,}/, '--')
+ // 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 = {
+ [DefaultValuesForTypeKey.BOOLEAN]: true,
+ [DefaultValuesForTypeKey.STRING]: '',
+ [DefaultValuesForTypeKey.NUMBER]: undefined,
+ [DefaultValuesForTypeKey.ARRAY]: []
+ }
+
+ return def[type]
+ }
+
+ // given a flag, enforce a default type.
+ function guessType (key: string): DefaultValuesForTypeKey {
+ let type: DefaultValuesForTypeKey = DefaultValuesForTypeKey.BOOLEAN
+ if (checkAllAliases(key, flags.strings)) type = DefaultValuesForTypeKey.STRING
+ else if (checkAllAliases(key, flags.numbers)) type = DefaultValuesForTypeKey.NUMBER
+ else if (checkAllAliases(key, flags.bools)) type = DefaultValuesForTypeKey.BOOLEAN
+ else if (checkAllAliases(key, flags.arrays)) type = DefaultValuesForTypeKey.ARRAY
+ return type
+ }
+
+ 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 {
+ aliases: Object.assign({}, flags.aliases),
+ argv: Object.assign(argvReturn, argv),
+ configuration: configuration,
+ defaulted: Object.assign({}, defaulted),
+ error: error,
+ newAliases: Object.assign({}, newAliases)
+ }
+ }
+}
+
+// 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 d025a7ef..f97aa9e5 100644
--- a/package.json
+++ b/package.json
@@ -1,16 +1,37 @@
{
"name": "yargs-parser",
- "version": "5.0.0",
+ "version": "20.2.9",
"description": "the mighty option parser used by yargs",
- "main": "index.js",
+ "main": "build/index.cjs",
+ "exports": {
+ ".": [
+ {
+ "import": "./build/lib/index.js",
+ "require": "./build/index.cjs"
+ },
+ "./build/index.cjs"
+ ]
+ },
+ "type": "module",
+ "module": "./build/lib/index.js",
"scripts": {
- "pretest": "standard",
- "test": "nyc mocha test/*.js",
- "coverage": "nyc report --reporter=text-lcov | coveralls",
- "release": "standard-version"
+ "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",
+ "coverage": "c8 report --check-coverage",
+ "precompile": "rimraf build",
+ "compile": "tsc",
+ "postcompile": "npm run build:cjs",
+ "build:cjs": "rollup -c",
+ "prepare": "npm run compile"
},
"repository": {
- "url": "git@github.com:yargs/yargs-parser.git"
+ "type": "git",
+ "url": "https://github.com/yargs/yargs-parser.git"
},
"keywords": [
"argument",
@@ -26,18 +47,41 @@
"author": "Ben Coe ",
"license": "ISC",
"devDependencies": {
- "chai": "^3.5.0",
- "coveralls": "^2.11.12",
- "mocha": "^3.0.1",
- "nyc": "^10.0.0",
- "standard": "^8.0.0",
- "standard-version": "^4.0.0"
- },
- "dependencies": {
- "camelcase": "^3.0.0"
+ "@types/chai": "^4.2.11",
+ "@types/mocha": "^8.0.0",
+ "@types/node": "^14.0.0",
+ "@typescript-eslint/eslint-plugin": "^3.10.1",
+ "@typescript-eslint/parser": "^3.10.1",
+ "@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": "^3.0.0",
+ "mocha": "^9.0.0",
+ "puppeteer": "^10.0.0",
+ "rimraf": "^3.0.2",
+ "rollup": "^2.22.1",
+ "rollup-plugin-cleanup": "^3.1.1",
+ "serve": "^12.0.0",
+ "standardx": "^7.0.0",
+ "start-server-and-test": "^1.11.2",
+ "ts-transform-default-export": "^1.0.2",
+ "typescript": "^4.0.0"
},
"files": [
- "lib",
- "index.js"
- ]
+ "browser.js",
+ "build",
+ "!*.d.ts"
+ ],
+ "engines": {
+ "node": ">=10"
+ },
+ "standardx": {
+ "ignore": [
+ "build"
+ ]
+ }
}
diff --git a/release-please-config.json b/release-please-config.json
new file mode 100644
index 00000000..fe643cbb
--- /dev/null
+++ b/release-please-config.json
@@ -0,0 +1,6 @@
+{
+ "bootstrap-sha": "686ec15fb7e4a996763c60cf6661b2698f28dac4",
+ "packages": {
+ ".": {}
+ }
+}
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 00000000..f6dd45e1
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,8 @@
+{
+ "extends": [
+ "config:base"
+ ],
+ "pinVersions": false,
+ "rebaseStalePrs": true,
+ "gitAuthor": null
+}
diff --git a/rollup.config.js b/rollup.config.js
new file mode 100644
index 00000000..ff47101d
--- /dev/null
+++ b/rollup.config.js
@@ -0,0 +1,27 @@
+import cleanup from 'rollup-plugin-cleanup'
+import ts from '@wessberg/rollup-plugin-ts'
+import transformDefaultExport from 'ts-transform-default-export'
+
+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({
+ transformers: ({ program }) => ({
+ afterDeclarations: transformDefaultExport(program)
+ })
+ }),
+ cleanup({
+ comments: 'none',
+ extensions: ['*']
+ })
+ ]
+}
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..f025ac62
--- /dev/null
+++ b/test/deno/yargs-test.ts
@@ -0,0 +1,65 @@
+/* global Deno */
+
+import {
+ assertEquals
+} from 'https://deno.land/std/testing/asserts.ts'
+import parser from '../../deno.ts'
+
+// Parser:
+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', () => {
+ const argv = parser(['--foo', 'bar'], {
+ alias: {
+ z: 'zoom'
+ },
+ default: {
+ settings: jsonPath
+ },
+ config: 'settings'
+ })
+ assertEquals(argv.herp, 'derp')
+ assertEquals(argv.zoom, 55)
+ assertEquals(argv.zoom, 55)
+})
+
+// String utilities:
+Deno.test('convert hyphenated string to camelcase', () => {
+ assertEquals(parser.camelCase('hello-world'), 'helloWorld')
+})
+
+Deno.test('convert camelcase string to hyphenated', () => {
+ assertEquals(parser.decamelize('helloWorld'), 'hello-world')
+})
+
+Deno.test('it detects strings that could be parsed as numbers', () => {
+ assertEquals(parser.looksLikeNumber('3293'), true)
+ assertEquals(parser.looksLikeNumber('0x10'), true)
+ assertEquals(parser.looksLikeNumber('0x10'), true)
+
+ assertEquals(parser.looksLikeNumber('0100'), false)
+ assertEquals(parser.looksLikeNumber('apple'), false)
+})
diff --git a/test/fixtures/config.json b/test/fixtures/config.json
index 43376463..7065ae61 100644
--- a/test/fixtures/config.json
+++ b/test/fixtures/config.json
@@ -3,5 +3,14 @@
"z": 55,
"foo": "baz",
"version": "1.0.2",
- "truthy": true
+ "truthy": true,
+ "toString": "method name",
+ "__proto__": {
+ "aaa": 99
+ },
+ "bar": {
+ "__proto__": {
+ "bbb": 100
+ }
+ }
}
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/string-utils.cjs b/test/string-utils.cjs
new file mode 100644
index 00000000..081dd084
--- /dev/null
+++ b/test/string-utils.cjs
@@ -0,0 +1,39 @@
+/* global describe, it */
+
+const { strictEqual } = require('assert')
+const { camelCase, decamelize, looksLikeNumber } = require('../build/index.cjs')
+
+describe('string-utils', function () {
+ describe('camelCase', () => {
+ it('converts string with hypen in middle to camel case', () => {
+ strictEqual(camelCase('hello-world'), 'helloWorld')
+ })
+ it('removes leading hyphens', () => {
+ strictEqual(camelCase('-goodnight-moon'), 'goodnightMoon')
+ })
+ it('camelCase string stays as is', () => {
+ strictEqual(camelCase('iAmCamelCase'), 'iAmCamelCase')
+ })
+ it('uppercase string with underscore to camel case', () => {
+ strictEqual(camelCase('NODE_VERSION'), 'nodeVersion')
+ })
+ })
+ describe('decamelize', () => {
+ it('adds hyphens back to camelcase string', () => {
+ strictEqual(decamelize('helloWorld'), 'hello-world')
+ })
+ })
+ describe('looksLikeNumber', () => {
+ it('it detects strings that could be parsed as numbers', () => {
+ strictEqual(looksLikeNumber('3293'), true)
+ strictEqual(looksLikeNumber('0x10'), true)
+ strictEqual(looksLikeNumber('.1'), true)
+ strictEqual(looksLikeNumber('0.1'), true)
+ strictEqual(looksLikeNumber('0.10'), true)
+
+ strictEqual(looksLikeNumber('00.1'), false)
+ strictEqual(looksLikeNumber('0100'), false)
+ strictEqual(looksLikeNumber('apple'), false)
+ })
+ })
+})
diff --git a/test/tokenize-arg-string.js b/test/tokenize-arg-string.js
deleted file mode 100644
index 9b9f9a06..00000000
--- a/test/tokenize-arg-string.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/* global describe, it */
-
-var tokenizeArgString = require('../lib/tokenize-arg-string')
-
-require('chai').should()
-
-describe('TokenizeArgString', function () {
- it('handles unquoted string', function () {
- var args = tokenizeArgString('--foo 99')
- args[0].should.equal('--foo')
- args[1].should.equal('99')
- })
-
- it('handles quoted string with no spaces', function () {
- var 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'")
- 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"')
- args[0].should.equal('--foo')
- args[1].should.equal('hello world')
- args[2].should.equal('--bar=foo 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"')
- })
-})
diff --git a/test/tscc/.gitignore b/test/tscc/.gitignore
new file mode 100644
index 00000000..a6c7c285
--- /dev/null
+++ b/test/tscc/.gitignore
@@ -0,0 +1 @@
+*.js
diff --git a/test/tscc/lib b/test/tscc/lib
new file mode 120000
index 00000000..58677ddb
--- /dev/null
+++ b/test/tscc/lib
@@ -0,0 +1 @@
+../../lib
\ No newline at end of file
diff --git a/test/tscc/optimized.ts b/test/tscc/optimized.ts
new file mode 100644
index 00000000..0efd1be7
--- /dev/null
+++ b/test/tscc/optimized.ts
@@ -0,0 +1,47 @@
+import { YargsParser } from './lib/yargs-parser'
+
+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: () => {}
+})
+
+// Workaround the need for proper nodejs typings and externs
+// eslint-disable-next-line no-eval
+const anyDeepEqual = eval('require("assert").strict.deepEqual') as any
+function deepEqual (actual: T, expected: T, message?: string): void {
+ anyDeepEqual(actual, expected, message)
+}
+
+// Quoted properties aren't allowed in these lint settings.
+const FLAG_X = 'x'
+
+function runTests () {
+ deepEqual(parser.parse([]).argv, { _: [] }, 'empty argv')
+ deepEqual(
+ parser.parse([`--${FLAG_X}=42`]).argv,
+ { _: [], [FLAG_X]: 42 },
+ 'guessed numeric value')
+ deepEqual(
+ parser.parse([`--${FLAG_X}=str`]).argv,
+ { _: [], [FLAG_X]: 'str' },
+ 'guessed string value')
+ deepEqual(
+ parser.parse([`--no-${FLAG_X}`]).argv,
+ { _: [], [FLAG_X]: false },
+ 'guessed boolean negation')
+
+ // Default values for types:
+ deepEqual(
+ parser.parse([`--${FLAG_X}`]).argv,
+ { _: [], [FLAG_X]: true },
+ 'guessed boolean default true')
+
+ console.log('ok')
+}
+runTests()
diff --git a/test/tscc/package.json b/test/tscc/package.json
new file mode 100644
index 00000000..2ac7a8ca
--- /dev/null
+++ b/test/tscc/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "optimized-test",
+ "version": "0.0.0",
+ "dependencies": {
+ "@tscc/tscc": "^0.6.4",
+ "@types/node": "^10.0.3"
+ }
+}
diff --git a/test/tscc/tscc.spec.json b/test/tscc/tscc.spec.json
new file mode 100644
index 00000000..ea86963a
--- /dev/null
+++ b/test/tscc/tscc.spec.json
@@ -0,0 +1,5 @@
+{
+ "modules": {
+ "out": "optimized.ts"
+ }
+}
diff --git a/test/tscc/tsconfig.json b/test/tscc/tsconfig.json
new file mode 100644
index 00000000..854aec18
--- /dev/null
+++ b/test/tscc/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "target": "es2017",
+ "moduleResolution": "node"
+ },
+ "include": [
+ "./*.ts",
+ "./lib/**/*.ts"
+ ]
+}
diff --git a/test/typescript/tokenize-arg-string.ts b/test/typescript/tokenize-arg-string.ts
new file mode 100644
index 00000000..efaae775
--- /dev/null
+++ b/test/typescript/tokenize-arg-string.ts
@@ -0,0 +1,131 @@
+/* global describe, it */
+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')
+ strictEqual(args[0], '--foo')
+ strictEqual(args[1], '99')
+ })
+
+ it('handles unquoted numbers', function () {
+ const args = tokenizeArgString(['--foo', 9])
+ strictEqual(args[0], '--foo')
+ strictEqual(args[1], '9')
+ })
+
+ it('handles quoted string with no spaces', function () {
+ const args = tokenizeArgString("--foo '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'")
+ strictEqual(args[0], '--foo')
+ strictEqual(args[1], "'hello world'")
+ strictEqual(args[2], "--bar='foo bar'")
+ })
+
+ it('handles double quoted string with spaces', function () {
+ const args = tokenizeArgString('--foo "hello world" --bar="foo bar"')
+ strictEqual(args[0], '--foo')
+ strictEqual(args[1], '"hello world"')
+ strictEqual(args[2], '--bar="foo bar"')
+ })
+
+ it('handles single quoted empty string', function () {
+ const args = tokenizeArgString('--foo \'\' --bar=\'\'')
+ strictEqual(args[0], '--foo')
+ strictEqual(args[1], "''")
+ strictEqual(args[2], "--bar=''")
+ })
+
+ it('handles double quoted empty string', function () {
+ const args = tokenizeArgString('--foo "" --bar=""')
+ strictEqual(args[0], '--foo')
+ strictEqual(args[1], '""')
+ strictEqual(args[2], '--bar=""')
+ })
+
+ it('handles quoted string with embedded quotes', function () {
+ const args = tokenizeArgString('--foo "hello \'world\'" --bar=\'foo "bar"\'')
+ strictEqual(args[0], '--foo')
+ strictEqual(args[1], '"hello \'world\'"')
+ strictEqual(args[2], '--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" ')
+ strictEqual(args[0], 'foo')
+ strictEqual(args[1], 'bar')
+ strictEqual(args[2], '"foo bar"')
+ })
+
+ it('handles boolean options', function () {
+ const args = tokenizeArgString('--foo -bar')
+ strictEqual(args[0], '--foo')
+ strictEqual(args[1], '-bar')
+ })
+
+ it('handles empty string', function () {
+ const args = tokenizeArgString('')
+ strictEqual(args.length, 0)
+ })
+
+ it('handles array with unquoted string', function () {
+ const args = tokenizeArgString(['--foo', '99'])
+ strictEqual(args[0], '--foo')
+ strictEqual(args[1], '99')
+ })
+
+ it('handles array with quoted string with no spaces', function () {
+ const args = tokenizeArgString(['--foo', "'hello'"])
+ strictEqual(args[0], '--foo')
+ strictEqual(args[1], "'hello'")
+ })
+
+ it('handles array with single quoted string with spaces', function () {
+ const args = tokenizeArgString(['--foo', "'hello world'", "--bar='foo bar'"])
+ strictEqual(args[0], '--foo')
+ strictEqual(args[1], "'hello world'")
+ strictEqual(args[2], "--bar='foo bar'")
+ })
+
+ it('handles array with double quoted string with spaces', function () {
+ const args = tokenizeArgString(['--foo', '"hello world"', '--bar="foo bar"'])
+ strictEqual(args[0], '--foo')
+ strictEqual(args[1], '"hello world"')
+ strictEqual(args[2], '--bar="foo bar"')
+ })
+
+ it('handles array with single quoted empty string', function () {
+ const args = tokenizeArgString(['--foo', "''", "--bar=''"])
+ strictEqual(args[0], '--foo')
+ strictEqual(args[1], "''")
+ strictEqual(args[2], "--bar=''")
+ })
+
+ it('handles array with double quoted empty string', function () {
+ const args = tokenizeArgString(['--foo', '""', '--bar=""'])
+ strictEqual(args[0], '--foo')
+ strictEqual(args[1], '""')
+ strictEqual(args[2], '--bar=""')
+ })
+
+ it('handles array with quoted string with embedded quotes', function () {
+ const args = tokenizeArgString(['--foo', '"hello \'world\'"', '--bar=\'foo "bar"\''])
+ strictEqual(args[0], '--foo')
+ strictEqual(args[1], '"hello \'world\'"')
+ strictEqual(args[2], '--bar=\'foo "bar"\'')
+ })
+
+ it('handles array with boolean options', function () {
+ const args = tokenizeArgString(['--foo', '-bar'])
+ strictEqual(args[0], '--foo')
+ strictEqual(args[1], '-bar')
+ })
+})
diff --git a/test/typescript/types.ts b/test/typescript/types.ts
new file mode 100644
index 00000000..39631057
--- /dev/null
+++ b/test/typescript/types.ts
@@ -0,0 +1,13 @@
+/* global describe, it */
+
+import yargsParser from '../../lib/index.js'
+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/test/yargs-parser.cjs b/test/yargs-parser.cjs
new file mode 100644
index 00000000..95bee24a
--- /dev/null
+++ b/test/yargs-parser.cjs
@@ -0,0 +1,3999 @@
+/* global beforeEach, describe, it */
+
+require('chai').should()
+
+const { expect } = require('chai')
+const fs = require('fs')
+const parser = require('../build/index.cjs')
+const path = require('path')
+
+describe('yargs-parser', function () {
+ it('should parse a "short boolean"', function () {
+ const 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)
+ })
+
+ it('should parse a "long boolean"', function () {
+ const parse = parser('--bool')
+ parse.should.not.have.property('--')
+ parse.should.have.property('bool', true)
+ parse.should.have.property('_').with.length(0)
+ })
+
+ it('should place bare options in the _ array', function () {
+ const parse = parser('foo bar baz')
+ parse.should.have.property('_').and.deep.equal(['foo', 'bar', 'baz'])
+ })
+
+ it('should set the value of the final option in a group to the next supplied value', function () {
+ const parse = parser(['-cats', 'meow'])
+ parse.should.have.property('c', true)
+ parse.should.have.property('a', true)
+ parse.should.have.property('t', true)
+ parse.should.have.property('s', 'meow')
+ parse.should.have.property('_').with.length(0)
+ })
+
+ it('should set the value of a single long option to the next supplied value', function () {
+ const parse = parser(['--pow', 'xixxle'])
+ parse.should.have.property('pow', 'xixxle')
+ parse.should.have.property('_').with.length(0)
+ })
+
+ it('should set the value of a single long option to the next supplied value, even if the value is empty', function () {
+ const parse = parser(['--pow', ''])
+ parse.should.have.property('pow', '')
+ parse.should.have.property('_').with.length(0)
+ })
+
+ it('should set the value of a single long option if an = was used', function () {
+ const parse = parser(['--pow=xixxle'])
+ parse.should.have.property('pow', 'xixxle')
+ parse.should.have.property('_').with.length(0)
+ })
+
+ it('should set the value of multiple long options to the next supplied values relative to each', function () {
+ const parse = parser(['--host', 'localhost', '--port', '555'])
+ parse.should.have.property('host', 'localhost')
+ parse.should.have.property('port', 555)
+ parse.should.have.property('_').with.length(0)
+ })
+
+ it('should set the value of multiple long options if = signs were used', function () {
+ const parse = parser(['--host=localhost', '--port=555'])
+ parse.should.have.property('host', 'localhost')
+ parse.should.have.property('port', 555)
+ parse.should.have.property('_').with.length(0)
+ })
+
+ it('should still set values appropriately if a mix of short, long, and grouped short options are specified', function () {
+ const parse = parser(['-h', 'localhost', '-fp', '555', 'script.js'])
+ parse.should.have.property('f', true)
+ parse.should.have.property('p', 555)
+ parse.should.have.property('h', 'localhost')
+ parse.should.have.property('_').and.deep.equal(['script.js'])
+ })
+
+ it('should still set values appropriately if a mix of short and long options are specified', function () {
+ const parse = parser(['-h', 'localhost', '--port', '555'])
+ parse.should.have.property('h', 'localhost')
+ parse.should.have.property('port', 555)
+ parse.should.have.property('_').with.length(0)
+ })
+
+ it('should explicitly set a boolean option to false if preceded by "--no-"', function () {
+ const parse = parser(['--no-moo'])
+ parse.should.have.property('moo', false)
+ parse.should.have.property('_').with.length(0)
+ })
+
+ it('should still set values appropriately if we supply a comprehensive list of various types of options', function () {
+ const parse = parser([
+ '--name=meowmers', 'bare', '-cats', 'woo',
+ '-h', 'awesome', '--multi=quux',
+ '--key', 'value',
+ '-b', '--bool', '--no-meep', '--multi=baz',
+ '--', '--not-a-flag', '-', '-h', '-multi', '--', 'eek'
+ ], {
+ configuration: {
+ 'populate--': false
+ }
+ })
+ parse.should.have.property('c', true)
+ parse.should.have.property('a', true)
+ parse.should.have.property('t', true)
+ parse.should.have.property('s', 'woo')
+ parse.should.have.property('h', 'awesome')
+ parse.should.have.property('b', true)
+ parse.should.have.property('bool', true)
+ parse.should.have.property('key', 'value')
+ parse.should.have.property('multi').and.deep.equal(['quux', 'baz'])
+ parse.should.have.property('meep', false)
+ parse.should.have.property('name', 'meowmers')
+ parse.should.have.property('_').and.deep.equal(['bare', '--not-a-flag', '-', '-h', '-multi', '--', 'eek'])
+ })
+
+ it('should parse numbers appropriately', function () {
+ const argv = parser([
+ '-x', '1234',
+ '-y', '5.67',
+ '-z', '1e7',
+ '-w', '10f',
+ '--hex', '0xdeadbeef',
+ '789'
+ ])
+ argv.should.have.property('x', 1234).and.be.a('number')
+ argv.should.have.property('y', 5.67).and.be.a('number')
+ argv.should.have.property('z', 1e7).and.be.a('number')
+ argv.should.have.property('w', '10f').and.be.a('string')
+ argv.should.have.property('hex', 0xdeadbeef).and.be.a('number')
+ argv.should.have.property('_').and.deep.equal([789])
+ argv._[0].should.be.a('number')
+ })
+
+ // addresses: https://github.com/yargs/yargs-parser/issues/33
+ it('should handle parsing negative #s', function () {
+ const argv = parser([
+ '-33', '-177', '33',
+ '--n1', '-33',
+ '-n', '-44',
+ '--n2=-55',
+ '--foo.bar', '-33',
+ '-o=-55',
+ '--bounds', '-180', '99', '-180', '90',
+ '--other', '-99', '-220'
+ ], {
+ array: 'bounds',
+ narg: { other: 2 }
+ })
+
+ argv._.should.deep.equal([-33, -177, 33])
+ argv.n1.should.equal(-33)
+ argv.n.should.equal(-44)
+ argv.n2.should.equal(-55)
+ argv.foo.bar.should.equal(-33)
+ argv.o.should.equal(-55)
+ argv.bounds.should.deep.equal([-180, 99, -180, 90])
+ argv.other.should.deep.equal([-99, -220])
+ })
+
+ it('should handle negative (and positive) numbers with decimal places, with or without a leading 0', function () {
+ const 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 () {
+ const parse = parser(['-p', ''])
+ parse.should.have.property('p', '')
+ parse.should.have.property('_').with.length(0)
+ })
+
+ it('should not set the next value as the value of a short option if that option is explicitly defined as a boolean', function () {
+ const parse = parser(['-t', 'moo'], {
+ boolean: 't'
+ })
+ parse.should.have.property('t', true).and.be.a('boolean')
+ parse.should.have.property('_').and.deep.equal(['moo'])
+ })
+
+ it('should set boolean options values if the next value is "true" or "false"', function () {
+ const parse = parser(['--verbose', 'false', 'moo', '-t', 'true'], {
+ boolean: ['t', 'verbose'],
+ default: {
+ verbose: true
+ }
+ })
+ parse.should.have.property('verbose', false).and.be.a('boolean')
+ parse.should.have.property('t', true).and.be.a('boolean')
+ parse.should.have.property('_').and.deep.equal(['moo'])
+ })
+
+ it('should not set boolean options values if the next value only contains the words "true" or "false"', function () {
+ const parse = parser(['--verbose', 'aaatrueaaa', 'moo', '-t', 'aaafalseaaa'], {
+ boolean: ['t', 'verbose']
+ })
+ parse.should.have.property('verbose', true).and.be.a('boolean')
+ parse.should.have.property('t', true).and.be.a('boolean')
+ parse.should.have.property('_').and.deep.equal(['aaatrueaaa', 'moo', 'aaafalseaaa'])
+ })
+
+ it('should not use next value for boolean/number/string configured with zero narg', function () {
+ const 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 () {
+ const parse = parser(['-x', '-z', 'one', 'two', 'three'], {
+ boolean: ['x', 'y', 'z']
+ })
+ parse.should.have.property('x', true).and.be.a('boolean')
+ parse.should.not.have.property('y')
+ parse.should.have.property('z', true).and.be.a('boolean')
+ parse.should.have.property('_').and.deep.equal(['one', 'two', 'three'])
+ })
+
+ it('should correctly parse dot-notation boolean flags', function () {
+ const parse = parser(['--nested', '--n.v', '--n.y', 'foo'], {
+ boolean: ['nested', 'n.v']
+ })
+
+ parse.should.have.property('nested', true).and.be.a('boolean')
+ parse.should.have.property('n').and.deep.equal({
+ v: true,
+ y: 'foo'
+ })
+ })
+
+ it('should preserve newlines in option values', function () {
+ let args = parser(['-s', 'X\nX'])
+ args.should.have.property('_').with.length(0)
+ args.should.have.property('s', 'X\nX')
+ // reproduce in bash:
+ // VALUE="new
+ // line"
+ // node program.js --s="$VALUE"
+ args = parser(['--s=X\nX'])
+ args.should.have.property('_').with.length(0)
+ args.should.have.property('s', 'X\nX')
+ })
+
+ it('should not convert numbers to type number if explicitly defined as strings', function () {
+ const s = parser(['-s', '0001234'], {
+ string: 's'
+ }).s
+ s.should.be.a('string').and.equal('0001234')
+ const x = parser(['-x', '56'], {
+ string: ['x']
+ }).x
+ x.should.be.a('string').and.equal('56')
+ })
+
+ it('should default numbers to undefined', function () {
+ const 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 () {
+ const n = parser(['-n', 'string'], {
+ number: ['n']
+ }).n
+ expect(n).to.deep.equal(NaN)
+ })
+
+ // 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 () {
+ let s = parser(['-s'], {
+ string: ['s']
+ }).s
+ s.should.be.a('string').and.equal('')
+
+ s = parser(['-sf'], {
+ string: ['s']
+ }).s
+ s.should.be.a('string').and.equal('')
+
+ 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 () {
+ const s = parser([' ', ' '], {
+ string: ['_']
+ })._
+ s.should.have.length(2)
+ s[0].should.be.a('string').and.equal(' ')
+ s[1].should.be.a('string').and.equal(' ')
+ })
+
+ describe('normalize', function () {
+ it('should normalize redundant paths', function () {
+ const a = parser(['-s', ['', 'tmp', '..', ''].join(path.sep)], {
+ alias: {
+ s: ['save']
+ },
+ normalize: 's'
+ })
+ a.should.have.property('s', path.sep)
+ a.should.have.property('save', path.sep)
+ })
+
+ it('should normalize redundant paths when a value is later assigned', function () {
+ const a = parser(['-s'], {
+ normalize: ['s']
+ })
+ a.should.have.property('s', true)
+ a.s = ['', 'path', 'to', 'new', 'dir', '..', '..', ''].join(path.sep)
+ a.s.should.equal(['', 'path', 'to', ''].join(path.sep))
+ })
+
+ it('should normalize when key is also an array', function () {
+ const a = parser(['-s', ['', 'tmp', '..', ''].join(path.sep), ['', 'path', 'to', 'new', 'dir', '..', '..', ''].join(path.sep)], {
+ alias: {
+ s: ['save']
+ },
+ normalize: 's',
+ array: 's'
+ })
+ const expected = [path.sep, ['', 'path', 'to', ''].join(path.sep)]
+ 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', () => {
+ const a = parser(['-s', ['', 'tmp', '..', ''].join(path.sep)], {
+ alias: {
+ s: ['save']
+ },
+ normalize: 's'
+ })
+ Object.keys(a).should.include('s')
+ })
+ })
+
+ describe('alias', function () {
+ it('should set alias value to the same value as the full option', function () {
+ const argv = parser(['-f', '11', '--zoom', '55'], {
+ alias: {
+ z: ['zoom']
+ }
+ })
+ argv.should.have.property('zoom', 55)
+ argv.should.have.property('z', 55)
+ argv.should.have.property('f', 11)
+ })
+
+ it('should allow multiple aliases to be specified', function () {
+ const argv = parser(['-f', '11', '--zoom', '55'], {
+ alias: {
+ z: ['zm', 'zoom']
+ }
+ })
+
+ argv.should.have.property('zoom', 55)
+ argv.should.have.property('z', 55)
+ argv.should.have.property('zm', 55)
+ argv.should.have.property('f', 11)
+ })
+
+ // regression, see https://github.com/chevex/yargs/issues/63
+ it('should not add the same key to argv multiple times, when creating camel-case aliases', function () {
+ const argv = parser(['--health-check=banana', '--second-key', 'apple', '-t=blarg'], {
+ alias: {
+ h: ['health-check'],
+ 'second-key': ['s'],
+ 'third-key': ['t']
+ },
+ default: {
+ h: 'apple',
+ 'second-key': 'banana',
+ 'third-key': 'third'
+ }
+ })
+
+ // before this fix, yargs failed parsing
+ // one but not all forms of an arg.
+ argv.secondKey.should.eql('apple')
+ argv.s.should.eql('apple')
+ argv['second-key'].should.eql('apple')
+
+ argv.healthCheck.should.eql('banana')
+ argv.h.should.eql('banana')
+ argv['health-check'].should.eql('banana')
+
+ argv.thirdKey.should.eql('blarg')
+ argv.t.should.eql('blarg')
+ argv['third-key'].should.eql('blarg')
+ })
+
+ it('should allow transitive aliases to be specified', function () {
+ const argv = parser(['-f', '11', '--zoom', '55'], {
+ alias: {
+ z: 'zm',
+ zm: 'zoom'
+ }
+ })
+
+ argv.should.have.property('zoom', 55)
+ argv.should.have.property('z', 55)
+ argv.should.have.property('zm', 55)
+ argv.should.have.property('f', 11)
+ })
+
+ it('should merge two lists of aliases if they collide', function () {
+ const argv = parser(['-f', '11', '--zoom', '55'], {
+ alias: {
+ z: 'zm',
+ zoom: 'zoop',
+ zm: 'zoom'
+ }
+ })
+
+ argv.should.have.property('zoom', 55)
+ argv.should.have.property('zoop', 55)
+ argv.should.have.property('z', 55)
+ argv.should.have.property('zm', 55)
+ argv.should.have.property('f', 11)
+ })
+
+ it('should set single-digit boolean alias', function () {
+ const 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 () {
+ const 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 () {
+ const 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 () {
+ const 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 () {
+ let parse = parser(['-I/foo/bar/baz'])
+ parse.should.have.property('_').with.length(0)
+ parse.should.have.property('I', '/foo/bar/baz')
+ parse = parser(['-xyz/foo/bar/baz'])
+ parse.should.have.property('x', true)
+ parse.should.have.property('y', true)
+ parse.should.have.property('z', '/foo/bar/baz')
+ parse.should.have.property('_').with.length(0)
+ })
+
+ 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 () {
+ 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')
+ })
+
+ it('should use value from config file, if argv value is using default value', function () {
+ const argv = parser([], {
+ alias: {
+ z: 'zoom'
+ },
+ config: ['settings'],
+ default: {
+ settings: jsonPath,
+ foo: 'banana'
+ }
+ })
+
+ argv.should.have.property('herp', 'derp')
+ argv.should.have.property('zoom', 55)
+ argv.should.have.property('foo').and.deep.equal('baz')
+ })
+
+ it('should combine values from config file and argv, if argv value is an array', function () {
+ const argv = parser(['--foo', 'bar'], {
+ config: ['settings'],
+ array: ['foo'],
+ default: {
+ settings: jsonPath
+ },
+ configuration: {
+ 'combine-arrays': true
+ }
+ })
+
+ argv.should.have.property('foo').and.deep.equal(['bar', 'baz'])
+ })
+
+ it('should use value from config file, if argv key is a boolean', function () {
+ const argv = parser([], {
+ config: ['settings'],
+ default: {
+ settings: jsonPath
+ },
+ boolean: ['truthy']
+ })
+
+ argv.should.have.property('truthy', true)
+ })
+
+ it('should use value from cli, if cli overrides boolean argv key', function () {
+ const argv = parser(['--no-truthy'], {
+ config: ['settings'],
+ default: {
+ settings: jsonPath
+ },
+ boolean: ['truthy']
+ })
+
+ argv.should.have.property('truthy', false)
+ })
+
+ it('should use cli value, if cli value is set and both cli and default value match', function () {
+ const argv = parser(['--foo', 'banana'], {
+ alias: {
+ z: 'zoom'
+ },
+ config: ['settings'],
+ default: {
+ settings: jsonPath,
+ foo: 'banana'
+ }
+ })
+
+ argv.should.have.property('herp', 'derp')
+ argv.should.have.property('zoom', 55)
+ argv.should.have.property('foo').and.deep.equal('banana')
+ })
+
+ it("should allow config to be set as flag in 'option'", function () {
+ const argv = parser(['--settings', jsonPath, '--foo', 'bar'], {
+ alias: {
+ z: 'zoom'
+ },
+ config: ['settings']
+ })
+
+ argv.should.have.property('herp', 'derp')
+ argv.should.have.property('zoom', 55)
+ argv.should.have.property('foo').and.deep.equal('bar')
+ })
+
+ it('should load options and values from a JS file when config has .js extention', function () {
+ const jsPath = path.resolve(__dirname, './fixtures/settings.cjs')
+ const argv = parser(['--settings', jsPath, '--foo', 'bar'], {
+ config: ['settings']
+ })
+
+ argv.should.have.property('herp', 'derp')
+ argv.should.have.property('foo', 'bar')
+ argv.should.have.property('calculate').and.be.a('function')
+ })
+
+ it('should raise an appropriate error if JSON file is not found', function () {
+ const argv = parser.detailed(['--settings', 'fake.json', '--foo', 'bar'], {
+ alias: {
+ z: 'zoom'
+ },
+ config: ['settings']
+ })
+
+ argv.error.message.should.equal('Invalid JSON config file: fake.json')
+ })
+
+ // see: https://github.com/bcoe/yargs/issues/172
+ it('should not raise an exception if config file is set as default argument value', function () {
+ const argv = parser.detailed([], {
+ default: {
+ config: 'foo.json'
+ },
+ config: ['config']
+ })
+
+ expect(argv.error).to.equal(null)
+ })
+
+ it('should load nested options from config file', function () {
+ const jsonPath = path.resolve(__dirname, './fixtures/nested_config.json')
+ const argv = parser(['--settings', jsonPath, '--nested.foo', 'bar'], {
+ config: ['settings']
+ })
+
+ argv.should.have.property('a', 'a')
+ argv.should.have.property('b', 'b')
+ argv.should.have.property('nested').and.deep.equal({
+ foo: 'bar',
+ bar: 'bar'
+ })
+ })
+
+ it('should use nested value from config file, if argv value is using default value', function () {
+ const jsonPath = path.resolve(__dirname, './fixtures/nested_config.json')
+ const argv = parser(['--settings', jsonPath], {
+ config: ['settings'],
+ default: {
+ 'nested.foo': 'banana'
+ }
+ })
+
+ argv.should.have.property('a', 'a')
+ argv.should.have.property('b', 'b')
+ argv.should.have.property('nested').and.deep.equal({
+ foo: 'baz',
+ bar: 'bar'
+ })
+ })
+
+ it('allows a custom parsing function to be provided', function () {
+ const jsPath = path.resolve(__dirname, './fixtures/config.txt')
+ const argv = parser(['--settings', jsPath, '--foo', 'bar'], {
+ config: {
+ settings: function (configPath) {
+ // as an example, parse an environment
+ // variable style config:
+ // FOO=99
+ // BATMAN=grumpy
+ const config = {}
+ const txt = fs.readFileSync(configPath, 'utf-8')
+ txt.split(/\r?\n/).forEach(function (l) {
+ const kv = l.split('=')
+ config[kv[0].toLowerCase()] = kv[1]
+ })
+ return config
+ }
+ }
+ })
+
+ argv.batman.should.equal('grumpy')
+ argv.awesome.should.equal('banana')
+ argv.foo.should.equal('bar')
+ })
+
+ it('allows a custom parsing function to be provided as an alias', function () {
+ const jsPath = path.resolve(__dirname, './fixtures/config.json')
+ const argv = parser(['--settings', jsPath, '--foo', 'bar'], {
+ config: {
+ s: function (configPath) {
+ return JSON.parse(fs.readFileSync(configPath, 'utf-8'))
+ }
+ },
+ alias: {
+ s: ['settings']
+ }
+ })
+
+ argv.should.have.property('herp', 'derp')
+ argv.should.have.property('foo', 'bar')
+ })
+
+ it('outputs an error returned by the parsing function', function () {
+ const argv = parser.detailed(['--settings=./package.json'], {
+ config: {
+ settings: function (configPath) {
+ return Error('someone set us up the bomb')
+ }
+ }
+ })
+
+ argv.error.message.should.equal('someone set us up the bomb')
+ })
+
+ it('outputs an error if thrown by the parsing function', function () {
+ const argv = parser.detailed(['--settings=./package.json'], {
+ config: {
+ settings: function (configPath) {
+ throw Error('someone set us up the bomb')
+ }
+ }
+ })
+
+ 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 () {
+ it('should load options from config object', function () {
+ const argv = parser(['--foo', 'bar'], {
+ configObjects: [{
+ apple: 'apple',
+ banana: 42,
+ foo: 'baz',
+ gotcha: null
+ }]
+ })
+
+ argv.should.have.property('apple', 'apple')
+ argv.should.have.property('banana', 42)
+ argv.should.have.property('foo', 'bar')
+ argv.should.have.property('gotcha', null)
+ })
+
+ it('should use value from config object, if argv value is using default value', function () {
+ const argv = parser([], {
+ configObjects: [{
+ apple: 'apple',
+ banana: 42,
+ foo: 'baz'
+ }],
+ default: {
+ foo: 'banana'
+ }
+ })
+
+ argv.should.have.property('apple', 'apple')
+ argv.should.have.property('banana', 42)
+ argv.should.have.property('foo', 'baz')
+ })
+
+ it('should use value from config object to all aliases', function () {
+ const argv = parser([], {
+ configObjects: [{
+ apple: 'apple',
+ banana: 42,
+ foo: 'baz'
+ }],
+ alias: {
+ a: ['apple'],
+ banana: ['b']
+ }
+ })
+
+ argv.should.have.property('apple', 'apple')
+ argv.should.have.property('a', 'apple')
+ argv.should.have.property('banana', 42)
+ argv.should.have.property('b', 42)
+ argv.should.have.property('foo', 'baz')
+ })
+
+ it('should load nested options from config object', function () {
+ const argv = parser(['--nested.foo', 'bar'], {
+ configObjects: [{
+ a: 'a',
+ nested: {
+ foo: 'baz',
+ bar: 'bar'
+ },
+ b: 'b'
+ }]
+ })
+
+ argv.should.have.property('a', 'a')
+ argv.should.have.property('b', 'b')
+ argv.should.have.property('nested').and.deep.equal({
+ foo: 'bar',
+ bar: 'bar'
+ })
+ })
+
+ it('should use nested value from config object, if argv value is using default value', function () {
+ const argv = parser([], {
+ configObjects: [{
+ a: 'a',
+ nested: {
+ foo: 'baz',
+ bar: 'bar'
+ },
+ b: 'b'
+ }],
+ default: {
+ 'nested.foo': 'banana'
+ }
+ })
+
+ argv.should.have.property('a', 'a')
+ argv.should.have.property('b', 'b')
+ argv.should.have.property('nested').and.deep.equal({
+ foo: 'baz',
+ bar: 'bar'
+ })
+ })
+
+ it('should load objects with first object having greatest priority', function () {
+ const argv = parser(['--foo', 'bar'], {
+ configObjects: [{
+ bar: 'baz'
+ }, {
+ bar: 'quux',
+ foo: 'spam'
+ }]
+ })
+
+ argv.should.have.property('foo', 'bar')
+ argv.should.have.property('bar', 'baz')
+ })
+
+ it('should combine array typed options with alias and camel-case', function () {
+ const 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 () {
+ it('should allow object graph traversal via dot notation', function () {
+ const argv = parser([
+ '--foo.bar', '3', '--foo.baz', '4',
+ '--foo.quux.quibble', '5', '--foo.quux.o_O',
+ '--beep.boop'
+ ])
+ argv.should.have.property('foo').and.deep.equal({
+ bar: 3,
+ baz: 4,
+ quux: {
+ quibble: 5,
+ o_O: true
+ }
+ })
+ argv.should.have.property('beep').and.deep.equal({ boop: true })
+ })
+
+ it('should apply defaults to dot notation arguments', function () {
+ const argv = parser([], {
+ default: {
+ 'foo.bar': 99
+ }
+ })
+
+ argv.foo.bar.should.eql(99)
+ })
+
+ // see #279
+ it('should allow default to be overridden when an alias is provided', function () {
+ const argv = parser(['--foo.bar', '200'], {
+ default: {
+ 'foo.bar': 99
+ }
+ })
+
+ argv.foo.bar.should.eql(200)
+ })
+
+ // see #279
+ it('should also override alias', function () {
+ const argv = parser(['--foo.bar', '200'], {
+ alias: {
+ 'foo.bar': ['f']
+ },
+ default: {
+ 'foo.bar': 99
+ }
+ })
+
+ argv.f.should.eql(200)
+ })
+
+ // see #279
+ it('should not set an undefined dot notation key', function () {
+ const argv = parser(['--foo.bar', '200'], {
+ default: {
+ 'foo.bar': 99
+ },
+ alias: {
+ 'foo.bar': ['f']
+ }
+ })
+
+ ; ('foo.bar' in argv).should.equal(false)
+ })
+
+ it('should respect .string() for dot notation arguments', function () {
+ const argv = parser(['--foo.bar', '99', '--bar.foo=99'], {
+ string: ['foo.bar']
+ })
+
+ argv.foo.bar.should.eql('99')
+ argv.bar.foo.should.eql(99)
+ })
+
+ it('should populate aliases when dot notation is used', function () {
+ const argv = parser(['--foo.bar', '99'], {
+ alias: {
+ foo: ['f']
+ }
+ })
+
+ 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 () {
+ const 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 () {
+ const argv = parser(['--foo.bar.snuh', '99', '--foo.apple', '33', '--foo.bar.cool', '11'], {
+ alias: {
+ foo: ['f']
+ }
+ })
+
+ argv.f.bar.snuh.should.eql(99)
+ argv.foo.bar.snuh.should.eql(99)
+
+ argv.f.apple.should.eql(33)
+ argv.foo.apple.should.eql(33)
+
+ argv.f.bar.cool.should.eql(11)
+ argv.foo.bar.cool.should.eql(11)
+ })
+
+ it("should allow flags to use dot notation, when separated by '='", function () {
+ const argv = parser(['-f.foo=99'])
+ argv.f.foo.should.eql(99)
+ })
+
+ it("should allow flags to use dot notation, when separated by ' '", function () {
+ const argv = parser(['-f.foo', '99'])
+ argv.f.foo.should.eql(99)
+ })
+
+ it('should allow flags to use dot notation when no right-hand-side is given', function () {
+ const argv = parser(['-f.foo', '99', '-f.bar'])
+ 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 () {
+ const aliased = ['-h', 'true']
+ const aliasedArgv = parser(aliased, {
+ boolean: ['h'],
+ alias: {
+ h: ['herp']
+ }
+ })
+
+ aliasedArgv.should.have.property('herp', true)
+ aliasedArgv.should.have.property('h', true)
+ aliasedArgv.should.have.property('_').with.length(0)
+ })
+
+ // regression, see https://github.com/substack/node-optimist/issues/71
+ it('should set boolean and --x=true', function () {
+ let parsed = parser(['--boool', '--other=true'], {
+ boolean: ['boool']
+ })
+ parsed.should.have.property('boool', true)
+ parsed.should.have.property('other', 'true')
+ parsed = parser(['--boool', '--other=false'], {
+ boolean: ['boool']
+ })
+ parsed.should.have.property('boool', true)
+ parsed.should.have.property('other', 'false')
+ })
+
+ // regression, see https://github.com/chevex/yargs/issues/66
+ it('should set boolean options values if next value is "true" or "false" with = as separator', function () {
+ const argv = parser(['--bool=false'], {
+ boolean: ['b'],
+ alias: {
+ b: ['bool']
+ },
+ default: {
+ b: true
+ }
+ })
+
+ argv.bool.should.eql(false)
+ })
+
+ describe('short options', function () {
+ it('should set the value of multiple single short options to the next supplied values relative to each', function () {
+ const parse = parser(['-h', 'localhost', '-p', '555'])
+ parse.should.have.property('h', 'localhost')
+ parse.should.have.property('p', 555)
+ parse.should.have.property('_').with.length(0)
+ })
+
+ it('should set the value of a single short option to the next supplied value', function () {
+ const parse = parser(['-h', 'localhost'])
+ parse.should.have.property('h', 'localhost')
+ parse.should.have.property('_').with.length(0)
+ })
+
+ it('should expand grouped short options to a hash with a key for each', function () {
+ const parse = parser(['-cats'])
+ parse.should.have.property('c', true)
+ parse.should.have.property('a', true)
+ parse.should.have.property('t', true)
+ parse.should.have.property('s', true)
+ parse.should.have.property('_').with.length(0)
+ })
+
+ it('should set n to the numeric value 123', function () {
+ const 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 () {
+ const argv = parser(['-ab5n123'])
+ argv.should.have.property('a', true)
+ argv.should.have.property('b', true)
+ argv.should.have.property('5', true)
+ argv.should.have.property('n', 123)
+ argv.should.have.property('_').with.length(0)
+ })
+
+ it('should set n to the numeric value 123, with = as separator', function () {
+ const 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 () {
+ const argv = parser(['-ab5n=123'])
+ argv.should.have.property('a', true)
+ argv.should.have.property('b', true)
+ argv.should.have.property('5', true)
+ argv.should.have.property('n', 123)
+ argv.should.have.property('_').with.length(0)
+ })
+ })
+
+ describe('whitespace', function () {
+ it('should be whitespace', function () {
+ const argv = parser(['-x', '\t'])
+ argv.should.have.property('x', '\t')
+ })
+ })
+
+ describe('boolean modifier function', function () {
+ it('should prevent yargs from sucking in the next option as the value of the first option', function () {
+ // Arrange & Act
+ const result = parser(['-b', '123'], {
+ boolean: ['b']
+ })
+ // Assert
+ 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 () {
+ function checkNoArgs (opts, hasAlias) {
+ it('should set defaults if no args', function () {
+ const result = parser([], opts)
+ result.should.have.property('flag', true)
+ if (hasAlias) {
+ result.should.have.property('f', true)
+ }
+ })
+ }
+
+ function checkExtraArg (opts, hasAlias) {
+ it('should set defaults if one extra arg', function () {
+ const result = parser(['extra'], opts)
+ result.should.have.property('flag', true)
+ result.should.have.property('_').and.deep.equal(['extra'])
+ if (hasAlias) {
+ result.should.have.property('f', true)
+ }
+ })
+ }
+
+ function checkStringArg (opts, hasAlias) {
+ it('should set defaults even if arg looks like a string', function () {
+ const result = parser(['--flag', 'extra'], opts)
+ result.should.have.property('flag', true)
+ result.should.have.property('_').and.deep.equal(['extra'])
+ if (hasAlias) {
+ result.should.have.property('f', true)
+ }
+ })
+ }
+
+ describe('for options with aliases', function () {
+ const opts = {
+ alias: {
+ flag: ['f']
+ },
+ default: {
+ flag: true
+ }
+ }
+
+ checkNoArgs(opts, true)
+ checkExtraArg(opts, true)
+ })
+
+ describe('for typed options without aliases', function () {
+ const opts = {
+ boolean: ['flag'],
+ default: {
+ flag: true
+ }
+ }
+
+ checkNoArgs(opts)
+ checkExtraArg(opts)
+ checkStringArg(opts)
+ })
+
+ describe('for typed options with aliases', function () {
+ const opts = {
+ alias: {
+ flag: ['f']
+ },
+ boolean: ['flag'],
+ default: {
+ flag: true
+ }
+ }
+
+ checkNoArgs(opts, true)
+ checkExtraArg(opts, true)
+ checkStringArg(opts, true)
+ })
+
+ describe('for boolean options', function () {
+ [true, false, undefined, null].forEach(function (def) {
+ describe('with explicit ' + def + ' default', function () {
+ const opts = {
+ default: {
+ flag: def
+ },
+ boolean: ['flag']
+ }
+
+ it('should set true if --flag in arg', function () {
+ parser(['--flag'], opts).flag.should.be.true // eslint-disable-line
+ })
+
+ it('should set false if --no-flag in arg', function () {
+ parser(['--no-flag'], opts).flag.should.be.false // eslint-disable-line
+ })
+
+ it('should set ' + def + ' if no flag in arg', function () {
+ expect(parser([], opts).flag).to.equal(def)
+ })
+ })
+ })
+
+ describe('without any default value', function () {
+ let opts = null
+
+ beforeEach(function () {
+ opts = {
+ boolean: ['flag']
+ }
+ })
+
+ it('should set true if --flag in arg', function () {
+ parser(['--flag'], opts).flag.should.be.true // eslint-disable-line
+ })
+
+ it('should set false if --no-flag in arg', function () {
+ parser(['--no-flag'], opts).flag.should.be.false // eslint-disable-line
+ })
+
+ it('should not add property if no flag in arg', function () {
+ parser([''], opts).should.not.have.property('flag')
+ })
+ })
+
+ // Fixes: https://github.com/bcoe/yargs/issues/341
+ it('should apply defaults to camel-case form of argument', function () {
+ const argv = parser([], {
+ default: {
+ 'foo-bar': 99
+ }
+ })
+
+ argv.fooBar.should.equal(99)
+ })
+
+ // Fixes: https://github.com/yargs/yargs-parser/issues/77
+ it('should combine dot-notation and camel-case expansion', function () {
+ const 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 () {
+ const argv = parser([], {
+ boolean: ['sometrue'],
+ default: {
+ sometrue: true
+ }
+ })
+ argv.should.have.property('sometrue', true)
+ })
+
+ it('should define option as boolean and set default to false', function () {
+ const argv = parser([], {
+ default: {
+ somefalse: false
+ },
+ boolean: ['somefalse']
+ })
+ argv.should.have.property('somefalse', false)
+ })
+
+ it('should set boolean options to false by default', function () {
+ const parse = parser(['moo'], {
+ boolean: ['t', 'verbose'],
+ default: {
+ verbose: false,
+ t: false
+ }
+ })
+ parse.should.have.property('verbose', false).and.be.a('boolean')
+ 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 () {
+ const parsed = parser.detailed('', {
+ default: { foo: 'abc', 'bar.prop': 33, baz: 'x' },
+ configObjects: [{ baz: 'xyz' }]
+ })
+ expect(parsed.argv).to.eql({ _: [], 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 () {
+ const 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 () {
+ const 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 () {
+ const 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 () {
+ function runTests (strict) {
+ if (!strict) {
+ // Skip this test in strict mode because this option is not specified
+ it('should provide options with dashes as camelCase properties', function () {
+ const result = parser(['--some-option'])
+
+ result.should.have.property('some-option').that.is.a('boolean').and.is.true // eslint-disable-line
+ result.should.have.property('someOption').that.is.a('boolean').and.is.true // eslint-disable-line
+ })
+ }
+
+ it('should provide count options with dashes as camelCase properties', function () {
+ const result = parser(['--some-option', '--some-option', '--some-option'], {
+ count: ['some-option']
+ })
+
+ result.should.have.property('some-option', 3)
+ result.should.have.property('someOption', 3)
+ })
+
+ it('should provide options with dashes and aliases as camelCase properties', function () {
+ const result = parser(['--some-option'], {
+ alias: {
+ 'some-horse': 'o'
+ }
+ })
+
+ result.should.have.property('some-option').that.is.a('boolean').and.is.true // eslint-disable-line
+ result.should.have.property('someOption').that.is.a('boolean').and.is.true // eslint-disable-line
+ })
+
+ it('should provide defaults of options with dashes as camelCase properties', function () {
+ const result = parser([], {
+ default: {
+ 'some-option': 'asdf'
+ }
+ })
+
+ result.should.have.property('some-option', 'asdf')
+ result.should.have.property('someOption', 'asdf')
+ })
+
+ it('should provide aliases of options with dashes as camelCase properties', function () {
+ const result = parser([], {
+ default: {
+ 'some-option': 'asdf'
+ },
+ alias: {
+ 'some-option': ['o']
+ }
+ })
+
+ result.should.have.property('o', 'asdf')
+ result.should.have.property('some-option', 'asdf')
+ result.should.have.property('someOption', 'asdf')
+ })
+
+ it('should not apply camel-case logic to 1-character options', function () {
+ const 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 () {
+ const result = parser([], {
+ alias: {
+ o: ['some-option']
+ },
+ default: {
+ o: 'asdf'
+ }
+ })
+
+ result.should.have.property('o', 'asdf')
+ result.should.have.property('some-option', 'asdf')
+ result.should.have.property('someOption', 'asdf')
+ })
+
+ it('should provide aliases with dashes as camelCase properties', function () {
+ const result = parser(['--some-option', 'val'], {
+ alias: {
+ o: 'some-option'
+ }
+ })
+
+ result.should.have.property('o').that.is.a('string').and.equals('val')
+ result.should.have.property('some-option').that.is.a('string').and.equals('val')
+ result.should.have.property('someOption').that.is.a('string').and.equals('val')
+ })
+
+ // https://github.com/yargs/yargs-parser/issues/95
+ it('should not duplicate option values when equivalent dashed aliases are provided', function () {
+ const result = parser(['--someOption', 'val'], {
+ alias: {
+ someOption: 'some-option'
+ }
+ })
+
+ result.should.have.property('some-option').that.is.a('string').and.equals('val')
+ result.should.have.property('someOption').that.is.a('string').and.equals('val')
+ })
+ }
+
+ describe('dashes and camelCase', function () {
+ runTests()
+ })
+
+ describe('dashes and camelCase (strict)', function () {
+ runTests(true)
+ })
+ })
+
+ describe('-', function () {
+ it('should set - as value of n', function () {
+ const argv = parser(['-n', '-'])
+ argv.should.have.property('n', '-')
+ argv.should.have.property('_').with.length(0)
+ })
+
+ it('should set - as a non-hyphenated value', function () {
+ const argv = parser(['-'])
+ argv.should.have.property('_').and.deep.equal(['-'])
+ })
+
+ it('should set - as a value of f', function () {
+ const argv = parser(['-f-'])
+ argv.should.have.property('f', '-')
+ argv.should.have.property('_').with.length(0)
+ })
+
+ it('should set b to true and set - as a non-hyphenated value when b is set as a boolean', function () {
+ const argv = parser(['-b', '-'], {
+ boolean: ['b']
+ })
+
+ argv.should.have.property('b', true)
+ argv.should.have.property('_').and.deep.equal(['-'])
+ })
+
+ it('should set - as the value of s when s is set as a string', function () {
+ const argv = parser(['-s', '-'], {
+ string: ['s']
+ })
+
+ argv.should.have.property('s', '-')
+ argv.should.have.property('_').with.length(0)
+ })
+ })
+
+ describe('count', function () {
+ it('should count the number of times a boolean is present', function () {
+ let parsed
+
+ parsed = parser(['-x'], {
+ count: ['verbose']
+ })
+ parsed.verbose.should.equal(0)
+
+ parsed = parser(['--verbose'], {
+ count: ['verbose']
+ })
+ parsed.verbose.should.equal(1)
+
+ parsed = parser(['--verbose', '--verbose'], {
+ count: ['verbose']
+ })
+ parsed.verbose.should.equal(2)
+
+ parsed = parser(['-vvv'], {
+ alias: {
+ v: ['verbose']
+ },
+ count: ['verbose']
+ })
+ parsed.verbose.should.equal(3)
+
+ parsed = parser(['--verbose', '--verbose', '-v', '--verbose'], {
+ count: ['verbose'],
+ alias: {
+ v: ['verbose']
+ }
+ })
+ parsed.verbose.should.equal(4)
+
+ parsed = parser(['--verbose', '--verbose', '-v', '-vv'], {
+ count: ['verbose'],
+ alias: {
+ v: ['verbose']
+ }
+ })
+ parsed.verbose.should.equal(5)
+ })
+
+ it('should not consume the next argument', function () {
+ let parsed = parser(['-v', 'moo'], {
+ count: 'v'
+ })
+ parsed.v.should.equal(1)
+ parsed.should.have.property('_').and.deep.equal(['moo'])
+
+ parsed = parser(['--verbose', 'moomoo', '--verbose'], {
+ count: 'verbose'
+ })
+ parsed.verbose.should.equal(2)
+ parsed.should.have.property('_').and.deep.equal(['moomoo'])
+ })
+
+ it('should use a default value as is when no arg given', function () {
+ let parsed = parser([], {
+ count: 'v',
+ default: { v: 3 }
+ })
+ parsed.v.should.equal(3)
+
+ parsed = parser([], {
+ count: 'v',
+ default: { v: undefined }
+ })
+ expect(parsed.v).to.be.undefined // eslint-disable-line
+
+ parsed = parser([], {
+ count: 'v',
+ default: { v: null }
+ })
+ expect(parsed.v).to.be.null // eslint-disable-line
+
+ parsed = parser([], {
+ count: 'v',
+ default: { v: false }
+ })
+ parsed.v.should.equal(false)
+
+ parsed = parser([], {
+ count: 'v',
+ default: { v: 'hello' }
+ })
+ parsed.v.should.equal('hello')
+ })
+
+ it('should ignore a default value when arg given', function () {
+ const parsed = parser(['-vv', '-v', '-v'], {
+ count: 'v',
+ default: { v: 1 }
+ })
+ parsed.v.should.equal(4)
+ })
+
+ it('should increment regardless of arg value', function () {
+ const parsed = parser([
+ '-v',
+ '-v=true',
+ '-v', 'true',
+ '-v=false',
+ '-v', 'false',
+ '--no-v',
+ '-v=999',
+ '-v=foobar'
+ ], { count: 'v' })
+ parsed.v.should.equal(8)
+ })
+
+ it('should add an error if counter is also set as array', function () {
+ const 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 () {
+ const 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 () {
+ it('should group values into an array if the same option is specified multiple times (duplicate-arguments-array=true)', function () {
+ const 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 () {
+ const 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)
+ })
+
+ it('should default an array to an empty array if passed as first option followed by another', function () {
+ const result = parser(['-a', '-b'], {
+ array: 'a'
+ })
+ result.should.have.property('a').and.deep.equal([])
+ })
+
+ it('should not attempt to default array if an element has already been populated', function () {
+ const result = parser(['-a', 'foo', 'bar', '-b'], {
+ array: 'a'
+ })
+ result.should.have.property('a').and.deep.equal(['foo', 'bar'])
+ })
+
+ it('should default argument to empty array if no value given', function () {
+ const result = parser(['-b', '--tag'], {
+ array: ['b', 'tag'],
+ default: { tag: [] }
+ })
+ result.b.should.deep.equal([])
+ result.tag.should.deep.equal([])
+ })
+
+ it('should place default of argument in array, when default provided', function () {
+ const result = parser(['-b', '--tag'], {
+ array: ['b', 'tag'],
+ default: { b: 33, tag: ['foo'] }
+ })
+ 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 () {
+ const result = parser(['-b', '33'], {
+ array: ['b']
+ })
+ Array.isArray(result.b).should.equal(true)
+ result.b[0].should.equal(33)
+ })
+
+ it('should add multiple argument values to the array', function () {
+ const result = parser(['-b', '33', '-b', 'hello'], {
+ array: 'b'
+ })
+ Array.isArray(result.b).should.equal(true)
+ result.b.should.include(33)
+ result.b.should.include('hello')
+ })
+
+ it('should allow array: true, to be set inside an option block', function () {
+ const result = parser(['-b', '33'], {
+ array: 'b'
+ })
+ Array.isArray(result.b).should.equal(true)
+ result.b.should.include(33)
+ })
+
+ // issue #103
+ it('should default camel-case alias to array type', function () {
+ const result = parser(['--ca-path', 'http://www.example.com'], {
+ array: ['ca-path']
+ })
+
+ Array.isArray(result['ca-path']).should.equal(true)
+ Array.isArray(result.caPath).should.equal(true)
+ })
+
+ it('should default alias to array type', function () {
+ const result = parser(['--ca-path', 'http://www.example.com'], {
+ array: 'ca-path',
+ alias: {
+ 'ca-path': 'c'
+ }
+ })
+
+ Array.isArray(result['ca-path']).should.equal(true)
+ Array.isArray(result.caPath).should.equal(true)
+ Array.isArray(result.c).should.equal(true)
+ })
+
+ // see: https://github.com/bcoe/yargs/issues/162
+ it('should eat non-hyphenated arguments until hyphenated option is hit', function () {
+ const result = parser(['-a=hello', 'world', '-b',
+ '33', '22', '--foo', 'red', 'green',
+ '--bar=cat', 'dog'], {
+ array: ['a', 'b', 'foo', '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('red')
+ result.foo.should.include('green')
+
+ Array.isArray(result.bar).should.equal(true)
+ result.bar.should.include('cat')
+ result.bar.should.include('dog')
+ })
+
+ // see: https://github.com/yargs/yargs-parser/pull/13
+ it('should support array for --foo= format when the key is a number', function () {
+ const result = parser(['--1=a', 'b'], {
+ array: ['1']
+ })
+
+ Array.isArray(result['1']).should.equal(true)
+ result['1'][0].should.equal('a')
+ result['1'][1].should.equal('b')
+ })
+
+ it('should support array for -f= and --bar= format when the value is dashed', function () {
+ const 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 () {
+ const 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 () {
+ const 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 () {
+ const 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 () {
+ const 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 () {
+ const result = parser(['--some-option', '1', '2'], {
+ array: ['someOption']
+ })
+ 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 () {
+ const 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 type `boolean` without value for arrays', function () {
+ const 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 () {
+ const 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 () {
+ const 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 () {
+ const 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 () {
+ const 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 () {
+ it('should allow the number of arguments following a key to be specified', function () {
+ const result = parser(['--foo', 'apple', 'bar'], {
+ narg: {
+ foo: 2
+ }
+ })
+
+ Array.isArray(result.foo).should.equal(true)
+ result.foo[0].should.equal('apple')
+ result.foo[1].should.equal('bar')
+ })
+
+ it('should raise an exception if -f== format is used for a key with no expected argument', function () {
+ const 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 () {
+ const 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 () {
+ const argv = parser.detailed('--foo apple', {
+ narg: {
+ foo: 2
+ }
+ })
+ argv.error.message.should.equal('Not enough arguments following: foo')
+ })
+
+ it('nargs is applied to aliases', function () {
+ const result = parser(['--bar', 'apple', 'bar'], {
+ narg: {
+ foo: 2
+ },
+ alias: {
+ foo: 'bar'
+ }
+ })
+ Array.isArray(result.foo).should.equal(true)
+ result.foo[0].should.equal('apple')
+ result.foo[1].should.equal('bar')
+ })
+
+ it('should apply nargs to flag arguments', function () {
+ const result = parser(['-f', 'apple', 'bar', 'blerg'], {
+ narg: {
+ f: 2
+ }
+ })
+
+ result.f[0].should.equal('apple')
+ result.f[1].should.equal('bar')
+ result._[0].should.equal('blerg')
+ })
+
+ it('should support nargs for -f= and --bar= format arguments', function () {
+ const 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 support nargs for -f= and --bar= format arguments with dashed values', function () {
+ const 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 () {
+ const expected = ['-f=apple', 'bar', 'blerg', '--bar=monkey', 'washing', 'cat']
+ const args = expected.slice()
+ parser(args, {
+ narg: {
+ f: 2,
+ bar: 2
+ }
+ })
+ args.should.deep.equal(expected)
+
+ parser.detailed(args, {
+ narg: {
+ f: 2,
+ bar: 2
+ }
+ })
+ args.should.deep.equal(expected)
+ })
+
+ it('allows multiple nargs to be set at the same time', function () {
+ const result = parser(['--foo', 'apple', 'bar', '--bar', 'banana', '-f'], {
+ narg: {
+ foo: 2,
+ bar: 1
+ }
+ })
+
+ Array.isArray(result.foo).should.equal(true)
+ result.foo[0].should.equal('apple')
+ result.foo[1].should.equal('bar')
+ result.bar.should.equal('banana')
+ result.f.should.equal(true)
+ })
+
+ // see: https://github.com/yargs/yargs-parser/pull/13
+ it('should support nargs for --foo= format when the key is a number', function () {
+ const result = parser(['--1=a', 'b'], {
+ narg: {
+ 1: 2
+ }
+ })
+
+ Array.isArray(result['1']).should.equal(true)
+ result['1'][0].should.equal('a')
+ result['1'][1].should.equal('b')
+ })
+
+ it('should not treat flag arguments as satisfying narg requirements', function () {
+ const result = parser.detailed(['--foo', '--bar', '99'], {
+ narg: {
+ foo: 1
+ }
+ })
+
+ result.argv.bar.should.equal(99)
+ 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 () {
+ const 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 () {
+ const result = parser(['--foo', 'a', 'b'], {
+ narg: {
+ foo: 1
+ }
+ })
+
+ result.foo.should.eql('a')
+ })
+
+ it('should ignore undefined configured nargs', function () {
+ const result = parser(['--foo', 'a', 'b'], {
+ narg: {
+ foo: undefined
+ }
+ })
+
+ result.foo.should.eql('a')
+ })
+
+ it('should default to 1 if configured narg is NaN', function () {
+ const result = parser(['--foo', 'a', 'b'], {
+ narg: {
+ foo: NaN
+ }
+ })
+
+ result.foo.should.eql('a')
+ })
+ })
+
+ describe('env vars', function () {
+ it('should apply all env vars if prefix is empty', function () {
+ process.env.ONE_FISH = 'twofish'
+ process.env.RED_FISH = 'bluefish'
+ const result = parser([], {
+ envPrefix: ''
+ })
+ result.oneFish.should.equal('twofish')
+ 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'
+ const 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'
+ const 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'
+ process.env.GREEN_EGGS = 'sam'
+ process.env.GREEN_HAM = 'iam'
+ const result = parser([], {
+ envPrefix: 'GREEN'
+ })
+
+ result.eggs.should.equal('sam')
+ result.ham.should.equal('iam')
+ expect(result.oneFish).to.be.undefined // eslint-disable-line
+ expect(result.redFish).to.be.undefined // eslint-disable-line
+ })
+
+ it('should set aliases for options defined by env var', function () {
+ process.env.AIRFORCE_ONE = 'two'
+ const result = parser([], {
+ envPrefix: 'AIRFORCE',
+ alias: {
+ 1: ['one', 'uno']
+ }
+ })
+
+ result['1'].should.equal('two')
+ result.one.should.equal('two')
+ result.uno.should.equal('two')
+ })
+
+ it('should prefer command line value over env var', function () {
+ process.env.FOO_BAR = 'ignore'
+ const result = parser(['--foo-bar', 'baz'], {
+ envPrefix: ''
+ })
+
+ result.fooBar.should.equal('baz')
+ })
+
+ it('should respect type for args defined by env var', function () {
+ process.env.MY_TEST_STRING = '1'
+ process.env.MY_TEST_NUMBER = '2'
+ const result = parser([], {
+ string: 'string',
+ envPrefix: 'MY_TEST_'
+ })
+
+ result.string.should.equal('1')
+ result.number.should.equal(2)
+ })
+
+ it('should set option from aliased env var', function () {
+ process.env.SPACE_X = 'awesome'
+ const result = parser([], {
+ alias: {
+ xactly: 'x'
+ },
+ envPrefix: 'SPACE'
+ })
+
+ result.xactly.should.equal('awesome')
+ })
+
+ it('should prefer env var value over configured default', function () {
+ process.env.FOO_BALL = 'wut'
+ process.env.FOO_BOOL = 'true'
+ const result = parser([], {
+ envPrefix: 'FOO',
+ default: {
+ ball: 'baz',
+ bool: false
+ },
+ boolean: 'bool',
+ string: 'ball'
+ })
+
+ result.ball.should.equal('wut')
+ result.bool.should.equal(true)
+ })
+
+ const jsonPath = path.resolve(__dirname, './fixtures/config.json')
+ it('should prefer environment variables over config file', function () {
+ process.env.CFG_HERP = 'zerp'
+ const result = parser(['--cfg', jsonPath], {
+ envPrefix: 'CFG',
+ config: 'cfg',
+ string: 'herp',
+ default: {
+ herp: 'nerp'
+ }
+ })
+
+ result.herp.should.equal('zerp')
+ })
+
+ it('should support an env var value as config file option', function () {
+ process.env.TUX_CFG = jsonPath
+ const result = parser([], {
+ envPrefix: 'TUX',
+ config: ['cfg'],
+ default: {
+ z: 44
+ }
+ })
+
+ result.should.have.property('herp')
+ result.should.have.property('foo')
+ result.should.have.property('version')
+ result.should.have.property('truthy')
+ result.z.should.equal(55)
+ })
+
+ it('should prefer cli config file option over env var config file option', function () {
+ process.env.MUX_CFG = path.resolve(__dirname, '../package.json')
+ const result = parser(['--cfg', jsonPath], {
+ envPrefix: 'MUX',
+ config: 'cfg'
+ })
+
+ result.should.have.property('herp')
+ result.should.have.property('foo')
+ result.should.have.property('version')
+ result.should.have.property('truthy')
+ result.z.should.equal(55)
+ })
+
+ it('should apply all nested env vars', function () {
+ process.env.TEST_A = 'a'
+ process.env.TEST_NESTED_OPTION__FOO = 'baz'
+ process.env.TEST_NESTED_OPTION__BAR = 'bar'
+ const result = parser(['--nestedOption.foo', 'bar'], {
+ envPrefix: 'TEST'
+ })
+
+ result.should.have.property('a', 'a')
+ result.should.have.property('nestedOption').and.deep.equal({
+ foo: 'bar',
+ bar: 'bar'
+ })
+ })
+
+ it('should apply nested env var if argv value is using default value', function () {
+ process.env.TEST_A = 'a'
+ process.env.TEST_NESTED_OPTION__FOO = 'baz'
+ process.env.TEST_NESTED_OPTION__BAR = 'bar'
+ const result = parser([], {
+ envPrefix: 'TEST',
+ default: {
+ 'nestedOption.foo': 'banana'
+ }
+ })
+
+ result.should.have.property('a', 'a')
+ result.should.have.property('nestedOption').and.deep.equal({
+ foo: 'baz',
+ bar: 'bar'
+ })
+ })
+ })
+
+ describe('configuration', function () {
+ describe('short option groups', function () {
+ it('allows short-option-groups to be disabled', function () {
+ let parse = parser(['-cats=meow'], {
+ configuration: {
+ 'short-option-groups': false
+ }
+ })
+ parse.cats.should.equal('meow')
+ parse = parser(['-cats', 'meow'], {
+ configuration: {
+ 'short-option-groups': false
+ }
+ })
+ parse.cats.should.equal('meow')
+ })
+ })
+
+ describe('camel-case expansion', function () {
+ it('does not expand camel-case aliases', function () {
+ const parsed = parser.detailed([], {
+ alias: {
+ 'foo-bar': ['x']
+ },
+ configuration: {
+ 'camel-case-expansion': false
+ }
+ })
+
+ expect(parsed.newAliases.fooBar).to.equal(undefined)
+ expect(parsed.aliases.fooBar).to.equal(undefined)
+ })
+
+ it('does not expand camel-case keys', function () {
+ const parsed = parser.detailed(['--foo-bar=apple'], {
+ configuration: {
+ 'camel-case-expansion': false
+ }
+ })
+
+ expect(parsed.argv.fooBar).to.equal(undefined)
+ expect(parsed.argv['foo-bar']).to.equal('apple')
+ })
+ })
+
+ describe('dot notation', function () {
+ it('does not expand dot notation defaults', function () {
+ const parsed = parser([], {
+ default: {
+ 'foo.bar': 'x'
+ },
+ configuration: {
+ 'dot-notation': false
+ }
+ })
+
+ expect(parsed['foo.bar']).to.equal('x')
+ })
+
+ it('does not expand dot notation arguments', function () {
+ let parsed = parser(['--foo.bar', 'banana'], {
+ configuration: {
+ 'dot-notation': false
+ }
+ })
+ expect(parsed['foo.bar']).to.equal('banana')
+ parsed = parser(['--foo.bar=banana'], {
+ configuration: {
+ 'dot-notation': false
+ }
+ })
+ expect(parsed['foo.bar']).to.equal('banana')
+ })
+
+ it('should use value from cli, if cli overrides dot notation default', function () {
+ const parsed = parser(['--foo.bar', 'abc'], {
+ default: {
+ 'foo.bar': 'default'
+ },
+ configuration: {
+ 'dot-notation': false
+ }
+ })
+
+ expect(parsed['foo.bar']).to.equal('abc')
+ })
+
+ it('should also override dot notation alias', function () {
+ const parsed = parser(['--foo.bar', 'abc'], {
+ alias: {
+ 'foo.bar': ['alias.bar']
+ },
+ default: {
+ 'foo.bar': 'default'
+ },
+ configuration: {
+ 'dot-notation': false
+ }
+ })
+
+ expect(parsed['alias.bar']).to.equal('abc')
+ })
+
+ it('does not expand alias of first element of dot notation arguments', function () {
+ const parsed = parser(['--foo.bar', 'banana'], {
+ alias: {
+ foo: ['f']
+ },
+ configuration: {
+ 'dot-notation': false
+ }
+ })
+ expect(parsed['foo.bar']).to.equal('banana')
+ expect(parsed).not.to.include.keys('f.bar')
+ })
+
+ // addresses https://github.com/yargs/yargs/issues/716
+ it('does not append nested-object keys from config to top-level key', function () {
+ const parsed = parser([], {
+ alias: {
+ foo: ['f']
+ },
+ configuration: {
+ 'dot-notation': false
+ },
+ configObjects: [
+ {
+ 'website.com': {
+ a: 'b',
+ b: 'c'
+ }
+ }
+ ]
+ })
+
+ parsed['website.com'].should.deep.equal({
+ a: 'b',
+ b: 'c'
+ })
+ })
+ })
+
+ describe('parse-positional-numbers', () => {
+ it('does not parse positionals into numbers when false', function () {
+ const parsed = parser(['5.0', '3'], {
+ configuration: {
+ 'parse-positional-numbers': false
+ }
+ })
+ expect(parsed._[0]).to.equal('5.0')
+ expect(parsed._[1]).to.equal('3')
+ })
+
+ it('parses positionals into numbers by default', function () {
+ const parsed = parser(['5.0', '3'])
+ expect(parsed._[0]).to.equal(5)
+ expect(parsed._[1]).to.equal(3)
+ })
+ })
+
+ describe('parse numbers', function () {
+ it('does not coerce defaults into numbers', function () {
+ const parsed = parser([], {
+ default: {
+ foo: '5'
+ },
+ configuration: {
+ 'parse-numbers': false
+ }
+ })
+
+ expect(parsed.foo).to.equal('5')
+ })
+
+ it('does not coerce arguments into numbers', function () {
+ const parsed = parser(['--foo', '5'], {
+ configuration: {
+ 'parse-numbers': false
+ }
+ })
+ expect(parsed.foo).to.equal('5')
+ })
+
+ it('does not coerce positional arguments into numbers', function () {
+ const parsed = parser(['5'], {
+ configuration: {
+ 'parse-numbers': false
+ }
+ })
+ expect(parsed._[0]).to.equal('5')
+ })
+
+ it('parses number if option explicitly set to number type', function () {
+ const 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)
+ })
+
+ it('should coerce elements of number typed arrays to numbers', function () {
+ const 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 () {
+ it('does not negate arguments prefixed with --no-', function () {
+ const parsed = parser(['--no-dice'], {
+ configuration: {
+ 'boolean-negation': false
+ }
+ })
+
+ parsed['no-dice'].should.equal(true)
+ expect(parsed.dice).to.equal(undefined)
+ })
+
+ it('negates boolean arguments with correct prefix', function () {
+ const parsed = parser(['--foodice'], {
+ configuration: {
+ 'negation-prefix': 'foo'
+ }
+ })
+
+ expect(parsed.dice).to.equal(false)
+ })
+ })
+
+ describe('duplicate arguments array', function () {
+ it('adds duplicate argument to array', function () {
+ const parsed = parser('-x a -x b', {
+ configuration: {
+ 'duplicate-arguments-array': true
+ }
+ })
+
+ parsed.x.should.deep.equal(['a', 'b'])
+ })
+ it('keeps only last argument', function () {
+ const parsed = parser('-x a -x b', {
+ configuration: {
+ 'duplicate-arguments-array': false
+ }
+ })
+
+ parsed.x.should.equal('b')
+ })
+ it('does not interfere with nargs', function () {
+ const 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 () {
+ it('flattens duplicate array type', function () {
+ const parsed = parser('-x a b -x c d', {
+ array: ['x'],
+ configuration: {
+ 'flatten-duplicate-arrays': true
+ }
+ })
+
+ parsed.x.should.deep.equal(['a', 'b', 'c', 'd'])
+ })
+ it('nests duplicate array types', function () {
+ const parsed = parser('-x a b -x c d', {
+ array: ['x'],
+ configuration: {
+ 'flatten-duplicate-arrays': false
+ }
+ })
+
+ parsed.x.should.deep.equal([['a', 'b'], ['c', 'd']])
+ })
+ it('nests duplicate array types of more than 2', function () {
+ const 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 () {
+ const parsed = parser('-x a b', {
+ array: ['x'],
+ configuration: {
+ 'flatten-duplicate-arrays': false
+ }
+ })
+
+ parsed.x.should.deep.equal(['a', 'b'])
+ })
+ it('flattens duplicate array type, when argument uses dot notation', function () {
+ const parsed = parser('-x.foo a -x.foo b', {
+ array: ['x.foo'],
+ configuration: {
+ 'flatten-duplicate-arrays': true
+ }
+ })
+
+ parsed.x.should.deep.equal({ foo: ['a', 'b'] })
+ })
+ })
+
+ describe('duplicate-arguments-array VS flatten-duplicate-arrays', function () {
+ /*
+ duplicate=false, flatten=false
+ type=array
+ [-x 1 2 3] => [1, 2, 3]
+ [-x 1 2 3 -x 2 3 4] => [2, 3, 4]
+ type=string/number/etc
+ [-x 1 -x 2 -x 3] => 3
+
+ duplicate=false, flatten=true
+ type=array
+ [-x 1 2 3] => [1, 2, 3]
+ [-x 1 2 3 -x 2 3 4] => [2, 3, 4]
+ type=string/number/etc
+ [-x 1 -x 2 -x 3] => 3
+
+ duplicate=true, flatten=true
+ type=array
+ [-x 1 2 3] => [1, 2, 3]
+ [-x 1 2 3 -x 2 3 4] => [1, 2, 3, 2, 3, 4]
+ type=string/number/etc
+ [-x 1 -x 2 -x 3] => [1, 2, 3]
+
+ duplicate=true, flatten=false
+ type=array
+ [-x 1 2 3] => [1, 2, 3]
+ [-x 1 2 3 -x 2 3 4] => [[1, 2, 3], [2, 3, 4]]
+ type=string/number/etc
+ [-x 1 -x 2 -x 3] => [1, 2, 3]
+ */
+ describe('duplicate=false, flatten=false,', function () {
+ describe('type=array', function () {
+ it('[-x 1 2 3] => [1, 2, 3]', function () {
+ const parsed = parser('-x 1 2 3', {
+ array: ['x'],
+ configuration: {
+ 'duplicate-arguments-array': false,
+ 'flatten-duplicate-arrays': false
+ }
+ })
+ parsed.x.should.deep.equal([1, 2, 3])
+ })
+ it('[-x 1 2 3 -x 2 3 4] => [2, 3, 4]', function () {
+ const parsed = parser('-x 1 2 3 -x 2 3 4', {
+ array: ['x'],
+ configuration: {
+ 'duplicate-arguments-array': false,
+ 'flatten-duplicate-arrays': false
+ }
+ })
+ parsed.x.should.deep.equal([2, 3, 4])
+ })
+ })
+ describe('type=number', function () {
+ it('[-x 1 -x 2 -x 3] => 3', function () {
+ const parsed = parser('-x 1 -x 2 -x 3', {
+ number: 'x',
+ configuration: {
+ 'duplicate-arguments-array': false,
+ 'flatten-duplicate-arrays': false
+ }
+ })
+ parsed.x.should.deep.equal(3)
+ })
+ })
+ describe('type=boolean', function () {
+ it('[-x true -x true -x false] => false', function () {
+ const 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 () {
+ it('[-x 1 2 3] => [1, 2, 3]', function () {
+ const parsed = parser('-x 1 2 3', {
+ array: ['x'],
+ configuration: {
+ 'duplicate-arguments-array': false,
+ 'flatten-duplicate-arrays': true
+ }
+ })
+ parsed.x.should.deep.equal([1, 2, 3])
+ })
+ it('[-x 1 2 3 -x 2 3 4] => [2, 3, 4]', function () {
+ const parsed = parser('-x 1 2 3 -x 2 3 4', {
+ array: ['x'],
+ configuration: {
+ 'duplicate-arguments-array': false,
+ 'flatten-duplicate-arrays': true
+ }
+ })
+ parsed.x.should.deep.equal([2, 3, 4])
+ })
+ })
+ describe('type=number', function () {
+ it('[-x 1 -x 2 -x 3] => 3', function () {
+ const parsed = parser('-x 1 -x 2 -x 3', {
+ number: 'x',
+ configuration: {
+ 'duplicate-arguments-array': false,
+ 'flatten-duplicate-arrays': true
+ }
+ })
+ parsed.x.should.deep.equal(3)
+ })
+ })
+ describe('type=boolean', function () {
+ it('[-x true -x true -x false] => false', function () {
+ const 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 () {
+ it('[-x 1 2 3] => [1, 2, 3]', function () {
+ const parsed = parser('-x 1 2 3', {
+ array: ['x'],
+ configuration: {
+ 'duplicate-arguments-array': true,
+ 'flatten-duplicate-arrays': true
+ }
+ })
+ parsed.x.should.deep.equal([1, 2, 3])
+ })
+ it('[-x 1 2 3 -x 2 3 4] => [1, 2, 3, 2, 3, 4]', function () {
+ const parsed = parser('-x 1 2 3 -x 2 3 4', {
+ array: ['x'],
+ configuration: {
+ 'duplicate-arguments-array': true,
+ 'flatten-duplicate-arrays': true
+ }
+ })
+ parsed.x.should.deep.equal([1, 2, 3, 2, 3, 4])
+ })
+ })
+ describe('type=number', function () {
+ it('[-x 1 -x 2 -x 3] => [1, 2, 3]', function () {
+ const parsed = parser('-x 1 -x 2 -x 3', {
+ number: 'x',
+ configuration: {
+ 'duplicate-arguments-array': true,
+ 'flatten-duplicate-arrays': true
+ }
+ })
+ parsed.x.should.deep.equal([1, 2, 3])
+ })
+ })
+ describe('type=boolean', function () {
+ // in the casse of boolean arguments, only the last argument is used:
+ it('[-x true -x true -x false] => false', function () {
+ const parsed = parser('-x true -x true -x false', {
+ boolean: ['x'],
+ configuration: {
+ 'duplicate-arguments-array': true,
+ 'flatten-duplicate-arrays': true
+ }
+ })
+ parsed.x.should.deep.equal(false)
+ })
+ })
+ })
+ describe('duplicate=true, flatten=false,', function () {
+ describe('type=array', function () {
+ it('[-x 1 -x 2 -x 3] => [[1], [2], [3]]', function () {
+ const parsed = parser('-x 1 -x 2 -x 3', {
+ array: ['x'],
+ configuration: {
+ 'duplicate-arguments-array': true,
+ 'flatten-duplicate-arrays': false
+ }
+ })
+ parsed.x.should.deep.equal([[1], [2], [3]])
+ })
+ it('[-x 1 2 3 -x 2 3 4] => [[1, 2, 3], [ 2, 3, 4]]', function () {
+ const parsed = parser('-x 1 2 3 -x 2 3 4', {
+ array: ['x'],
+ configuration: {
+ 'duplicate-arguments-array': true,
+ 'flatten-duplicate-arrays': false
+ }
+ })
+ parsed.x.should.deep.equal([[1, 2, 3], [2, 3, 4]])
+ })
+ })
+ describe('type=number', function () {
+ it('[-x 1 -x 2 -x 3] => [1, 2, 3]', function () {
+ const parsed = parser('-x 1 -x 2 -x 3', {
+ number: 'x',
+ configuration: {
+ 'duplicate-arguments-array': true,
+ 'flatten-duplicate-arrays': false
+ }
+ })
+ parsed.x.should.deep.equal([1, 2, 3])
+ })
+ })
+ describe('type=boolean', function () {
+ it('[-x true -x true -x false] => false', function () {
+ const parsed = parser('-x true -x true -x false', {
+ boolean: ['x'],
+ configuration: {
+ 'duplicate-arguments-array': true,
+ 'flatten-duplicate-arrays': false
+ }
+ })
+ parsed.x.should.deep.equal(false)
+ })
+ })
+ })
+ })
+
+ describe('populate--', function () {
+ it('should populate "_" by default', function () {
+ const result = parser([
+ 'bare',
+ '--', '-h', 'eek', '--'
+ ])
+ result.should.have.property('_').and.deep.equal(['bare', '-h', 'eek', '--'])
+ result.should.not.have.property('--')
+ })
+
+ it('should populate "_" when given config with "short-option-groups" false', function () {
+ const 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 () {
+ const result = parser([
+ '--name=meowmers', 'bare', '-cats', 'woo', 'moxy',
+ '-h', 'awesome', '--multi=quux',
+ '--key', 'value',
+ '-b', '--bool', '--no-meep', '--multi=baz',
+ '--', '--not-a-flag', '-', '-h', '-multi', '--', 'eek'
+ ], {
+ configuration: {
+ 'populate--': true
+ }
+ })
+ result.should.have.property('c', true)
+ result.should.have.property('a', true)
+ result.should.have.property('t', true)
+ result.should.have.property('s', 'woo')
+ result.should.have.property('h', 'awesome')
+ result.should.have.property('b', true)
+ result.should.have.property('bool', true)
+ result.should.have.property('key', 'value')
+ result.should.have.property('multi').and.deep.equal(['quux', 'baz'])
+ result.should.have.property('meep', false)
+ result.should.have.property('name', 'meowmers')
+ result.should.have.property('_').and.deep.equal(['bare', 'moxy'])
+ result.should.have.property('--').and.deep.equal(['--not-a-flag', '-', '-h', '-multi', '--', 'eek'])
+ })
+ })
+
+ describe('set-placeholder-key', function () {
+ it('should not set placeholder key by default', function () {
+ const parsed = parser([], {
+ string: ['a']
+ })
+ parsed.should.not.have.property('a')
+ })
+
+ it('should set placeholder key to "undefined"', function () {
+ const parsed = parser([], {
+ array: ['a'],
+ boolean: ['b'],
+ string: ['c'],
+ number: ['d'],
+ count: ['e'],
+ normalize: ['f'],
+ narg: { g: 2 },
+ coerce: {
+ h: function (arg) {
+ return arg
+ }
+ },
+ configuration: { 'set-placeholder-key': true }
+ })
+ parsed.should.have.property('a')
+ expect(parsed.a).to.be.equal(undefined)
+ parsed.should.have.property('b')
+ expect(parsed.b).to.be.equal(undefined)
+ parsed.should.have.property('c')
+ expect(parsed.c).to.be.equal(undefined)
+ parsed.should.have.property('d')
+ expect(parsed.d).to.be.equal(undefined)
+ parsed.should.have.property('e')
+ expect(parsed.f).to.be.equal(undefined)
+ parsed.should.have.property('g')
+ expect(parsed.g).to.be.equal(undefined)
+ parsed.should.have.property('h')
+ expect(parsed.h).to.be.equal(undefined)
+ })
+
+ it('should not set placeholder for key with a default value', function () {
+ const parsed = parser([], {
+ string: ['a'],
+ default: { a: 'hello' },
+ configuration: { 'set-placeholder-key': true }
+ })
+ parsed.a.should.equal('hello')
+ })
+
+ it('should not set placeholder key with dot notation', function () {
+ const parsed = parser([], {
+ string: ['a.b']
+ })
+ parsed.should.not.have.property('a')
+ 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 () {
+ const 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 () {
+ it('gets the entire rest of line', function () {
+ const 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 () {
+ const 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 () {
+ const 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']
+ })
+ })
+ })
+
+ 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: {
+ 'unknown-options-as-args': 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: {
+ 'unknown-options-as-args': 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: {
+ 'unknown-options-as-args': 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: {
+ 'unknown-options-as-args': 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: {
+ 'unknown-options-as-args': 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: {
+ 'unknown-options-as-args': 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: {
+ 'unknown-options-as-args': 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: {
+ 'unknown-options-as-args': true
+ }
+ })
+ argv.should.deep.equal({
+ _: ['-u', 2],
+ 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: {
+ 'unknown-options-as-args': 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: {
+ 'unknown-options-as-args': 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: {
+ 'unknown-options-as-args': 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: {
+ 'unknown-options-as-args': true
+ }
+ })
+ argv.should.deep.equal({
+ _: [],
+ k: true,
+ v: true
+ })
+ })
+ it('should parse negative numbers', function () {
+ const argv = parser('-k -33', {
+ boolean: ['k'],
+ configuration: {
+ 'unknown-options-as-args': true
+ }
+ })
+ argv.should.deep.equal({
+ _: [-33],
+ 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
+ })
+ })
+ // See: https://github.com/yargs/yargs/issues/1869
+ // ----=hello ---=hello ---- hello.
+ it('should not populate "--" for key values other than "--"', () => {
+ const argv = parser('----=test', {
+ configuration: {
+ 'populate--': true
+ }
+ })
+ argv._.should.eql(['----=test'])
+ expect(argv['--']).to.equal(undefined)
+ })
+ // 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
+ })
+ })
+
+ it('should coerce unknown options that look numeric into numbers', () => {
+ const argv = parser('--known-arg 33', {
+ boolean: ['known-arg']
+ })
+ argv.should.deep.equal({
+ _: [33],
+ 'known-arg': true,
+ knownArg: true
+ })
+ })
+ })
+
+ // 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
+ it('defaults to empty array if array option is provided no values', function () {
+ let parsed = parser(['-f'], {
+ alias: {
+ f: 'files'
+ },
+ array: ['files']
+ })
+ parsed.f.should.deep.equal([])
+ parsed.files.should.deep.equal([])
+
+ parsed = parser(['--files'], {
+ alias: {
+ f: 'files'
+ },
+ array: ['files']
+ })
+ parsed.f.should.deep.equal([])
+ parsed.files.should.deep.equal([])
+
+ parsed = parser(['-f', '-y'], {
+ alias: {
+ f: 'files'
+ },
+ array: ['files']
+ })
+ parsed.f.should.deep.equal([])
+ parsed.files.should.deep.equal([])
+ })
+
+ describe('coerce', function () {
+ it('applies coercion function to simple arguments', function () {
+ const parsed = parser(['--foo', '99'], {
+ coerce: {
+ foo: function (arg) {
+ return arg * -1
+ }
+ }
+ })
+ parsed.foo.should.equal(-99)
+ })
+
+ it('applies coercion function to aliases', function () {
+ const parsed = parser(['--foo', '99'], {
+ coerce: {
+ f: function (arg) {
+ return arg * -1
+ }
+ },
+ alias: {
+ f: ['foo']
+ }
+ })
+ parsed.foo.should.equal(-99)
+ parsed.f.should.equal(-99)
+ })
+
+ it('applies coercion function to all dot options', function () {
+ const parsed = parser(['--foo.bar', 'nananana'], {
+ coerce: {
+ foo: function (val) {
+ val.bar += ', batman!'
+ return val
+ }
+ }
+ })
+ parsed.foo.bar.should.equal('nananana, batman!')
+ })
+
+ it('applies coercion to defaults', function () {
+ const parsed = parser([], {
+ default: { foo: 'bar' },
+ coerce: {
+ foo: function (val) {
+ return val.toUpperCase()
+ }
+ }
+ })
+ parsed.foo.should.equal('BAR')
+ })
+
+ it('applies coercion function to an implicit array', function () {
+ const parsed = parser(['--foo', '99', '-f', '33'], {
+ coerce: {
+ f: function (arg) {
+ return arg.map(function (a) {
+ return a * -1
+ })
+ }
+ },
+ alias: {
+ f: ['foo']
+ }
+ })
+ parsed.f.should.deep.equal([-99, -33])
+ parsed.foo.should.deep.equal([-99, -33])
+ })
+
+ it('applies coercion function to an explicit array', function () {
+ const parsed = parser(['--foo', '99', '-f', '33'], {
+ coerce: {
+ f: function (arg) {
+ return arg.map(function (a) {
+ return a * -1
+ })
+ }
+ },
+ array: ['foo'],
+ alias: {
+ f: ['foo']
+ }
+ })
+ parsed.f.should.deep.equal([-99, -33])
+ parsed.foo.should.deep.equal([-99, -33])
+ })
+
+ it('applies coercion function to _', function () {
+ const parsed = parser(['99', '33'], {
+ coerce: {
+ _: function (arg) {
+ return arg.map(function (a) {
+ return a * -1
+ })
+ }
+ }
+ })
+ parsed._.should.deep.equal([-99, -33])
+ })
+
+ // see: https://github.com/yargs/yargs/issues/550
+ it('coercion function can be used to parse large #s', function () {
+ const fancyNumberParser = function (arg) {
+ if (arg.length > 10) return arg
+ else return parseInt(arg)
+ }
+ const parsed = parser(['--foo', '88888889999990000998989898989898', '--bar', '998'], {
+ coerce: {
+ foo: fancyNumberParser,
+ bar: fancyNumberParser
+ }
+ })
+ ; (typeof parsed.foo).should.equal('string')
+ parsed.foo.should.equal('88888889999990000998989898989898')
+ ; (typeof parsed.bar).should.equal('number')
+ parsed.bar.should.equal(998)
+ })
+
+ it('populates argv.error, if an error is thrown', function () {
+ const parsed = parser.detailed(['--foo', '99'], {
+ coerce: {
+ foo: function (arg) {
+ throw Error('banana')
+ }
+ }
+ })
+ parsed.error.message.should.equal('banana')
+ })
+
+ it('populates argv.error, if an error is thrown for an explicit array', function () {
+ const parsed = parser.detailed(['--foo', '99'], {
+ array: ['foo'],
+ coerce: {
+ foo: function (arg) {
+ throw Error('foo is array: ' + Array.isArray(arg))
+ }
+ }
+ })
+ parsed.error.message.should.equal('foo is array: true')
+ })
+
+ // see: https://github.com/yargs/yargs-parser/issues/76
+ it('only runs coercion functions once, even with aliases', function () {
+ let runcount = 0
+ const func = (arg) => {
+ runcount++
+ return undefined
+ }
+ parser(['--foo', 'bar'], {
+ alias: {
+ foo: ['f', 'foo-bar', 'bar'],
+ b: ['bar']
+ },
+ coerce: {
+ bar: func
+ }
+ })
+ runcount.should.equal(1)
+ })
+ })
+
+ // see: https://github.com/yargs/yargs-parser/issues/37
+ it('normalizes all paths in array when provided via config object', function () {
+ const argv = parser(['--foo', 'bar'], {
+ array: ['a'],
+ normalize: ['a'],
+ configObjects: [{ a: ['bin/../a.txt', 'bin/../b.txt'] }]
+ })
+ argv.a.should.deep.equal(['a.txt', 'b.txt'])
+ })
+
+ // 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'])
+ argv.foo.should.equal('93940495950949399948393')
+ })
+
+ it('does not magically convert scientific notation larger than Number.MAX_SAFE_INTEGER', () => {
+ 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'], {
+ number: ['foo']
+ })
+ 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 () {
+ const 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 () {
+ const 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 () {
+ const 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')
+ argv.foo[1].should.equal('bux')
+ argv.foo[2].bar.should.equal('baz')
+ 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')
+ })
+ })
+
+ // 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')
+ })
+
+ 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
+ })
+ })
+
+ it('only removes camel case expansion if keys have hyphen', function () {
+ const argv = parser(['--foo', '1', '-a', '2'], {
+ configuration: {
+ 'strip-aliased': true
+ },
+ alias: {
+ aliased1: ['Foo'],
+ aliased2: ['A']
+ }
+ })
+ argv.should.deep.equal({
+ _: [],
+ foo: 1,
+ a: 2
+ })
+ })
+ })
+
+ describe('prototype collisions', () => {
+ it('parses unknown argument colliding with prototype', () => {
+ const parse = parser(['--toString'])
+ parse.toString.should.equal(true)
+ })
+
+ it('parses unknown argument colliding with prototype, when unknown options as args', () => {
+ const parse = parser(['--toString'], {
+ configuration: {
+ 'unknown-options-as-args': true
+ }
+ })
+ parse._.should.include('--toString')
+ })
+
+ it('handles "alias" colliding with prototype', () => {
+ const 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', () => {
+ const parse = parser(['--toString', '88', '--toString', '99'])
+ parse.toString.should.eql([88, 99])
+ })
+
+ it('handle dot notation colliding with alias', () => {
+ const parse = parser(['--toString.cool', 'apple'])
+ parse.toString.cool.should.equal('apple')
+ })
+
+ it('handles "arrays" colliding with prototype', () => {
+ const parse = parser(['--toString', '99', '100'], {
+ array: ['toString']
+ })
+ parse.toString.should.eql([99, 100])
+ })
+
+ it('handles "arrays" colliding with prototype', () => {
+ const parse = parser(['--toString', '99', '100'], {
+ array: ['toString']
+ })
+ parse.toString.should.eql([99, 100])
+ })
+
+ it('handles "strings" colliding with prototype', () => {
+ const parse = parser(['--toString', '99'], {
+ string: ['toString']
+ })
+ parse.toString.should.eql('99')
+ })
+
+ it('handles "numbers" colliding with prototype', () => {
+ const parse = parser(['--toString', '99'], {
+ number: ['toString'],
+ configuration: {
+ 'parse-numbers': false
+ }
+ })
+ parse.toString.should.eql(99)
+ })
+
+ it('handles "counts" colliding with prototype', () => {
+ const parse = parser(['--toString', '--toString', '--toString'], {
+ count: ['toString']
+ })
+ parse.toString.should.eql(3)
+ })
+
+ it('handles "normalize" colliding with prototype', () => {
+ const parse = parser(['--toString', './node_modules/chai'], {
+ normalize: ['toString']
+ })
+ parse.toString.should.include('node_modules')
+ })
+
+ it('handles "normalize" colliding with prototype', () => {
+ const 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 () {
+ const argv = parser(['--foo', 'bar'], {
+ alias: {
+ z: 'zoom'
+ },
+ default: {
+ settings: jsonPath
+ },
+ config: 'settings'
+ })
+ argv.toString.should.equal('method name')
+ })
+
+ it('handles "nargs" colliding with prototype', () => {
+ const 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', () => {
+ const parse = parser(['--toString', '33'], {
+ coerce: {
+ toString: (val) => {
+ return val * 2
+ }
+ }
+ })
+ parse.toString.should.equal(66)
+ })
+ })
+
+ // See: https://github.com/facebook/jest/issues/9517
+ it('does not collect arguments configured as booleans into implicit array', () => {
+ const parse = parser(['--infinite', 'true', '--infinite', 'true', '--no-infinite'], {
+ boolean: 'infinite'
+ })
+ parse.infinite.should.equal(false)
+ })
+
+ // 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', () => {
+ const parse = parser.detailed(['-a', 'apple', 'banana'], {
+ array: 'a',
+ narg: {
+ a: 1
+ }
+ })
+ expect(parse.error).to.be.null // eslint-disable-line
+ parse.argv.a.should.eql(['apple'])
+ parse.argv._.should.eql(['banana'])
+ })
+
+ // 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', () => {
+ const 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', () => {
+ const parse = parser.detailed(['-a'], {
+ array: 'a',
+ narg: {
+ a: NaN
+ }
+ })
+ parse.error.message.should.match(/Not enough arguments/)
+ })
+
+ it('returns an error if not enough positionals were provided for nargs', () => {
+ const parse = parser.detailed(['-a', '33'], {
+ array: 'a',
+ narg: {
+ a: 2
+ }
+ })
+ parse.argv.a.should.eql([33])
+ 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', () => {
+ const 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', () => {
+ const 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', () => {
+ 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])
+ })
+ })
+
+ 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
+ }
+ }
+ }
+ })
+ })
+
+ 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
+ })
+
+ // Refs: https://github.com/yargs/yargs-parser/issues/386
+ describe('perf', () => {
+ const i = 100000
+ describe('unknown-options-as-args', () => {
+ it('parses long chain of "-" with reasonable performance', function () {
+ this.timeout(500)
+ const s = (new Array(i).fill('-').join('')) + 'a'
+ const parsed = parser([s], { configuration: { 'unknown-options-as-args': true } })
+ parsed._[0].should.equal(s)
+ })
+ it('parses long chain of "-a-a" with reasonable performance', function () {
+ this.timeout(500)
+ const s = '-' + (new Array(i).fill('-a').join('')) + '=35'
+ const parsed = parser([s], { configuration: { 'unknown-options-as-args': true } })
+ parsed._[0].should.equal(s)
+ })
+ })
+ it('parses long chain of "-" with reasonable performance', function () {
+ this.timeout(500)
+ const s = (new Array(i).fill('-').join('')) + 'a'
+ const arg = (new Array(i - 2).fill('-').join('')) + 'a'
+ const parsed = parser([s])
+ parsed[arg].should.equal(true)
+ })
+ it('parses long chain of "-a-a" with reasonable performance', function () {
+ this.timeout(500)
+ const s = '-' + (new Array(i).fill('-a').join('')) + '=35'
+ const arg = 'a' + (new Array(i - 1).fill('A').join(''))
+ const parsed = parser([s])
+ parsed[arg].should.equal(35)
+ })
+ })
+})
diff --git a/test/yargs-parser.js b/test/yargs-parser.js
deleted file mode 100644
index 5b59c615..00000000
--- a/test/yargs-parser.js
+++ /dev/null
@@ -1,2378 +0,0 @@
-/* global beforeEach, describe, it */
-
-require('chai').should()
-
-var expect = require('chai').expect
-var fs = require('fs')
-var parser = require('../')
-var path = require('path')
-
-describe('yargs-parser', function () {
- it('should parse a "short boolean"', function () {
- var parse = parser([ '-b' ])
- parse.should.have.property('b').to.be.ok.and.be.a('boolean')
- parse.should.have.property('_').with.length(0)
- })
-
- it('should parse a "long boolean"', function () {
- var parse = parser('--bool')
- parse.should.have.property('bool', true)
- parse.should.have.property('_').with.length(0)
- })
-
- it('should place bare options in the _ array', function () {
- var parse = parser('foo bar baz')
- parse.should.have.property('_').and.deep.equal(['foo', 'bar', 'baz'])
- })
-
- it('should set the value of the final option in a group to the next supplied value', function () {
- var parse = parser(['-cats', 'meow'])
- parse.should.have.property('c', true)
- parse.should.have.property('a', true)
- parse.should.have.property('t', true)
- parse.should.have.property('s', 'meow')
- parse.should.have.property('_').with.length(0)
- })
-
- it('should set the value of a single long option to the next supplied value', function () {
- var parse = parser(['--pow', 'xixxle'])
- parse.should.have.property('pow', 'xixxle')
- parse.should.have.property('_').with.length(0)
- })
-
- it('should set the value of a single long option to the next supplied value, even if the value is empty', function () {
- var parse = parser(['--pow', ''])
- parse.should.have.property('pow', '')
- parse.should.have.property('_').with.length(0)
- })
-
- it('should set the value of a single long option if an = was used', function () {
- var parse = parser(['--pow=xixxle'])
- parse.should.have.property('pow', 'xixxle')
- parse.should.have.property('_').with.length(0)
- })
-
- it('should set the value of multiple long options to the next supplied values relative to each', function () {
- var parse = parser(['--host', 'localhost', '--port', '555'])
- parse.should.have.property('host', 'localhost')
- parse.should.have.property('port', 555)
- parse.should.have.property('_').with.length(0)
- })
-
- it('should set the value of multiple long options if = signs were used', function () {
- var parse = parser(['--host=localhost', '--port=555'])
- parse.should.have.property('host', 'localhost')
- parse.should.have.property('port', 555)
- parse.should.have.property('_').with.length(0)
- })
-
- it('should still set values appropriately if a mix of short, long, and grouped short options are specified', function () {
- var parse = parser(['-h', 'localhost', '-fp', '555', 'script.js'])
- parse.should.have.property('f', true)
- parse.should.have.property('p', 555)
- parse.should.have.property('h', 'localhost')
- parse.should.have.property('_').and.deep.equal(['script.js'])
- })
-
- it('should still set values appropriately if a mix of short and long options are specified', function () {
- var parse = parser(['-h', 'localhost', '--port', '555'])
- parse.should.have.property('h', 'localhost')
- parse.should.have.property('port', 555)
- parse.should.have.property('_').with.length(0)
- })
-
- it('should explicitly set a boolean option to false if preceeded by "--no-"', function () {
- var parse = parser(['--no-moo'])
- parse.should.have.property('moo', false)
- parse.should.have.property('_').with.length(0)
- })
-
- it('should still set values appropriately if we supply a comprehensive list of various types of options', function () {
- var parse = parser([
- '--name=meowmers', 'bare', '-cats', 'woo',
- '-h', 'awesome', '--multi=quux',
- '--key', 'value',
- '-b', '--bool', '--no-meep', '--multi=baz',
- '--', '--not-a-flag', '-', '-h', '-multi', '--', 'eek'
- ])
- parse.should.have.property('c', true)
- parse.should.have.property('a', true)
- parse.should.have.property('t', true)
- parse.should.have.property('s', 'woo')
- parse.should.have.property('h', 'awesome')
- parse.should.have.property('b', true)
- parse.should.have.property('bool', true)
- parse.should.have.property('key', 'value')
- parse.should.have.property('multi').and.deep.equal(['quux', 'baz'])
- parse.should.have.property('meep', false)
- parse.should.have.property('name', 'meowmers')
- parse.should.have.property('_').and.deep.equal(['bare', '--not-a-flag', '-', '-h', '-multi', '--', 'eek'])
- })
-
- it('should parse numbers appropriately', function () {
- var argv = parser([
- '-x', '1234',
- '-y', '5.67',
- '-z', '1e7',
- '-w', '10f',
- '--hex', '0xdeadbeef',
- '789'
- ])
- argv.should.have.property('x', 1234).and.be.a('number')
- argv.should.have.property('y', 5.67).and.be.a('number')
- argv.should.have.property('z', 1e7).and.be.a('number')
- argv.should.have.property('w', '10f').and.be.a('string')
- argv.should.have.property('hex', 0xdeadbeef).and.be.a('number')
- argv.should.have.property('_').and.deep.equal([789])
- argv._[0].should.be.a('number')
- })
-
- // addresses: https://github.com/yargs/yargs-parser/issues/33
- it('should handle parsing negative #s', function () {
- var argv = parser([
- '-33', '-177', '33',
- '--n1', '-33',
- '-n', '-44',
- '--n2=-55',
- '--foo.bar', '-33',
- '-o=-55',
- '--bounds', '-180', '99', '-180', '90',
- '--other', '-99', '-220'
- ], {
- array: 'bounds',
- narg: {'other': 2}
- })
-
- argv._.should.deep.equal([-33, -177, 33])
- argv.n1.should.equal(-33)
- argv.n.should.equal(-44)
- argv.n2.should.equal(-55)
- argv.foo.bar.should.equal(-33)
- argv.o.should.equal(-55)
- argv.bounds.should.deep.equal([-180, 99, -180, 90])
- argv.other.should.deep.equal([-99, -220])
- })
-
- 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', '')
- parse.should.have.property('_').with.length(0)
- })
-
- 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' ], {
- boolean: 't'
- })
- parse.should.have.property('t', true).and.be.a('boolean')
- parse.should.have.property('_').and.deep.equal(['moo'])
- })
-
- it('should set boolean options values if the next value is "true" or "false"', function () {
- var parse = parser(['--verbose', 'false', 'moo', '-t', 'true'], {
- boolean: ['t', 'verbose'],
- default: {
- verbose: true
- }
- })
- parse.should.have.property('verbose', false).and.be.a('boolean')
- parse.should.have.property('t', true).and.be.a('boolean')
- parse.should.have.property('_').and.deep.equal(['moo'])
- })
-
- it('should not set boolean options values if the next value only contains the words "true" or "false"', function () {
- var parse = parser(['--verbose', 'aaatrueaaa', 'moo', '-t', 'aaafalseaaa'], {
- boolean: ['t', 'verbose']
- })
- parse.should.have.property('verbose', true).and.be.a('boolean')
- parse.should.have.property('t', true).and.be.a('boolean')
- parse.should.have.property('_').and.deep.equal(['aaatrueaaa', 'moo', 'aaafalseaaa'])
- })
-
- it('should allow defining options as boolean in groups', function () {
- var parse = parser([ '-x', '-z', 'one', 'two', 'three' ], {
- boolean: ['x', 'y', 'z']
- })
- parse.should.have.property('x', true).and.be.a('boolean')
- parse.should.have.property('y', false).and.be.a('boolean')
- parse.should.have.property('z', true).and.be.a('boolean')
- parse.should.have.property('_').and.deep.equal(['one', 'two', 'three'])
- })
-
- it('should correctly parse dot-notation boolean flags', function () {
- var parse = parser(['--nested', '--n.v', '--n.y', 'foo'], {
- boolean: ['nested', 'n.v']
- })
-
- parse.should.have.property('nested', true).and.be.a('boolean')
- parse.should.have.property('n').and.deep.equal({
- v: true,
- y: 'foo'
- })
- })
-
- it('should preserve newlines in option values', function () {
- var args = parser(['-s', 'X\nX'])
- args.should.have.property('_').with.length(0)
- args.should.have.property('s', 'X\nX')
- // reproduce in bash:
- // VALUE="new
- // line"
- // node program.js --s="$VALUE"
- args = parser(['--s=X\nX'])
- args.should.have.property('_').with.length(0)
- args.should.have.property('s', 'X\nX')
- })
-
- it('should not convert numbers to type number if explicitly defined as strings', function () {
- var s = parser([ '-s', '0001234' ], {
- string: 's'
- }).s
- s.should.be.a('string').and.equal('0001234')
- 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' ], {
- 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' ], {
- number: ['n']
- }).n
- expect(n).to.deep.equal(NaN)
- })
-
- // 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' ], {
- string: ['s']
- }).s
- s.should.be.a('string').and.equal('')
-
- s = parser([ '-sf' ], {
- string: ['s']
- }).s
- s.should.be.a('string').and.equal('')
-
- 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([ ' ', ' ' ], {
- string: ['_']
- })._
- s.should.have.length(2)
- s[0].should.be.a('string').and.equal(' ')
- s[1].should.be.a('string').and.equal(' ')
- })
-
- describe('normalize', function () {
- it('should normalize redundant paths', function () {
- var a = parser([ '-s', ['', 'tmp', '..', ''].join(path.sep) ], {
- alias: {
- s: ['save']
- },
- normalize: 's'
- })
- a.should.have.property('s', path.sep)
- a.should.have.property('save', path.sep)
- })
-
- it('should normalize redundant paths when a value is later assigned', function () {
- var a = parser(['-s'], {
- normalize: ['s']
- })
- a.should.have.property('s', true)
- a.s = ['', 'path', 'to', 'new', 'dir', '..', '..', ''].join(path.sep)
- a.s.should.equal(['', 'path', 'to', ''].join(path.sep))
- })
-
- 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) ], {
- alias: {
- s: ['save']
- },
- normalize: 's',
- array: 's'
- })
- var expected = [path.sep, ['', 'path', 'to', ''].join(path.sep)]
- a.should.have.property('s').and.deep.equal(expected)
- a.should.have.property('save').and.deep.equal(expected)
- })
- })
-
- describe('alias', function () {
- it('should set alias value to the same value as the full option', function () {
- var argv = parser([ '-f', '11', '--zoom', '55' ], {
- alias: {
- z: ['zoom']
- }
- })
- argv.should.have.property('zoom', 55)
- argv.should.have.property('z', 55)
- argv.should.have.property('f', 11)
- })
-
- it('should allow multiple aliases to be specified', function () {
- var argv = parser([ '-f', '11', '--zoom', '55' ], {
- alias: {
- z: ['zm', 'zoom']
- }
- })
-
- argv.should.have.property('zoom', 55)
- argv.should.have.property('z', 55)
- argv.should.have.property('zm', 55)
- argv.should.have.property('f', 11)
- })
-
- // regression, see https://github.com/chevex/yargs/issues/63
- it('should not add the same key to argv multiple times, when creating camel-case aliases', function () {
- var argv = parser(['--health-check=banana', '--second-key', 'apple', '-t=blarg'], {
- alias: {
- h: ['health-check'],
- 'second-key': ['s'],
- 'third-key': ['t']
- },
- default: {
- h: 'apple',
- 'second-key': 'banana',
- 'third-key': 'third'
- }
- })
-
- // before this fix, yargs failed parsing
- // one but not all forms of an arg.
- argv.secondKey.should.eql('apple')
- argv.s.should.eql('apple')
- argv['second-key'].should.eql('apple')
-
- argv.healthCheck.should.eql('banana')
- argv.h.should.eql('banana')
- argv['health-check'].should.eql('banana')
-
- argv.thirdKey.should.eql('blarg')
- argv.t.should.eql('blarg')
- argv['third-key'].should.eql('blarg')
- })
-
- it('should allow transitive aliases to be specified', function () {
- var argv = parser([ '-f', '11', '--zoom', '55' ], {
- alias: {
- z: 'zm',
- zm: 'zoom'
- }
- })
-
- argv.should.have.property('zoom', 55)
- argv.should.have.property('z', 55)
- argv.should.have.property('zm', 55)
- argv.should.have.property('f', 11)
- })
-
- it('should merge two lists of aliases if they collide', function () {
- var argv = parser(['-f', '11', '--zoom', '55'], {
- alias: {
- z: 'zm',
- zoom: 'zoop',
- zm: 'zoom'
- }
- })
-
- argv.should.have.property('zoom', 55)
- argv.should.have.property('zoop', 55)
- argv.should.have.property('z', 55)
- argv.should.have.property('zm', 55)
- argv.should.have.property('f', 11)
- })
- })
-
- it('should assign data after forward slash to the option before the slash', function () {
- var parse = parser(['-I/foo/bar/baz'])
- parse.should.have.property('_').with.length(0)
- parse.should.have.property('I', '/foo/bar/baz')
- parse = parser(['-xyz/foo/bar/baz'])
- parse.should.have.property('x', true)
- parse.should.have.property('y', true)
- parse.should.have.property('z', '/foo/bar/baz')
- parse.should.have.property('_').with.length(0)
- })
-
- 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' ], {
- 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')
- })
-
- it('should use value from config file, if argv value is using default value', function () {
- var argv = parser([], {
- alias: {
- z: 'zoom'
- },
- config: ['settings'],
- default: {
- settings: jsonPath,
- foo: 'banana'
- }
- })
-
- argv.should.have.property('herp', 'derp')
- argv.should.have.property('zoom', 55)
- argv.should.have.property('foo').and.deep.equal('baz')
- })
-
- it('should use value from config file, if argv key is a boolean', function () {
- var argv = parser([], {
- config: ['settings'],
- default: {
- settings: jsonPath
- },
- boolean: ['truthy']
- })
-
- argv.should.have.property('truthy', true)
- })
-
- it('should use value from cli, if cli overrides boolean argv key', function () {
- var argv = parser(['--no-truthy'], {
- config: ['settings'],
- default: {
- settings: jsonPath
- },
- boolean: ['truthy']
- })
-
- argv.should.have.property('truthy', false)
- })
-
- it('should use cli value, if cli value is set and both cli and default value match', function () {
- var argv = parser(['--foo', 'banana'], {
- alias: {
- z: 'zoom'
- },
- config: ['settings'],
- default: {
- settings: jsonPath,
- foo: 'banana'
- }
- })
-
- argv.should.have.property('herp', 'derp')
- argv.should.have.property('zoom', 55)
- argv.should.have.property('foo').and.deep.equal('banana')
- })
-
- it("should allow config to be set as flag in 'option'", function () {
- var argv = parser([ '--settings', jsonPath, '--foo', 'bar' ], {
- alias: {
- z: 'zoom'
- },
- config: ['settings']
- })
-
- argv.should.have.property('herp', 'derp')
- argv.should.have.property('zoom', 55)
- argv.should.have.property('foo').and.deep.equal('bar')
- })
-
- 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' ], {
- config: ['settings']
- })
-
- argv.should.have.property('herp', 'derp')
- argv.should.have.property('foo', 'bar')
- argv.should.have.property('calculate').and.be.a('function')
- })
-
- it('should raise an appropriate error if JSON file is not found', function () {
- var argv = parser.detailed(['--settings', 'fake.json', '--foo', 'bar'], {
- alias: {
- z: 'zoom'
- },
- config: ['settings']
- })
-
- argv.error.message.should.equal('Invalid JSON config file: fake.json')
- })
-
- // see: https://github.com/bcoe/yargs/issues/172
- it('should not raise an exception if config file is set as default argument value', function () {
- var argv = parser.detailed([], {
- default: {
- config: 'foo.json'
- },
- config: ['config']
- })
-
- expect(argv.error).to.equal(null)
- })
-
- it('should load nested options from config file', function () {
- var jsonPath = path.resolve(__dirname, './fixtures/nested_config.json')
- var argv = parser(['--settings', jsonPath, '--nested.foo', 'bar'], {
- config: ['settings']
- })
-
- argv.should.have.property('a', 'a')
- argv.should.have.property('b', 'b')
- argv.should.have.property('nested').and.deep.equal({
- foo: 'bar',
- bar: 'bar'
- })
- })
-
- it('should use nested value from config file, if argv value is using default value', function () {
- var jsonPath = path.resolve(__dirname, './fixtures/nested_config.json')
- var argv = parser(['--settings', jsonPath], {
- config: ['settings'],
- default: {
- 'nested.foo': 'banana'
- }
- })
-
- argv.should.have.property('a', 'a')
- argv.should.have.property('b', 'b')
- argv.should.have.property('nested').and.deep.equal({
- foo: 'baz',
- bar: 'bar'
- })
- })
-
- 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' ], {
- config: {
- settings: function (configPath) {
- // as an example, parse an environment
- // variable style config:
- // FOO=99
- // BATMAN=grumpy
- var config = {}
- var txt = fs.readFileSync(configPath, 'utf-8')
- txt.split(/\r?\n/).forEach(function (l) {
- var kv = l.split('=')
- config[kv[0].toLowerCase()] = kv[1]
- })
- return config
- }
- }
- })
-
- argv.batman.should.equal('grumpy')
- argv.awesome.should.equal('banana')
- argv.foo.should.equal('bar')
- })
-
- 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' ], {
- config: {
- s: function (configPath) {
- return JSON.parse(fs.readFileSync(configPath, 'utf-8'))
- }
- },
- alias: {
- s: ['settings']
- }
- })
-
- argv.should.have.property('herp', 'derp')
- argv.should.have.property('foo', 'bar')
- })
-
- it('outputs an error returned by the parsing function', function () {
- var argv = parser.detailed(['--settings=./package.json'], {
- config: {
- settings: function (configPath) {
- return Error('someone set us up the bomb')
- }
- }
- })
-
- argv.error.message.should.equal('someone set us up the bomb')
- })
-
- it('outputs an error if thrown by the parsing function', function () {
- var argv = parser.detailed(['--settings=./package.json'], {
- config: {
- settings: function (configPath) {
- throw Error('someone set us up the bomb')
- }
- }
- })
-
- argv.error.message.should.equal('someone set us up the bomb')
- })
- })
-
- describe('config objects', function () {
- it('should load options from config object', function () {
- var argv = parser([ '--foo', 'bar' ], {
- configObjects: [{
- apple: 'apple',
- banana: 42,
- foo: 'baz'
- }]
- })
-
- argv.should.have.property('apple', 'apple')
- argv.should.have.property('banana', 42)
- argv.should.have.property('foo', 'bar')
- })
-
- it('should use value from config object, if argv value is using default value', function () {
- var argv = parser([], {
- configObjects: [{
- apple: 'apple',
- banana: 42,
- foo: 'baz'
- }],
- default: {
- foo: 'banana'
- }
- })
-
- argv.should.have.property('apple', 'apple')
- argv.should.have.property('banana', 42)
- argv.should.have.property('foo', 'baz')
- })
-
- it('should use value from config object to all aliases', function () {
- var argv = parser([], {
- configObjects: [{
- apple: 'apple',
- banana: 42,
- foo: 'baz'
- }],
- alias: {
- a: ['apple'],
- banana: ['b']
- }
- })
-
- argv.should.have.property('apple', 'apple')
- argv.should.have.property('a', 'apple')
- argv.should.have.property('banana', 42)
- argv.should.have.property('b', 42)
- argv.should.have.property('foo', 'baz')
- })
-
- it('should load nested options from config object', function () {
- var argv = parser(['--nested.foo', 'bar'], {
- configObjects: [{
- a: 'a',
- nested: {
- foo: 'baz',
- bar: 'bar'
- },
- b: 'b'
- }]
- })
-
- argv.should.have.property('a', 'a')
- argv.should.have.property('b', 'b')
- argv.should.have.property('nested').and.deep.equal({
- foo: 'bar',
- bar: 'bar'
- })
- })
-
- it('should use nested value from config object, if argv value is using default value', function () {
- var argv = parser([], {
- configObjects: [{
- a: 'a',
- nested: {
- foo: 'baz',
- bar: 'bar'
- },
- b: 'b'
- }],
- default: {
- 'nested.foo': 'banana'
- }
- })
-
- argv.should.have.property('a', 'a')
- argv.should.have.property('b', 'b')
- argv.should.have.property('nested').and.deep.equal({
- foo: 'baz',
- bar: 'bar'
- })
- })
- })
-
- describe('dot notation', function () {
- it('should allow object graph traversal via dot notation', function () {
- var argv = parser([
- '--foo.bar', '3', '--foo.baz', '4',
- '--foo.quux.quibble', '5', '--foo.quux.o_O',
- '--beep.boop'
- ])
- argv.should.have.property('foo').and.deep.equal({
- bar: 3,
- baz: 4,
- quux: {
- quibble: 5,
- o_O: true
- }
- })
- argv.should.have.property('beep').and.deep.equal({ boop: true })
- })
-
- it('should apply defaults to dot notation arguments', function () {
- var argv = parser([], {
- default: {
- 'foo.bar': 99
- }
- })
-
- argv.foo.bar.should.eql(99)
- })
-
- // see #279
- it('should allow default to be overridden when an alias is provided', function () {
- var argv = parser(['--foo.bar', '200'], {
- default: {
- 'foo.bar': 99
- }
- })
-
- argv.foo.bar.should.eql(200)
- })
-
- // see #279
- it('should also override alias', function () {
- var argv = parser(['--foo.bar', '200'], {
- alias: {
- 'foo.bar': ['f']
- },
- default: {
- 'foo.bar': 99
- }
- })
-
- argv.f.should.eql(200)
- })
-
- // see #279
- it('should not set an undefined dot notation key', function () {
- var argv = parser(['--foo.bar', '200'], {
- default: {
- 'foo.bar': 99
- },
- alias: {
- 'foo.bar': ['f']
- }
- })
-
- ;('foo.bar' in argv).should.equal(false)
- })
-
- it('should respect .string() for dot notation arguments', function () {
- var argv = parser(['--foo.bar', '99', '--bar.foo=99'], {
- string: ['foo.bar']
- })
-
- argv.foo.bar.should.eql('99')
- argv.bar.foo.should.eql(99)
- })
-
- it('should populate aliases when dot notation is used', function () {
- var argv = parser(['--foo.bar', '99'], {
- alias: {
- foo: ['f']
- }
- })
-
- argv.f.bar.should.eql(99)
- })
-
- it('should populate aliases when nested dot notation is used', function () {
- var argv = parser(['--foo.bar.snuh', '99', '--foo.apple', '33', '--foo.bar.cool', '11'], {
- alias: {
- foo: ['f']
- }
- })
-
- argv.f.bar.snuh.should.eql(99)
- argv.foo.bar.snuh.should.eql(99)
-
- argv.f.apple.should.eql(33)
- argv.foo.apple.should.eql(33)
-
- argv.f.bar.cool.should.eql(11)
- argv.foo.bar.cool.should.eql(11)
- })
-
- it("should allow flags to use dot notation, when seperated 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 () {
- var argv = parser(['-f.foo', '99'])
- argv.f.foo.should.eql(99)
- })
-
- it('should allow flags to use dot notation when no right-hand-side is given', function () {
- var argv = parser(['-f.foo', '99', '-f.bar'])
- argv.f.foo.should.eql(99)
- argv.f.bar.should.eql(true)
- })
- })
-
- it('should set boolean and alias using explicit true', function () {
- var aliased = [ '-h', 'true' ]
- var aliasedArgv = parser(aliased, {
- boolean: ['h'],
- alias: {
- h: ['herp']
- }
- })
-
- aliasedArgv.should.have.property('herp', true)
- aliasedArgv.should.have.property('h', true)
- aliasedArgv.should.have.property('_').with.length(0)
- })
-
- // regression, see https://github.com/substack/node-optimist/issues/71
- it('should set boolean and --x=true', function () {
- var parsed = parser(['--boool', '--other=true'], {
- boolean: ['boool']
- })
- parsed.should.have.property('boool', true)
- parsed.should.have.property('other', 'true')
- parsed = parser(['--boool', '--other=false'], {
- boolean: ['boool']
- })
- parsed.should.have.property('boool', true)
- parsed.should.have.property('other', 'false')
- })
-
- // regression, see https://github.com/chevex/yargs/issues/66
- it('should set boolean options values if next value is "true" or "false" with = as separator', function () {
- var argv = parser(['--bool=false'], {
- boolean: ['b'],
- alias: {
- b: ['bool']
- },
- default: {
- b: true
- }
- })
-
- argv.bool.should.eql(false)
- })
-
- describe('short options', function () {
- it('should set the value of multiple single short options to the next supplied values relative to each', function () {
- var parse = parser(['-h', 'localhost', '-p', '555'])
- parse.should.have.property('h', 'localhost')
- parse.should.have.property('p', 555)
- parse.should.have.property('_').with.length(0)
- })
-
- it('should set the value of a single short option to the next supplied value', function () {
- var parse = parser(['-h', 'localhost'])
- parse.should.have.property('h', 'localhost')
- parse.should.have.property('_').with.length(0)
- })
-
- it('should expand grouped short options to a hash with a key for each', function () {
- var parse = parser(['-cats'])
- parse.should.have.property('c', true)
- parse.should.have.property('a', true)
- parse.should.have.property('t', true)
- parse.should.have.property('s', true)
- parse.should.have.property('_').with.length(0)
- })
-
- it('should set n to the numeric value 123', function () {
- 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' ])
- argv.should.have.property('a', true)
- argv.should.have.property('b', true)
- argv.should.have.property('5', true)
- argv.should.have.property('n', 123)
- argv.should.have.property('_').with.length(0)
- })
-
- it('should set n to the numeric value 123, with = as separator', function () {
- 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' ])
- argv.should.have.property('a', true)
- argv.should.have.property('b', true)
- argv.should.have.property('5', true)
- argv.should.have.property('n', 123)
- argv.should.have.property('_').with.length(0)
- })
- })
-
- describe('whitespace', function () {
- it('should be whitespace', function () {
- var argv = parser([ '-x', '\t' ])
- argv.should.have.property('x', '\t')
- })
- })
-
- describe('boolean modifier function', function () {
- it('should prevent yargs from sucking in the next option as the value of the first option', function () {
- // Arrange & Act
- var result = parser(['-b', '123'], {
- boolean: ['b']
- })
- // Assert
- result.should.have.property('b').that.is.a('boolean').and.is.true
- result.should.have.property('_').and.deep.equal([123])
- })
- })
-
- describe('defaults', function () {
- function checkNoArgs (opts, hasAlias) {
- it('should set defaults if no args', function () {
- var result = parser([], opts)
- result.should.have.property('flag', true)
- if (hasAlias) {
- result.should.have.property('f', true)
- }
- })
- }
-
- function checkExtraArg (opts, hasAlias) {
- it('should set defaults if one extra arg', function () {
- var result = parser(['extra'], opts)
- result.should.have.property('flag', true)
- result.should.have.property('_').and.deep.equal(['extra'])
- if (hasAlias) {
- result.should.have.property('f', true)
- }
- })
- }
-
- function checkStringArg (opts, hasAlias) {
- it('should set defaults even if arg looks like a string', function () {
- var result = parser([ '--flag', 'extra' ], opts)
- result.should.have.property('flag', true)
- result.should.have.property('_').and.deep.equal(['extra'])
- if (hasAlias) {
- result.should.have.property('f', true)
- }
- })
- }
-
- describe('for options with aliases', function () {
- var opts = {
- alias: {
- flag: ['f']
- },
- default: {
- flag: true
- }
- }
-
- checkNoArgs(opts, true)
- checkExtraArg(opts, true)
- })
-
- describe('for typed options without aliases', function () {
- var opts = {
- boolean: ['flag'],
- default: {
- flag: true
- }
- }
-
- checkNoArgs(opts)
- checkExtraArg(opts)
- checkStringArg(opts)
- })
-
- describe('for typed options with aliases', function () {
- var opts = {
- alias: {
- flag: ['f']
- },
- boolean: ['flag'],
- default: {
- flag: true
- }
- }
-
- checkNoArgs(opts, true)
- checkExtraArg(opts, true)
- checkStringArg(opts, true)
- })
-
- describe('for boolean options', function () {
- [true, false, undefined, null].forEach(function (def) {
- describe('with explicit ' + def + ' default', function () {
- var opts = {
- default: {
- flag: def
- },
- boolean: ['flag']
- }
-
- it('should set true if --flag in arg', function () {
- parser(['--flag'], opts).flag.should.be.true
- })
-
- it('should set false if --no-flag in arg', function () {
- parser(['--no-flag'], opts).flag.should.be.false
- })
-
- it('should set ' + def + ' if no flag in arg', function () {
- expect(parser([], opts).flag).to.equal(def)
- })
- })
- })
-
- describe('with implied false default', function () {
- var opts = null
-
- beforeEach(function () {
- opts = {
- boolean: ['flag']
- }
- })
-
- it('should set true if --flag in arg', function () {
- parser(['--flag'], opts).flag.should.be.true
- })
-
- it('should set false if --no-flag in arg', function () {
- parser(['--no-flag'], opts).flag.should.be.false
- })
-
- it('should set false if no flag in arg', function () {
- parser([], opts).flag.should.be.false
- })
- })
-
- // Fixes: https://github.com/bcoe/yargs/issues/341
- it('should apply defaults to camel-case form of argument', function () {
- var argv = parser([], {
- default: {
- 'foo-bar': 99
- }
- })
-
- argv.fooBar.should.equal(99)
- })
- })
-
- it('should define option as boolean and set default to true', function () {
- var argv = parser([], {
- boolean: ['sometrue'],
- default: {
- sometrue: true
- }
- })
- argv.should.have.property('sometrue', true)
- })
-
- it('should define option as boolean and set default to false', function () {
- var argv = parser([], {
- default: {
- somefalse: false
- },
- boolean: ['somefalse']
- })
- argv.should.have.property('somefalse', false)
- })
-
- it('should set boolean options to false by default', function () {
- var parse = parser(['moo'], {
- boolean: ['t', 'verbose'],
- default: {
- verbose: false,
- t: false
- }
- })
- parse.should.have.property('verbose', false).and.be.a('boolean')
- parse.should.have.property('t', false).and.be.a('boolean')
- parse.should.have.property('_').and.deep.equal(['moo'])
- })
- })
-
- describe('camelCase', function () {
- function runTests (strict) {
- if (!strict) {
- // Skip this test in strict mode because this option is not specified
- it('should provide options with dashes as camelCase properties', function () {
- var result = parser(['--some-option'])
-
- result.should.have.property('some-option').that.is.a('boolean').and.is.true
- result.should.have.property('someOption').that.is.a('boolean').and.is.true
- })
- }
-
- it('should provide count options with dashes as camelCase properties', function () {
- var result = parser([ '--some-option', '--some-option', '--some-option' ], {
- count: ['some-option']
- })
-
- result.should.have.property('some-option', 3)
- result.should.have.property('someOption', 3)
- })
-
- it('should provide options with dashes and aliases as camelCase properties', function () {
- var result = parser(['--some-option'], {
- alias: {
- 'some-horse': 'o'
- }
- })
-
- result.should.have.property('some-option').that.is.a('boolean').and.is.true
- result.should.have.property('someOption').that.is.a('boolean').and.is.true
- })
-
- it('should provide defaults of options with dashes as camelCase properties', function () {
- var result = parser([], {
- default: {
- 'some-option': 'asdf'
- }
- })
-
- result.should.have.property('some-option', 'asdf')
- result.should.have.property('someOption', 'asdf')
- })
-
- it('should provide aliases of options with dashes as camelCase properties', function () {
- var result = parser([], {
- default: {
- 'some-option': 'asdf'
- },
- alias: {
- 'some-option': ['o']
- }
- })
-
- result.should.have.property('o', 'asdf')
- result.should.have.property('some-option', 'asdf')
- result.should.have.property('someOption', 'asdf')
- })
-
- it('should provide aliases of options with dashes as camelCase properties', function () {
- var result = parser([], {
- alias: {
- o: ['some-option']
- },
- default: {
- o: 'asdf'
- }
- })
-
- result.should.have.property('o', 'asdf')
- result.should.have.property('some-option', 'asdf')
- result.should.have.property('someOption', 'asdf')
- })
-
- it('should provide aliases with dashes as camelCase properties', function () {
- var result = parser(['--some-option', 'val'], {
- alias: {
- o: 'some-option'
- }
- })
-
- result.should.have.property('o').that.is.a('string').and.equals('val')
- result.should.have.property('some-option').that.is.a('string').and.equals('val')
- result.should.have.property('someOption').that.is.a('string').and.equals('val')
- })
- }
-
- describe('dashes and camelCase', function () {
- runTests()
- })
-
- describe('dashes and camelCase (strict)', function () {
- runTests(true)
- })
- })
-
- describe('-', function () {
- it('should set - as value of n', function () {
- var argv = parser(['-n', '-'])
- argv.should.have.property('n', '-')
- argv.should.have.property('_').with.length(0)
- })
-
- it('should set - as a non-hyphenated value', function () {
- var argv = parser(['-'])
- argv.should.have.property('_').and.deep.equal(['-'])
- })
-
- it('should set - as a value of f', function () {
- var argv = parser(['-f-'])
- argv.should.have.property('f', '-')
- argv.should.have.property('_').with.length(0)
- })
-
- it('should set b to true and set - as a non-hyphenated value when b is set as a boolean', function () {
- var argv = parser(['-b', '-'], {
- boolean: ['b']
- })
-
- argv.should.have.property('b', true)
- argv.should.have.property('_').and.deep.equal(['-'])
- })
-
- it('should set - as the value of s when s is set as a string', function () {
- var argv = parser([ '-s', '-' ], {
- string: ['s']
- })
-
- argv.should.have.property('s', '-')
- argv.should.have.property('_').with.length(0)
- })
- })
-
- describe('count', function () {
- it('should count the number of times a boolean is present', function () {
- var parsed
-
- parsed = parser(['-x'], {
- count: ['verbose']
- })
- parsed.verbose.should.equal(0)
-
- parsed = parser(['--verbose'], {
- count: ['verbose']
- })
- parsed.verbose.should.equal(1)
-
- parsed = parser(['--verbose', '--verbose'], {
- count: ['verbose']
- })
- parsed.verbose.should.equal(2)
-
- parsed = parser(['-vvv'], {
- alias: {
- v: ['verbose']
- },
- count: ['verbose']
- })
- parsed.verbose.should.equal(3)
-
- parsed = parser(['--verbose', '--verbose', '-v', '--verbose'], {
- count: ['verbose'],
- alias: {
- v: ['verbose']
- }
- })
- parsed.verbose.should.equal(4)
-
- parsed = parser(['--verbose', '--verbose', '-v', '-vv'], {
- count: ['verbose'],
- alias: {
- v: ['verbose']
- }
- })
- parsed.verbose.should.equal(5)
- })
-
- it('should not consume the next argument', function () {
- 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' ], {
- count: 'verbose'
- })
- parsed.verbose.should.equal(2)
- parsed.should.have.property('_').and.deep.equal(['moomoo'])
- })
-
- it('should use a default value as is when no arg given', function () {
- var parsed = parser([], {
- count: 'v',
- default: { v: 3 }
- })
- parsed.v.should.equal(3)
-
- parsed = parser([], {
- count: 'v',
- default: { v: undefined }
- })
- expect(parsed.v).to.be.undefined
-
- parsed = parser([], {
- count: 'v',
- default: { v: null }
- })
- expect(parsed.v).to.be.null
-
- parsed = parser([], {
- count: 'v',
- default: { v: false }
- })
- parsed.v.should.equal(false)
-
- parsed = parser([], {
- count: 'v',
- default: { v: 'hello' }
- })
- parsed.v.should.equal('hello')
- })
-
- it('should ignore a default value when arg given', function () {
- var parsed = parser(['-vv', '-v', '-v'], {
- count: 'v',
- default: { v: 1 }
- })
- parsed.v.should.equal(4)
- })
-
- it('should increment regardless of arg value', function () {
- var parsed = parser([
- '-v',
- '-v=true',
- '-v', 'true',
- '-v=false',
- '-v', 'false',
- '--no-v',
- '-v=999',
- '-v=foobar'
- ], { count: 'v' })
- parsed.v.should.equal(8)
- })
- })
-
- 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}})
- 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}})
- parse.should.have.property('v').and.equal('c')
- parse.should.have.property('_').with.length(0)
- })
-
- it('should default an array to an empty array if passed as first option followed by another', function () {
- var result = parser(['-a', '-b'], {
- array: 'a'
- })
- result.should.have.property('a').and.deep.equal([])
- })
-
- it('should not attempt to default array if an element has already been populated', function () {
- var result = parser(['-a', 'foo', 'bar', '-b'], {
- array: 'a'
- })
- result.should.have.property('a').and.deep.equal(['foo', 'bar'])
- })
-
- it('should default argument to empty array if no value given', function () {
- var result = parser(['-b'], {
- array: 'b'
- })
- result.should.have.property('b').and.deep.equal([])
- })
-
- it('should place value of argument in array, when one argument provided', function () {
- var result = parser(['-b', '33'], {
- array: ['b']
- })
- Array.isArray(result.b).should.equal(true)
- result.b[0].should.equal(33)
- })
-
- it('should add multiple argument values to the array', function () {
- var result = parser(['-b', '33', '-b', 'hello'], {
- array: 'b'
- })
- Array.isArray(result.b).should.equal(true)
- result.b.should.include(33)
- result.b.should.include('hello')
- })
-
- it('should allow array: true, to be set inside an option block', function () {
- var result = parser(['-b', '33'], {
- array: 'b'
- })
- Array.isArray(result.b).should.equal(true)
- result.b.should.include(33)
- })
-
- // issue #103
- it('should default camel-case alias to array type', function () {
- var result = parser(['--ca-path', 'http://www.example.com'], {
- array: ['ca-path']
- })
-
- Array.isArray(result['ca-path']).should.equal(true)
- Array.isArray(result.caPath).should.equal(true)
- })
-
- it('should default alias to array type', function () {
- var result = parser(['--ca-path', 'http://www.example.com'], {
- array: 'ca-path',
- alias: {
- 'ca-path': 'c'
- }
- })
-
- Array.isArray(result['ca-path']).should.equal(true)
- Array.isArray(result.caPath).should.equal(true)
- Array.isArray(result.c).should.equal(true)
- })
-
- // see: https://github.com/bcoe/yargs/issues/162
- it('should eat non-hyphenated arguments until hyphenated option is hit', function () {
- var result = parser(['-a=hello', 'world', '-b',
- '33', '22', '--foo', 'red', 'green',
- '--bar=cat', 'dog'], {
- array: ['a', 'b', 'foo', '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('red')
- result.foo.should.include('green')
-
- Array.isArray(result.bar).should.equal(true)
- result.bar.should.include('cat')
- result.bar.should.include('dog')
- })
-
- // see: https://github.com/yargs/yargs-parser/pull/13
- it('should support array for --foo= format when the key is a number', function () {
- var result = parser(['--1=a', 'b'], {
- array: ['1']
- })
-
- Array.isArray(result['1']).should.equal(true)
- result['1'][0].should.equal('a')
- result['1'][1].should.equal('b')
- })
- })
-
- describe('nargs', function () {
- it('should allow the number of arguments following a key to be specified', function () {
- var result = parser([ '--foo', 'apple', 'bar' ], {
- narg: {
- foo: 2
- }
- })
-
- Array.isArray(result.foo).should.equal(true)
- result.foo[0].should.equal('apple')
- result.foo[1].should.equal('bar')
- })
-
- it('should raise an exception if there are not enough arguments following key', function () {
- var argv = parser.detailed('--foo apple', {
- narg: {
- foo: 2
- }
- })
- argv.error.message.should.equal('Not enough arguments following: foo')
- })
-
- it('nargs is applied to aliases', function () {
- var result = parser(['--bar', 'apple', 'bar'], {
- narg: {
- foo: 2
- },
- alias: {
- foo: 'bar'
- }
- })
- Array.isArray(result.foo).should.equal(true)
- result.foo[0].should.equal('apple')
- result.foo[1].should.equal('bar')
- })
-
- it('should apply nargs to flag arguments', function () {
- var result = parser([ '-f', 'apple', 'bar', 'blerg' ], {
- narg: {
- f: 2
- }
- })
-
- result.f[0].should.equal('apple')
- result.f[1].should.equal('bar')
- result._[0].should.equal('blerg')
- })
-
- it('should support nargs for -f= and --bar= format arguments', 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()
- parser(args, {
- narg: {
- f: 2,
- bar: 2
- }
- })
- args.should.deep.equal(expected)
-
- parser.detailed(args, {
- narg: {
- f: 2,
- bar: 2
- }
- })
- args.should.deep.equal(expected)
- })
-
- it('allows multiple nargs to be set at the same time', function () {
- var result = parser([ '--foo', 'apple', 'bar', '--bar', 'banana', '-f' ], {
- narg: {
- foo: 2,
- bar: 1
- }
- })
-
- Array.isArray(result.foo).should.equal(true)
- result.foo[0].should.equal('apple')
- result.foo[1].should.equal('bar')
- result.bar.should.equal('banana')
- result.f.should.equal(true)
- })
-
- // see: https://github.com/yargs/yargs-parser/pull/13
- it('should support nargs for --foo= format when the key is a number', function () {
- var result = parser(['--1=a', 'b'], {
- narg: {
- 1: 2
- }
- })
-
- Array.isArray(result['1']).should.equal(true)
- result['1'][0].should.equal('a')
- result['1'][1].should.equal('b')
- })
- })
-
- describe('env vars', function () {
- it('should apply all env vars if prefix is empty', function () {
- process.env.ONE_FISH = 'twofish'
- process.env.RED_FISH = 'bluefish'
- var result = parser([], {
- envPrefix: ''
- })
-
- result.oneFish.should.equal('twofish')
- result.redFish.should.equal('bluefish')
- })
-
- it('should apply only env vars matching prefix if prefix is valid string', function () {
- process.env.ONE_FISH = 'twofish'
- process.env.RED_FISH = 'bluefish'
- process.env.GREEN_EGGS = 'sam'
- process.env.GREEN_HAM = 'iam'
- var result = parser([], {
- envPrefix: 'GREEN'
- })
-
- result.eggs.should.equal('sam')
- result.ham.should.equal('iam')
- expect(result.oneFish).to.be.undefined
- expect(result.redFish).to.be.undefined
- })
-
- it('should set aliases for options defined by env var', function () {
- process.env.AIRFORCE_ONE = 'two'
- var result = parser([], {
- envPrefix: 'AIRFORCE',
- alias: {
- '1': ['one', 'uno']
- }
- })
-
- result['1'].should.equal('two')
- result.one.should.equal('two')
- result.uno.should.equal('two')
- })
-
- it('should prefer command line value over env var', function () {
- process.env.FOO_BAR = 'ignore'
- var result = parser(['--foo-bar', 'baz'], {
- envPrefix: ''
- })
-
- result.fooBar.should.equal('baz')
- })
-
- it('should respect type for args defined by env var', function () {
- process.env.MY_TEST_STRING = '1'
- process.env.MY_TEST_NUMBER = '2'
- var result = parser([], {
- string: 'string',
- envPrefix: 'MY_TEST_'
- })
-
- result.string.should.equal('1')
- result.number.should.equal(2)
- })
-
- it('should set option from aliased env var', function () {
- process.env.SPACE_X = 'awesome'
- var result = parser([], {
- alias: {
- xactly: 'x'
- },
- envPrefix: 'SPACE'
- })
-
- result.xactly.should.equal('awesome')
- })
-
- it('should prefer env var value over configured default', function () {
- process.env.FOO_BALL = 'wut'
- process.env.FOO_BOOL = 'true'
- var result = parser([], {
- envPrefix: 'FOO',
- default: {
- ball: 'baz',
- bool: false
- },
- boolean: 'bool',
- string: 'ball'
- })
-
- result.ball.should.equal('wut')
- result.bool.should.equal(true)
- })
-
- var jsonPath = path.resolve(__dirname, './fixtures/config.json')
- it('should prefer environment variables over config file', function () {
- process.env.CFG_HERP = 'zerp'
- var result = parser(['--cfg', jsonPath], {
- envPrefix: 'CFG',
- config: 'cfg',
- string: 'herp',
- default: {
- herp: 'nerp'
- }
- })
-
- result.herp.should.equal('zerp')
- })
-
- it('should support an env var value as config file option', function () {
- process.env.TUX_CFG = jsonPath
- var result = parser([], {
- envPrefix: 'TUX',
- config: ['cfg'],
- default: {
- z: 44
- }
- })
-
- result.should.have.property('herp')
- result.should.have.property('foo')
- result.should.have.property('version')
- result.should.have.property('truthy')
- result.z.should.equal(55)
- })
-
- it('should prefer cli config file option over env var config file option', function () {
- process.env.MUX_CFG = path.resolve(__dirname, '../package.json')
- var result = parser(['--cfg', jsonPath], {
- envPrefix: 'MUX',
- config: 'cfg'
- })
-
- result.should.have.property('herp')
- result.should.have.property('foo')
- result.should.have.property('version')
- result.should.have.property('truthy')
- result.z.should.equal(55)
- })
-
- it('should apply all nested env vars', function () {
- process.env.TEST_A = 'a'
- process.env.TEST_NESTED_OPTION__FOO = 'baz'
- process.env.TEST_NESTED_OPTION__BAR = 'bar'
- var result = parser(['--nestedOption.foo', 'bar'], {
- envPrefix: 'TEST'
- })
-
- result.should.have.property('a', 'a')
- result.should.have.property('nestedOption').and.deep.equal({
- foo: 'bar',
- bar: 'bar'
- })
- })
-
- it('should apply nested env var if argv value is using default value', function () {
- process.env.TEST_A = 'a'
- process.env.TEST_NESTED_OPTION__FOO = 'baz'
- process.env.TEST_NESTED_OPTION__BAR = 'bar'
- var result = parser([], {
- envPrefix: 'TEST',
- default: {
- 'nestedOption.foo': 'banana'
- }
- })
-
- result.should.have.property('a', 'a')
- result.should.have.property('nestedOption').and.deep.equal({
- foo: 'baz',
- bar: 'bar'
- })
- })
- })
-
- describe('configuration', function () {
- describe('short option groups', function () {
- it('allows short-option-groups to be disabled', function () {
- var parse = parser(['-cats=meow'], {
- configuration: {
- 'short-option-groups': false
- }
- })
- parse.cats.should.equal('meow')
- parse = parser(['-cats', 'meow'], {
- configuration: {
- 'short-option-groups': false
- }
- })
- parse.cats.should.equal('meow')
- })
- })
-
- describe('camel-case expansion', function () {
- it('does not expand camel-case aliases', function () {
- var parsed = parser.detailed([], {
- alias: {
- 'foo-bar': ['x']
- },
- configuration: {
- 'camel-case-expansion': false
- }
- })
-
- expect(parsed.newAliases.fooBar).to.equal(undefined)
- expect(parsed.aliases.fooBar).to.equal(undefined)
- })
-
- it('does not expand camel-case keys', function () {
- var parsed = parser.detailed(['--foo-bar=apple'], {
- configuration: {
- 'camel-case-expansion': false
- }
- })
-
- expect(parsed.argv.fooBar).to.equal(undefined)
- expect(parsed.argv['foo-bar']).to.equal('apple')
- })
- })
-
- describe('dot notation', function () {
- it('does not expand dot notation defaults', function () {
- var parsed = parser([], {
- default: {
- 'foo.bar': 'x'
- },
- configuration: {
- 'dot-notation': false
- }
- })
-
- expect(parsed['foo.bar']).to.equal('x')
- })
-
- it('does not expand dot notation arguments', function () {
- var parsed = parser(['--foo.bar', 'banana'], {
- configuration: {
- 'dot-notation': false
- }
- })
- expect(parsed['foo.bar']).to.equal('banana')
- parsed = parser(['--foo.bar=banana'], {
- configuration: {
- 'dot-notation': false
- }
- })
- expect(parsed['foo.bar']).to.equal('banana')
- })
-
- it('should use value from cli, if cli overrides dot notation default', function () {
- var parsed = parser(['--foo.bar', 'abc'], {
- default: {
- 'foo.bar': 'default'
- },
- configuration: {
- 'dot-notation': false
- }
- })
-
- expect(parsed['foo.bar']).to.equal('abc')
- })
-
- it('should also override dot notation alias', function () {
- var parsed = parser(['--foo.bar', 'abc'], {
- alias: {
- 'foo.bar': ['alias.bar']
- },
- default: {
- 'foo.bar': 'default'
- },
- configuration: {
- 'dot-notation': false
- }
- })
-
- expect(parsed['alias.bar']).to.equal('abc')
- })
-
- it('does not expand alias of first element of dot notation arguments', function () {
- var parsed = parser(['--foo.bar', 'banana'], {
- alias: {
- 'foo': ['f']
- },
- configuration: {
- 'dot-notation': false
- }
- })
- expect(parsed['foo.bar']).to.equal('banana')
- expect(parsed).not.to.include.keys('f.bar')
- })
-
- // addresses https://github.com/yargs/yargs/issues/716
- it('does not append nested-object keys from config to top-level key', function () {
- var parsed = parser([], {
- alias: {
- 'foo': ['f']
- },
- configuration: {
- 'dot-notation': false
- },
- configObjects: [
- {
- 'website.com': {
- a: 'b',
- b: 'c'
- }
- }
- ]
- })
-
- parsed['website.com'].should.deep.equal({
- a: 'b',
- b: 'c'
- })
- })
- })
-
- describe('parse numbers', function () {
- it('does not coerce defaults into numbers', function () {
- var parsed = parser([], {
- default: {
- 'foo': '5'
- },
- configuration: {
- 'parse-numbers': false
- }
- })
-
- expect(parsed['foo']).to.equal('5')
- })
-
- it('does not coerce arguments into numbers', function () {
- var parsed = parser(['--foo', '5'], {
- configuration: {
- 'parse-numbers': false
- }
- })
- expect(parsed['foo']).to.equal('5')
- })
-
- it('does not coerce positional arguments into numbers', function () {
- var parsed = parser(['5'], {
- configuration: {
- 'parse-numbers': false
- }
- })
- expect(parsed._[0]).to.equal('5')
- })
- })
-
- describe('boolean negation', function () {
- it('does not negate arguments prefixed with --no-', function () {
- var parsed = parser(['--no-dice'], {
- configuration: {
- 'boolean-negation': false
- }
- })
-
- parsed['no-dice'].should.equal(true)
- expect(parsed.dice).to.equal(undefined)
- })
- })
-
- describe('duplicate arguments array', function () {
- it('adds duplicate argument to array', function () {
- var parsed = parser('-x a -x b', {
- configuration: {
- 'duplicate-arguments-array': true
- }
- })
-
- parsed['x'].should.deep.equal(['a', 'b'])
- })
- it('keeps only last argument', function () {
- var parsed = parser('-x a -x b', {
- configuration: {
- 'duplicate-arguments-array': false
- }
- })
-
- parsed['x'].should.equal('b')
- })
- })
-
- describe('flatten duplicate arrays', function () {
- it('flattens duplicate array type', function () {
- var parsed = parser('-x a b -x c d', {
- array: ['x'],
- configuration: {
- 'flatten-duplicate-arrays': true
- }
- })
-
- parsed['x'].should.deep.equal(['a', 'b', 'c', 'd'])
- })
- it('nests duplicate array types', function () {
- var parsed = parser('-x a b -x c d', {
- array: ['x'],
- configuration: {
- 'flatten-duplicate-arrays': false
- }
- })
-
- parsed['x'].should.deep.equal([['a', 'b'], ['c', 'd']])
- })
- it('doesn\'t nests single arrays', function () {
- var parsed = parser('-x a b', {
- array: ['x'],
- configuration: {
- 'flatten-duplicate-arrays': false
- }
- })
-
- parsed['x'].should.deep.equal(['a', 'b'])
- })
- })
-
- describe('duplicate-arguments-array VS flatten-duplicate-arrays', function () {
- /*
- duplicate=false, flatten=false
- type=array
- [-x 1 2 3] => [1, 2, 3]
- [-x 1 2 3 -x 2 3 4] => [2, 3, 4]
- type=string/number/etc
- [-x 1 -x 2 -x 3] => 3
-
- duplicate=false, flatten=true
- type=array
- [-x 1 2 3] => [1, 2, 3]
- [-x 1 2 3 -x 2 3 4] => [2, 3, 4]
- type=string/number/etc
- [-x 1 -x 2 -x 3] => 3
-
- duplicate=true, flatten=true
- type=array
- [-x 1 2 3] => [1, 2, 3]
- [-x 1 2 3 -x 2 3 4] => [1, 2, 3, 2, 3, 4]
- type=string/number/etc
- [-x 1 -x 2 -x 3] => [1, 2, 3]
-
- duplicate=true, flatten=false
- type=array
- [-x 1 2 3] => [1, 2, 3]
- [-x 1 2 3 -x 2 3 4] => [[1, 2, 3], [2, 3, 4]]
- type=string/number/etc
- [-x 1 -x 2 -x 3] => [1, 2, 3]
- */
- describe('duplicate=false, flatten=false,', function () {
- describe('type=array', function () {
- it('[-x 1 2 3] => [1, 2, 3]', function () {
- var parsed = parser('-x 1 2 3', {
- array: ['x'],
- configuration: {
- 'duplicate-arguments-array': false,
- 'flatten-duplicate-arrays': false
- }
- })
- 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', {
- array: ['x'],
- configuration: {
- 'duplicate-arguments-array': false,
- 'flatten-duplicate-arrays': false
- }
- })
- parsed['x'].should.deep.equal([2, 3, 4])
- })
- })
- describe('type=number', function () {
- it('[-x 1 -x 2 -x 3] => 3', function () {
- var parsed = parser('-x 1 -x 2 -x 3', {
- number: 'x',
- configuration: {
- 'duplicate-arguments-array': false,
- 'flatten-duplicate-arrays': false
- }
- })
- parsed['x'].should.deep.equal(3)
- })
- })
- })
- describe('duplicate=false, flatten=true,', function () {
- describe('type=array', function () {
- it('[-x 1 2 3] => [1, 2, 3]', function () {
- var parsed = parser('-x 1 2 3', {
- array: ['x'],
- configuration: {
- 'duplicate-arguments-array': false,
- 'flatten-duplicate-arrays': true
- }
- })
- 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', {
- array: ['x'],
- configuration: {
- 'duplicate-arguments-array': false,
- 'flatten-duplicate-arrays': true
- }
- })
- parsed['x'].should.deep.equal([2, 3, 4])
- })
- })
- describe('type=number', function () {
- it('[-x 1 -x 2 -x 3] => 3', function () {
- var parsed = parser('-x 1 -x 2 -x 3', {
- number: 'x',
- configuration: {
- 'duplicate-arguments-array': false,
- 'flatten-duplicate-arrays': true
- }
- })
- parsed['x'].should.deep.equal(3)
- })
- })
- })
- describe('duplicate=true, flatten=true,', function () {
- describe('type=array', function () {
- it('[-x 1 2 3] => [1, 2, 3]', function () {
- var parsed = parser('-x 1 2 3', {
- array: ['x'],
- configuration: {
- 'duplicate-arguments-array': true,
- 'flatten-duplicate-arrays': true
- }
- })
- 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', {
- array: ['x'],
- configuration: {
- 'duplicate-arguments-array': true,
- 'flatten-duplicate-arrays': true
- }
- })
- parsed['x'].should.deep.equal([1, 2, 3, 2, 3, 4])
- })
- })
- describe('type=number', function () {
- it('[-x 1 -x 2 -x 3] => [1, 2, 3]', function () {
- var parsed = parser('-x 1 -x 2 -x 3', {
- number: 'x',
- configuration: {
- 'duplicate-arguments-array': true,
- 'flatten-duplicate-arrays': true
- }
- })
- parsed['x'].should.deep.equal([1, 2, 3])
- })
- })
- })
- describe('duplicate=true, flatten=false,', function () {
- describe('type=array', function () {
- it('[-x 1 -x 2 -x 3] => [1, 2, 3]', function () {
- var parsed = parser('-x 1 -x 2 -x 3', {
- array: ['x'],
- configuration: {
- 'duplicate-arguments-array': true,
- 'flatten-duplicate-arrays': false
- }
- })
- 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', {
- array: ['x'],
- configuration: {
- 'duplicate-arguments-array': true,
- 'flatten-duplicate-arrays': false
- }
- })
- parsed['x'].should.deep.equal([[1, 2, 3], [2, 3, 4]])
- })
- })
- describe('type=number', function () {
- it('[-x 1 -x 2 -x 3] => [1, 2, 3]', function () {
- var parsed = parser('-x 1 -x 2 -x 3', {
- number: 'x',
- configuration: {
- 'duplicate-arguments-array': true,
- 'flatten-duplicate-arrays': false
- }
- })
- parsed['x'].should.deep.equal([1, 2, 3])
- })
- })
- })
- })
- })
-
- // 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'
- },
- 'array': ['files']
- })
- parsed.f.should.deep.equal([])
- parsed.files.should.deep.equal([])
-
- parsed = parser(['--files'], {
- 'alias': {
- 'f': 'files'
- },
- 'array': ['files']
- })
- parsed.f.should.deep.equal([])
- parsed.files.should.deep.equal([])
-
- parsed = parser(['-f', '-y'], {
- 'alias': {
- 'f': 'files'
- },
- 'array': ['files']
- })
- parsed.f.should.deep.equal([])
- parsed.files.should.deep.equal([])
- })
-
- describe('coerce', function () {
- it('applies coercion function to simple arguments', function () {
- var parsed = parser(['--foo', '99'], {
- coerce: {
- foo: function (arg) {
- return arg * -1
- }
- }
- })
- parsed.foo.should.equal(-99)
- })
-
- it('applies coercion function to aliases', function () {
- var parsed = parser(['--foo', '99'], {
- coerce: {
- f: function (arg) {
- return arg * -1
- }
- },
- alias: {
- f: ['foo']
- }
- })
- parsed.foo.should.equal(-99)
- parsed.f.should.equal(-99)
- })
-
- it('applies coercion function to all dot options', function () {
- var parsed = parser(['--foo.bar', 'nananana'], {
- coerce: {
- foo: function (val) {
- val.bar += ', batman!'
- return val
- }
- }
- })
- parsed.foo.bar.should.equal('nananana, batman!')
- })
-
- it('applies coercion to defaults', function () {
- var parsed = parser([], {
- default: { foo: 'bar' },
- coerce: {
- foo: function (val) {
- return val.toUpperCase()
- }
- }
- })
- parsed.foo.should.equal('BAR')
- })
-
- it('applies coercion function to an implicit array', function () {
- var parsed = parser(['--foo', '99', '-f', '33'], {
- coerce: {
- f: function (arg) {
- return arg.map(function (a) {
- return a * -1
- })
- }
- },
- alias: {
- f: ['foo']
- }
- })
- parsed.f.should.deep.equal([-99, -33])
- parsed.foo.should.deep.equal([-99, -33])
- })
-
- it('applies coercion function to an explicit array', function () {
- var parsed = parser(['--foo', '99', '-f', '33'], {
- coerce: {
- f: function (arg) {
- return arg.map(function (a) {
- return a * -1
- })
- }
- },
- array: ['foo'],
- alias: {
- f: ['foo']
- }
- })
- parsed.f.should.deep.equal([-99, -33])
- parsed.foo.should.deep.equal([-99, -33])
- })
-
- it('applies coercion function to _', function () {
- var parsed = parser(['99', '33'], {
- coerce: {
- _: function (arg) {
- return arg.map(function (a) {
- return a * -1
- })
- }
- }
- })
- parsed._.should.deep.equal([-99, -33])
- })
-
- // see: https://github.com/yargs/yargs/issues/550
- it('coercion function can be used to parse large #s', function () {
- var fancyNumberParser = function (arg) {
- if (arg.length > 10) return arg
- else return parseInt(arg)
- }
- var parsed = parser(['--foo', '88888889999990000998989898989898', '--bar', '998'], {
- coerce: {
- foo: fancyNumberParser,
- bar: fancyNumberParser
- }
- })
- ;(typeof parsed.foo).should.equal('string')
- parsed.foo.should.equal('88888889999990000998989898989898')
- ;(typeof parsed.bar).should.equal('number')
- parsed.bar.should.equal(998)
- })
-
- it('populates argv.error, if an error is thrown', function () {
- var parsed = parser.detailed(['--foo', '99'], {
- coerce: {
- foo: function (arg) {
- throw Error('banana')
- }
- }
- })
- parsed.error.message.should.equal('banana')
- })
-
- it('populates argv.error, if an error is thrown for an explicit array', function () {
- var parsed = parser.detailed(['--foo', '99'], {
- array: ['foo'],
- coerce: {
- foo: function (arg) {
- throw Error('foo is array: ' + Array.isArray(arg))
- }
- }
- })
- parsed.error.message.should.equal('foo is array: true')
- })
- })
-
- // 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' ], {
- array: ['a'],
- normalize: ['a'],
- configObjects: [{'a': ['bin/../a.txt', 'bin/../b.txt']}]
- })
- argv.a.should.deep.equal(['a.txt', 'b.txt'])
- })
-})
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..9cf3eed3
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "extends": "./node_modules/gts/tsconfig-google.json",
+ "compilerOptions": {
+ "outDir": "build",
+ "rootDir": ".",
+ "sourceMap": false,
+ "target": "es2017",
+ "moduleResolution": "node",
+ "module": "es2015"
+ },
+ "include": [
+ "lib/**/*.ts"
+ ]
+}
\ No newline at end of file
diff --git a/tsconfig.test.json b/tsconfig.test.json
new file mode 100644
index 00000000..0a884940
--- /dev/null
+++ b/tsconfig.test.json
@@ -0,0 +1,9 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "sourceMap": true
+ },
+ "include": [
+ "test/typescript/*.ts"
+ ]
+}
\ No newline at end of file