Skip to content

Commit

Permalink
feat: middleware (#881)
Browse files Browse the repository at this point in the history
  • Loading branch information
Khaledgarbaya authored and bcoe committed Nov 23, 2017
1 parent e1117c5 commit 77b8dbc
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 9 deletions.
44 changes: 44 additions & 0 deletions docs/advanced.md
Expand Up @@ -425,3 +425,47 @@ some of yargs' parsing features:

See the [yargs-parser](https://github.com/yargs/yargs-parser#configuration) module
for detailed documentation of this feature.

## Midleware
Sometimes you might want to transform arguments before they reach the command handler.
For example, you perhaps you want to validate that credentials have been provided and otherwise load credentials from a file.
Middleware is simply a stack of functions, each of which is passed the the current parsed arguments, which it can in turn update by adding values, removing values, or overwriting values.

Diagram:

```
-------------- -------------- ---------
stdin ----> argv ----> | Middleware 1 | ----> | Middleware 2 | ---> | Command |
-------------- -------------- ---------
```

### Example Credentials Middleware

In this example, our middleware will check if the `username` and `password` is provided. If not, it will load them from `~/.credentials`, and fill in the `argv.username` and `argv.password` values.

#### Middleware function

```
const normalizeCredentials = (argv) => {
if (!argv.username || !argv.password) {
const credentials = JSON.parse(fs.readSync('~/.credentials'))
return credentials
}
return {}
}
```

#### yargs parsing configuration

```
var argv = require('yargs')
.usage('Usage: $0 <command> [options]')
.command('login', 'Authenticate user', (yargs) =>{
return yargs.option('username')
.option('password')
} ,(argv) => {
authenticateUser(argv.username, argv.password)
})
.middlewares([normalizeCredentials])
.argv;
```
17 changes: 12 additions & 5 deletions lib/command.js
Expand Up @@ -11,27 +11,26 @@ const DEFAULT_MARKER = /(^\*)|(^\$0)/
// arguments.
module.exports = function command (yargs, usage, validation) {
const self = {}

let handlers = {}
let aliasMap = {}
let defaultCommand
self.addHandler = function addHandler (cmd, description, builder, handler) {
self.addHandler = function addHandler (cmd, description, builder, handler, middlewares) {
let aliases = []
handler = handler || (() => {})

middlewares = middlewares || []
if (Array.isArray(cmd)) {
aliases = cmd.slice(1)
cmd = cmd[0]
} else if (typeof cmd === 'object') {
let command = (Array.isArray(cmd.command) || typeof cmd.command === 'string') ? cmd.command : moduleName(cmd)
if (cmd.aliases) command = [].concat(command).concat(cmd.aliases)
self.addHandler(command, extractDesc(cmd), cmd.builder, cmd.handler)
self.addHandler(command, extractDesc(cmd), cmd.builder, cmd.handler, cmd.middlewares)
return
}

// allow a module to be provided instead of separate builder and handler
if (typeof builder === 'object' && builder.builder && typeof builder.handler === 'function') {
self.addHandler([cmd].concat(aliases), description, builder.builder, builder.handler)
self.addHandler([cmd].concat(aliases), description, builder.builder, builder.handler, builder.middlewares)
return
}

Expand All @@ -50,6 +49,7 @@ module.exports = function command (yargs, usage, validation) {
}
return true
})

// standardize on $0 for default command.
if (parsedAliases.length === 0 && isDefault) parsedAliases.push('$0')

Expand All @@ -74,6 +74,7 @@ module.exports = function command (yargs, usage, validation) {
description: description,
handler,
builder: builder || {},
middlewares: middlewares || [],
demanded: parsedCommand.demanded,
optional: parsedCommand.optional
}
Expand Down Expand Up @@ -225,6 +226,12 @@ module.exports = function command (yargs, usage, validation) {

if (commandHandler.handler && !yargs._hasOutput()) {
yargs._setHasOutput()
if (commandHandler.middlewares.length > 0) {
const middlewareArgs = commandHandler.middlewares.reduce(function (initialObj, middleware) {
return Object.assign(initialObj, middleware(innerArgv))
}, {})
Object.assign(innerArgv, middlewareArgs)
}
commandHandler.handler(innerArgv)
}

Expand Down
20 changes: 19 additions & 1 deletion test/yargs.js
Expand Up @@ -289,7 +289,25 @@ describe('yargs dsl tests', () => {
.exitProcess(false) // defaults to true.
.argv
})

it('runs all middleware before reaching the handler', function (done) {
yargs(['foo'])
.command(
'foo',
'handle foo things',
function () {},
function (argv) {
// we should get the argv filled with data from the middleware
argv._[0].should.equal('foo')
argv.hello.should.equal('world')
return done()
},
[function (argv) {
return {hello: 'world'}
}]
)
.exitProcess(false) // defaults to true.
.argv
})
it('recommends a similar command if no command handler is found', () => {
const r = checkOutput(() => {
yargs(['boat'])
Expand Down
6 changes: 3 additions & 3 deletions yargs.js
Expand Up @@ -338,9 +338,9 @@ function Yargs (processArgs, cwd, parentRequire) {
return self
}

self.command = function (cmd, description, builder, handler) {
argsert('<string|array|object> [string|boolean] [function|object] [function]', [cmd, description, builder, handler], arguments.length)
command.addHandler(cmd, description, builder, handler)
self.command = function (cmd, description, builder, handler, middlewares) {
argsert('<string|array|object> [string|boolean] [function|object] [function] [array]', [cmd, description, builder, handler, middlewares], arguments.length)
command.addHandler(cmd, description, builder, handler, middlewares)
return self
}

Expand Down

0 comments on commit 77b8dbc

Please sign in to comment.