| 'use strict' | 
|   | 
| const inspect = require('util').inspect | 
| const isPromise = require('./is-promise') | 
| const { applyMiddleware, commandMiddlewareFactory } = require('./middleware') | 
| const path = require('path') | 
| const Parser = require('yargs-parser') | 
|   | 
| const DEFAULT_MARKER = /(^\*)|(^\$0)/ | 
|   | 
| // handles parsing positional arguments, | 
| // and populating argv with said positional | 
| // arguments. | 
| module.exports = function command (yargs, usage, validation, globalMiddleware) { | 
|   const self = {} | 
|   let handlers = {} | 
|   let aliasMap = {} | 
|   let defaultCommand | 
|   globalMiddleware = globalMiddleware || [] | 
|   | 
|   self.addHandler = function addHandler (cmd, description, builder, handler, commandMiddleware) { | 
|     let aliases = [] | 
|     const middlewares = commandMiddlewareFactory(commandMiddleware) | 
|     handler = handler || (() => {}) | 
|   | 
|     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, 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, builder.middlewares) | 
|       return | 
|     } | 
|   | 
|     // parse positionals out of cmd string | 
|     const parsedCommand = self.parseCommand(cmd) | 
|   | 
|     // remove positional args from aliases only | 
|     aliases = aliases.map(alias => self.parseCommand(alias).cmd) | 
|   | 
|     // check for default and filter out '*'' | 
|     let isDefault = false | 
|     const parsedAliases = [parsedCommand.cmd].concat(aliases).filter((c) => { | 
|       if (DEFAULT_MARKER.test(c)) { | 
|         isDefault = true | 
|         return false | 
|       } | 
|       return true | 
|     }) | 
|   | 
|     // standardize on $0 for default command. | 
|     if (parsedAliases.length === 0 && isDefault) parsedAliases.push('$0') | 
|   | 
|     // shift cmd and aliases after filtering out '*' | 
|     if (isDefault) { | 
|       parsedCommand.cmd = parsedAliases[0] | 
|       aliases = parsedAliases.slice(1) | 
|       cmd = cmd.replace(DEFAULT_MARKER, parsedCommand.cmd) | 
|     } | 
|   | 
|     // populate aliasMap | 
|     aliases.forEach((alias) => { | 
|       aliasMap[alias] = parsedCommand.cmd | 
|     }) | 
|   | 
|     if (description !== false) { | 
|       usage.command(cmd, description, isDefault, aliases) | 
|     } | 
|   | 
|     handlers[parsedCommand.cmd] = { | 
|       original: cmd, | 
|       description: description, | 
|       handler, | 
|       builder: builder || {}, | 
|       middlewares: middlewares || [], | 
|       demanded: parsedCommand.demanded, | 
|       optional: parsedCommand.optional | 
|     } | 
|   | 
|     if (isDefault) defaultCommand = handlers[parsedCommand.cmd] | 
|   } | 
|   | 
|   self.addDirectory = function addDirectory (dir, context, req, callerFile, opts) { | 
|     opts = opts || {} | 
|     // disable recursion to support nested directories of subcommands | 
|     if (typeof opts.recurse !== 'boolean') opts.recurse = false | 
|     // exclude 'json', 'coffee' from require-directory defaults | 
|     if (!Array.isArray(opts.extensions)) opts.extensions = ['js'] | 
|     // allow consumer to define their own visitor function | 
|     const parentVisit = typeof opts.visit === 'function' ? opts.visit : o => o | 
|     // call addHandler via visitor function | 
|     opts.visit = function visit (obj, joined, filename) { | 
|       const visited = parentVisit(obj, joined, filename) | 
|       // allow consumer to skip modules with their own visitor | 
|       if (visited) { | 
|         // check for cyclic reference | 
|         // each command file path should only be seen once per execution | 
|         if (~context.files.indexOf(joined)) return visited | 
|         // keep track of visited files in context.files | 
|         context.files.push(joined) | 
|         self.addHandler(visited) | 
|       } | 
|       return visited | 
|     } | 
|     require('require-directory')({ require: req, filename: callerFile }, dir, opts) | 
|   } | 
|   | 
|   // lookup module object from require()d command and derive name | 
|   // if module was not require()d and no name given, throw error | 
|   function moduleName (obj) { | 
|     const mod = require('which-module')(obj) | 
|     if (!mod) throw new Error(`No command name given for module: ${inspect(obj)}`) | 
|     return commandFromFilename(mod.filename) | 
|   } | 
|   | 
|   // derive command name from filename | 
|   function commandFromFilename (filename) { | 
|     return path.basename(filename, path.extname(filename)) | 
|   } | 
|   | 
|   function extractDesc (obj) { | 
|     for (let keys = ['describe', 'description', 'desc'], i = 0, l = keys.length, test; i < l; i++) { | 
|       test = obj[keys[i]] | 
|       if (typeof test === 'string' || typeof test === 'boolean') return test | 
|     } | 
|     return false | 
|   } | 
|   | 
|   self.parseCommand = function parseCommand (cmd) { | 
|     const extraSpacesStrippedCommand = cmd.replace(/\s{2,}/g, ' ') | 
|     const splitCommand = extraSpacesStrippedCommand.split(/\s+(?![^[]*]|[^<]*>)/) | 
|     const bregex = /\.*[\][<>]/g | 
|     const parsedCommand = { | 
|       cmd: (splitCommand.shift()).replace(bregex, ''), | 
|       demanded: [], | 
|       optional: [] | 
|     } | 
|     splitCommand.forEach((cmd, i) => { | 
|       let 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, '').split('|'), | 
|           variadic | 
|         }) | 
|       } else { | 
|         parsedCommand.demanded.push({ | 
|           cmd: cmd.replace(bregex, '').split('|'), | 
|           variadic | 
|         }) | 
|       } | 
|     }) | 
|     return parsedCommand | 
|   } | 
|   | 
|   self.getCommands = () => Object.keys(handlers).concat(Object.keys(aliasMap)) | 
|   | 
|   self.getCommandHandlers = () => handlers | 
|   | 
|   self.hasDefaultCommand = () => !!defaultCommand | 
|   | 
|   self.runCommand = function runCommand (command, yargs, parsed, commandIndex) { | 
|     let aliases = parsed.aliases | 
|     const commandHandler = handlers[command] || handlers[aliasMap[command]] || defaultCommand | 
|     const currentContext = yargs.getContext() | 
|     let numFiles = currentContext.files.length | 
|     const parentCommands = currentContext.commands.slice() | 
|   | 
|     // what does yargs look like after the buidler is run? | 
|     let innerArgv = parsed.argv | 
|     let innerYargs = null | 
|     let positionalMap = {} | 
|     if (command) { | 
|       currentContext.commands.push(command) | 
|       currentContext.fullCommands.push(commandHandler.original) | 
|     } | 
|     if (typeof commandHandler.builder === 'function') { | 
|       // a function can be provided, which builds | 
|       // up a yargs chain and possibly returns it. | 
|       innerYargs = commandHandler.builder(yargs.reset(parsed.aliases)) | 
|       // if the builder function did not yet parse argv with reset yargs | 
|       // and did not explicitly set a usage() string, then apply the | 
|       // original command string as usage() for consistent behavior with | 
|       // options object below. | 
|       if (yargs.parsed === false) { | 
|         if (shouldUpdateUsage(yargs)) { | 
|           yargs.getUsageInstance().usage( | 
|             usageFromParentCommandsCommandHandler(parentCommands, commandHandler), | 
|             commandHandler.description | 
|           ) | 
|         } | 
|         innerArgv = innerYargs ? innerYargs._parseArgs(null, null, true, commandIndex) : yargs._parseArgs(null, null, true, commandIndex) | 
|       } else { | 
|         innerArgv = yargs.parsed.argv | 
|       } | 
|   | 
|       if (innerYargs && yargs.parsed === false) aliases = innerYargs.parsed.aliases | 
|       else aliases = yargs.parsed.aliases | 
|     } else if (typeof commandHandler.builder === 'object') { | 
|       // as a short hand, an object can instead be provided, specifying | 
|       // the options that a command takes. | 
|       innerYargs = yargs.reset(parsed.aliases) | 
|       if (shouldUpdateUsage(innerYargs)) { | 
|         innerYargs.getUsageInstance().usage( | 
|           usageFromParentCommandsCommandHandler(parentCommands, commandHandler), | 
|           commandHandler.description | 
|         ) | 
|       } | 
|       Object.keys(commandHandler.builder).forEach((key) => { | 
|         innerYargs.option(key, commandHandler.builder[key]) | 
|       }) | 
|       innerArgv = innerYargs._parseArgs(null, null, true, commandIndex) | 
|       aliases = innerYargs.parsed.aliases | 
|     } | 
|   | 
|     if (!yargs._hasOutput()) { | 
|       positionalMap = populatePositionals(commandHandler, innerArgv, currentContext, 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. | 
|     if (!yargs._hasOutput()) yargs._runValidation(innerArgv, aliases, positionalMap, yargs.parsed.error) | 
|   | 
|     if (commandHandler.handler && !yargs._hasOutput()) { | 
|       yargs._setHasOutput() | 
|   | 
|       innerArgv = applyMiddleware(innerArgv, yargs, middlewares, false) | 
|   | 
|       const handlerResult = isPromise(innerArgv) | 
|         ? innerArgv.then(argv => commandHandler.handler(argv)) | 
|         : commandHandler.handler(innerArgv) | 
|   | 
|       if (isPromise(handlerResult)) { | 
|         handlerResult.catch(error => | 
|           yargs.getUsageInstance().fail(null, error) | 
|         ) | 
|       } | 
|     } | 
|   | 
|     if (command) { | 
|       currentContext.commands.pop() | 
|       currentContext.fullCommands.pop() | 
|     } | 
|     numFiles = currentContext.files.length - numFiles | 
|     if (numFiles > 0) currentContext.files.splice(numFiles * -1, numFiles) | 
|   | 
|     return innerArgv | 
|   } | 
|   | 
|   function shouldUpdateUsage (yargs) { | 
|     return !yargs.getUsageInstance().getUsageDisabled() && | 
|       yargs.getUsageInstance().getUsage().length === 0 | 
|   } | 
|   | 
|   function usageFromParentCommandsCommandHandler (parentCommands, commandHandler) { | 
|     const c = DEFAULT_MARKER.test(commandHandler.original) ? commandHandler.original.replace(DEFAULT_MARKER, '').trim() : commandHandler.original | 
|     const pc = parentCommands.filter((c) => { return !DEFAULT_MARKER.test(c) }) | 
|     pc.push(c) | 
|     return `$0 ${pc.join(' ')}` | 
|   } | 
|   | 
|   self.runDefaultBuilderOn = function (yargs) { | 
|     if (shouldUpdateUsage(yargs)) { | 
|       // build the root-level command string from the default string. | 
|       const commandString = DEFAULT_MARKER.test(defaultCommand.original) | 
|         ? defaultCommand.original : defaultCommand.original.replace(/^[^[\]<>]*/, '$0 ') | 
|       yargs.getUsageInstance().usage( | 
|         commandString, | 
|         defaultCommand.description | 
|       ) | 
|     } | 
|     const builder = defaultCommand.builder | 
|     if (typeof builder === 'function') { | 
|       builder(yargs) | 
|     } else { | 
|       Object.keys(builder).forEach((key) => { | 
|         yargs.option(key, builder[key]) | 
|       }) | 
|     } | 
|   } | 
|   | 
|   // 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 | 
|     const demanded = commandHandler.demanded.slice(0) | 
|     const optional = commandHandler.optional.slice(0) | 
|     const positionalMap = {} | 
|   | 
|     validation.positionalCount(demanded.length, argv._.length) | 
|   | 
|     while (demanded.length) { | 
|       const demand = demanded.shift() | 
|       populatePositional(demand, argv, positionalMap) | 
|     } | 
|   | 
|     while (optional.length) { | 
|       const maybe = optional.shift() | 
|       populatePositional(maybe, argv, positionalMap) | 
|     } | 
|   | 
|     argv._ = context.commands.concat(argv._) | 
|   | 
|     postProcessPositionals(argv, positionalMap, self.cmdToParseOptions(commandHandler.original)) | 
|   | 
|     return positionalMap | 
|   } | 
|   | 
|   function populatePositional (positional, argv, positionalMap, parseOptions) { | 
|     const cmd = positional.cmd[0] | 
|     if (positional.variadic) { | 
|       positionalMap[cmd] = argv._.splice(0).map(String) | 
|     } else { | 
|       if (argv._.length) positionalMap[cmd] = [String(argv._.shift())] | 
|     } | 
|   } | 
|   | 
|   // we run yargs-parser against the positional arguments | 
|   // applying the same parsing logic used for flags. | 
|   function postProcessPositionals (argv, positionalMap, parseOptions) { | 
|     // combine the parsing hints we've inferred from the command | 
|     // string with explicitly configured parsing hints. | 
|     const options = Object.assign({}, yargs.getOptions()) | 
|     options.default = Object.assign(parseOptions.default, options.default) | 
|     options.alias = Object.assign(parseOptions.alias, options.alias) | 
|     options.array = options.array.concat(parseOptions.array) | 
|     delete options.config //  don't load config when processing positionals. | 
|   | 
|     const unparsed = [] | 
|     Object.keys(positionalMap).forEach((key) => { | 
|       positionalMap[key].map((value) => { | 
|         unparsed.push(`--${key}`) | 
|         unparsed.push(value) | 
|       }) | 
|     }) | 
|   | 
|     // short-circuit parse. | 
|     if (!unparsed.length) return | 
|   | 
|     const parsed = Parser.detailed(unparsed, options) | 
|   | 
|     if (parsed.error) { | 
|       yargs.getUsageInstance().fail(parsed.error.message, parsed.error) | 
|     } else { | 
|       // only copy over positional keys (don't overwrite | 
|       // flag arguments that were already parsed). | 
|       const positionalKeys = Object.keys(positionalMap) | 
|       Object.keys(positionalMap).forEach((key) => { | 
|         [].push.apply(positionalKeys, parsed.aliases[key]) | 
|       }) | 
|   | 
|       Object.keys(parsed.argv).forEach((key) => { | 
|         if (positionalKeys.indexOf(key) !== -1) { | 
|           // any new aliases need to be placed in positionalMap, which | 
|           // is used for validation. | 
|           if (!positionalMap[key]) positionalMap[key] = parsed.argv[key] | 
|           argv[key] = parsed.argv[key] | 
|         } | 
|       }) | 
|     } | 
|   } | 
|   | 
|   self.cmdToParseOptions = function (cmdString) { | 
|     const parseOptions = { | 
|       array: [], | 
|       default: {}, | 
|       alias: {}, | 
|       demand: {} | 
|     } | 
|   | 
|     const parsed = self.parseCommand(cmdString) | 
|     parsed.demanded.forEach((d) => { | 
|       const cmds = d.cmd.slice(0) | 
|       const cmd = cmds.shift() | 
|       if (d.variadic) { | 
|         parseOptions.array.push(cmd) | 
|         parseOptions.default[cmd] = [] | 
|       } | 
|       cmds.forEach((c) => { | 
|         parseOptions.alias[cmd] = c | 
|       }) | 
|       parseOptions.demand[cmd] = true | 
|     }) | 
|   | 
|     parsed.optional.forEach((o) => { | 
|       const cmds = o.cmd.slice(0) | 
|       const cmd = cmds.shift() | 
|       if (o.variadic) { | 
|         parseOptions.array.push(cmd) | 
|         parseOptions.default[cmd] = [] | 
|       } | 
|       cmds.forEach((c) => { | 
|         parseOptions.alias[cmd] = c | 
|       }) | 
|     }) | 
|   | 
|     return parseOptions | 
|   } | 
|   | 
|   self.reset = () => { | 
|     handlers = {} | 
|     aliasMap = {} | 
|     defaultCommand = undefined | 
|     return self | 
|   } | 
|   | 
|   // used by yargs.parse() to freeze | 
|   // the state of commands such that | 
|   // we can apply .parse() multiple times | 
|   // with the same yargs instance. | 
|   let frozen | 
|   self.freeze = () => { | 
|     frozen = {} | 
|     frozen.handlers = handlers | 
|     frozen.aliasMap = aliasMap | 
|     frozen.defaultCommand = defaultCommand | 
|   } | 
|   self.unfreeze = () => { | 
|     handlers = frozen.handlers | 
|     aliasMap = frozen.aliasMap | 
|     defaultCommand = frozen.defaultCommand | 
|     frozen = undefined | 
|   } | 
|   | 
|   return self | 
| } |