Skip to content

Commit d78a0f5

Browse files
committed
feat: introduces support for default commands, using the '*' identifier (#785)
1 parent 8a992f5 commit d78a0f5

File tree

5 files changed

+214
-21
lines changed

5 files changed

+214
-21
lines changed

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,34 @@ yargs
591591
.argv
592592
```
593593

594+
### Default Commands
595+
596+
To specify a default command use the character `*`. A default command
597+
will be run if the positional arguments provided match no known
598+
commands:
599+
600+
```js
601+
const argv = require('yargs')
602+
.command('*', 'the default command', () => {}, (argv) => {
603+
console.log('this command will be run by default')
604+
})
605+
```
606+
607+
The command defined above will be executed if the program
608+
is run with `./my-cli.js --x=22`.
609+
610+
Default commands can also be used as a command alias, like so:
611+
612+
```js
613+
const argv = require('yargs')
614+
.command(['serve', '*'], 'the serve command', () => {}, (argv) => {
615+
console.log('this command will be run by default')
616+
})
617+
```
618+
619+
The command defined above will be executed if the program
620+
is run with `./my-cli.js --x=22`, or with `./my-cli.js serve --x=22`.
621+
594622
### Positional Arguments
595623

596624
Commands can accept _optional_ and _required_ positional arguments. Required

lib/command.js

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ const path = require('path')
22
const inspect = require('util').inspect
33
const camelCase = require('camelcase')
44

5+
const DEFAULT_MARKER = '*'
6+
57
// handles parsing positional arguments,
68
// and populating argv with said positional
79
// arguments.
@@ -10,6 +12,7 @@ module.exports = function (yargs, usage, validation) {
1012

1113
var handlers = {}
1214
var aliasMap = {}
15+
var defaultCommand
1316
self.addHandler = function (cmd, description, builder, handler) {
1417
var aliases = []
1518
if (Array.isArray(cmd)) {
@@ -28,15 +31,50 @@ module.exports = function (yargs, usage, validation) {
2831
return
2932
}
3033

34+
// parse positionals out of cmd string
3135
var parsedCommand = self.parseCommand(cmd)
36+
37+
// remove positional args from aliases only
3238
aliases = aliases.map(function (alias) {
33-
alias = self.parseCommand(alias).cmd // remove positional args
39+
return self.parseCommand(alias).cmd
40+
})
41+
42+
// check for default and filter out '*''
43+
var isDefault = false
44+
var parsedAliases = [parsedCommand.cmd].concat(aliases).filter(function (c) {
45+
if (c === DEFAULT_MARKER) {
46+
isDefault = true
47+
return false
48+
}
49+
return true
50+
})
51+
52+
// short-circuit if default with no aliases
53+
if (isDefault && parsedAliases.length === 0) {
54+
defaultCommand = {
55+
original: cmd.replace(DEFAULT_MARKER, '').trim(),
56+
handler: handler,
57+
builder: builder || {},
58+
demanded: parsedCommand.demanded,
59+
optional: parsedCommand.optional
60+
}
61+
return
62+
}
63+
64+
// shift cmd and aliases after filtering out '*'
65+
if (isDefault) {
66+
parsedCommand.cmd = parsedAliases[0]
67+
aliases = parsedAliases.slice(1)
68+
cmd = cmd.replace(DEFAULT_MARKER, parsedCommand.cmd)
69+
}
70+
71+
// populate aliasMap
72+
aliases.forEach(function (alias) {
3473
aliasMap[alias] = parsedCommand.cmd
35-
return alias
3674
})
3775

3876
if (description !== false) {
39-
usage.command(cmd, description, aliases)
77+
usage.command(cmd, description, isDefault, aliases)
4078
}
4179

4280
handlers[parsedCommand.cmd] = {
@@ -46,6 +84,8 @@ module.exports = function (yargs, usage, validation) {
4684
demanded: parsedCommand.demanded,
4785
optional: parsedCommand.optional
4886
}
87+
88+
if (isDefault) defaultCommand = handlers[parsedCommand.cmd]
4989
}
5090

5191
self.addDirectory = function (dir, context, req, callerFile, opts) {
@@ -130,9 +170,13 @@ module.exports = function (yargs, usage, validation) {
130170
return handlers
131171
}
132172

173+
self.hasDefaultCommand = function () {
174+
return !!defaultCommand
175+
}
176+
133177
self.runCommand = function (command, yargs, parsed) {
134178
var aliases = parsed.aliases
135-
var commandHandler = handlers[command] || handlers[aliasMap[command]]
179+
var commandHandler = handlers[command] || handlers[aliasMap[command]] || defaultCommand
136180
var currentContext = yargs.getContext()
137181
var numFiles = currentContext.files.length
138182
var parentCommands = currentContext.commands.slice()
@@ -142,7 +186,7 @@ module.exports = function (yargs, usage, validation) {
142186
var innerYargs = null
143187
var positionalMap = {}
144188

145-
currentContext.commands.push(command)
189+
if (command) currentContext.commands.push(command)
146190
if (typeof commandHandler.builder === 'function') {
147191
// a function can be provided, which builds
148192
// up a yargs chain and possibly returns it.
@@ -186,7 +230,7 @@ module.exports = function (yargs, usage, validation) {
186230
commandHandler.handler(innerArgv)
187231
}
188232

189-
currentContext.commands.pop()
233+
if (command) currentContext.commands.pop()
190234
numFiles = currentContext.files.length - numFiles
191235
if (numFiles > 0) currentContext.files.splice(numFiles * -1, numFiles)
192236

@@ -263,6 +307,7 @@ module.exports = function (yargs, usage, validation) {
263307
self.reset = function () {
264308
handlers = {}
265309
aliasMap = {}
310+
defaultCommand = undefined
266311
return self
267312
}
268313

@@ -275,10 +320,12 @@ module.exports = function (yargs, usage, validation) {
275320
frozen = {}
276321
frozen.handlers = handlers
277322
frozen.aliasMap = aliasMap
323+
frozen.defaultCommand = defaultCommand
278324
}
279325
self.unfreeze = function () {
280326
handlers = frozen.handlers
281327
aliasMap = frozen.aliasMap
328+
defaultCommand = frozen.defaultCommand
282329
frozen = undefined
283330
}
284331

lib/usage.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,15 @@ module.exports = function (yargs, y18n) {
7777
}
7878

7979
var commands = []
80-
self.command = function (cmd, description, aliases) {
81-
commands.push([cmd, description || '', aliases])
80+
self.command = function (cmd, description, isDefault, aliases) {
81+
// the last default wins, so cancel out any previously set default
82+
if (isDefault) {
83+
commands = commands.map(function (cmdArray) {
84+
cmdArray[2] = false
85+
return cmdArray
86+
})
87+
}
88+
commands.push([cmd, description || '', isDefault, aliases])
8289
}
8390
self.getCommands = function () {
8491
return commands
@@ -166,8 +173,13 @@ module.exports = function (yargs, y18n) {
166173
{text: command[0], padding: [0, 2, 0, 2], width: maxWidth(commands, theWrap) + 4},
167174
{text: command[1]}
168175
)
169-
if (command[2] && command[2].length) {
170-
ui.div({text: '[' + __('aliases:') + ' ' + command[2].join(', ') + ']', padding: [0, 0, 0, 2], align: 'right'})
176+
var hints = []
177+
if (command[2]) hints.push('[' + __('default:').slice(0, -1) + ']') // TODO hacking around i18n here
178+
if (command[3] && command[3].length) {
179+
hints.push('[' + __('aliases:') + ' ' + command[3].join(', ') + ']')
180+
}
181+
if (hints.length) {
182+
ui.div({text: hints.join(' '), padding: [0, 0, 0, 2], align: 'right'})
171183
} else {
172184
ui.div()
173185
}

0 commit comments

Comments
 (0)