Skip to content

Commit

Permalink
feat: split demand() into demandCommand()/demandOption() (#740)
Browse files Browse the repository at this point in the history
  • Loading branch information
bcoe committed Dec 29, 2016
1 parent 5883779 commit 66573c8
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 71 deletions.
144 changes: 113 additions & 31 deletions README.md
Expand Up @@ -160,7 +160,7 @@ area.js:
#!/usr/bin/env node
var argv = require('yargs')
.usage('Usage: $0 -w [num] -h [num]')
.demand(['w','h'])
.demandOption(['w','h'])
.argv;

console.log("The area is:", argv.w * argv.h);
Expand Down Expand Up @@ -188,7 +188,7 @@ demand_count.js:
````javascript
#!/usr/bin/env node
var argv = require('yargs')
.demand(2)
.demandCommand(2)
.argv;
console.dir(argv);
````
Expand Down Expand Up @@ -298,7 +298,7 @@ var argv = require('yargs')
.alias('f', 'file')
.nargs('f', 1)
.describe('f', 'Load a file')
.demand(1, ['f'])
.demandOption(['f'])
.help('h')
.alias('h', 'help')
.epilog('copyright 2015')
Expand Down Expand Up @@ -678,7 +678,7 @@ require('yargs')
console.log(`setting ${argv.key} to ${argv.value}`)
}
})
.demand(1)
.demandCommand(1)
.help()
.wrap(72)
.argv
Expand Down Expand Up @@ -824,7 +824,7 @@ cli.js:
#!/usr/bin/env node
require('yargs')
.commandDir('cmds')
.demand(1)
.demandCommand(1)
.help()
.argv
```
Expand Down Expand Up @@ -1027,45 +1027,127 @@ displaying the value in the usage instructions:
.default('timeout', 60000, '(one-minute)')
```

<a name="demand"></a>.demand(key, [msg | boolean])
<a name="demand"></a>.demand(count, [max], [msg]) [DEPRECATED]
--------------------

