Skip to content

Commit 127a040

Browse files
authored
feat: initial support for command aliases (#647)
* feat: initial support for command aliases * fix standard nits and failing tests * tests for command aliases * define the wrap value for consistency wih all terminals * allow command module to use `aliases` property * docs: document command aliases and command execution * add German translation for "aliases:" thanks to @maxrimue!
1 parent 23cf836 commit 127a040

File tree

7 files changed

+284
-24
lines changed

7 files changed

+284
-24
lines changed

README.md

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,11 @@ var argv = require('yargs')
525525
.command(module)
526526
----------------
527527

528-
Document the commands exposed by your application.
528+
Define the commands exposed by your application.
529+
530+
`cmd` should be a string representing the command or an array of strings
531+
representing the command and its aliases. Read more about command aliases in the
532+
subsection below.
529533

530534
Use `desc` to provide a description for each command your application accepts (the
531535
values stored in `argv._`). Set `desc` to `false` to create a hidden command.
@@ -536,7 +540,8 @@ Optionally, you can provide a `builder` object to give hints about the
536540
options that your command accepts:
537541

538542
```js
539-
yargs.command('get', 'make a get HTTP request', {
543+
yargs
544+
.command('get', 'make a get HTTP request', {
540545
url: {
541546
alias: 'u',
542547
default: 'http://yargs.js.org/'
@@ -558,7 +563,8 @@ options (if used) **always** apply globally, just like the
558563
with a `yargs` instance, and can be used to provide _advanced_ command specific help:
559564

560565
```js
561-
yargs.command('get', 'make a get HTTP request', function (yargs) {
566+
yargs
567+
.command('get', 'make a get HTTP request', function (yargs) {
562568
return yargs.option('url', {
563569
alias: 'u',
564570
default: 'http://yargs.js.org/'
@@ -614,12 +620,78 @@ yargs.command('download <url> [files..]', 'download several files')
614620
.argv
615621
```
616622

623+
### Command Execution
624+
625+
When a command is given on the command line, yargs will execute the following:
626+
627+
1. push the command into the current context
628+
2. reset non-global configuration
629+
3. apply command configuration via the `builder`, if given
630+
4. parse and validate args from the command line, including positional args
631+
5. if validation succeeds, run the `handler` function, if given
632+
6. pop the command from the current context
633+
634+
### Command Aliases
635+
636+
You can define aliases for a command by putting the command and all of its
637+
aliases into an array.
638+
639+
Alternatively, a command module may specify an `aliases` property, which may be
640+
a string or an array of strings. All aliases defined via the `command` property
641+
and the `aliases` property will be concatenated together.
642+
643+
The first element in the array is considered the canonical command, which may
644+
define positional arguments, and the remaining elements in the array are
645+
considered aliases. Aliases inherit positional args from the canonical command,
646+
and thus any positional args defined in the aliases themselves are ignored.
647+
648+
If either the canonical command or any of its aliases are given on the command
649+
line, the command will be executed.
650+
651+
```js
652+
#!/usr/bin/env node
653+
require('yargs')
654+
.command(['start [app]', 'run', 'up'], 'Start up an app', {}, (argv) => {
655+
console.log('starting up the', argv.app || 'default', 'app')
656+
})
657+
.command({
658+
command: 'configure <key> [value]',
659+
aliases: ['config', 'cfg'],
660+
desc: 'Set a config variable',
661+
builder: (yargs) => yargs.default('value', 'true'),
662+
handler: (argv) => {
663+
console.log(`setting ${argv.key} to ${argv.value}`)
664+
}
665+
})
666+
.demand(1)
667+
.help()
668+
.wrap(72)
669+
.argv
670+
```
671+
672+
```
673+
$ ./svc.js help
674+
Commands:
675+
start [app] Start up an app [aliases: run, up]
676+
configure <key> [value] Set a config variable [aliases: config, cfg]
677+
678+
Options:
679+
--help Show help [boolean]
680+
681+
$ ./svc.js cfg concurrency 4
682+
setting concurrency to 4
683+
684+
$ ./svc.js run web
685+
starting up the web app
686+
```
687+
617688
### Providing a Command Module
618689

619690
For complicated commands you can pull the logic into a module. A module
620691
simply needs to export:
621692

622-
* `exports.command`: string that executes this command when given on the command line, may contain positional args
693+
* `exports.command`: string (or array of strings) that executes this command when given on the command line, first string may contain positional args
694+
* `exports.aliases`: array of strings (or a single string) representing aliases of `exports.command`, positional args defined in an alias are ignored
623695
* `exports.describe`: string used as the description for the command in help text, use `false` for a hidden command
624696
* `exports.builder`: object declaring the options the command accepts, or a function accepting and returning a yargs instance
625697
* `exports.handler`: a function which will be passed the parsed argv.

lib/command.js

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,36 @@ module.exports = function (yargs, usage, validation) {
88
const self = {}
99

1010
var handlers = {}
11+
var aliasMap = {}
1112
self.addHandler = function (cmd, description, builder, handler) {
12-
if (typeof cmd === 'object') {
13-
const commandString = typeof cmd.command === 'string' ? cmd.command : moduleName(cmd)
14-
self.addHandler(commandString, extractDesc(cmd), cmd.builder, cmd.handler)
13+
var aliases = []
14+
if (Array.isArray(cmd)) {
15+
aliases = cmd.slice(1)
16+
cmd = cmd[0]
17+
} else if (typeof cmd === 'object') {
18+
var command = (Array.isArray(cmd.command) || typeof cmd.command === 'string') ? cmd.command : moduleName(cmd)
19+
if (cmd.aliases) command = [].concat(command).concat(cmd.aliases)
20+
self.addHandler(command, extractDesc(cmd), cmd.builder, cmd.handler)
1521
return
1622
}
1723

1824
// allow a module to be provided instead of separate builder and handler
1925
if (typeof builder === 'object' && builder.builder && typeof builder.handler === 'function') {
20-
self.addHandler(cmd, description, builder.builder, builder.handler)
26+
self.addHandler([cmd].concat(aliases), description, builder.builder, builder.handler)
2127
return
2228
}
2329

30+
var parsedCommand = parseCommand(cmd)
31+
aliases = aliases.map(function (alias) {
32+
alias = parseCommand(alias).cmd // remove positional args
33+
aliasMap[alias] = parsedCommand.cmd
34+
return alias
35+
})
36+
2437
if (description !== false) {
25-
usage.command(cmd, description)
38+
usage.command(cmd, description, aliases)
2639
}
2740

28-
// we should not register a handler if no
29-
// builder is provided, e.g., user will
30-
// handle command themselves with '_'.
31-
var parsedCommand = parseCommand(cmd)
3241
handlers[parsedCommand.cmd] = {
3342
original: cmd,
3443
handler: handler,
@@ -112,7 +121,7 @@ module.exports = function (yargs, usage, validation) {
112121
}
113122

114123
self.getCommands = function () {
115-
return Object.keys(handlers)
124+
return Object.keys(handlers).concat(Object.keys(aliasMap))
116125
}
117126

118127
self.getCommandHandlers = function () {
@@ -121,7 +130,7 @@ module.exports = function (yargs, usage, validation) {
121130

122131
self.runCommand = function (command, yargs, parsed) {
123132
var argv = parsed.argv
124-
var commandHandler = handlers[command]
133+
var commandHandler = handlers[command] || handlers[aliasMap[command]]
125134
var innerArgv = argv
126135
var currentContext = yargs.getContext()
127136
var parentCommands = currentContext.commands.slice()
@@ -204,6 +213,7 @@ module.exports = function (yargs, usage, validation) {
204213

205214
self.reset = function () {
206215
handlers = {}
216+
aliasMap = {}
207217
return self
208218
}
209219

lib/usage.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ module.exports = function (yargs, y18n) {
7070
}
7171

7272
var commands = []
73-
self.command = function (cmd, description) {
74-
commands.push([cmd, description || ''])
73+
self.command = function (cmd, description, aliases) {
74+
commands.push([cmd, description || '', aliases])
7575
}
7676
self.getCommands = function () {
7777
return commands
@@ -152,10 +152,15 @@ module.exports = function (yargs, y18n) {
152152
ui.div(__('Commands:'))
153153

154154
commands.forEach(function (command) {
155-
ui.div(
155+
ui.span(
156156
{text: command[0], padding: [0, 2, 0, 2], width: maxWidth(commands, theWrap) + 4},
157157
{text: command[1]}
158158
)
159+
if (command[2] && command[2].length) {
160+
ui.div({text: '[' + __('aliases:') + ' ' + command[2].join(', ') + ']', padding: [0, 0, 0, 2], align: 'right'})
161+
} else {
162+
ui.div()
163+
}
159164
})
160165

161166
ui.div()

locales/de.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"required": "erforderlich",
1111
"default:": "Standard:",
1212
"choices:": "Möglichkeiten:",
13+
"aliases:": "Aliase:",
1314
"generated-value": "Generierter-Wert",
1415
"Not enough non-option arguments: got %s, need at least %s": "Nicht genügend Argumente ohne Optionen: %s vorhanden, mindestens %s benötigt",
1516
"Too many non-option arguments: got %s, maximum of %s": "Zu viele Argumente ohne Optionen: %s vorhanden, maximal %s erlaubt",

locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"required": "required",
1111
"default:": "default:",
1212
"choices:": "choices:",
13+
"aliases:": "aliases:",
1314
"generated-value": "generated-value",
1415
"Not enough non-option arguments: got %s, need at least %s": "Not enough non-option arguments: got %s, need at least %s",
1516
"Too many non-option arguments: got %s, maximum of %s": "Too many non-option arguments: got %s, maximum of %s",

0 commit comments

Comments
 (0)