Skip to content

Commit

Permalink
feat: reworking yargs API to make it easier to run in headless enviro…
Browse files Browse the repository at this point in the history
…nments, e.g., Slack (#646)
  • Loading branch information
bcoe committed Oct 15, 2016
1 parent b1cabae commit f284c29
Show file tree
Hide file tree
Showing 6 changed files with 526 additions and 46 deletions.
40 changes: 37 additions & 3 deletions README.md
Expand Up @@ -1137,7 +1137,7 @@ Give some example invocations of your program. Inside `cmd`, the string
present script similar to how `$0` works in bash or perl.
Examples will be printed out as part of the help message.

.exitProcess(enable)
<a name="exitprocess"></a>.exitProcess(enable)
----------------------------------

By default, yargs exits the process when the user passes a help flag, uses the
Expand Down Expand Up @@ -1488,13 +1488,47 @@ Valid `opt` keys include:
- `'number'`: synonymous for `number: true`, see [`number()`](#number)
- `'string'`: synonymous for `string: true`, see [`string()`](#string)

.parse(args)
.parse(args, [context], [parseCallback])
------------

Parse `args` instead of `process.argv`. Returns the `argv` object.

`args` may either be a pre-processed argv array, or a raw argument string.

A `context` object can optionally be given as the second argument to `parse()`, providing a
useful mechanism for passing state information to commands:

```js
const parser = yargs
.command('lunch-train <restaurant>', 'start lunch train', function () {}, function (argv) {
console.log(argv.restaurant, argv.time)
})
.parse("lunch-train rudy's", {time: '12:15'})
```

A `parseCallback` can also be provided to `.parse()`. If a callback is given, it will be invoked with three arguments:

1. `err`: populated if any validation errors raised while parsing.
2. `argv`: the parsed argv object.
3. `output`: any text that would have been output to the terminal, had a
callback not been provided.

```js
// providing the `fn` argument to `parse()` runs yargs in headless mode, this
// makes it easy to use yargs in contexts other than the CLI, e.g., writing
// a chat-bot.
const parser = yargs
.command('lunch-train <restaurant> <time>', 'start lunch train', function () {}, function (argv) {
api.scheduleLunch(argv.restaurant, moment(argv.time))
})
.help()

parser.parse(bot.userText, function (err, argv, output) {
if (output) bot.respond(output)
})
```

***Note:*** Providing a callback to `parse()` disables the [`exitProcess` setting](#exitprocess) until after the callback is invoked.

.pkgConf(key, [cwd])
------------

Expand Down
21 changes: 18 additions & 3 deletions lib/command.js
Expand Up @@ -162,10 +162,9 @@ module.exports = function (yargs, usage, validation) {
})
innerArgv = innerArgv.argv
}
if (!yargs._hasOutput()) populatePositional(commandHandler, innerArgv, currentContext, yargs)

populatePositional(commandHandler, innerArgv, currentContext, yargs)

if (commandHandler.handler) {
if (commandHandler.handler && !yargs._hasOutput()) {
commandHandler.handler(innerArgv)
}
currentContext.commands.pop()
Expand Down Expand Up @@ -228,5 +227,21 @@ module.exports = function (yargs, usage, validation) {
return self
}

// used by yargs.parse() to freeze
// the state of commands such that
// we can apply .parse() multiple times
// with the same yargs instance.
var frozen
self.freeze = function () {
frozen = {}
frozen.handlers = handlers
frozen.aliasMap = aliasMap
}
self.unfreeze = function () {
handlers = frozen.handlers
aliasMap = frozen.aliasMap
frozen = undefined
}

return self
}
48 changes: 39 additions & 9 deletions lib/usage.js
Expand Up @@ -30,6 +30,8 @@ module.exports = function (yargs, y18n) {

var failureOutput = false
self.fail = function (msg, err) {
const logger = yargs._getLoggerInstance()

if (fails.length) {
for (var i = fails.length - 1; i >= 0; --i) {
fails[i](msg, err)
Expand All @@ -41,16 +43,20 @@ module.exports = function (yargs, y18n) {
if (!failureOutput) {
failureOutput = true
if (showHelpOnFail) yargs.showHelp('error')
if (msg) console.error(msg)
if (msg) logger.error(msg)
if (failMessage) {
if (msg) console.error('')
console.error(failMessage)
if (msg) logger.error('')
logger.error(failMessage)
}
}

err = err || new Error(msg)
if (yargs.getExitProcess()) {
process.exit(1)
return yargs.exit(1)
} else if (yargs._hasParseCallback()) {
return yargs.exit(1, err)
} else {
throw err || new Error(msg)
throw err
}
}
}
Expand Down Expand Up @@ -276,7 +282,7 @@ module.exports = function (yargs, y18n) {
var width = 0

// table might be of the form [leftColumn],
// or {key: leftColumn}}
// or {key: leftColumn}
if (!Array.isArray(table)) {
table = Object.keys(table).map(function (key) {
return [table[key]]
Expand Down Expand Up @@ -338,8 +344,9 @@ module.exports = function (yargs, y18n) {
}

self.showHelp = function (level) {
const logger = yargs._getLoggerInstance()
if (!level) level = 'error'
var emit = typeof level === 'function' ? level : console[ level ]
var emit = typeof level === 'function' ? level : logger[level]
emit(self.help())
}

Expand Down Expand Up @@ -401,8 +408,9 @@ module.exports = function (yargs, y18n) {
}

self.showVersion = function () {
if (typeof version === 'function') console.log(version())
else console.log(version)
const logger = yargs._getLoggerInstance()
if (typeof version === 'function') logger.log(version())
else logger.log(version)
}

self.reset = function (globalLookup) {
Expand All @@ -420,5 +428,27 @@ module.exports = function (yargs, y18n) {
return self
}

var frozen
self.freeze = function () {
frozen = {}
frozen.failMessage = failMessage
frozen.failureOutput = failureOutput
frozen.usage = usage
frozen.epilog = epilog
frozen.examples = examples
frozen.commands = commands
frozen.descriptions = descriptions
}
self.unfreeze = function () {
failMessage = frozen.failMessage
failureOutput = frozen.failureOutput
usage = frozen.usage
epilog = frozen.epilog
examples = frozen.examples
commands = frozen.commands
descriptions = frozen.descriptions
frozen = undefined
}

return self
}
31 changes: 23 additions & 8 deletions lib/validation.js
Expand Up @@ -187,18 +187,21 @@ module.exports = function (yargs, usage, y18n) {
}

self.customChecks = function (argv, aliases) {
checks.forEach(function (f) {
for (var i = 0, f; (f = checks[i]) !== undefined; i++) {
var result = null
try {
const result = f(argv, aliases)
if (!result) {
usage.fail(__('Argument check failed: %s', f.toString()))
} else if (typeof result === 'string') {
usage.fail(result)
}
result = f(argv, aliases)
} catch (err) {
usage.fail(err.message ? err.message : err, err)
continue
}
})

if (!result) {
usage.fail(__('Argument check failed: %s', f.toString()))
} else if (typeof result === 'string' || result instanceof Error) {
usage.fail(result.toString(), result)
}
}
}

// check implications, argument foo implies => argument bar.
Expand Down Expand Up @@ -299,5 +302,17 @@ module.exports = function (yargs, usage, y18n) {
return self
}

var frozen
self.freeze = function () {
frozen = {}
frozen.implied = implied
frozen.checks = checks
}
self.unfreeze = function () {
implied = frozen.implied
checks = frozen.checks
frozen = undefined
}

return self
}

0 comments on commit f284c29

Please sign in to comment.