Skip to content

Commit

Permalink
feat: support for positional argument aliases (#727)
Browse files Browse the repository at this point in the history
  • Loading branch information
bcoe committed Dec 17, 2016
1 parent 9bdaab7 commit 27e1a57
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 21 deletions.
17 changes: 16 additions & 1 deletion README.md
Expand Up @@ -609,6 +609,21 @@ yargs.command('get <source> [proxy]', 'make a get HTTP request')
.argv
```

#### Positional Argument Aliases

Aliases can be provided for positional arguments using the `|` character.
As an example, suppose our application allows either a username _or_
an email as the first argument:

```js
yargs.command('get <username|email> [password]', 'fetch a user by username or email.')
.help()
.argv
```

In this way, both `argv.username` and `argv.email` would be populated with the
same value when the command is executed.

#### Variadic Positional Arguments

The last positional argument can optionally accept an array of
Expand Down Expand Up @@ -1152,7 +1167,7 @@ Method to execute when a failure occurs, rather than printing the failure messag

`fn` is called with the failure message that would have been printed, the
`Error` instance originally thrown and yargs state when the failure
occured.
occured.

```js
var argv = require('yargs')
Expand Down
49 changes: 32 additions & 17 deletions lib/command.js
Expand Up @@ -96,7 +96,7 @@ module.exports = function (yargs, usage, validation) {

function parseCommand (cmd) {
var extraSpacesStrippedCommand = cmd.replace(/\s{2,}/g, ' ')
var splitCommand = extraSpacesStrippedCommand.split(/\s/)
var splitCommand = extraSpacesStrippedCommand.split(/\s+(?![^[]*]|[^<]*>)/)
var bregex = /\.*[\][<>]/g
var parsedCommand = {
cmd: (splitCommand.shift()).replace(bregex, ''),
Expand All @@ -105,15 +105,16 @@ module.exports = function (yargs, usage, validation) {
}
splitCommand.forEach(function (cmd, i) {
var variadic = false
cmd = cmd.replace(/\s/g, '')
if (/\.+[\]>]/.test(cmd) && i === splitCommand.length - 1) variadic = true
if (/^\[/.test(cmd)) {
parsedCommand.optional.push({
cmd: cmd.replace(bregex, ''),
cmd: cmd.replace(bregex, '').split('|'),
variadic: variadic
})
} else {
parsedCommand.demanded.push({
cmd: cmd.replace(bregex, ''),
cmd: cmd.replace(bregex, '').split('|'),
variadic: variadic
})
}
Expand Down Expand Up @@ -163,7 +164,7 @@ module.exports = function (yargs, usage, validation) {
})
innerArgv = innerArgv.argv
}
if (!yargs._hasOutput()) populatePositional(commandHandler, innerArgv, currentContext, yargs)
if (!yargs._hasOutput()) populatePositionals(commandHandler, innerArgv, currentContext, yargs)

if (commandHandler.handler && !yargs._hasOutput()) {
commandHandler.handler(innerArgv)
Expand All @@ -174,7 +175,9 @@ module.exports = function (yargs, usage, validation) {
return innerArgv
}

function populatePositional (commandHandler, argv, context, yargs) {
// transcribe all positional arguments "command <foo> <bar> [apple]"
// onto argv.
function populatePositionals (commandHandler, argv, context, yargs) {
argv._ = argv._.slice(context.commands.length) // nuke the current commands
var demanded = commandHandler.demanded.slice(0)
var optional = commandHandler.optional.slice(0)
Expand All @@ -183,27 +186,39 @@ module.exports = function (yargs, usage, validation) {

while (demanded.length) {
var demand = demanded.shift()
if (demand.variadic) argv[demand.cmd] = []
if (!argv._.length) break
if (demand.variadic) argv[demand.cmd] = argv._.splice(0)
else argv[demand.cmd] = argv._.shift()
postProcessPositional(yargs, argv, demand.cmd)
addCamelCaseExpansions(argv, demand.cmd)
populatePositional(demand, argv, yargs)
}

while (optional.length) {
var maybe = optional.shift()
if (maybe.variadic) argv[maybe.cmd] = []
if (!argv._.length) break
if (maybe.variadic) argv[maybe.cmd] = argv._.splice(0)
else argv[maybe.cmd] = argv._.shift()
postProcessPositional(yargs, argv, maybe.cmd)
addCamelCaseExpansions(argv, maybe.cmd)
populatePositional(maybe, argv, yargs)
}

argv._ = context.commands.concat(argv._)
}

// populate a single positional argument and its
// aliases onto argv.
function populatePositional (positional, argv, yargs) {
// "positional" consists of the positional.cmd, an array representing
// the positional's name and aliases, and positional.variadic
// indicating whether or not it is a variadic array.
var variadics = null
var value = null
for (var i = 0, cmd; (cmd = positional.cmd[i]) !== undefined; i++) {
if (positional.variadic) {
if (variadics) argv[cmd] = variadics.slice(0)
else argv[cmd] = variadics = argv._.splice(0)
} else {
if (!value && !argv._.length) continue
if (value) argv[cmd] = value
else argv[cmd] = value = argv._.shift()
}
postProcessPositional(yargs, argv, cmd)
addCamelCaseExpansions(argv, cmd)
}
}

// TODO move positional arg logic to yargs-parser and remove this duplication
function postProcessPositional (yargs, argv, key) {
var coerce = yargs.getOptions().coerce[key]
Expand Down
38 changes: 35 additions & 3 deletions test/command.js
Expand Up @@ -19,11 +19,11 @@ describe('Command', function () {
var command = y.getCommandInstance()
var handlers = command.getCommandHandlers()
handlers.foo.demanded.should.include({
cmd: 'bar',
cmd: ['bar'],
variadic: false
})
handlers.foo.optional.should.include({
cmd: 'awesome',
cmd: ['awesome'],
variadic: false
})
})
Expand Down Expand Up @@ -96,7 +96,7 @@ describe('Command', function () {
var command = y.getCommandInstance()
var handlers = command.getCommandHandlers()
handlers.foo.optional.should.include({
cmd: 'awesome',
cmd: ['awesome'],
variadic: false
})
handlers.foo.demanded.should.deep.equal([])
Expand Down Expand Up @@ -807,4 +807,36 @@ describe('Command', function () {
argv.should.have.property('someone').and.equal('world')
commandCalled.should.be.true
})

describe('positional aliases', function () {
it('allows an alias to be defined for a required positional argument', function () {
var argv = yargs('yo bcoe 113993')
.command('yo <user | email> [ssn]', 'Send someone a yo')
.argv
argv.user.should.equal('bcoe')
argv.email.should.equal('bcoe')
argv.ssn.should.equal(113993)
})

it('allows an alias to be defined for an optional positional argument', function () {
var argv
yargs('yo 113993')
.command('yo [ssn|sin]', 'Send someone a yo', {}, function (_argv) {
argv = _argv
})
.argv
argv.ssn.should.equal(113993)
argv.sin.should.equal(113993)
})

it('allows variadic and positional arguments to be combined', function () {
var parser = yargs
.command('yo <user|email> [ ssns | sins... ]', 'Send someone a yo')
var argv = parser.parse('yo bcoe 113993 112888')
argv.user.should.equal('bcoe')
argv.email.should.equal('bcoe')
argv.ssns.should.deep.equal([113993, 112888])
argv.sins.should.deep.equal([113993, 112888])
})
})
})

0 comments on commit 27e1a57

Please sign in to comment.