Repository: yargs/yargs
Pull request #1057 (2018-01-24 09:49:31)

Add documentation for array()
Add some useful documentation for when using array() with positional()


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0eaf2f3..f3912fe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,22 @@
 
 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.
 
+<a name="11.1.0"></a>
+# [11.1.0](https://github.com/yargs/yargs/compare/v11.0.0...v11.1.0) (2018-03-04)
+
+
+### Bug Fixes
+
+* choose correct config directory when require.main does not exist ([#1056](https://github.com/yargs/yargs/issues/1056)) ([a04678c](https://github.com/yargs/yargs/commit/a04678c))
+
+
+### Features
+
+* allow hidden options to be displayed with --show-hidden ([#1061](https://github.com/yargs/yargs/issues/1061)) ([ea862ae](https://github.com/yargs/yargs/commit/ea862ae))
+* extend *.rc files in addition to json ([#1080](https://github.com/yargs/yargs/issues/1080)) ([11691a6](https://github.com/yargs/yargs/commit/11691a6))
+
+
+
 <a name="11.0.0"></a>
 # [11.0.0](https://github.com/yargs/yargs/compare/v10.1.2...v11.0.0) (2018-01-22)
 
diff --git a/README.md b/README.md
index d16b82d..a09b322 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,11 @@
-# Yargs
+<p align="center">
+  <img width="250" src="/yargs-logo.png">
+</p>
+<h1 align="center"> Yargs </h1>
+<p align="center">
+  <b >Yargs be a node.js library fer hearties tryin' ter parse optstrings</b>
+</p>
+<br>
 
 [![Build Status][travis-image]][travis-url]
 [![Coverage Status][coveralls-image]][coveralls-url]
@@ -8,13 +15,10 @@
 [![Conventional Commits][conventional-commits-image]][conventional-commits-url]
 [![Slack][slack-image]][slack-url]
 
-_Having problems? want to contribute? join our [community slack](http://devtoolscommunity.herokuapp.com)_.
+## Description :
+Yargs helps you build interactive command line tools, by parsing arguments and generating an elegant user interface. 
 
-> Yargs be a node.js library fer hearties tryin' ter parse optstrings.
-
-<img width="250" src="/yargs-logo.png">
-
-Yargs helps you build interactive command line tools, by parsing arguments and generating an elegant user interface. It gives you:
+It gives you:
 
 * commands and (grouped) options (`my-program.js serve --port=5000`).
 * a dynamically generated help menu based on your arguments.
@@ -30,7 +34,9 @@ Yargs helps you build interactive command line tools, by parsing arguments and g
 npm i yargs --save
 ```
 
-## Simple Example
+## Usage :
+
+### Simple Example
 
 ````javascript
 #!/usr/bin/env node
@@ -51,9 +57,9 @@ $ ./plunder.js --ships 12 --distance 98.7
 Retreat from the xupptumblers!
 ```
 
-## Complex Example
+### Complex Example
 
-```js
+```javascript
 #!/usr/bin/env node
 require('yargs') // eslint-disable-line
   .command('serve [port]', 'start the server', (yargs) => {
@@ -75,7 +81,13 @@ require('yargs') // eslint-disable-line
 
 Run the example above with `--help` to see the help for the application.
 
-## Table of Contents
+## Community :
+
+Having problems? want to contribute? join our [community slack](http://devtoolscommunity.herokuapp.com).
+
+## Documentation :
+
+### Table of Contents
 
 * [Yargs' API](/docs/api.md)
 * [Examples](/docs/examples.md)
diff --git a/docs/api.md b/docs/api.md
index 193056b..0ffb43c 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -61,6 +61,16 @@ Tell the parser to interpret `key` as an array. If `.array('foo')` is set,
 Also, if you use the option multiple times all the values will be flattened in one array
 so `--foo foo --foo bar` will be parsed as `['foo', 'bar']`
 
+When the option is used with a positional, use `--` to tell `yargs` to stop adding values to the array.
+
+For example: `--foo foo bar -- val` will be parsed as
+```javascript
+{
+  _: ['val'],
+  foo: ['foo', 'bar']
+}
+```
+
 <a name="boolean"></a>.boolean(key)
 -------------
 
@@ -576,6 +586,11 @@ Describe a `key` for the generated usage information.
 
 Optionally `.describe()` can take an object that maps keys to descriptions.
 
+<a name="hide"></a>.hide(key)
+--------------------
+
+Hides a `key` from the generated usage information. Unless a `--show-hidden` option is also passed with `--help` (see [`showHidden()`](#showHidden)).
+
 .detectLocale(boolean)
 -----------
 
@@ -798,6 +813,29 @@ var yargs = require("yargs")(['--info'])
   .argv
 ```
 
+<a name="showHidden"></a>.showHidden()
+-----------------------------------------
+.showHidden([option | boolean])
+-----------------------------------------
+.showHidden([option, [description]])
+-----------------------------------------
+
+Configure the `--show-hidden` option that displays the hidden keys (see [`hide()`](#hide)).
+
+If the first argument is a boolean, it enables/disables this option altogether. i.e. hidden keys will be permanently hidden if first argument is `false`.
+
+If the first argument is a string it changes the key name ("--show-hidden").
+
+Second argument changes the default description ("Show hidden options")
+
+Example:
+
+```js
+var yargs = require("yargs")(['--help'])
+  .showHidden('show-hidden', 'Show hidden options')
+  .argv
+```
+
 <a name="implies"></a>.implies(x, y)
 --------------
 
diff --git a/example/cmds/init.js b/example/cmds/init.js
new file mode 100644
index 0000000..ecfd314
--- /dev/null
+++ b/example/cmds/init.js
@@ -0,0 +1,10 @@
+exports.command = 'init [dir]'
+exports.desc = 'Create an empty repo'
+exports.builder = {
+  dir: {
+    default: '.'
+  }
+}
+exports.handler = function (argv) {
+  console.log('init called for dir', argv.dir)
+}
diff --git a/example/cmds/remote.js b/example/cmds/remote.js
new file mode 100644
index 0000000..e0ea5dc
--- /dev/null
+++ b/example/cmds/remote.js
@@ -0,0 +1,6 @@
+exports.command = 'remote <command>'
+exports.description = 'Manage set of tracked repos'
+exports.builder = function (yargs) {
+  return yargs.commandDir('remote_cmds')
+}
+exports.handler = function (argv) {}
diff --git a/example/cmds/remote_cmds/add.js b/example/cmds/remote_cmds/add.js
new file mode 100644
index 0000000..4f3b0e3
--- /dev/null
+++ b/example/cmds/remote_cmds/add.js
@@ -0,0 +1,6 @@
+exports.command = 'add <name> <url>'
+exports.desc = 'Add remote named <name> for repo at url <url>'
+exports.builder = {}
+exports.handler = function (argv) {
+  console.log('adding remote %s at url %s', argv.name, argv.url)
+}
diff --git a/example/cmds/remote_cmds/prune.js b/example/cmds/remote_cmds/prune.js
new file mode 100644
index 0000000..b791da8
--- /dev/null
+++ b/example/cmds/remote_cmds/prune.js
@@ -0,0 +1,6 @@
+exports.command = 'prune <name> [names..]'
+exports.desc = 'Delete tracked branches gone stale for remotes'
+exports.builder = {}
+exports.handler = function (argv) {
+  console.log('pruning remotes %s', [].concat(argv.name).concat(argv.names).join(', '))
+}
diff --git a/example/command_hierarchy.js b/example/command_hierarchy.js
new file mode 100644
index 0000000..850d4ba
--- /dev/null
+++ b/example/command_hierarchy.js
@@ -0,0 +1,6 @@
+#!/usr/bin/env node
+require('yargs')
+  .commandDir('cmds')
+  .demandCommand()
+  .help()
+  .argv
diff --git a/lib/apply-extends.js b/lib/apply-extends.js
index 3005848..1436b65 100644
--- a/lib/apply-extends.js
+++ b/lib/apply-extends.js
@@ -6,9 +6,9 @@ const YError = require('./yerror')
 
 let previouslyVisitedConfigs = []
 
-function checkForCircularExtends (path) {
-  if (previouslyVisitedConfigs.indexOf(path) > -1) {
-    throw new YError(`Circular extended configurations: '${path}'.`)
+function checkForCircularExtends (cfgPath) {
+  if (previouslyVisitedConfigs.indexOf(cfgPath) > -1) {
+    throw new YError(`Circular extended configurations: '${cfgPath}'.`)
   }
 }
 
@@ -21,7 +21,7 @@ function applyExtends (config, cwd) {
 
   if (config.hasOwnProperty('extends')) {
     if (typeof config.extends !== 'string') return defaultConfig
-    const isPath = /\.json$/.test(config.extends)
+    const isPath = /\.json|\..*rc$/.test(config.extends)
     let pathToDefault = null
     if (!isPath) {
       try {
diff --git a/lib/usage.js b/lib/usage.js
index bd0906a..c323c1b 100644
--- a/lib/usage.js
+++ b/lib/usage.js
@@ -152,16 +152,23 @@ module.exports = function usage (yargs, y18n) {
     const demandedCommands = yargs.getDemandedCommands()
     const groups = yargs.getGroups()
     const options = yargs.getOptions()
-    let keys = Object.keys(
-      Object.keys(descriptions)
-      .concat(Object.keys(demandedOptions))
-      .concat(Object.keys(demandedCommands))
-      .concat(Object.keys(options.default))
-      .reduce((acc, key) => {
-        if (key !== '_') acc[key] = true
-        return acc
-      }, {})
-    )
+
+    let keys = []
+    keys = keys.concat(Object.keys(descriptions))
+    keys = keys.concat(Object.keys(demandedOptions))
+    keys = keys.concat(Object.keys(demandedCommands))
+    keys = keys.concat(Object.keys(options.default))
+    keys = keys.filter(key => {
+      if (options.hiddenOptions.indexOf(key) < 0) {
+        return true
+      } else if (yargs.parsed.argv[options.showHiddenOpt]) {
+        return true
+      }
+    })
+    keys = Object.keys(keys.reduce((acc, key) => {
+      if (key !== '_') acc[key] = true
+      return acc
+    }, {}))
 
     const theWrap = getWrap()
     const ui = require('cliui')({
diff --git a/package.json b/package.json
index adc1cba..3ddba37 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "yargs",
-  "version": "11.0.0",
+  "version": "11.1.0",
   "description": "yargs the modern, pirate-themed, successor to optimist.",
   "main": "./index.js",
   "files": [
@@ -30,7 +30,7 @@
     "chalk": "^1.1.3",
     "coveralls": "^2.11.11",
     "cpr": "^2.0.0",
-    "cross-spawn": "^5.0.1",
+    "cross-spawn": "^6.0.4",
     "es6-promise": "^4.0.2",
     "hashish": "0.0.4",
     "mocha": "^3.0.1",
diff --git a/yargs.js b/yargs.js
index 9b50b8b..d9d34a4 100644
--- a/yargs.js
+++ b/yargs.js
@@ -93,7 +93,8 @@ function Yargs (processArgs, cwd, parentRequire) {
 
     const arrayOptions = [
       'array', 'boolean', 'string', 'skipValidation',
-      'count', 'normalize', 'number'
+      'count', 'normalize', 'number',
+      'hiddenOptions'
     ]
 
     const objectOptions = [
@@ -476,17 +477,17 @@ function Yargs (processArgs, cwd, parentRequire) {
     return self
   }
 
-  self.pkgConf = function pkgConf (key, path) {
-    argsert('<string> [string]', [key, path], arguments.length)
+  self.pkgConf = function pkgConf (key, rootPath) {
+    argsert('<string> [string]', [key, rootPath], arguments.length)
     let conf = null
     // prefer cwd to require-main-filename in this method
     // since we're looking for e.g. "nyc" config in nyc consumer
     // rather than "yargs" config in nyc (where nyc is the main filename)
-    const obj = pkgUp(path || cwd)
+    const obj = pkgUp(rootPath || cwd)
 
     // If an object exists in the key, add it to options.configObjects
     if (obj[key] && typeof obj[key] === 'object') {
-      conf = applyExtends(obj[key], path || cwd)
+      conf = applyExtends(obj[key], rootPath || cwd)
       options.configObjects = (options.configObjects || []).concat(conf)
     }
 
@@ -494,16 +495,24 @@ function Yargs (processArgs, cwd, parentRequire) {
   }
 
   const pkgs = {}
-  function pkgUp (path) {
-    const npath = path || '*'
+  function pkgUp (rootPath) {
+    const npath = rootPath || '*'
     if (pkgs[npath]) return pkgs[npath]
     const findUp = require('find-up')
 
     let obj = {}
     try {
+      let startDir = rootPath || require('require-main-filename')(parentRequire || require)
+
+      // When called in an environment that lacks require.main.filename, such as a jest test runner,
+      // startDir is already process.cwd(), and should not be shortened.
+      // Whether or not it is _actually_ a directory (e.g., extensionless bin) is irrelevant, find-up handles it.
+      if (!rootPath && path.extname(startDir)) {
+        startDir = path.dirname(startDir)
+      }
+
       const pkgJsonPath = findUp.sync('package.json', {
-        cwd: path || require('path').dirname(require('require-main-filename')(parentRequire || require)),
-        normalize: false
+        cwd: startDir
       })
       obj = JSON.parse(fs.readFileSync(pkgJsonPath))
     } catch (noop) {}
@@ -649,8 +658,9 @@ function Yargs (processArgs, cwd, parentRequire) {
       }
 
       const desc = opt.describe || opt.description || opt.desc
-      if (!opt.hidden) {
-        self.describe(key, desc)
+      self.describe(key, desc)
+      if (opt.hidden) {
+        self.hide(key)
       }
 
       if (opt.requiresArg) {
@@ -816,6 +826,28 @@ function Yargs (processArgs, cwd, parentRequire) {
     return self
   }
 
+  const defaultShowHiddenOpt = 'show-hidden'
+  options.showHiddenOpt = defaultShowHiddenOpt
+  self.addShowHiddenOpt = self.showHidden = function addShowHiddenOpt (opt, msg) {
+    argsert('[string|boolean] [string]', [opt, msg], arguments.length)
+
+    if (arguments.length === 1) {
+      if (opt === false) return self
+    }
+
+    const showHiddenOpt = typeof opt === 'string' ? opt : defaultShowHiddenOpt
+    self.boolean(showHiddenOpt)
+    self.describe(showHiddenOpt, msg || usage.deferY18nLookup('Show hidden options'))
+    options.showHiddenOpt = showHiddenOpt
+    return self
+  }
+
+  self.hide = function hide (key) {
+    argsert('<string|object>', [key], arguments.length)
+    options.hiddenOptions.push(key)
+    return self
+  }
+
   self.showHelpOnFail = function showHelpOnFail (enabled, message) {
     argsert('[boolean|string] [string]', [enabled, message], arguments.length)
     usage.showHelpOnFail(enabled, message)


Test patch:
diff --git a/test/fixtures/extends/.myotherrc b/test/fixtures/extends/.myotherrc
new file mode 100644
index 0000000..8c686ca
--- /dev/null
+++ b/test/fixtures/extends/.myotherrc
@@ -0,0 +1,4 @@
+{
+    "extends": "./config_1.json",
+    "c": 201
+}
\ No newline at end of file
diff --git a/test/fixtures/extends/.myrc b/test/fixtures/extends/.myrc
new file mode 100644
index 0000000..0d45cf9
--- /dev/null
+++ b/test/fixtures/extends/.myrc
@@ -0,0 +1,4 @@
+{
+    "extends": "./.myotherrc",
+    "a": 5
+}
\ No newline at end of file
diff --git a/test/fixtures/no-extension b/test/fixtures/no-extension
new file mode 100755
index 0000000..85c5620
--- /dev/null
+++ b/test/fixtures/no-extension
@@ -0,0 +1,7 @@
+#!/usr/bin/env node
+
+'use strict'
+
+var parser = require('../../yargs.js')(process.argv.slice(2))
+
+console.log(parser.argv)
diff --git a/test/fixtures/no-require-main.js b/test/fixtures/no-require-main.js
new file mode 100755
index 0000000..3e46c8b
--- /dev/null
+++ b/test/fixtures/no-require-main.js
@@ -0,0 +1,10 @@
+#!/usr/bin/env node
+
+'use strict'
+
+// for some unknown reason, a test environment has decided to omit require.main
+delete require.main
+
+var parser = require('../../yargs.js')(process.argv.slice(2), undefined, require)
+
+console.log(parser.argv)
diff --git a/test/integration.js b/test/integration.js
index a47812d..232f493 100644
--- a/test/integration.js
+++ b/test/integration.js
@@ -29,17 +29,17 @@ describe('integration tests', () => {
 
   describe('path returned by "which"', () => {
     it('should match the actual path to the script file', (done) => {
-      which('node', (err, path) => {
+      which('node', (err, resolvedPath) => {
         if (err) return done(err)
-        testArgs(`${path.replace('Program Files (x86)', 'Progra~2')
+        testArgs(`${resolvedPath.replace('Program Files (x86)', 'Progra~2')
                      .replace('Program Files', 'Progra~1')} bin.js`, [], done)
       })
     })
 
     it('should match the actual path to the script file, with arguments', (done) => {
-      which('node', (err, path) => {
+      which('node', (err, resolvedPath) => {
         if (err) return done(err)
-        testArgs(`${path.replace('Program Files (x86)', 'Progra~2')
+        testArgs(`${resolvedPath.replace('Program Files (x86)', 'Progra~2')
                      .replace('Program Files', 'Progra~1')} bin.js`, [ 'q', 'r' ], done)
       })
     })
@@ -169,6 +169,28 @@ describe('integration tests', () => {
             return done()
           })
         })
+
+        it('reads parser config settings when somebody obscures require.main', (done) => {
+          testCmd('./no-require-main.js', [ '--foo.bar' ], (code, stdout) => {
+            if (code) {
+              return done(new Error(`cmd exited with code ${code}`))
+            }
+
+            stdout.should.match(/foo\.bar/)
+            return done()
+          })
+        })
+
+        it('reads parser config settings when entry file has no extension', (done) => {
+          testCmd('./no-extension', [ '--foo.bar' ], (code, stdout) => {
+            if (code) {
+              return done(new Error(`cmd exited with code ${code}`))
+            }
+
+            stdout.should.match(/foo\.bar/)
+            return done()
+          })
+        })
       })
 
       after(() => {
@@ -180,13 +202,11 @@ describe('integration tests', () => {
 })
 
 function testCmd (cmd, args, cb) {
-  const oldDir = process.cwd()
-  process.chdir(path.join(__dirname, '/fixtures'))
-
   const cmds = cmd.split(' ')
 
-  const bin = spawn(cmds[0], cmds.slice(1).concat(args.map(String)))
-  process.chdir(oldDir)
+  const bin = spawn(cmds[0], cmds.slice(1).concat(args.map(String)), {
+    cwd: path.join(__dirname, '/fixtures')
+  })
 
   let stdout = ''
   bin.stdout.setEncoding('utf8')
diff --git a/test/usage.js b/test/usage.js
index e1ce066..611f72e 100644
--- a/test/usage.js
+++ b/test/usage.js
@@ -2887,5 +2887,83 @@ describe('usage tests', () => {
         ''
       ])
     })
+    it('--help should display all options (including hidden ones) with --show-hidden', () => {
+      const r = checkUsage(() => yargs('--help --show-hidden --mama ama')
+          .options({
+            foo: {
+              describe: 'FOO'
+            },
+            bar: {},
+            baz: {
+              describe: 'BAZ',
+              hidden: true
+            }
+          })
+          .argv
+        )
+
+      r.logs[0].split('\n').should.deep.equal([
+        'Options:',
+        '  --help     Show help                                                 [boolean]',
+        '  --version  Show version number                                       [boolean]',
+        '  --foo      FOO',
+        '  --bar',
+        '  --baz      BAZ',
+        ''
+      ])
+    })
+    it('--help should display --custom-show-hidden', () => {
+      const r = checkUsage(() => yargs('--help')
+          .options({
+            foo: {
+              describe: 'FOO'
+            },
+            bar: {},
+            baz: {
+              describe: 'BAZ',
+              hidden: true
+            }
+          })
+          .showHidden('custom-show-hidden')
+          .argv
+        )
+
+      r.logs[0].split('\n').should.deep.equal([
+        'Options:',
+        '  --help                Show help                                      [boolean]',
+        '  --version             Show version number                            [boolean]',
+        '  --foo                 FOO',
+        '  --bar',
+        '  --custom-show-hidden  Show hidden options                            [boolean]',
+        ''
+      ])
+    })
+    it('--help should display all options with --custom-show-hidden', () => {
+      const r = checkUsage(() => yargs('--help --custom-show-hidden')
+          .options({
+            foo: {
+              describe: 'FOO'
+            },
+            bar: {},
+            baz: {
+              describe: 'BAZ',
+              hidden: true
+            }
+          })
+          .showHidden('custom-show-hidden')
+          .argv
+        )
+
+      r.logs[0].split('\n').should.deep.equal([
+        'Options:',
+        '  --help                Show help                                      [boolean]',
+        '  --version             Show version number                            [boolean]',
+        '  --foo                 FOO',
+        '  --bar',
+        '  --baz                 BAZ',
+        '  --custom-show-hidden  Show hidden options                            [boolean]',
+        ''
+      ])
+    })
   })
 })
diff --git a/test/yargs.js b/test/yargs.js
index 1945228..70438c3 100644
--- a/test/yargs.js
+++ b/test/yargs.js
@@ -129,6 +129,42 @@ describe('yargs dsl tests', () => {
     expect(r.errors).to.deep.equal([])
   })
 
+  describe('hide', () => {
+    it('should add the key to hiddenOptions', () => {
+      const options = yargs('')
+        .hide('someKey')
+        .getOptions()
+      options.should.have.property('hiddenOptions')
+      options.hiddenOptions.should.include('someKey')
+    })
+  })
+
+  describe('showHidden', () => {
+    it('should have a default show-hidden private option pre-configured', () => {
+      const options = yargs('').getOptions()
+      options.should.have.property('showHiddenOpt')
+      options.showHiddenOpt.should.eql('show-hidden')
+    })
+    it('should not have show-hidden as an actual option described by default', () => {
+      const options = yargs('').getOptions()
+      options.key.should.not.have.property('show-hidden')
+    })
+    it('should set show-hidden option', () => {
+      const options = yargs('')
+        .showHidden()
+        .getOptions()
+      options.key.should.have.property('show-hidden')
+    })
+    it('should set custom-show-hidden option', () => {
+      const options = yargs('')
+        .showHidden('custom-show-hidden')
+        .getOptions()
+      options.key.should.have.property('custom-show-hidden')
+      options.should.have.property('showHiddenOpt')
+      options.showHiddenOpt.should.eql('custom-show-hidden')
+    })
+  })
+
   describe('showHelpOnFail', () => {
     it('should display custom failure message, if string is provided as first argument', () => {
       const r = checkOutput(() => yargs([])
@@ -233,6 +269,7 @@ describe('yargs dsl tests', () => {
         config: {},
         configObjects: [],
         envPrefix: 'YARGS', // preserved as global
+        hiddenOptions: [],
         demandedCommands: {},
         demandedOptions: {},
         local: [
@@ -1187,7 +1224,7 @@ describe('yargs dsl tests', () => {
   describe('config', () => {
     it('allows a parsing function to be provided as a second argument', () => {
       const argv = yargs('--config ./test/fixtures/config.json')
-        .config('config', path => JSON.parse(fs.readFileSync(path)))
+        .config('config', cfgPath => JSON.parse(fs.readFileSync(cfgPath)))
         .global('config', false)
         .argv
 
@@ -1284,6 +1321,20 @@ describe('yargs dsl tests', () => {
         argv.a.should.equal(2)
         argv.extends.should.equal('batman')
       })
+
+      it('allows files with .*rc extension to be extended', () => {
+        const argv = yargs()
+          .config({
+            extends: './test/fixtures/extends/.myrc',
+            a: 3
+          })
+          .argv
+
+        argv.a.should.equal(3)
+        argv.b.should.equal(22)
+        argv.c.should.equal(201)
+        argv.z.should.equal(15)
+      })
     })
   })