Skip to content

Commit 77b8dbc

Browse files
Khaledgarbayabcoe
authored andcommitted
feat: middleware (#881)
1 parent e1117c5 commit 77b8dbc

File tree

4 files changed

+78
-9
lines changed

4 files changed

+78
-9
lines changed

docs/advanced.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,3 +425,47 @@ some of yargs' parsing features:
425425

426426
See the [yargs-parser](https://github.com/yargs/yargs-parser#configuration) module
427427
for detailed documentation of this feature.
428+
429+
## Midleware
430+
Sometimes you might want to transform arguments before they reach the command handler.
431+
For example, you perhaps you want to validate that credentials have been provided and otherwise load credentials from a file.
432+
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.
433+
434+
Diagram:
435+
436+
```
437+
-------------- -------------- ---------
438+
stdin ----> argv ----> | Middleware 1 | ----> | Middleware 2 | ---> | Command |
439+
-------------- -------------- ---------
440+
```
441+
442+
### Example Credentials Middleware
443+
444+
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.
445+
446+
#### Middleware function
447+
448+
```
449+
const normalizeCredentials = (argv) => {
450+
if (!argv.username || !argv.password) {
451+
const credentials = JSON.parse(fs.readSync('~/.credentials'))
452+
return credentials
453+
}
454+
return {}
455+
}
456+
```
457+
458+
#### yargs parsing configuration
459+
460+
```
461+
var argv = require('yargs')
462+
.usage('Usage: $0 <command> [options]')
463+
.command('login', 'Authenticate user', (yargs) =>{
464+
return yargs.option('username')
465+
.option('password')
466+
} ,(argv) => {
467+
authenticateUser(argv.username, argv.password)
468+
})
469+
.middlewares([normalizeCredentials])
470+
.argv;
471+
```

lib/command.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,26 @@ const DEFAULT_MARKER = /(^\*)|(^\$0)/
1111
// arguments.
1212
module.exports = function command (yargs, usage, validation) {
1313
const self = {}
14-
1514
let handlers = {}
1615
let aliasMap = {}
1716
let defaultCommand
18-
self.addHandler = function addHandler (cmd, description, builder, handler) {
17+
self.addHandler = function addHandler (cmd, description, builder, handler, middlewares) {
1918
let aliases = []
2019
handler = handler || (() => {})
21-
20+
middlewares = middlewares || []
2221
if (Array.isArray(cmd)) {
2322
aliases = cmd.slice(1)
2423
cmd = cmd[0]
2524
} else if (typeof cmd === 'object') {
2625
let command = (Array.isArray(cmd.command) || typeof cmd.command === 'string') ? cmd.command : moduleName(cmd)
2726
if (cmd.aliases) command = [].concat(command).concat(cmd.aliases)
28-
self.addHandler(command, extractDesc(cmd), cmd.builder, cmd.handler)
27+
self.addHandler(command, extractDesc(cmd), cmd.builder, cmd.handler, cmd.middlewares)
2928
return
3029
}
3130

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

@@ -50,6 +49,7 @@ module.exports = function command (yargs, usage, validation) {
5049
}
5150
return true
5251
})
52+
5353
// standardize on $0 for default command.
5454
if (parsedAliases.length === 0 && isDefault) parsedAliases.push('$0')
5555

@@ -74,6 +74,7 @@ module.exports = function command (yargs, usage, validation) {
7474
description: description,
7575
handler,
7676
builder: builder || {},
77+
middlewares: middlewares || [],
7778
demanded: parsedCommand.demanded,
7879
optional: parsedCommand.optional
7980
}
@@ -225,6 +226,12 @@ module.exports = function command (yargs, usage, validation) {
225226

226227
if (commandHandler.handler && !yargs._hasOutput()) {
227228
yargs._setHasOutput()
229+
if (commandHandler.middlewares.length > 0) {
230+
const middlewareArgs = commandHandler.middlewares.reduce(function (initialObj, middleware) {
231+
return Object.assign(initialObj, middleware(innerArgv))
232+
}, {})
233+
Object.assign(innerArgv, middlewareArgs)
234+
}
228235
commandHandler.handler(innerArgv)
229236
}
230237

test/yargs.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,25 @@ describe('yargs dsl tests', () => {
289289
.exitProcess(false) // defaults to true.
290290
.argv
291291
})
292-
292+
it('runs all middleware before reaching the handler', function (done) {
293+
yargs(['foo'])
294+
.command(
295+
'foo',
296+
'handle foo things',
297+
function () {},
298+
function (argv) {
299+
// we should get the argv filled with data from the middleware
300+
argv._[0].should.equal('foo')
301+
argv.hello.should.equal('world')
302+
return done()
303+
},
304+
[function (argv) {
305+
return {hello: 'world'}
306+
}]
307+
)
308+
.exitProcess(false) // defaults to true.
309+
.argv
310+
})
293311
it('recommends a similar command if no command handler is found', () => {
294312
const r = checkOutput(() => {
295313
yargs(['boat'])

yargs.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,9 +338,9 @@ function Yargs (processArgs, cwd, parentRequire) {
338338
return self
339339
}
340340

341-
self.command = function (cmd, description, builder, handler) {
342-
argsert('<string|array|object> [string|boolean] [function|object] [function]', [cmd, description, builder, handler], arguments.length)
343-
command.addHandler(cmd, description, builder, handler)
341+
self.command = function (cmd, description, builder, handler, middlewares) {
342+
argsert('<string|array|object> [string|boolean] [function|object] [function] [array]', [cmd, description, builder, handler, middlewares], arguments.length)
343+
command.addHandler(cmd, description, builder, handler, middlewares)
344344
return self
345345
}
346346

0 commit comments

Comments
 (0)