Skip to content

Commit

Permalink
chore: slight refactor of approach being used, add support for per-co…
Browse files Browse the repository at this point in the history
…mmand
  • Loading branch information
bcoe committed Feb 8, 2019
1 parent 5be206a commit f45a817
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 195 deletions.
32 changes: 16 additions & 16 deletions docs/api.md
Expand Up @@ -962,7 +962,9 @@ yargs
})
.middleware([mwFunc1, mwFunc2]).argv;
```

When calling `myCommand` from the command line, mwFunc1 gets called first, then mwFunc2, and finally the command's handler. The console output is:

```
I'm a middleware function
I'm another middleware function
Expand All @@ -971,34 +973,32 @@ Running myCommand!

Middleware can be applied before validation by setting the second parameter to `true`. This will execute the middleware prior to validation checks, but after parsing.

Each callback is passed a reference to argv. The argv can be modified to affect the behavior of the validation and command execution.
Middleware is passed two parameters `argv`, the current parsed options object,
and `yargs` the yargs instance itself, which provides contextual information
about the current state of parsing.

For example, an environment variable could potentially populate a required option:
A modified `argv` object will ultimately be what is passed to a command's
handler function.

```js
var argv = require('yargs')
// populating home directory from an environment variable.
require('yargs')
.middleware(function (argv) {
argv.username = process.env.USERNAME
argv.password = process.env.PASSWORD
if (process.env.HOME) argv.home = process.env.HOME
}, true)
.command('do-something-logged-in', 'You must be logged in to perform this task'
.command('configure-home', "do something with a user's home directory",
{
'username': {
'demand': true,
'string': true
},
'password': {
'home': {
'demand': true,
'string': true
}
},
function(argv) {
console.log('do something with the user login and password', argv.username, argv.password)
})
console.info(`we know the user's home directory is ${argv.home}`)
}
)
.argv

```
.parse()
```

<a name="nargs"></a>.nargs(key, count)
-----------
Expand Down
6 changes: 4 additions & 2 deletions lib/argsert.js
@@ -1,10 +1,12 @@
'use strict'

// hoisted due to circular dependency on command.
module.exports = argsert
const command = require('./command')()
const YError = require('./yerror')

const positionName = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth']

module.exports = function argsert (expected, callerArguments, length) {
function argsert (expected, callerArguments, length) {
// TODO: should this eventually raise an exception.
try {
// preface the argument description with "cmd", so
Expand Down
19 changes: 7 additions & 12 deletions lib/command.js
Expand Up @@ -2,7 +2,7 @@

const inspect = require('util').inspect
const isPromise = require('./is-promise')
const {applyMiddleware, applyPreCheckMiddlware} = require('./middleware')
const {applyMiddleware, commandMiddlewareFactory} = require('./middleware')
const path = require('path')
const Parser = require('yargs-parser')

Expand All @@ -17,9 +17,10 @@ module.exports = function command (yargs, usage, validation, globalMiddleware) {
let aliasMap = {}
let defaultCommand
globalMiddleware = globalMiddleware || []
self.addHandler = function addHandler (cmd, description, builder, handler, _middlewares) {

self.addHandler = function addHandler (cmd, description, builder, handler, commandMiddleware) {
let aliases = []
const middlewares = (_middlewares || []).slice(0)
const middlewares = commandMiddlewareFactory(commandMiddleware)
handler = handler || (() => {})

if (Array.isArray(cmd)) {
Expand Down Expand Up @@ -224,11 +225,8 @@ module.exports = function command (yargs, usage, validation, globalMiddleware) {
positionalMap = populatePositionals(commandHandler, innerArgv, currentContext, yargs)
}

let preCheckMiddlewares = globalMiddleware.slice(0).concat(commandHandler.middlewares || [])
preCheckMiddlewares = preCheckMiddlewares.filter((x) => x.applyBeforeValidation === true)
if (preCheckMiddlewares.length > 0 && !yargs._hasOutput()) {
applyPreCheckMiddlware(innerArgv, preCheckMiddlewares, yargs)
}
const middlewares = globalMiddleware.slice(0).concat(commandHandler.middlewares || [])
applyMiddleware(innerArgv, yargs, middlewares, true)

// we apply validation post-hoc, so that custom
// checks get passed populated positional arguments.
Expand All @@ -237,10 +235,7 @@ module.exports = function command (yargs, usage, validation, globalMiddleware) {
if (commandHandler.handler && !yargs._hasOutput()) {
yargs._setHasOutput()

let middlewares = globalMiddleware.slice(0).concat(commandHandler.middlewares || [])
middlewares = middlewares.filter((x) => x.applyBeforeValidation !== true)

innerArgv = applyMiddleware(innerArgv, middlewares, yargs)
innerArgv = applyMiddleware(innerArgv, yargs, middlewares, false)

const handlerResult = isPromise(innerArgv)
? innerArgv.then(argv => commandHandler.handler(argv))
Expand Down
42 changes: 31 additions & 11 deletions lib/middleware.js
@@ -1,9 +1,22 @@
'use strict'

// hoisted due to circular dependency on command.
module.exports = {
applyMiddleware,
commandMiddlewareFactory,
globalMiddlewareFactory
}
const isPromise = require('./is-promise')
const argsert = require('./argsert')

module.exports = function (globalMiddleware, context) {
function globalMiddlewareFactory (globalMiddleware, context) {
return function (callback, applyBeforeValidation = false) {
argsert('<array|function> [boolean]', [callback, applyBeforeValidation], arguments.length)
if (Array.isArray(callback)) {
for (let i = 0; i < callback.length; i++) {
if (typeof callback[i] !== 'function') {
throw Error('middleware must be a function')
}
callback[i].applyBeforeValidation = applyBeforeValidation
}
Array.prototype.push.apply(globalMiddleware, callback)
Expand All @@ -15,9 +28,23 @@ module.exports = function (globalMiddleware, context) {
}
}

module.exports.applyMiddleware = function (argv, middlewares, yargs) {
function commandMiddlewareFactory (commandMiddleware) {
if (!commandMiddleware) return []
return commandMiddleware.map(middleware => {
middleware.applyBeforeValidation = false
return middleware
})
}

function applyMiddleware (argv, yargs, middlewares, beforeValidation) {
const beforeValidationError = new Error('middleware cannot return a promise when applyBeforeValidation is true')
return middlewares
.reduce((accumulation, middleware) => {
if (middleware.applyBeforeValidation !== beforeValidation &&
!isPromise(accumulation)) {
return accumulation
}

if (isPromise(accumulation)) {
return accumulation
.then(initialObj =>
Expand All @@ -27,19 +54,12 @@ module.exports.applyMiddleware = function (argv, middlewares, yargs) {
Object.assign(initialObj, middlewareObj)
)
} else {
const result = middleware(argv)
const result = middleware(argv, yargs)
if (beforeValidation && isPromise(result)) throw beforeValidationError

return isPromise(result)
? result.then(middlewareObj => Object.assign(accumulation, middlewareObj))
: Object.assign(accumulation, result)
}
}, argv)
}

module.exports.applyPreCheckMiddlware = function (argv, middlewares, yargs) {
for (let i = 0; i < middlewares.length; i++) {
if (middlewares[i](argv, yargs) instanceof Promise) {
throw new Error('The passed in middleware with applyBeforeValidation set to true may not be used with async functions.')
}
}
}

0 comments on commit f45a817

Please sign in to comment.