`demand()` has been deprecated, please instead see [`demandOption()`](#demandOption) and
[`demandCommand()`](#demandCommand).

<a name="demandOption"></a>.demandOption(key, [msg | boolean])
------------------------------
.demand(count, [max], [msg])
.demandOption(key, msg)
------------------------------

If `key` is a string, show the usage information and exit if `key` wasn't
specified in `process.argv`.

If `key` is a number, demand at least as many non-option arguments, which show
up in `argv._`. A second number can also optionally be provided, which indicates
the maximum number of non-option arguments.

If `key` is an array, demand each element.

If a `msg` string is given, it will be printed when the argument is missing,
instead of the standard error message. This is especially helpful for the non-option arguments in `argv._`.
If a `msg` string is given, it will be printed when the argument is missing, instead of the standard error message.

```javascript
// demand an array of keys to be provided
require('yargs')
.option('run', {
alias: 'r',
describe: 'run your program'
})
.option('path', {
alias: 'p',
describe: 'provide a path to file'
})
.option('spec', {
alias: 's',
describe: 'program specifications'
})
.demandOption(['run', 'path'], 'Please provide both run and path arguments to work with this tool')
.help()
.argv
```
which will provide the following output:
```bash
Options:
--run, -r run your program [required]
--path, -p provide a path to file [required]
--spec, -s program specifications
--help Show help [boolean]

Missing required arguments: run, path
Please provide both run and path arguments to work with this tool
```

If a `boolean` value is given, it controls whether the option is demanded;
this is useful when using `.options()` to specify command line parameters.

A combination of `.demand(1)` and `.strict()` will allow you to require a user to pass at least one command:

```js
var argv = require('yargs')
.command('install', 'tis a mighty fine package to install')
.demand(1)
.strict()
```javascript
// demand individual options within the option constructor
require('yargs')
.options({
'run': {
alias: 'r',
describe: 'run your program',
demand: true
},
'path': {
alias: 'p',
describe: 'provide a path to file',
demand: true
},
'spec': {
alias: 's',
describe: 'program specifications'
}
})
.help()
.argv
```
which will provide the following output:
```bash
Options:
--run, -r run your program [required]
--path, -p provide a path to file [required]
--spec, -s program specifications
--help Show help [boolean]

Similarly, you can require a command and arguments at the same time:
Missing required arguments: run, path
```

```js
var argv = require('yargs')
.command('install', 'tis a mighty fine package to install')
.demand(1, ['w', 'm'])
.strict()
<a name="demandCommand"></a>.demandCommand(min, [minMsg])
------------------------------
.demandCommand(min, [max], [minMsg], [maxMsg])
------------------------------

Demand in context of commands. You can demand a minimum and a maximum number a user can have within your program, as well as provide corresponding error messages if either of the demands is not met.
```javascript
require('yargs')
.command({
command: 'configure <key> [value]',
aliases: ['config', 'cfg'],
desc: 'Set a config variable',
builder: (yargs) => yargs.default('value', 'true'),
handler: (argv) => {
console.log(`setting ${argv.key} to ${argv.value}`)
}
})
// provide a minimum demand and a minimum demand message
.demandCommand(1, 'You need at least one command before moving on')
.help()
.argv
```
which will provide the following output:
```bash
Commands:
configure <key> [value] Set a config variable [aliases: config, cfg]

Options:
--help Show help [boolean]

You need at least one command before moving on
```

_Note: in `minMsg` and `maxMsg`, every occurrence of `$0` will be replaced
with the observed value, and every instance of `$1` will be replaced with the
expected value._

<a name="describe"></a>.describe(key, desc)
--------------------
Expand Down Expand Up @@ -1440,7 +1522,7 @@ var argv = require('yargs')

This method can be used to make yargs aware of options that _could_
exist. You can also pass an `opt` object which can hold further
customization, like `.alias()`, `.demand()` etc. for that option.
customization, like `.alias()`, `.demandOption()` etc. for that option.

For example:

Expand All @@ -1462,7 +1544,7 @@ is the same as
````javascript
var argv = require('yargs')
.alias('f', 'file')
.demand('f')
.demandOption('f')
.default('f', '/etc/passwd')
.describe('f', 'x marks the spot')
.string('f')
Expand Down Expand Up @@ -1499,7 +1581,7 @@ Valid `opt` keys include:
- `count`: boolean, interpret option as a count of boolean flags, see [`count()`](#count)
- `default`: value, set a default value for the option, see [`default()`](#default)
- `defaultDescription`: string, use this description for the default value in help content, see [`default()`](#default)
- `demand`/`require`/`required`: boolean or string, demand the option be given, with optional error message, see [`demand()`](#demand)
- `demandOption`: boolean or string, demand the option be given, with optional error message, see [`demandOption()`](#demandOption)
- `desc`/`describe`/`description`: string, the option description for help content, see [`describe()`](#describe)
- `global`: boolean, indicate that this key should not be [reset](#reset) when a command is invoked, see [`global()`](#global)
- `group`: string, when displaying usage instructions place the option under an alternative group heading, see [`group()`](#group)
Expand Down Expand Up @@ -1601,7 +1683,7 @@ var yargs = require('yargs')
.usage('$0 command')
.command('hello', 'hello command')
.command('world', 'world command')
.demand(1, 'must provide a valid command'),
.demandCommand(1, 'must provide a valid command'),
argv = yargs.argv,
command = argv._[0];

Expand Down Expand Up @@ -1668,7 +1750,7 @@ line_count.js:
#!/usr/bin/env node
var argv = require('yargs')
.usage('Count the lines in a file.\nUsage: $0 -f <file>')
.demand('f')
.demandOption('f')
.alias('f', 'file')
.describe('f', 'Load a file')
.string('f')
Expand Down
14 changes: 9 additions & 5 deletions lib/usage.js
Expand Up @@ -128,12 +128,15 @@ module.exports = function (yargs, y18n) {
self.help = function () {
normalizeAliases()

var demanded = yargs.getDemanded()
// handle old demanded API
var demandedOptions = yargs.getDemandedOptions()
var demandedCommands = yargs.getDemandedCommands()
var groups = yargs.getGroups()
var options = yargs.getOptions()
var keys = Object.keys(
Object.keys(descriptions)
.concat(Object.keys(demanded))
.concat(Object.keys(demandedOptions))
.concat(Object.keys(demandedCommands))
.concat(Object.keys(options.default))
.reduce(function (acc, key) {
if (key !== '_') acc[key] = true
Expand Down Expand Up @@ -231,7 +234,7 @@ module.exports = function (yargs, y18n) {

var extra = [
type,
demanded[key] ? '[' + __('required') + ']' : null,
demandedOptions[key] ? '[' + __('required') + ']' : null,
options.choices && options.choices[key] ? '[' + __('choices:') + ' ' +
self.stringifiedValues(options.choices[key]) + ']' : null,
defaultString(options.default[key], options.defaultDescription[key])
Expand Down Expand Up @@ -303,15 +306,16 @@ module.exports = function (yargs, y18n) {
// make sure any options set for aliases,
// are copied to the keys being aliased.
function normalizeAliases () {
var demanded = yargs.getDemanded()
// handle old demanded API
var demandedOptions = yargs.getDemandedOptions()
var options = yargs.getOptions()

;(Object.keys(options.alias) || []).forEach(function (key) {
options.alias[key].forEach(function (alias) {
// copy descriptions.
if (descriptions[alias]) self.describe(key, descriptions[alias])
// copy demanded.
if (demanded[alias]) yargs.demand(key, demanded[alias].msg)
if (demandedOptions[alias]) yargs.demandOption(key, demandedOptions[alias].msg)
// type messages.
if (~options.boolean.indexOf(alias)) yargs.boolean(key)
if (~options.count.indexOf(alias)) yargs.count(key)
Expand Down
46 changes: 29 additions & 17 deletions lib/validation.js
Expand Up @@ -10,21 +10,33 @@ module.exports = function (yargs, usage, y18n) {
// validate appropriate # of non-option
// arguments were provided, i.e., '_'.
self.nonOptionCount = function (argv) {
const demanded = yargs.getDemanded()
const demandedCommands = yargs.getDemandedCommands()
// don't count currently executing commands
const _s = argv._.length - yargs.getContext().commands.length

if (demanded._ && (_s < demanded._.count || _s > demanded._.max)) {
if (demanded._.msg !== undefined) {
usage.fail(demanded._.msg)
} else if (_s < demanded._.count) {
usage.fail(
__('Not enough non-option arguments: got %s, need at least %s', _s, demanded._.count)
)
} else {
usage.fail(
__('Too many non-option arguments: got %s, maximum of %s', _s, demanded._.max)
)
if (demandedCommands._ && (_s < demandedCommands._.min || _s > demandedCommands._.max)) {
if (_s < demandedCommands._.min) {
if (demandedCommands._.minMsg !== undefined) {
usage.fail(
// replace $0 with observed, $1 with expected.
demandedCommands._.minMsg ? demandedCommands._.minMsg.replace(/\$0/g, _s).replace(/\$1/, demandedCommands._.min) : null
)
} else {
usage.fail(
__('Not enough non-option arguments: got %s, need at least %s', _s, demandedCommands._.min)
)
}
} else if (_s > demandedCommands._.max) {
if (demandedCommands._.maxMsg !== undefined) {
usage.fail(
// replace $0 with observed, $1 with expected.
demandedCommands._.maxMsg ? demandedCommands._.maxMsg.replace(/\$0/g, _s).replace(/\$1/, demandedCommands._.max) : null
)
} else {
usage.fail(
__('Too many non-option arguments: got %s, maximum of %s', _s, demandedCommands._.max)
)
}
}
}
}
Expand Down Expand Up @@ -73,13 +85,13 @@ module.exports = function (yargs, usage, y18n) {

// make sure all the required arguments are present.
self.requiredArguments = function (argv) {
const demanded = yargs.getDemanded()
const demandedOptions = yargs.getDemandedOptions()
var missing = null

Object.keys(demanded).forEach(function (key) {
Object.keys(demandedOptions).forEach(function (key) {
if (!argv.hasOwnProperty(key)) {
missing = missing || {}
missing[key] = demanded[key]
missing[key] = demandedOptions[key]
}
})

Expand Down Expand Up @@ -107,7 +119,7 @@ module.exports = function (yargs, usage, y18n) {
self.unknownArguments = function (argv, aliases) {
const aliasLookup = {}
const descriptions = usage.getDescriptions()
const demanded = yargs.getDemanded()
const demandedOptions = yargs.getDemandedOptions()
const commandKeys = yargs.getCommandInstance().getCommands()
const unknown = []
const currentContext = yargs.getContext()
Expand All @@ -121,7 +133,7 @@ module.exports = function (yargs, usage, y18n) {
Object.keys(argv).forEach(function (key) {
if (key !== '$0' && key !== '_' &&
!descriptions.hasOwnProperty(key) &&
!demanded.hasOwnProperty(key) &&
!demandedOptions.hasOwnProperty(key) &&
!aliasLookup.hasOwnProperty(key)) {
unknown.push(key)
}
Expand Down

0 comments on commit 66573c8

Please sign in to comment.