yargs 4.8.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,57 @@
2
2
 
3
3
  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.
4
4
 
5
+ <a name="5.0.0"></a>
6
+ # [5.0.0](https://github.com/yargs/yargs/compare/v4.8.1...v5.0.0) (2016-08-14)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **default:** Remove undocumented alias of default() ([#469](https://github.com/yargs/yargs/issues/469)) ([b8591b2](https://github.com/yargs/yargs/commit/b8591b2))
12
+ * remove deprecated zh.json ([#578](https://github.com/yargs/yargs/issues/578)) ([317c62c](https://github.com/yargs/yargs/commit/317c62c))
13
+
14
+
15
+ ### Features
16
+
17
+ * .help() API can now enable implicit help command ([#574](https://github.com/yargs/yargs/issues/574)) ([7645019](https://github.com/yargs/yargs/commit/7645019))
18
+ * **command:** builder function no longer needs to return the yargs instance ([#549](https://github.com/yargs/yargs/issues/549)) ([eaa2873](https://github.com/yargs/yargs/commit/eaa2873))
19
+ * add coerce api ([#586](https://github.com/yargs/yargs/issues/586)) ([1d53ccb](https://github.com/yargs/yargs/commit/1d53ccb))
20
+ * adds recommendCommands() for command suggestions ([#580](https://github.com/yargs/yargs/issues/580)) ([59474dc](https://github.com/yargs/yargs/commit/59474dc))
21
+ * apply .env() globally ([#553](https://github.com/yargs/yargs/issues/553)) ([be65728](https://github.com/yargs/yargs/commit/be65728))
22
+ * apply default builder to command() and apply fail() handlers globally ([#583](https://github.com/yargs/yargs/issues/583)) ([0aaa68b](https://github.com/yargs/yargs/commit/0aaa68b))
23
+ * interpret demand() numbers as relative to executing command ([#582](https://github.com/yargs/yargs/issues/582)) ([927810c](https://github.com/yargs/yargs/commit/927810c))
24
+ * update yargs-parser to version 3.1.0 ([#581](https://github.com/yargs/yargs/issues/581)) ([882a127](https://github.com/yargs/yargs/commit/882a127))
25
+
26
+
27
+ ### Performance Improvements
28
+
29
+ * defer requiring most external libs until needed ([#584](https://github.com/yargs/yargs/issues/584)) ([f9b0ed4](https://github.com/yargs/yargs/commit/f9b0ed4))
30
+
31
+
32
+ ### BREAKING CHANGES
33
+
34
+ * fail is now applied globally.
35
+ * we now default to an empty builder function when command is executed with no builder.
36
+ * yargs-parser now better handles negative integer values, at the cost of handling numeric option names, e.g., -1 hello
37
+ * default: removed undocumented `defaults` alias for `default`.
38
+ * introduces a default `help` command which outputs help, as an alternative to a help flag.
39
+
40
+
41
+
42
+ <a name="4.8.1"></a>
43
+ ## [4.8.1](https://github.com/yargs/yargs/compare/v4.8.0...v4.8.1) (2016-07-16)
44
+
45
+
46
+ ### Bug Fixes
47
+
48
+ * **commandDir:** make dir relative to caller instead of require.main.filename ([#548](https://github.com/yargs/yargs/issues/548)) ([3c2e479](https://github.com/yargs/yargs/commit/3c2e479))
49
+ * add config lookup for .implies() ([#556](https://github.com/yargs/yargs/issues/556)) ([8d7585c](https://github.com/yargs/yargs/commit/8d7585c))
50
+ * cache pkg lookups by path to avoid returning the wrong one ([#552](https://github.com/yargs/yargs/issues/552)) ([fea7e0b](https://github.com/yargs/yargs/commit/fea7e0b))
51
+ * positional arguments were not being handled appropriately by parse() ([#559](https://github.com/yargs/yargs/issues/559)) ([063a866](https://github.com/yargs/yargs/commit/063a866))
52
+ * pull in [@nexdrew](https://github.com/nexdrew)'s fixes to yargs-parser ([#560](https://github.com/yargs/yargs/issues/560)) ([c77c080](https://github.com/yargs/yargs/commit/c77c080)), closes [#560](https://github.com/yargs/yargs/issues/560)
53
+
54
+
55
+
5
56
  <a name="4.8.0"></a>
6
57
  # [4.8.0](https://github.com/yargs/yargs/compare/v4.7.1...v4.8.0) (2016-07-09)
7
58
 
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- yargs
1
+ yargs
2
2
  ========
3
3
 
4
4
  Yargs be a node.js library fer hearties tryin' ter parse optstrings.
@@ -6,12 +6,12 @@ Yargs be a node.js library fer hearties tryin' ter parse optstrings.
6
6
  With yargs, ye be havin' a map that leads straight to yer treasure! Treasure of course, being a simple option hash.
7
7
 
8
8
  [![Build Status][travis-image]][travis-url]
9
- [![Dependency Status][gemnasium-image]][gemnasium-url]
10
9
  [![Coverage Status][coveralls-image]][coveralls-url]
11
10
  [![NPM version][npm-image]][npm-url]
12
11
  [![Windows Tests][windows-image]][windows-url]
13
12
  [![js-standard-style][standard-image]][standard-url]
14
13
  [![standard-version][standard-version-image]][standard-version-url]
14
+ [![Gitter][gitter-image]][gitter-url]
15
15
 
16
16
  > Yargs is the official successor to optimist. Please feel free to submit issues and pull requests. If you'd like to contribute and don't know where to start, have a look at [the issue list](https://github.com/yargs/yargs/issues) :)
17
17
 
@@ -294,12 +294,11 @@ line_count.js:
294
294
  var argv = require('yargs')
295
295
  .usage('Usage: $0 <command> [options]')
296
296
  .command('count', 'Count the lines in a file')
297
- .demand(1)
298
297
  .example('$0 count -f foo.js', 'count the lines in the given file')
299
- .demand('f')
300
298
  .alias('f', 'file')
301
299
  .nargs('f', 1)
302
300
  .describe('f', 'Load a file')
301
+ .demand(1, ['f'])
303
302
  .help('h')
304
303
  .alias('h', 'help')
305
304
  .epilog('copyright 2015')
@@ -453,6 +452,49 @@ var argv = require('yargs')
453
452
  .argv
454
453
  ```
455
454
 
455
+ <a name="coerce"></a>.coerce(key, fn)
456
+ ----------------
457
+
458
+ Provide a synchronous function to coerce or transform the value(s) given on the
459
+ command line for `key`.
460
+
461
+ The coercion function should accept one argument, representing the parsed value
462
+ from the command line, and should return a new value or throw an error. The
463
+ returned value will be used as the value for `key` (or one of its aliases) in
464
+ `argv`. If the function throws, the error will be treated as a validation
465
+ failure, delegating to either a custom [`.fail()`](#fail) handler or printing
466
+ the error message in the console.
467
+
468
+ ```js
469
+ var argv = require('yargs')
470
+ .coerce('file', function (arg) {
471
+ return require('fs').readFileSync(arg, 'utf8')
472
+ })
473
+ .argv
474
+ ```
475
+
476
+ Optionally `.coerce()` can take an object that maps several keys to their
477
+ respective coercion function.
478
+
479
+ ```js
480
+ var argv = require('yargs')
481
+ .coerce({
482
+ date: Date.parse,
483
+ json: JSON.parse
484
+ })
485
+ .argv
486
+ ```
487
+
488
+ You can also map the same function to several keys at one time. Just pass an
489
+ array of keys as the first argument to `.coerce()`:
490
+
491
+ ```js
492
+ var path = require('path')
493
+ var argv = require('yargs')
494
+ .coerce(['src', 'dest'], path.resolve)
495
+ .argv
496
+ ```
497
+
456
498
  .command(cmd, desc, [builder], [handler])
457
499
  -----------------------------------------
458
500
  .command(cmd, desc, [module])
@@ -1006,7 +1048,7 @@ By default, yargs exits the process when the user passes a help flag, uses the
1006
1048
  `.exitProcess(false)` disables this behavior, enabling further actions after
1007
1049
  yargs have been validated.
1008
1050
 
1009
- .fail(fn)
1051
+ <a name="fail"></a>.fail(fn)
1010
1052
  ---------
1011
1053
 
1012
1054
  Method to execute when a failure occurs, rather than printing the failure message.
@@ -1100,15 +1142,31 @@ var yargs = require('yargs')(['--help'])
1100
1142
  Options:
1101
1143
  --help Show help [boolean]
1102
1144
 
1103
- <a name="help"></a>.help([option, [description]])
1104
- ------------------------------
1145
+ <a name="help"></a>.help()
1146
+ -----------------------------------------
1147
+ .help([option | boolean])
1148
+ -----------------------------------------
1149
+ .help([option, [description | boolean]])
1150
+ -----------------------------------------
1151
+ .help([option, [description, [boolean]]])
1152
+ -----------------------------------------
1105
1153
 
1106
- Add an option (e.g. `--help`) that displays the usage string and exits the
1107
- process. If present, the `description` parameter customizes the description of
1154
+ Add an option (e.g. `--help`) and implicit command that displays the usage
1155
+ string and exits the process.
1156
+
1157
+ If present, the `description` parameter customizes the description of
1108
1158
  the help option in the usage string.
1109
1159
 
1110
- If invoked without parameters, `.help()` will make `--help` the option to trigger
1111
- help output.
1160
+ If a boolean argument is provided, it will enable or disable the use of an
1161
+ implicit command. The implicit command is enabled by default, but it can be
1162
+ disabled by passing `false`.
1163
+
1164
+ Note that any multi-char aliases (e.g. `help`) used for the help option will
1165
+ also be used for the implicit command. If there are no multi-char aliases (e.g.
1166
+ `h`), then all single-char aliases will be used for the command.
1167
+
1168
+ If invoked without parameters, `.help()` will use `--help` as the option and
1169
+ `help` as the implicit command to trigger help output.
1112
1170
 
1113
1171
  Example:
1114
1172
 
@@ -1309,6 +1367,7 @@ Valid `opt` keys include:
1309
1367
  - `array`: boolean, interpret option as an array, see [`array()`](#array)
1310
1368
  - `boolean`: boolean, interpret option as a boolean flag, see [`boolean()`](#boolean)
1311
1369
  - `choices`: value or array of values, limit valid option arguments to a predefined set, see [`choices()`](#choices)
1370
+ - `coerce`: function, coerce or transform parsed command line values into another value, see [`coerce()`](#coerce)
1312
1371
  - `config`: boolean, interpret option as a path to a JSON config file, see [`config()`](#config)
1313
1372
  - `configParser`: function, provide a custom config parsing function, see [`config()`](#config)
1314
1373
  - `count`: boolean, interpret option as a count of boolean flags, see [`count()`](#count)
@@ -1347,6 +1406,12 @@ as a configuration object.
1347
1406
  `cwd` can optionally be provided, the package.json will be read
1348
1407
  from this location.
1349
1408
 
1409
+ .recommendCommands()
1410
+ ---------------------------
1411
+
1412
+ Should yargs provide suggestions regarding similar commands if no matching
1413
+ command is found?
1414
+
1350
1415
  .require(key, [msg | boolean])
1351
1416
  ------------------------------
1352
1417
  .required(key, [msg | boolean])
@@ -1660,8 +1725,6 @@ This module is loosely inspired by Perl's
1660
1725
 
1661
1726
  [travis-url]: https://travis-ci.org/yargs/yargs
1662
1727
  [travis-image]: https://img.shields.io/travis/yargs/yargs/master.svg
1663
- [gemnasium-url]: https://gemnasium.com/yargs/yargs
1664
- [gemnasium-image]: https://img.shields.io/gemnasium/yargs/yargs.svg
1665
1728
  [coveralls-url]: https://coveralls.io/github/yargs/yargs
1666
1729
  [coveralls-image]: https://img.shields.io/coveralls/yargs/yargs.svg
1667
1730
  [npm-url]: https://www.npmjs.com/package/yargs
@@ -1672,3 +1735,5 @@ This module is loosely inspired by Perl's
1672
1735
  [standard-url]: http://standardjs.com/
1673
1736
  [standard-version-image]: https://img.shields.io/badge/release-standard%20version-brightgreen.svg
1674
1737
  [standard-version-url]: https://github.com/conventional-changelog/standard-version
1738
+ [gitter-image]: https://img.shields.io/gitter/room/nwjs/nw.js.svg?maxAge=2592000
1739
+ [gitter-url]: https://gitter.im/yargs/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link
package/lib/command.js CHANGED
@@ -1,7 +1,5 @@
1
1
  const path = require('path')
2
2
  const inspect = require('util').inspect
3
- const requireDirectory = require('require-directory')
4
- const whichModule = require('which-module')
5
3
 
6
4
  // handles parsing positional arguments,
7
5
  // and populating argv with said positional
@@ -34,18 +32,14 @@ module.exports = function (yargs, usage, validation) {
34
32
  handlers[parsedCommand.cmd] = {
35
33
  original: cmd,
36
34
  handler: handler,
37
- // TODO: default to a noop builder in
38
- // yargs@5.x
39
- builder: builder,
35
+ builder: builder || {},
40
36
  demanded: parsedCommand.demanded,
41
37
  optional: parsedCommand.optional
42
38
  }
43
39
  }
44
40
 
45
- self.addDirectory = function (dir, context, req, mainFilename, opts) {
41
+ self.addDirectory = function (dir, context, req, callerFile, opts) {
46
42
  opts = opts || {}
47
- // dir should be relative to the command module
48
- dir = path.join(context.dirs[context.commands.join('|')] || '', dir)
49
43
  // disable recursion to support nested directories of subcommands
50
44
  if (typeof opts.recurse !== 'boolean') opts.recurse = false
51
45
  // exclude 'json', 'coffee' from require-directory defaults
@@ -62,20 +56,17 @@ module.exports = function (yargs, usage, validation) {
62
56
  if (~context.files.indexOf(joined)) return visited
63
57
  // keep track of visited files in context.files
64
58
  context.files.push(joined)
65
- // map "command path" to the directory path it came from
66
- // so that dir can be relative in the API
67
- context.dirs[context.commands.concat(parseCommand(visited.command || commandFromFilename(filename)).cmd).join('|')] = dir
68
59
  self.addHandler(visited)
69
60
  }
70
61
  return visited
71
62
  }
72
- requireDirectory({ require: req, filename: mainFilename }, dir, opts)
63
+ require('require-directory')({ require: req, filename: callerFile }, dir, opts)
73
64
  }
74
65
 
75
66
  // lookup module object from require()d command and derive name
76
67
  // if module was not require()d and no name given, throw error
77
68
  function moduleName (obj) {
78
- const mod = whichModule(obj)
69
+ const mod = require('which-module')(obj)
79
70
  if (!mod) throw new Error('No command name given for module: ' + inspect(obj))
80
71
  return commandFromFilename(mod.filename)
81
72
  }
@@ -134,19 +125,23 @@ module.exports = function (yargs, usage, validation) {
134
125
  var currentContext = yargs.getContext()
135
126
  var parentCommands = currentContext.commands.slice()
136
127
  currentContext.commands.push(command)
137
- if (commandHandler.builder && typeof commandHandler.builder === 'function') {
138
- // a function can be provided, which interacts which builds
139
- // up a yargs chain and returns it.
128
+ if (typeof commandHandler.builder === 'function') {
129
+ // a function can be provided, which builds
130
+ // up a yargs chain and possibly returns it.
140
131
  innerArgv = commandHandler.builder(yargs.reset(parsed.aliases))
141
132
  // if the builder function did not yet parse argv with reset yargs
142
133
  // and did not explicitly set a usage() string, then apply the
143
134
  // original command string as usage() for consistent behavior with
144
135
  // options object below
145
- if (yargs.parsed === false && typeof yargs.getUsageInstance().getUsage() === 'undefined') {
146
- yargs.usage('$0 ' + (parentCommands.length ? parentCommands.join(' ') + ' ' : '') + commandHandler.original)
136
+ if (yargs.parsed === false) {
137
+ if (typeof yargs.getUsageInstance().getUsage() === 'undefined') {
138
+ yargs.usage('$0 ' + (parentCommands.length ? parentCommands.join(' ') + ' ' : '') + commandHandler.original)
139
+ }
140
+ innerArgv = innerArgv ? innerArgv.argv : yargs.argv
141
+ } else {
142
+ innerArgv = yargs.parsed.argv
147
143
  }
148
- innerArgv = innerArgv ? innerArgv.argv : argv
149
- } else if (commandHandler.builder && typeof commandHandler.builder === 'object') {
144
+ } else if (typeof commandHandler.builder === 'object') {
150
145
  // as a short hand, an object can instead be provided, specifying
151
146
  // the options that a command takes.
152
147
  innerArgv = yargs.reset(parsed.aliases)
@@ -157,7 +152,7 @@ module.exports = function (yargs, usage, validation) {
157
152
  innerArgv = innerArgv.argv
158
153
  }
159
154
 
160
- populatePositional(commandHandler, innerArgv, currentContext)
155
+ populatePositional(commandHandler, innerArgv, currentContext, yargs)
161
156
 
162
157
  if (commandHandler.handler) {
163
158
  commandHandler.handler(innerArgv)
@@ -166,7 +161,7 @@ module.exports = function (yargs, usage, validation) {
166
161
  return innerArgv
167
162
  }
168
163
 
169
- function populatePositional (commandHandler, argv, context) {
164
+ function populatePositional (commandHandler, argv, context, yargs) {
170
165
  argv._ = argv._.slice(context.commands.length) // nuke the current commands
171
166
  var demanded = commandHandler.demanded.slice(0)
172
167
  var optional = commandHandler.optional.slice(0)
@@ -179,6 +174,7 @@ module.exports = function (yargs, usage, validation) {
179
174
  if (!argv._.length) break
180
175
  if (demand.variadic) argv[demand.cmd] = argv._.splice(0)
181
176
  else argv[demand.cmd] = argv._.shift()
177
+ postProcessPositional(yargs, argv, demand.cmd)
182
178
  }
183
179
 
184
180
  while (optional.length) {
@@ -187,11 +183,24 @@ module.exports = function (yargs, usage, validation) {
187
183
  if (!argv._.length) break
188
184
  if (maybe.variadic) argv[maybe.cmd] = argv._.splice(0)
189
185
  else argv[maybe.cmd] = argv._.shift()
186
+ postProcessPositional(yargs, argv, maybe.cmd)
190
187
  }
191
188
 
192
189
  argv._ = context.commands.concat(argv._)
193
190
  }
194
191
 
192
+ // TODO move positional arg logic to yargs-parser and remove this duplication
193
+ function postProcessPositional (yargs, argv, key) {
194
+ var coerce = yargs.getOptions().coerce[key]
195
+ if (typeof coerce === 'function') {
196
+ try {
197
+ argv[key] = coerce(argv[key])
198
+ } catch (err) {
199
+ yargs.getUsageInstance().fail(err.message, err)
200
+ }
201
+ }
202
+ }
203
+
195
204
  self.reset = function () {
196
205
  handlers = {}
197
206
  return self
@@ -0,0 +1,47 @@
1
+ /*
2
+ Copyright (c) 2011 Andrei Mackenzie
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+
6
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9
+ */
10
+
11
+ // levenshtein distance algorithm, pulled from Andrei Mackenzie's MIT licensed.
12
+ // gist, which can be found here: https://gist.github.com/andrei-m/982927
13
+
14
+ // Compute the edit distance between the two given strings
15
+ module.exports = function (a, b) {
16
+ if (a.length === 0) return b.length
17
+ if (b.length === 0) return a.length
18
+
19
+ var matrix = []
20
+
21
+ // increment along the first column of each row
22
+ var i
23
+ for (i = 0; i <= b.length; i++) {
24
+ matrix[i] = [i]
25
+ }
26
+
27
+ // increment each column in the first row
28
+ var j
29
+ for (j = 0; j <= a.length; j++) {
30
+ matrix[0][j] = j
31
+ }
32
+
33
+ // Fill in the rest of the matrix
34
+ for (i = 1; i <= b.length; i++) {
35
+ for (j = 1; j <= a.length; j++) {
36
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
37
+ matrix[i][j] = matrix[i - 1][j - 1]
38
+ } else {
39
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
40
+ Math.min(matrix[i][j - 1] + 1, // insertion
41
+ matrix[i - 1][j] + 1)) // deletion
42
+ }
43
+ }
44
+ }
45
+
46
+ return matrix[b.length][a.length]
47
+ }
package/lib/usage.js CHANGED
@@ -1,9 +1,6 @@
1
1
  // this file handles outputting usage instructions,
2
2
  // failures, etc. keeps logging in one place.
3
- const cliui = require('cliui')
4
- const decamelize = require('decamelize')
5
3
  const stringWidth = require('string-width')
6
- const wsize = require('window-size')
7
4
  const objFilter = require('./obj-filter')
8
5
  const setBlocking = require('set-blocking')
9
6
 
@@ -34,9 +31,9 @@ module.exports = function (yargs, y18n) {
34
31
  var failureOutput = false
35
32
  self.fail = function (msg, err) {
36
33
  if (fails.length) {
37
- fails.forEach(function (f) {
38
- f(msg, err)
39
- })
34
+ for (var i = fails.length - 1; i >= 0; --i) {
35
+ fails[i](msg, err)
36
+ }
40
37
  } else {
41
38
  if (yargs.getExitProcess()) setBlocking(true)
42
39
 
@@ -125,7 +122,7 @@ module.exports = function (yargs, y18n) {
125
122
  return acc
126
123
  }, {})
127
124
  )
128
- var ui = cliui({
125
+ var ui = require('cliui')({
129
126
  width: wrap,
130
127
  wrap: !!wrap
131
128
  })
@@ -329,7 +326,7 @@ module.exports = function (yargs, y18n) {
329
326
  }
330
327
 
331
328
  self.functionDescription = function (fn) {
332
- var description = fn.name ? decamelize(fn.name, '-') : __('generated-value')
329
+ var description = fn.name ? require('decamelize')(fn.name, '-') : __('generated-value')
333
330
  return ['(', description, ')'].join('')
334
331
  }
335
332
 
@@ -375,6 +372,7 @@ module.exports = function (yargs, y18n) {
375
372
 
376
373
  // guess the width of the console window, max-width 80.
377
374
  function windowWidth () {
375
+ const wsize = require('window-size')
378
376
  return wsize.width ? Math.min(80, wsize.width) : null
379
377
  }
380
378
 
@@ -391,7 +389,7 @@ module.exports = function (yargs, y18n) {
391
389
 
392
390
  self.reset = function (globalLookup) {
393
391
  // do not reset wrap here
394
- fails = []
392
+ // do not reset fails here
395
393
  failMessage = null
396
394
  failureOutput = false
397
395
  usage = undefined
package/lib/validation.js CHANGED
@@ -11,18 +11,19 @@ module.exports = function (yargs, usage, y18n) {
11
11
  // arguments were provided, i.e., '_'.
12
12
  self.nonOptionCount = function (argv) {
13
13
  const demanded = yargs.getDemanded()
14
- const _s = argv._.length
14
+ // don't count currently executing commands
15
+ const _s = argv._.length - yargs.getContext().commands.length
15
16
 
16
17
  if (demanded._ && (_s < demanded._.count || _s > demanded._.max)) {
17
18
  if (demanded._.msg !== undefined) {
18
19
  usage.fail(demanded._.msg)
19
20
  } else if (_s < demanded._.count) {
20
21
  usage.fail(
21
- __('Not enough non-option arguments: got %s, need at least %s', argv._.length, demanded._.count)
22
+ __('Not enough non-option arguments: got %s, need at least %s', _s, demanded._.count)
22
23
  )
23
24
  } else {
24
25
  usage.fail(
25
- __('Too many non-option arguments: got %s, maximum of %s', argv._.length, demanded._.max)
26
+ __('Too many non-option arguments: got %s, maximum of %s', _s, demanded._.max)
26
27
  )
27
28
  }
28
29
  }
@@ -219,6 +220,12 @@ module.exports = function (yargs, usage, y18n) {
219
220
  const implyFail = []
220
221
 
221
222
  Object.keys(implied).forEach(function (key) {
223
+ var booleanNegation
224
+ if (yargs.getOptions().configuration['boolean-negation'] === false) {
225
+ booleanNegation = false
226
+ } else {
227
+ booleanNegation = true
228
+ }
222
229
  var num
223
230
  const origKey = key
224
231
  var value = implied[key]
@@ -230,7 +237,7 @@ module.exports = function (yargs, usage, y18n) {
230
237
  if (typeof key === 'number') {
231
238
  // check length of argv._
232
239
  key = argv._.length >= key
233
- } else if (key.match(/^--no-.+/)) {
240
+ } else if (key.match(/^--no-.+/) && booleanNegation) {
234
241
  // check if key doesn't exist
235
242
  key = key.match(/^--no-(.+)/)[1]
236
243
  key = !argv[key]
@@ -244,7 +251,7 @@ module.exports = function (yargs, usage, y18n) {
244
251
 
245
252
  if (typeof value === 'number') {
246
253
  value = argv._.length >= value
247
- } else if (value.match(/^--no-.+/)) {
254
+ } else if (value.match(/^--no-.+/) && booleanNegation) {
248
255
  value = value.match(/^--no-(.+)/)[1]
249
256
  value = !argv[value]
250
257
  } else {
@@ -267,6 +274,23 @@ module.exports = function (yargs, usage, y18n) {
267
274
  }
268
275
  }
269
276
 
277
+ self.recommendCommands = function (cmd, potentialCommands) {
278
+ const distance = require('./levenshtein')
279
+ const threshold = 3 // if it takes more than three edits, let's move on.
280
+ potentialCommands = potentialCommands.sort(function (a, b) { return b.length - a.length })
281
+
282
+ var recommended = null
283
+ var bestDistance = Infinity
284
+ for (var i = 0, candidate; (candidate = potentialCommands[i]) !== undefined; i++) {
285
+ var d = distance(cmd, candidate)
286
+ if (d <= threshold && d < bestDistance) {
287
+ bestDistance = d
288
+ recommended = candidate
289
+ }
290
+ }
291
+ if (recommended) usage.fail(__('Did you mean %s?', recommended))
292
+ }
293
+
270
294
  self.reset = function (globalLookup) {
271
295
  implied = objFilter(implied, function (k, v) {
272
296
  return globalLookup[k]
package/locales/de.json CHANGED
@@ -33,5 +33,6 @@
33
33
  "Invalid JSON config file: %s": "Fehlerhafte JSON-Config Datei: %s",
34
34
  "Path to JSON config file": "Pfad zur JSON-Config Datei",
35
35
  "Show help": "Hilfe anzeigen",
36
- "Show version number": "Version anzeigen"
36
+ "Show version number": "Version anzeigen",
37
+ "Did you mean %s?": "Meintest du %s?"
37
38
  }
package/locales/en.json CHANGED
@@ -33,5 +33,6 @@
33
33
  "Invalid JSON config file: %s": "Invalid JSON config file: %s",
34
34
  "Path to JSON config file": "Path to JSON config file",
35
35
  "Show help": "Show help",
36
- "Show version number": "Show version number"
36
+ "Show version number": "Show version number",
37
+ "Did you mean %s?": "Did you mean %s?"
37
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yargs",
3
- "version": "4.8.0",
3
+ "version": "5.0.0",
4
4
  "description": "yargs the modern, pirate-themed, successor to optimist.",
5
5
  "main": "./index.js",
6
6
  "files": [
@@ -14,28 +14,29 @@
14
14
  "dependencies": {
15
15
  "cliui": "^3.2.0",
16
16
  "decamelize": "^1.1.1",
17
- "lodash.assign": "^4.0.3",
17
+ "get-caller-file": "^1.0.1",
18
+ "lodash.assign": "^4.2.0",
18
19
  "os-locale": "^1.4.0",
19
20
  "read-pkg-up": "^1.0.1",
20
21
  "require-directory": "^2.1.1",
21
22
  "require-main-filename": "^1.0.1",
22
23
  "set-blocking": "^2.0.0",
23
- "string-width": "^1.0.1",
24
+ "string-width": "^1.0.2",
24
25
  "which-module": "^1.0.0",
25
26
  "window-size": "^0.2.0",
26
27
  "y18n": "^3.2.1",
27
- "yargs-parser": "^2.4.0"
28
+ "yargs-parser": "^3.2.0"
28
29
  },
29
30
  "devDependencies": {
30
31
  "chai": "^3.4.1",
31
32
  "chalk": "^1.1.3",
32
- "coveralls": "^2.11.9",
33
+ "coveralls": "^2.11.11",
33
34
  "cpr": "^1.0.0",
34
- "cross-spawn-async": "^2.2.1",
35
+ "cross-spawn": "^4.0.0",
35
36
  "es6-promise": "^3.0.2",
36
37
  "hashish": "0.0.4",
37
- "mocha": "^2.5.2",
38
- "nyc": "^7.0.0",
38
+ "mocha": "^3.0.1",
39
+ "nyc": "^8.1.0",
39
40
  "rimraf": "^2.5.0",
40
41
  "standard": "^7.0.0",
41
42
  "standard-version": "^2.2.1",
package/yargs.js CHANGED
@@ -1,5 +1,4 @@
1
1
  const assert = require('assert')
2
- const assign = require('lodash.assign')
3
2
  const Command = require('./lib/command')
4
3
  const Completion = require('./lib/completion')
5
4
  const Parser = require('yargs-parser')
@@ -7,7 +6,6 @@ const path = require('path')
7
6
  const Usage = require('./lib/usage')
8
7
  const Validation = require('./lib/validation')
9
8
  const Y18n = require('y18n')
10
- const requireMainFilename = require('require-main-filename')
11
9
  const objFilter = require('./lib/obj-filter')
12
10
  const setBlocking = require('set-blocking')
13
11
 
@@ -49,7 +47,7 @@ function Yargs (processArgs, cwd, parentRequire) {
49
47
 
50
48
  // use context object to keep track of resets, subcommand execution, etc
51
49
  // submodules should modify and check the state of context as necessary
52
- const context = { resets: -1, commands: [], dirs: {}, files: [] }
50
+ const context = { resets: -1, commands: [], files: [] }
53
51
  self.getContext = function () {
54
52
  return context
55
53
  }
@@ -98,7 +96,7 @@ function Yargs (processArgs, cwd, parentRequire) {
98
96
 
99
97
  var objectOptions = [
100
98
  'narg', 'key', 'alias', 'default', 'defaultDescription',
101
- 'config', 'choices', 'demanded'
99
+ 'config', 'choices', 'demanded', 'coerce'
102
100
  ]
103
101
 
104
102
  arrayOptions.forEach(function (k) {
@@ -113,7 +111,7 @@ function Yargs (processArgs, cwd, parentRequire) {
113
111
  })
114
112
  })
115
113
 
116
- tmpOptions.envPrefix = undefined
114
+ tmpOptions.envPrefix = options.envPrefix
117
115
  options = tmpOptions
118
116
 
119
117
  // if this is the first time being executed, create
@@ -207,7 +205,7 @@ function Yargs (processArgs, cwd, parentRequire) {
207
205
 
208
206
  self.commandDir = function (dir, opts) {
209
207
  const req = parentRequire || require
210
- command.addDirectory(dir, self.getContext(), req, requireMainFilename(req), opts)
208
+ command.addDirectory(dir, self.getContext(), req, require('get-caller-file')(), opts)
211
209
  return self
212
210
  }
213
211
 
@@ -244,6 +242,19 @@ function Yargs (processArgs, cwd, parentRequire) {
244
242
  return self
245
243
  }
246
244
 
245
+ self.coerce = function (key, fn) {
246
+ if (typeof key === 'object' && !Array.isArray(key)) {
247
+ Object.keys(key).forEach(function (k) {
248
+ self.coerce(k, key[k])
249
+ })
250
+ } else {
251
+ [].concat(key).forEach(function (k) {
252
+ options.coerce[k] = fn
253
+ })
254
+ }
255
+ return self
256
+ }
257
+
247
258
  self.count = function (counts) {
248
259
  options.count.push.apply(options.count, [].concat(counts))
249
260
  return self
@@ -355,23 +366,25 @@ function Yargs (processArgs, cwd, parentRequire) {
355
366
  return self
356
367
  }
357
368
 
358
- var pkg = null
369
+ var pkgs = {}
359
370
  function pkgUp (path) {
360
- if (pkg && !path) return pkg
371
+ var npath = path || '*'
372
+ if (pkgs[npath]) return pkgs[npath]
361
373
  const readPkgUp = require('read-pkg-up')
362
374
 
363
375
  var obj = {}
364
376
  try {
365
377
  obj = readPkgUp.sync({
366
- cwd: path || requireMainFilename(parentRequire || require)
378
+ cwd: path || require('require-main-filename')(parentRequire || require)
367
379
  })
368
380
  } catch (noop) {}
369
381
 
370
- if (obj.pkg) pkg = obj.pkg
371
- return pkg || {}
382
+ pkgs[npath] = obj.pkg || {}
383
+ return pkgs[npath]
372
384
  }
373
385
 
374
386
  self.parse = function (args, shortCircuit) {
387
+ if (!shortCircuit) processArgs = args
375
388
  return parseArgs(args, shortCircuit)
376
389
  }
377
390
 
@@ -401,6 +414,8 @@ function Yargs (processArgs, cwd, parentRequire) {
401
414
  self.normalize(key)
402
415
  } if ('choices' in opt) {
403
416
  self.choices(key, opt.choices)
417
+ } if ('coerce' in opt) {
418
+ self.coerce(key, opt.coerce)
404
419
  } if ('group' in opt) {
405
420
  self.group(key, opt.group)
406
421
  } if (opt.global) {
@@ -458,7 +473,7 @@ function Yargs (processArgs, cwd, parentRequire) {
458
473
  }
459
474
  self.getGroups = function () {
460
475
  // combine explicit and preserved groups. explicit groups should be first
461
- return assign({}, groups, preservedGroups)
476
+ return require('lodash.assign')({}, groups, preservedGroups)
462
477
  }
463
478
 
464
479
  // as long as options.envPrefix is not undefined,
@@ -518,12 +533,33 @@ function Yargs (processArgs, cwd, parentRequire) {
518
533
  }
519
534
 
520
535
  var helpOpt = null
521
- self.addHelpOpt = self.help = function (opt, msg) {
522
- opt = opt || 'help'
523
- helpOpt = opt
524
- self.boolean(opt)
525
- self.global(opt)
526
- self.describe(opt, msg || usage.deferY18nLookup('Show help'))
536
+ var useHelpOptAsCommand = false // a call to .help() will enable this
537
+ self.addHelpOpt = self.help = function (opt, msg, addImplicitCmd) {
538
+ // argument shuffle
539
+ if (arguments.length === 0) {
540
+ useHelpOptAsCommand = true
541
+ } else if (arguments.length === 1) {
542
+ if (typeof opt === 'boolean') {
543
+ useHelpOptAsCommand = opt
544
+ opt = null
545
+ } else {
546
+ useHelpOptAsCommand = true
547
+ }
548
+ } else if (arguments.length === 2) {
549
+ if (typeof msg === 'boolean') {
550
+ useHelpOptAsCommand = msg
551
+ msg = null
552
+ } else {
553
+ useHelpOptAsCommand = true
554
+ }
555
+ } else {
556
+ useHelpOptAsCommand = Boolean(addImplicitCmd)
557
+ }
558
+ // use arguments, fallback to defaults for opt and msg
559
+ helpOpt = opt || 'help'
560
+ self.boolean(helpOpt)
561
+ self.global(helpOpt)
562
+ self.describe(helpOpt, msg || usage.deferY18nLookup('Show help'))
527
563
  return self
528
564
  }
529
565
 
@@ -602,6 +638,12 @@ function Yargs (processArgs, cwd, parentRequire) {
602
638
  return detectLocale
603
639
  }
604
640
 
641
+ var recommendCommands
642
+ self.recommendCommands = function () {
643
+ recommendCommands = true
644
+ return self
645
+ }
646
+
605
647
  self.getUsageInstance = function () {
606
648
  return usage
607
649
  }
@@ -635,7 +677,7 @@ function Yargs (processArgs, cwd, parentRequire) {
635
677
 
636
678
  function parseArgs (args, shortCircuit) {
637
679
  options.__ = y18n.__
638
- options.configuration = pkgUp()['yargs'] || {}
680
+ options.configuration = pkgUp(cwd)['yargs'] || {}
639
681
  const parsed = Parser.detailed(args, options)
640
682
  const argv = parsed.argv
641
683
  var aliases = parsed.aliases
@@ -652,22 +694,56 @@ function Yargs (processArgs, cwd, parentRequire) {
652
694
  return argv
653
695
  }
654
696
 
655
- // if there's a handler associated with a
656
- // command defer processing to it.
657
- var handlerKeys = command.getCommands()
658
- for (var i = 0, cmd; (cmd = argv._[i]) !== undefined; i++) {
659
- if (~handlerKeys.indexOf(cmd) && cmd !== completionCommand) {
660
- setPlaceholderKeys(argv)
661
- return command.runCommand(cmd, self, parsed)
697
+ if (argv._.length) {
698
+ // check for helpOpt in argv._ before running commands
699
+ // assumes helpOpt must be valid if useHelpOptAsCommand is true
700
+ if (useHelpOptAsCommand) {
701
+ // consider any multi-char helpOpt alias as a valid help command
702
+ // unless all helpOpt aliases are single-char
703
+ // note that parsed.aliases is a normalized bidirectional map :)
704
+ var helpCmds = [helpOpt].concat(aliases[helpOpt])
705
+ var multiCharHelpCmds = helpCmds.filter(function (k) {
706
+ return k.length > 1
707
+ })
708
+ if (multiCharHelpCmds.length) helpCmds = multiCharHelpCmds
709
+ // look for and strip any helpCmds from argv._
710
+ argv._ = argv._.filter(function (cmd) {
711
+ if (~helpCmds.indexOf(cmd)) {
712
+ argv[helpOpt] = true
713
+ return false
714
+ }
715
+ return true
716
+ })
662
717
  }
663
- }
664
718
 
665
- // generate a completion script for adding to ~/.bashrc.
666
- if (completionCommand && ~argv._.indexOf(completionCommand) && !argv[completion.completionKey]) {
667
- if (exitProcess) setBlocking(true)
668
- self.showCompletionScript()
669
- if (exitProcess) {
670
- process.exit(0)
719
+ // if there's a handler associated with a
720
+ // command defer processing to it.
721
+ var handlerKeys = command.getCommands()
722
+ if (handlerKeys.length) {
723
+ var firstUnknownCommand
724
+ for (var i = 0, cmd; (cmd = argv._[i]) !== undefined; i++) {
725
+ if (~handlerKeys.indexOf(cmd) && cmd !== completionCommand) {
726
+ setPlaceholderKeys(argv)
727
+ return command.runCommand(cmd, self, parsed)
728
+ } else if (!firstUnknownCommand && cmd !== completionCommand) {
729
+ firstUnknownCommand = cmd
730
+ }
731
+ }
732
+
733
+ // recommend a command if recommendCommands() has
734
+ // been enabled, and no commands were found to execute
735
+ if (recommendCommands && firstUnknownCommand) {
736
+ validation.recommendCommands(firstUnknownCommand, handlerKeys)
737
+ }
738
+ }
739
+
740
+ // generate a completion script for adding to ~/.bashrc.
741
+ if (completionCommand && ~argv._.indexOf(completionCommand) && !argv[completion.completionKey]) {
742
+ if (exitProcess) setBlocking(true)
743
+ self.showCompletionScript()
744
+ if (exitProcess) {
745
+ process.exit(0)
746
+ }
671
747
  }
672
748
  }
673
749
 
package/locales/zh.json DELETED
@@ -1,37 +0,0 @@
1
- {
2
- "Commands:": "命令:",
3
- "Options:": "选项:",
4
- "Examples:": "示例:",
5
- "boolean": "布尔",
6
- "count": "计数",
7
- "string": "字符串",
8
- "number": "数字",
9
- "array": "数组",
10
- "required": "必需",
11
- "default:": "默认值:",
12
- "choices:": "可选值:",
13
- "generated-value": "生成的值",
14
- "Not enough non-option arguments: got %s, need at least %s": "缺少 non-option 参数:传入了 %s 个, 至少需要 %s 个",
15
- "Too many non-option arguments: got %s, maximum of %s": "non-option 参数过多:传入了 %s 个, 最大允许 %s 个",
16
- "Missing argument value: %s": {
17
- "one": "没有给此选项指定值:%s",
18
- "other": "没有给这些选项指定值:%s"
19
- },
20
- "Missing required argument: %s": {
21
- "one": "缺少必须的选项:%s",
22
- "other": "缺少这些必须的选项:%s"
23
- },
24
- "Unknown argument: %s": {
25
- "one": "无法识别的选项:%s",
26
- "other": "无法识别这些选项:%s"
27
- },
28
- "Invalid values:": "无效的选项值:",
29
- "Argument: %s, Given: %s, Choices: %s": "选项名称: %s, 传入的值: %s, 可选的值:%s",
30
- "Argument check failed: %s": "选项值验证失败:%s",
31
- "Implications failed:": "缺少依赖的选项:",
32
- "Not enough arguments following: %s": "没有提供足够的值给此选项:%s",
33
- "Invalid JSON config file: %s": "无效的 JSON 配置文件:%s",
34
- "Path to JSON config file": "JSON 配置文件的路径",
35
- "Show help": "显示帮助信息",
36
- "Show version number": "显示版本号"
37
- }