diff --git a/README.md b/README.md
index c14cb2928..c4c006c9c 100644
--- a/README.md
+++ b/README.md
@@ -408,7 +408,7 @@ explicitly set.
If `key` is an array, interpret all the elements as booleans.
-.check(fn)
+.check(fn, [global=true])
----------
Check that certain conditions are met in the provided arguments.
@@ -418,6 +418,9 @@ Check that certain conditions are met in the provided arguments.
If `fn` throws or returns a non-truthy value, show the thrown error, usage information, and
exit.
+`global` indicates whether `check()` should be enabled both
+at the top-level and for each sub-command.
+
.choices(key, choices)
----------------------
@@ -551,14 +554,6 @@ yargs
.argv
```
-Note that commands will not automatically inherit configuration _or_ options
-of their parent context. This means you'll have to re-apply configuration
-if necessary, and make options global manually using the [global](#global) method.
-
-Additionally, the [`help`](#help) and [`version`](#version)
-options (if used) **always** apply globally, just like the
-[`.wrap()`](#wrap) configuration.
-
`builder` can also be a function. This function is executed
with a `yargs` instance, and can be used to provide _advanced_ command specific help:
@@ -678,7 +673,7 @@ require('yargs')
console.log(`setting ${argv.key} to ${argv.value}`)
}
})
- .demandCommand(1)
+ .demandCommand()
.help()
.wrap(72)
.argv
@@ -824,7 +819,7 @@ cli.js:
#!/usr/bin/env node
require('yargs')
.commandDir('cmds')
- .demandCommand(1)
+ .demandCommand()
.help()
.argv
```
@@ -1112,9 +1107,9 @@ Options:
Missing required arguments: run, path
```
-.demandCommand(min, [minMsg])
+.demandCommand([min=1], [minMsg])
------------------------------
-.demandCommand(min, [max], [minMsg], [maxMsg])
+.demandCommand([min=1], [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.
@@ -1134,7 +1129,9 @@ require('yargs')
.help()
.argv
```
+
which will provide the following output:
+
```bash
Commands:
configure [value] Set a config variable [aliases: config, cfg]
@@ -1293,7 +1290,7 @@ require('yargs')
Outputs the same completion choices as `./test.js --foo`TAB: `--foobar` and `--foobaz`
-.global(globals)
+.global(globals, [global=true])
------------
Indicate that an option (or group of options) should not be reset when a command
@@ -1303,11 +1300,13 @@ is executed, as an example:
var argv = require('yargs')
.option('a', {
alias: 'all',
- default: true
+ default: true,
+ global: false
})
.option('n', {
alias: 'none',
- default: true
+ default: true,
+ global: false
})
.command('foo', 'foo command', function (yargs) {
return yargs.option('b', {
@@ -1322,7 +1321,7 @@ var argv = require('yargs')
If the `foo` command is executed the `all` option will remain, but the `none`
option will have been eliminated.
-`help`, `version`, and `completion` options default to being global.
+Options default to being global.
.group(key(s), groupName)
--------------------
@@ -1778,12 +1777,15 @@ Specify --help for available options
Specifies either a single option key (string), or an array of options.
If any of the options is present, yargs validation is skipped.
-.strict()
+.strict([global=true])
---------
Any command-line argument given that is not demanded, or does not have a
corresponding description, will be reported as an error.
+`global` indicates whether `strict()` should be enabled both
+at the top-level and for each sub-command.
+
.string(key)
------------
diff --git a/lib/command.js b/lib/command.js
index c1afc1c55..4b4cbeaaa 100644
--- a/lib/command.js
+++ b/lib/command.js
@@ -131,47 +131,62 @@ module.exports = function (yargs, usage, validation) {
}
self.runCommand = function (command, yargs, parsed) {
- var argv = parsed.argv
+ var aliases = parsed.aliases
var commandHandler = handlers[command] || handlers[aliasMap[command]]
- var innerArgv = argv
var currentContext = yargs.getContext()
var numFiles = currentContext.files.length
var parentCommands = currentContext.commands.slice()
+
+ // what does yargs look like after the buidler is run?
+ var innerArgv = parsed.argv
+ var innerYargs = null
+
currentContext.commands.push(command)
if (typeof commandHandler.builder === 'function') {
// a function can be provided, which builds
// up a yargs chain and possibly returns it.
- innerArgv = commandHandler.builder(yargs.reset(parsed.aliases))
+ innerYargs = commandHandler.builder(yargs.reset(parsed.aliases))
// if the builder function did not yet parse argv with reset yargs
// and did not explicitly set a usage() string, then apply the
// original command string as usage() for consistent behavior with
- // options object below
+ // options object below.
if (yargs.parsed === false) {
if (typeof yargs.getUsageInstance().getUsage() === 'undefined') {
yargs.usage('$0 ' + (parentCommands.length ? parentCommands.join(' ') + ' ' : '') + commandHandler.original)
}
- innerArgv = innerArgv ? innerArgv.argv : yargs.argv
+ innerArgv = innerYargs ? innerYargs._parseArgs(null, null, true) : yargs._parseArgs(null, null, true)
} else {
innerArgv = yargs.parsed.argv
}
+
+ if (innerYargs && yargs.parsed === false) aliases = innerYargs.parsed.aliases
+ else aliases = yargs.parsed.aliases
} else if (typeof commandHandler.builder === 'object') {
// as a short hand, an object can instead be provided, specifying
// the options that a command takes.
- innerArgv = yargs.reset(parsed.aliases)
- innerArgv.usage('$0 ' + (parentCommands.length ? parentCommands.join(' ') + ' ' : '') + commandHandler.original)
+ innerYargs = yargs.reset(parsed.aliases)
+ innerYargs.usage('$0 ' + (parentCommands.length ? parentCommands.join(' ') + ' ' : '') + commandHandler.original)
Object.keys(commandHandler.builder).forEach(function (key) {
- innerArgv.option(key, commandHandler.builder[key])
+ innerYargs.option(key, commandHandler.builder[key])
})
- innerArgv = innerArgv.argv
+ innerArgv = innerYargs._parseArgs(null, null, true)
+ aliases = innerYargs.parsed.aliases
}
+
if (!yargs._hasOutput()) populatePositionals(commandHandler, innerArgv, currentContext, yargs)
if (commandHandler.handler && !yargs._hasOutput()) {
commandHandler.handler(innerArgv)
}
+
+ // we apply validation post-hoc, so that custom
+ // checks get passed populated positional arguments.
+ yargs._runValidation(innerArgv, aliases)
+
currentContext.commands.pop()
numFiles = currentContext.files.length - numFiles
if (numFiles > 0) currentContext.files.splice(numFiles * -1, numFiles)
+
return innerArgv
}
diff --git a/lib/usage.js b/lib/usage.js
index 2e503acfa..0efabe868 100644
--- a/lib/usage.js
+++ b/lib/usage.js
@@ -436,7 +436,7 @@ module.exports = function (yargs, y18n) {
else logger.log(version)
}
- self.reset = function (globalLookup) {
+ self.reset = function (localLookup) {
// do not reset wrap here
// do not reset fails here
failMessage = null
@@ -446,7 +446,7 @@ module.exports = function (yargs, y18n) {
examples = []
commands = []
descriptions = objFilter(descriptions, function (k, v) {
- return globalLookup[k]
+ return !localLookup[k]
})
return self
}
diff --git a/lib/validation.js b/lib/validation.js
index 328d83aff..475ee3180 100644
--- a/lib/validation.js
+++ b/lib/validation.js
@@ -194,22 +194,26 @@ module.exports = function (yargs, usage, y18n) {
// custom checks, added using the `check` option on yargs.
var checks = []
- self.check = function (f) {
- checks.push(f)
+ self.check = function (f, global) {
+ checks.push({
+ func: f,
+ global: global
+ })
}
self.customChecks = function (argv, aliases) {
for (var i = 0, f; (f = checks[i]) !== undefined; i++) {
+ var func = f.func
var result = null
try {
- result = f(argv, aliases)
+ result = func(argv, aliases)
} catch (err) {
usage.fail(err.message ? err.message : err, err)
continue
}
if (!result) {
- usage.fail(__('Argument check failed: %s', f.toString()))
+ usage.fail(__('Argument check failed: %s', func.toString()))
} else if (typeof result === 'string' || result instanceof Error) {
usage.fail(result.toString(), result)
}
@@ -224,6 +228,7 @@ module.exports = function (yargs, usage, y18n) {
self.implies(k, key[k])
})
} else {
+ yargs.global(key)
implied[key] = value
}
}
@@ -290,6 +295,7 @@ module.exports = function (yargs, usage, y18n) {
self.conflicts(k, key[k])
})
} else {
+ yargs.global(key)
conflicting[key] = value
}
}
@@ -324,12 +330,16 @@ module.exports = function (yargs, usage, y18n) {
if (recommended) usage.fail(__('Did you mean %s?', recommended))
}
- self.reset = function (globalLookup) {
+ self.reset = function (localLookup) {
implied = objFilter(implied, function (k, v) {
- return globalLookup[k]
+ return !localLookup[k]
+ })
+ conflicting = objFilter(conflicting, function (k, v) {
+ return !localLookup[k]
+ })
+ checks = checks.filter(function (c) {
+ return c.global
})
- checks = []
- conflicting = {}
return self
}
diff --git a/test/command.js b/test/command.js
index be4f0dca8..ebdf881bc 100644
--- a/test/command.js
+++ b/test/command.js
@@ -839,4 +839,300 @@ describe('Command', function () {
argv.sins.should.deep.equal([113993, 112888])
})
})
+
+ describe('global parsing hints', function () {
+ describe('config', function () {
+ it('does not load config for command if global is false', function (done) {
+ yargs('command --foo ./package.json')
+ .command('command', 'a command', {}, function (argv) {
+ expect(argv.license).to.equal(undefined)
+ return done()
+ })
+ .config('foo')
+ .global('foo', false)
+ .argv
+ })
+
+ it('loads config for command by default', function (done) {
+ yargs('command --foo ./package.json')
+ .command('command', 'a command', {}, function (argv) {
+ argv.license.should.equal('MIT')
+ return done()
+ })
+ .config('foo')
+ .argv
+ })
+ })
+
+ describe('validation', function () {
+ it('resets implies logic for command if global is false', function (done) {
+ yargs('command --foo 99')
+ .command('command', 'a command', {}, function (argv) {
+ argv.foo.should.equal(99)
+ return done()
+ })
+ .implies('foo', 'bar')
+ .global('foo', false)
+ .argv
+ })
+
+ it('applies conflicts logic for command by default', function (done) {
+ yargs('command --foo --bar')
+ .command('command', 'a command', {}, function (argv) {})
+ .fail(function (msg) {
+ msg.should.match(/mutually exclusive/)
+ return done()
+ })
+ .conflicts('foo', 'bar')
+ .argv
+ })
+
+ it('resets conflicts logic for command if global is false', function (done) {
+ yargs('command --foo --bar')
+ .command('command', 'a command', {}, function (argv) {
+ argv.foo.should.equal(true)
+ argv.bar.should.equal(true)
+ return done()
+ })
+ .conflicts('foo', 'bar')
+ .global('foo', false)
+ .argv
+ })
+
+ it('applies custom checks globally by default', function (done) {
+ yargs('command blerg --foo')
+ .command('command ', 'a command')
+ .check(function (argv) {
+ argv.snuh.should.equal('blerg')
+ argv.foo.should.equal(true)
+ argv._.should.include('command')
+ done()
+ return true
+ })
+ .argv
+ })
+
+ it('resets custom check if global is false', function () {
+ var checkCalled = false
+ yargs('command blerg --foo')
+ .command('command ', 'a command')
+ .check(function (argv) {
+ checkCalled = true
+ return true
+ }, false)
+ .argv
+ checkCalled.should.equal(false)
+ })
+
+ it('applies demandOption globally', function (done) {
+ yargs('command blerg --foo')
+ .command('command ', 'a command')
+ .fail(function (msg) {
+ msg.should.match(/Missing required argument: bar/)
+ return done()
+ })
+ .demandOption('bar')
+ .argv
+ })
+ })
+
+ describe('strict', function () {
+ it('defaults to false when not called', function () {
+ var commandCalled = false
+ yargs('hi')
+ .command('hi', 'The hi command', function (innerYargs) {
+ commandCalled = true
+ innerYargs.getStrict().should.be.false
+ })
+ yargs.getStrict().should.be.false
+ yargs.argv // parse and run command
+ commandCalled.should.be.true
+ })
+
+ it('can be enabled just for a command', function () {
+ var commandCalled = false
+ yargs('hi')
+ .command('hi', 'The hi command', function (innerYargs) {
+ commandCalled = true
+ innerYargs.strict().getStrict().should.be.true
+ })
+ yargs.getStrict().should.be.false
+ yargs.argv // parse and run command
+ commandCalled.should.be.true
+ })
+
+ it('applies strict globally by default', function () {
+ var commandCalled = false
+ yargs('hi')
+ .strict()
+ .command('hi', 'The hi command', function (innerYargs) {
+ commandCalled = true
+ innerYargs.getStrict().should.be.true
+ })
+ yargs.getStrict().should.be.true
+ yargs.argv // parse and run command
+ commandCalled.should.be.true
+ })
+
+ it('does not apply strict globally when passed value of `false`', function () {
+ var commandCalled = false
+ yargs('hi')
+ .strict(false)
+ .command('hi', 'The hi command', function (innerYargs) {
+ commandCalled = true
+ innerYargs.getStrict().should.be.false
+ })
+ yargs.getStrict().should.be.true
+ yargs.argv // parse and run command
+ commandCalled.should.be.true
+ })
+ })
+
+ describe('types', function () {
+ it('applies array type globally', function () {
+ const argv = yargs('command --foo 1 2')
+ .command('command', 'a command')
+ .array('foo')
+ .argv
+ argv.foo.should.eql([1, 2])
+ })
+
+ it('allows global setting to be disabled for array type', function () {
+ const argv = yargs('command --foo 1 2')
+ .command('command', 'a command')
+ .array('foo')
+ .global('foo', false)
+ .argv
+ argv.foo.should.eql(1)
+ })
+
+ it('applies choices type globally', function (done) {
+ yargs('command --foo 99')
+ .command('command', 'a command')
+ .choices('foo', [33, 88])
+ .fail(function (msg) {
+ msg.should.match(/Choices: 33, 88/)
+ return done()
+ })
+ .argv
+ })
+ })
+
+ describe('aliases', function () {
+ it('defaults to applying aliases globally', function (done) {
+ yargs('command blerg --foo 22')
+ .command('command ', 'a command', {}, function (argv) {
+ argv.foo.should.equal(22)
+ argv.bar.should.equal(22)
+ argv.snuh.should.equal('blerg')
+ return done()
+ })
+ .alias('foo', 'bar')
+ .argv
+ })
+
+ it('allows global application of alias to be disabled', function (done) {
+ yargs('command blerg --foo 22')
+ .command('command ', 'a command', {}, function (argv) {
+ argv.foo.should.equal(22)
+ expect(argv.bar).to.equal(undefined)
+ argv.snuh.should.equal('blerg')
+ return done()
+ })
+ .option('foo', {
+ alias: 'bar',
+ global: false
+ })
+ .argv
+ })
+ })
+
+ describe('coerce', function () {
+ it('defaults to applying coerce rules globally', function (done) {
+ yargs('command blerg --foo 22')
+ .command('command ', 'a command', {}, function (argv) {
+ argv.foo.should.equal(44)
+ argv.snuh.should.equal('blerg')
+ return done()
+ })
+ .coerce('foo', function (arg) {
+ return arg * 2
+ })
+ .argv
+ })
+ })
+
+ describe('defaults', function () {
+ it('applies defaults globally', function (done) {
+ yargs('command --foo 22')
+ .command('command [snuh]', 'a command', {}, function (argv) {
+ argv.foo.should.equal(22)
+ argv.snuh.should.equal(55)
+ return done()
+ })
+ .default('snuh', 55)
+ .argv
+ })
+ })
+
+ describe('describe', function () {
+ it('flags an option as global if a description is set', function (done) {
+ yargs()
+ .command('command [snuh]', 'a command')
+ .describe('foo', 'an awesome argument')
+ .help()
+ .parse('command --help', function (err, argv, output) {
+ if (err) return done(err)
+ output.should.not.match(/Commands:/)
+ output.should.match(/an awesome argument/)
+ return done()
+ })
+ })
+ })
+
+ describe('help', function () {
+ it('applies help globally', function (done) {
+ yargs()
+ .command('command [snuh]', 'a command')
+ .describe('foo', 'an awesome argument')
+ .help('hellllllp')
+ .parse('command --hellllllp', function (err, argv, output) {
+ if (err) return done(err)
+ output.should.match(/--hellllllp {2}Show help/)
+ return done()
+ })
+ })
+ })
+
+ describe('version', function () {
+ it('applies version globally', function (done) {
+ yargs()
+ .command('command [snuh]', 'a command')
+ .describe('foo', 'an awesome argument')
+ .version('ver', 'show version', '9.9.9')
+ .parse('command --ver', function (err, argv, output) {
+ if (err) return done(err)
+ output.should.equal('9.9.9')
+ return done()
+ })
+ })
+ })
+
+ describe('groups', function () {
+ it('should apply custom option groups globally', function (done) {
+ yargs()
+ .command('command [snuh]', 'a command')
+ .group('foo', 'Bad Variable Names:')
+ .group('snuh', 'Bad Variable Names:')
+ .describe('foo', 'foo option')
+ .describe('snuh', 'snuh positional')
+ .help()
+ .parse('command --help', function (err, argv, output) {
+ if (err) return done(err)
+ output.should.match(/Bad Variable Names:\W*--foo/)
+ return done()
+ })
+ })
+ })
+ })
})
diff --git a/test/usage.js b/test/usage.js
index b3f532d76..87635aa21 100644
--- a/test/usage.js
+++ b/test/usage.js
@@ -84,16 +84,16 @@ describe('usage tests', function () {
r.exit.should.be.ok
})
- it('no failure occurs if the required arguments and the required number of commands are provided.', function () {
+ it('no failure occurs if the required arguments and the required number of commands are provided', function () {
var r = checkUsage(function () {
return yargs('wombat -w 10 -m 10')
.usage('Usage: $0 -w NUM -m NUM')
.command('wombat', 'wombat handlers')
.demand(1, ['w', 'm'])
- .strict()
.wrap(null)
.argv
})
+
r.result.should.have.property('w', 10)
r.result.should.have.property('m', 10)
r.result.should.have.property('_').with.length(1)
@@ -170,24 +170,6 @@ describe('usage tests', function () {
r.logs.should.have.length(0)
r.exit.should.be.ok
})
-
- it('no failure occurs if the required arguments and the required number of commands are provided.', function () {
- var r = checkUsage(function () {
- return yargs('wombat -w 10 -m 10')
- .usage('Usage: $0 -w NUM -m NUM')
- .command('wombat', 'wombat handlers')
- .require(1, ['w', 'm'])
- .strict()
- .wrap(null)
- .argv
- })
- r.result.should.have.property('w', 10)
- r.result.should.have.property('m', 10)
- r.result.should.have.property('_').with.length(1)
- r.should.have.property('errors').with.length(0)
- r.should.have.property('logs').with.length(0)
- r.should.have.property('exit', false)
- })
})
it('should show an error along with a custom message on demand fail', function () {
@@ -1598,7 +1580,7 @@ describe('usage tests', function () {
])
})
- it('preserves groups with global keys', function () {
+ it('allows global option to be disabled', function () {
var r = checkUsage(function () {
return yargs(['upload', '-h'])
.command('upload', 'upload something', function (yargs) {
diff --git a/test/validation.js b/test/validation.js
index f08080a23..a831dea71 100644
--- a/test/validation.js
+++ b/test/validation.js
@@ -589,5 +589,15 @@ describe('validation tests', function () {
})
.argv
})
+
+ it('defaults to demanding 1 command', function (done) {
+ yargs('-a 10')
+ .demandCommand()
+ .fail(function (msg) {
+ msg.should.equal('Not enough non-option arguments: got 0, need at least 1')
+ return done()
+ })
+ .argv
+ })
})
})
diff --git a/test/yargs.js b/test/yargs.js
index b8777e748..5d5771df8 100644
--- a/test/yargs.js
+++ b/test/yargs.js
@@ -220,8 +220,10 @@ describe('yargs dsl tests', function () {
.implies('foo', 'snuh')
.conflicts('qux', 'xyzzy')
.group('foo', 'Group:')
- .strict()
+ .strict(false)
.exitProcess(false) // defaults to true.
+ .global('foo', false)
+ .global('qux', false)
.env('YARGS')
.reset()
@@ -244,9 +246,13 @@ describe('yargs dsl tests', function () {
config: {},
configObjects: [],
envPrefix: 'YARGS', // preserved as global
- global: ['help'],
demandedCommands: {},
- demandedOptions: {}
+ demandedOptions: {},
+ local: [
+ '_',
+ 'foo',
+ 'qux'
+ ]
}
expect(y.getOptions()).to.deep.equal(emptyOptions)
@@ -261,9 +267,10 @@ describe('yargs dsl tests', function () {
expect(y.getGroups()).to.deep.equal({})
})
- it('does not invoke parse with an error if reset has been called', function (done) {
+ it('does not invoke parse with an error if reset has been called and option is not global', function (done) {
var y = yargs()
.demand('cake')
+ .global('cake', false)
y.parse('hello', function (err) {
err.message.should.match(/Missing required argument/)
@@ -1123,6 +1130,7 @@ describe('yargs dsl tests', function () {
.config('config', function (path) {
return JSON.parse(fs.readFileSync(path))
})
+ .global('config', false)
.argv
argv.foo.should.equal('baz')
@@ -1131,7 +1139,8 @@ describe('yargs dsl tests', function () {
it('allows key to be specified with option shorthand', function () {
var argv = yargs('--config ./test/fixtures/config.json')
.option('config', {
- config: true
+ config: true,
+ global: false
})
.argv
@@ -1218,7 +1227,8 @@ describe('yargs dsl tests', function () {
nargs: 2
})
.option('bar', {
- nargs: 2
+ nargs: 2,
+ global: false
})
.global('foo')
.reset()
@@ -1238,7 +1248,8 @@ describe('yargs dsl tests', function () {
.option('bar', {
nargs: 2,
string: true,
- demand: true
+ demand: true,
+ global: false
})
.global('foo')
.reset({
@@ -1259,14 +1270,14 @@ describe('yargs dsl tests', function () {
var y = yargs('--foo')
.help('help')
var options = y.getOptions()
- options.global.should.include('help')
+ options.local.should.not.include('help')
})
it('should set version to global option by default', function () {
var y = yargs('--foo')
.version()
var options = y.getOptions()
- options.global.should.include('version')
+ options.local.should.not.include('version')
})
it('should not reset usage descriptions of global options', function () {
@@ -1274,6 +1285,7 @@ describe('yargs dsl tests', function () {
.describe('bar', 'my awesome bar option')
.describe('foo', 'my awesome foo option')
.global('foo')
+ .global('bar', false)
.reset()
var descriptions = y.getUsageInstance().getDescriptions()
Object.keys(descriptions).should.include('foo')
@@ -1288,7 +1300,7 @@ describe('yargs dsl tests', function () {
.implies({
z: 'w'
})
- .global(['x'])
+ .global(['z'], false)
.reset()
var implied = y.getValidationInstance().getImplied()
Object.keys(implied).should.include('x')
@@ -1298,11 +1310,11 @@ describe('yargs dsl tests', function () {
it('should expose an options short-hand for declaring global options', function () {
var y = yargs('--foo a b c')
.option('foo', {
- nargs: 2,
- global: true
+ nargs: 2
})
.option('bar', {
- nargs: 2
+ nargs: 2,
+ global: false
})
.reset()
var options = y.getOptions()
diff --git a/yargs.js b/yargs.js
index bbb3d64c9..6e9ea9093 100644
--- a/yargs.js
+++ b/yargs.js
@@ -66,23 +66,23 @@ function Yargs (processArgs, cwd, parentRequire) {
// logic is used to build a nested command
// hierarchy.
var tmpOptions = {}
- tmpOptions.global = options.global ? options.global : []
+ tmpOptions.local = options.local ? options.local : []
tmpOptions.configObjects = options.configObjects ? options.configObjects : []
- // if a key has been set as a global, we
- // do not want to reset it or its aliases.
- var globalLookup = {}
- tmpOptions.global.forEach(function (g) {
- globalLookup[g] = true
- ;(aliases[g] || []).forEach(function (a) {
- globalLookup[a] = true
+ // if a key has been explicitly set as local,
+ // we should reset it before passing options to command.
+ var localLookup = {}
+ tmpOptions.local.forEach(function (l) {
+ localLookup[l] = true
+ ;(aliases[l] || []).forEach(function (a) {
+ localLookup[a] = true
})
})
- // preserve groups containing global keys
+ // preserve all groups not set to local.
preservedGroups = Object.keys(groups).reduce(function (acc, groupName) {
var keys = groups[groupName].filter(function (key) {
- return key in globalLookup
+ return !(key in localLookup)
})
if (keys.length > 0) {
acc[groupName] = keys
@@ -104,13 +104,13 @@ function Yargs (processArgs, cwd, parentRequire) {
arrayOptions.forEach(function (k) {
tmpOptions[k] = (options[k] || []).filter(function (k) {
- return globalLookup[k]
+ return !localLookup[k]
})
})
objectOptions.forEach(function (k) {
tmpOptions[k] = objFilter(options[k], function (k, v) {
- return globalLookup[k]
+ return !localLookup[k]
})
})
@@ -119,12 +119,12 @@ function Yargs (processArgs, cwd, parentRequire) {
// if this is the first time being executed, create
// instances of all our helpers -- otherwise just reset.
- usage = usage ? usage.reset(globalLookup) : Usage(self, y18n)
- validation = validation ? validation.reset(globalLookup) : Validation(self, usage, y18n)
+ usage = usage ? usage.reset(localLookup) : Usage(self, y18n)
+ validation = validation ? validation.reset(localLookup) : Validation(self, usage, y18n)
command = command ? command.reset() : Command(self, usage, validation)
if (!completion) completion = Completion(self, usage, command)
- strict = false
+ if (!strictGlobal) strict = false
completionCommand = null
output = ''
exitError = null
@@ -172,56 +172,127 @@ function Yargs (processArgs, cwd, parentRequire) {
frozen = undefined
}
- self.boolean = function (bools) {
- options.boolean.push.apply(options.boolean, [].concat(bools))
+ self.boolean = function (keys) {
+ populateParserHintArray('boolean', keys)
return self
}
- self.array = function (arrays) {
- options.array.push.apply(options.array, [].concat(arrays))
+ self.array = function (keys) {
+ populateParserHintArray('array', keys)
return self
}
- self.nargs = function (key, n) {
- if (typeof key === 'object') {
- Object.keys(key).forEach(function (k) {
- self.nargs(k, key[k])
- })
- } else {
- options.narg[key] = n
- }
+ self.number = function (keys) {
+ populateParserHintArray('number', keys)
return self
}
- self.number = function (numbers) {
- options.number.push.apply(options.number, [].concat(numbers))
+ self.normalize = function (keys) {
+ populateParserHintArray('normalize', keys)
return self
}
- self.choices = function (key, values) {
- if (typeof key === 'object') {
- Object.keys(key).forEach(function (k) {
- self.choices(k, key[k])
- })
- } else {
- options.choices[key] = (options.choices[key] || []).concat(values)
+ self.count = function (keys) {
+ populateParserHintArray('count', keys)
+ return self
+ }
+
+ self.string = function (keys) {
+ populateParserHintArray('string', keys)
+ return self
+ }
+
+ self.requiresArg = function (keys) {
+ populateParserHintArray('requiresArg', keys)
+ return self
+ }
+
+ self.skipValidation = function (keys) {
+ populateParserHintArray('skipValidation', keys)
+ return self
+ }
+
+ function populateParserHintArray (type, keys, value) {
+ keys = [].concat(keys)
+ keys.forEach(function (key) {
+ options[type].push(key)
+ })
+ }
+
+ self.nargs = function (key, value) {
+ populateParserHintObject(self.nargs, false, 'narg', key, value)
+ return self
+ }
+
+ self.choices = function (key, value) {
+ populateParserHintObject(self.choices, true, 'choices', key, value)
+ return self
+ }
+
+ self.alias = function (key, value) {
+ populateParserHintObject(self.alias, true, 'alias', key, value)
+ return self
+ }
+
+ // TODO: actually deprecate self.defaults.
+ self.default = self.defaults = function (key, value, defaultDescription) {
+ if (defaultDescription) options.defaultDescription[key] = defaultDescription
+ if (typeof value === 'function') {
+ if (!options.defaultDescription[key]) options.defaultDescription[key] = usage.functionDescription(value)
+ value = value.call()
}
+ populateParserHintObject(self.default, false, 'default', key, value)
return self
}
- self.normalize = function (strings) {
- options.normalize.push.apply(options.normalize, [].concat(strings))
+ self.describe = function (key, desc) {
+ populateParserHintObject(self.describe, false, 'key', key, true)
+ usage.describe(key, desc)
return self
}
+ self.demandOption = function (keys, msg) {
+ var value = { msg: typeof msg === 'string' ? msg : undefined }
+ populateParserHintObject(self.demandOption, false, 'demandedOptions', keys, value)
+ return self
+ }
+
+ self.coerce = function (keys, value) {
+ populateParserHintObject(self.coerce, false, 'coerce', keys, value)
+ return self
+ }
+
+ function populateParserHintObject (builder, isArray, type, key, value) {
+ if (Array.isArray(key)) {
+ // an array of keys with one value ['x', 'y', 'z'], function parse () {}
+ var temp = {}
+ key.forEach(function (k) {
+ temp[k] = value
+ })
+ builder(temp)
+ } else if (typeof key === 'object') {
+ // an object of key value pairs: {'x': parse () {}, 'y': parse() {}}
+ Object.keys(key).forEach(function (k) {
+ builder(k, key[k])
+ })
+ } else {
+ // a single key value pair 'x', parse() {}
+ if (isArray) {
+ options[type][key] = (options[type][key] || []).concat(value)
+ } else {
+ options[type][key] = value
+ }
+ }
+ }
+
self.config = function (key, msg, parseFn) {
- // allow to pass a configuration object
+ // allow a config object to be provided directly.
if (typeof key === 'object') {
options.configObjects = (options.configObjects || []).concat(key)
return self
}
- // allow to provide a parsing function
+ // allow for a custom parsing function.
if (typeof msg === 'function') {
parseFn = msg
msg = null
@@ -251,59 +322,8 @@ function Yargs (processArgs, cwd, parentRequire) {
return self
}
- self.string = function (strings) {
- options.string.push.apply(options.string, [].concat(strings))
- return self
- }
-
- // The 'defaults' alias is deprecated. It will be removed in the next major version.
- self.default = self.defaults = function (key, value, defaultDescription) {
- if (typeof key === 'object') {
- Object.keys(key).forEach(function (k) {
- self.default(k, key[k])
- })
- } else {
- if (defaultDescription) options.defaultDescription[key] = defaultDescription
- if (typeof value === 'function') {
- if (!options.defaultDescription[key]) options.defaultDescription[key] = usage.functionDescription(value)
- value = value.call()
- }
- options.default[key] = value
- }
- return self
- }
-
- self.alias = function (x, y) {
- if (typeof x === 'object') {
- Object.keys(x).forEach(function (key) {
- self.alias(key, x[key])
- })
- } else {
- options.alias[x] = (options.alias[x] || []).concat(y)
- }
- return self
- }
-
- self.coerce = function (key, fn) {
- if (typeof key === 'object' && !Array.isArray(key)) {
- Object.keys(key).forEach(function (k) {
- self.coerce(k, key[k])
- })
- } else {
- [].concat(key).forEach(function (k) {
- options.coerce[k] = fn
- })
- }
- return self
- }
-
- self.count = function (counts) {
- options.count.push.apply(options.count, [].concat(counts))
- return self
- }
-
- // deprecated: the demand API is too overloaded, and is being
- // deprecated in favor of .demandCommand() .demandOption().
+ // TODO: deprecate self.demand in favor of
+ // .demandCommand() .demandOption().
self.demand = self.required = self.require = function (keys, max, msg) {
// you can optionally provide a 'max' key,
// which will raise an exception if too many '_'
@@ -335,29 +355,16 @@ function Yargs (processArgs, cwd, parentRequire) {
return self
}
- self.demandOption = function (key, msg) {
- if (Array.isArray(key)) {
- key.forEach(function (key) {
- self.demandOption(key, msg)
- })
- } else {
- if (typeof msg === 'string') {
- options.demandedOptions[key] = { msg: msg }
- // allow edge-case of options: {a: {demand: true}, b: {demand: false}}
- } else if (msg === true || typeof msg === 'undefined') {
- options.demandedOptions[key] = { msg: undefined }
- }
- }
-
- return self
- }
-
self.demandCommand = function (min, max, minMsg, maxMsg) {
+ if (typeof min === 'undefined') min = 1
+
if (typeof max !== 'number') {
minMsg = max
max = Infinity
}
+ self.global('_', false)
+
options.demandedCommands._ = {
min: min,
max: max,
@@ -376,16 +383,6 @@ function Yargs (processArgs, cwd, parentRequire) {
return options.demandedCommands
}
- self.requiresArg = function (requiresArgs) {
- options.requiresArg.push.apply(options.requiresArg, [].concat(requiresArgs))
- return self
- }
-
- self.skipValidation = function (skipValidations) {
- options.skipValidation.push.apply(options.skipValidation, [].concat(skipValidations))
- return self
- }
-
self.implies = function (key, value) {
validation.implies(key, value)
return self
@@ -419,31 +416,27 @@ function Yargs (processArgs, cwd, parentRequire) {
return self
}
- self.check = function (f) {
- validation.check(f)
+ self.check = function (f, _global) {
+ validation.check(f, _global !== false)
return self
}
- self.describe = function (key, desc) {
- if (typeof key === 'object') {
- Object.keys(key).forEach(function (k) {
- options.key[k] = true
+ self.global = function (globals, global) {
+ globals = [].concat(globals)
+ if (global !== false) {
+ options.local = options.local.filter(function (l) {
+ return globals.indexOf(l) === -1
})
} else {
- options.key[key] = true
+ globals.forEach(function (g) {
+ if (options.local.indexOf(g) === -1) options.local.push(g)
+ })
}
- usage.describe(key, desc)
- return self
- }
-
- self.global = function (globals) {
- options.global.push.apply(options.global, [].concat(globals))
return self
}
self.pkgConf = function (key, path) {
var conf = null
-
var obj = pkgUp(path)
// If an object exists in the key, add it to options.configObjects
@@ -497,7 +490,7 @@ function Yargs (processArgs, cwd, parentRequire) {
freeze()
if (parseFn) exitProcess = false
- var parsed = parseArgs(args, shortCircuit)
+ var parsed = self._parseArgs(args, shortCircuit)
if (parseFn) parseFn(exitError, parsed, output)
unfreeze()
@@ -568,10 +561,6 @@ function Yargs (processArgs, cwd, parentRequire) {
self.group(key, opt.group)
}
- if (opt.global) {
- self.global(key)
- }
-
if (opt.boolean || opt.type === 'boolean') {
self.boolean(key)
if (opt.alias) self.boolean(opt.alias)
@@ -596,6 +585,10 @@ function Yargs (processArgs, cwd, parentRequire) {
self.count(key)
}
+ if (typeof opt.global === 'boolean') {
+ self.global(key, opt.global)
+ }
+
if (opt.defaultDescription) {
options.defaultDescription[key] = opt.defaultDescription
}
@@ -623,8 +616,7 @@ function Yargs (processArgs, cwd, parentRequire) {
self.group = function (opts, groupName) {
var existing = preservedGroups[groupName] || groups[groupName]
if (preservedGroups[groupName]) {
- // the preserved group will be moved to the set of explicitly declared
- // groups
+ // we now only need to track this group name in groups.
delete preservedGroups[groupName]
}
@@ -654,8 +646,10 @@ function Yargs (processArgs, cwd, parentRequire) {
}
var strict = false
- self.strict = function () {
+ var strictGlobal = false
+ self.strict = function (global) {
strict = true
+ strictGlobal = global !== false
return self
}
self.getStrict = function () {
@@ -663,7 +657,7 @@ function Yargs (processArgs, cwd, parentRequire) {
}
self.showHelp = function (level) {
- if (!self.parsed) parseArgs(processArgs) // run parser, if it has not already been executed.
+ if (!self.parsed) self._parseArgs(processArgs) // run parser, if it has not already been executed.
usage.showHelp(level)
return self
}
@@ -685,7 +679,6 @@ function Yargs (processArgs, cwd, parentRequire) {
usage.version(ver || undefined)
self.boolean(versionOpt)
- self.global(versionOpt)
self.describe(versionOpt, msg)
return self
}
@@ -722,7 +715,6 @@ function Yargs (processArgs, cwd, parentRequire) {
// use arguments, fallback to defaults for opt and msg
helpOpt = opt || 'help'
self.boolean(helpOpt)
- self.global(helpOpt)
self.describe(helpOpt, msg || usage.deferY18nLookup('Show help'))
return self
}
@@ -866,7 +858,7 @@ function Yargs (processArgs, cwd, parentRequire) {
var args = null
try {
- args = parseArgs(processArgs)
+ args = self._parseArgs(processArgs)
} catch (err) {
if (err instanceof YError) usage.fail(err.message, err)
else throw err
@@ -877,7 +869,10 @@ function Yargs (processArgs, cwd, parentRequire) {
enumerable: true
})
- function parseArgs (args, shortCircuit) {
+ self._parseArgs = function (args, shortCircuit, _skipValidation) {
+ var skipValidation = !!_skipValidation
+ args = args || processArgs
+
options.__ = y18n.__
options.configuration = pkgUp()['yargs'] || {}
const parsed = Parser.detailed(args, options)
@@ -966,8 +961,6 @@ function Yargs (processArgs, cwd, parentRequire) {
return setPlaceholderKeys(argv)
}
- var skipValidation = false
-
// Handle 'help' and 'version' options
Object.keys(argv).forEach(function (key) {
if (key === helpOpt && argv[key]) {
@@ -993,27 +986,31 @@ function Yargs (processArgs, cwd, parentRequire) {
}
// If the help or version options where used and exitProcess is false,
- // or if explicitly skipped, we won't run validations
+ // or if explicitly skipped, we won't run validations.
if (!skipValidation) {
if (parsed.error) throw new YError(parsed.error.message)
// if we're executed via bash completion, don't
// bother with validation.
if (!argv[completion.completionKey]) {
- validation.nonOptionCount(argv)
- validation.missingArgumentValue(argv)
- validation.requiredArguments(argv)
- if (strict) validation.unknownArguments(argv, aliases)
- validation.customChecks(argv, aliases)
- validation.limitedChoices(argv)
- validation.implications(argv)
- validation.conflicting(argv)
+ self._runValidation(argv, aliases)
}
}
return setPlaceholderKeys(argv)
}
+ self._runValidation = function (argv, aliases) {
+ validation.nonOptionCount(argv)
+ validation.missingArgumentValue(argv)
+ validation.requiredArguments(argv)
+ if (strict) validation.unknownArguments(argv, aliases)
+ validation.customChecks(argv, aliases)
+ validation.limitedChoices(argv)
+ validation.implications(argv)
+ validation.conflicting(argv)
+ }
+
function guessLocale () {
if (!detectLocale) return