.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / yargs / lib / command.js
1 'use strict'
2
3 const inspect = require('util').inspect
4 const path = require('path')
5 const Parser = require('yargs-parser')
6
7 const DEFAULT_MARKER = /(^\*)|(^\$0)/
8
9 // handles parsing positional arguments,
10 // and populating argv with said positional
11 // arguments.
12 module.exports = function command (yargs, usage, validation) {
13   const self = {}
14   let handlers = {}
15   let aliasMap = {}
16   let defaultCommand
17   self.addHandler = function addHandler (cmd, description, builder, handler, middlewares) {
18     let aliases = []
19     handler = handler || (() => {})
20     middlewares = middlewares || []
21     if (Array.isArray(cmd)) {
22       aliases = cmd.slice(1)
23       cmd = cmd[0]
24     } else if (typeof cmd === 'object') {
25       let command = (Array.isArray(cmd.command) || typeof cmd.command === 'string') ? cmd.command : moduleName(cmd)
26       if (cmd.aliases) command = [].concat(command).concat(cmd.aliases)
27       self.addHandler(command, extractDesc(cmd), cmd.builder, cmd.handler, cmd.middlewares)
28       return
29     }
30
31     // allow a module to be provided instead of separate builder and handler
32     if (typeof builder === 'object' && builder.builder && typeof builder.handler === 'function') {
33       self.addHandler([cmd].concat(aliases), description, builder.builder, builder.handler, builder.middlewares)
34       return
35     }
36
37     // parse positionals out of cmd string
38     const parsedCommand = self.parseCommand(cmd)
39
40     // remove positional args from aliases only
41     aliases = aliases.map(alias => self.parseCommand(alias).cmd)
42
43     // check for default and filter out '*''
44     let isDefault = false
45     const parsedAliases = [parsedCommand.cmd].concat(aliases).filter((c) => {
46       if (DEFAULT_MARKER.test(c)) {
47         isDefault = true
48         return false
49       }
50       return true
51     })
52
53     // standardize on $0 for default command.
54     if (parsedAliases.length === 0 && isDefault) parsedAliases.push('$0')
55
56     // shift cmd and aliases after filtering out '*'
57     if (isDefault) {
58       parsedCommand.cmd = parsedAliases[0]
59       aliases = parsedAliases.slice(1)
60       cmd = cmd.replace(DEFAULT_MARKER, parsedCommand.cmd)
61     }
62
63     // populate aliasMap
64     aliases.forEach((alias) => {
65       aliasMap[alias] = parsedCommand.cmd
66     })
67
68     if (description !== false) {
69       usage.command(cmd, description, isDefault, aliases)
70     }
71
72     handlers[parsedCommand.cmd] = {
73       original: cmd,
74       description: description,
75       handler,
76       builder: builder || {},
77       middlewares: middlewares || [],
78       demanded: parsedCommand.demanded,
79       optional: parsedCommand.optional
80     }
81
82     if (isDefault) defaultCommand = handlers[parsedCommand.cmd]
83   }
84
85   self.addDirectory = function addDirectory (dir, context, req, callerFile, opts) {
86     opts = opts || {}
87     // disable recursion to support nested directories of subcommands
88     if (typeof opts.recurse !== 'boolean') opts.recurse = false
89     // exclude 'json', 'coffee' from require-directory defaults
90     if (!Array.isArray(opts.extensions)) opts.extensions = ['js']
91     // allow consumer to define their own visitor function
92     const parentVisit = typeof opts.visit === 'function' ? opts.visit : o => o
93     // call addHandler via visitor function
94     opts.visit = function visit (obj, joined, filename) {
95       const visited = parentVisit(obj, joined, filename)
96       // allow consumer to skip modules with their own visitor
97       if (visited) {
98         // check for cyclic reference
99         // each command file path should only be seen once per execution
100         if (~context.files.indexOf(joined)) return visited
101         // keep track of visited files in context.files
102         context.files.push(joined)
103         self.addHandler(visited)
104       }
105       return visited
106     }
107     require('require-directory')({ require: req, filename: callerFile }, dir, opts)
108   }
109
110   // lookup module object from require()d command and derive name
111   // if module was not require()d and no name given, throw error
112   function moduleName (obj) {
113     const mod = require('which-module')(obj)
114     if (!mod) throw new Error(`No command name given for module: ${inspect(obj)}`)
115     return commandFromFilename(mod.filename)
116   }
117
118   // derive command name from filename
119   function commandFromFilename (filename) {
120     return path.basename(filename, path.extname(filename))
121   }
122
123   function extractDesc (obj) {
124     for (let keys = ['describe', 'description', 'desc'], i = 0, l = keys.length, test; i < l; i++) {
125       test = obj[keys[i]]
126       if (typeof test === 'string' || typeof test === 'boolean') return test
127     }
128     return false
129   }
130
131   self.parseCommand = function parseCommand (cmd) {
132     const extraSpacesStrippedCommand = cmd.replace(/\s{2,}/g, ' ')
133     const splitCommand = extraSpacesStrippedCommand.split(/\s+(?![^[]*]|[^<]*>)/)
134     const bregex = /\.*[\][<>]/g
135     const parsedCommand = {
136       cmd: (splitCommand.shift()).replace(bregex, ''),
137       demanded: [],
138       optional: []
139     }
140     splitCommand.forEach((cmd, i) => {
141       let variadic = false
142       cmd = cmd.replace(/\s/g, '')
143       if (/\.+[\]>]/.test(cmd) && i === splitCommand.length - 1) variadic = true
144       if (/^\[/.test(cmd)) {
145         parsedCommand.optional.push({
146           cmd: cmd.replace(bregex, '').split('|'),
147           variadic
148         })
149       } else {
150         parsedCommand.demanded.push({
151           cmd: cmd.replace(bregex, '').split('|'),
152           variadic
153         })
154       }
155     })
156     return parsedCommand
157   }
158
159   self.getCommands = () => Object.keys(handlers).concat(Object.keys(aliasMap))
160
161   self.getCommandHandlers = () => handlers
162
163   self.hasDefaultCommand = () => !!defaultCommand
164
165   self.runCommand = function runCommand (command, yargs, parsed, commandIndex) {
166     let aliases = parsed.aliases
167     const commandHandler = handlers[command] || handlers[aliasMap[command]] || defaultCommand
168     const currentContext = yargs.getContext()
169     let numFiles = currentContext.files.length
170     const parentCommands = currentContext.commands.slice()
171
172     // what does yargs look like after the buidler is run?
173     let innerArgv = parsed.argv
174     let innerYargs = null
175     let positionalMap = {}
176     if (command) {
177       currentContext.commands.push(command)
178       currentContext.fullCommands.push(commandHandler.original)
179     }
180     if (typeof commandHandler.builder === 'function') {
181       // a function can be provided, which builds
182       // up a yargs chain and possibly returns it.
183       innerYargs = commandHandler.builder(yargs.reset(parsed.aliases))
184       // if the builder function did not yet parse argv with reset yargs
185       // and did not explicitly set a usage() string, then apply the
186       // original command string as usage() for consistent behavior with
187       // options object below.
188       if (yargs.parsed === false) {
189         if (shouldUpdateUsage(yargs)) {
190           yargs.getUsageInstance().usage(
191             usageFromParentCommandsCommandHandler(parentCommands, commandHandler),
192             commandHandler.description
193           )
194         }
195         innerArgv = innerYargs ? innerYargs._parseArgs(null, null, true, commandIndex) : yargs._parseArgs(null, null, true, commandIndex)
196       } else {
197         innerArgv = yargs.parsed.argv
198       }
199
200       if (innerYargs && yargs.parsed === false) aliases = innerYargs.parsed.aliases
201       else aliases = yargs.parsed.aliases
202     } else if (typeof commandHandler.builder === 'object') {
203       // as a short hand, an object can instead be provided, specifying
204       // the options that a command takes.
205       innerYargs = yargs.reset(parsed.aliases)
206       if (shouldUpdateUsage(innerYargs)) {
207         innerYargs.getUsageInstance().usage(
208           usageFromParentCommandsCommandHandler(parentCommands, commandHandler),
209           commandHandler.description
210         )
211       }
212       Object.keys(commandHandler.builder).forEach((key) => {
213         innerYargs.option(key, commandHandler.builder[key])
214       })
215       innerArgv = innerYargs._parseArgs(null, null, true, commandIndex)
216       aliases = innerYargs.parsed.aliases
217     }
218
219     if (!yargs._hasOutput()) {
220       positionalMap = populatePositionals(commandHandler, innerArgv, currentContext, yargs)
221     }
222
223     // we apply validation post-hoc, so that custom
224     // checks get passed populated positional arguments.
225     if (!yargs._hasOutput()) yargs._runValidation(innerArgv, aliases, positionalMap, yargs.parsed.error)
226
227     if (commandHandler.handler && !yargs._hasOutput()) {
228       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       }
235       const handlerResult = commandHandler.handler(innerArgv)
236       if (handlerResult && typeof handlerResult.then === 'function') {
237         handlerResult.then(
238           null,
239           (error) => yargs.getUsageInstance().fail(null, error)
240         )
241       }
242     }
243
244     if (command) {
245       currentContext.commands.pop()
246       currentContext.fullCommands.pop()
247     }
248     numFiles = currentContext.files.length - numFiles
249     if (numFiles > 0) currentContext.files.splice(numFiles * -1, numFiles)
250
251     return innerArgv
252   }
253
254   function shouldUpdateUsage (yargs) {
255     return !yargs.getUsageInstance().getUsageDisabled() &&
256       yargs.getUsageInstance().getUsage().length === 0
257   }
258
259   function usageFromParentCommandsCommandHandler (parentCommands, commandHandler) {
260     const c = DEFAULT_MARKER.test(commandHandler.original) ? commandHandler.original.replace(DEFAULT_MARKER, '').trim() : commandHandler.original
261     const pc = parentCommands.filter((c) => { return !DEFAULT_MARKER.test(c) })
262     pc.push(c)
263     return `$0 ${pc.join(' ')}`
264   }
265
266   self.runDefaultBuilderOn = function (yargs) {
267     if (shouldUpdateUsage(yargs)) {
268       // build the root-level command string from the default string.
269       const commandString = DEFAULT_MARKER.test(defaultCommand.original)
270         ? defaultCommand.original : defaultCommand.original.replace(/^[^[\]<>]*/, '$0 ')
271       yargs.getUsageInstance().usage(
272         commandString,
273         defaultCommand.description
274       )
275     }
276     const builder = defaultCommand.builder
277     if (typeof builder === 'function') {
278       builder(yargs)
279     } else {
280       Object.keys(builder).forEach((key) => {
281         yargs.option(key, builder[key])
282       })
283     }
284   }
285
286   // transcribe all positional arguments "command <foo> <bar> [apple]"
287   // onto argv.
288   function populatePositionals (commandHandler, argv, context, yargs) {
289     argv._ = argv._.slice(context.commands.length) // nuke the current commands
290     const demanded = commandHandler.demanded.slice(0)
291     const optional = commandHandler.optional.slice(0)
292     const positionalMap = {}
293
294     validation.positionalCount(demanded.length, argv._.length)
295
296     while (demanded.length) {
297       const demand = demanded.shift()
298       populatePositional(demand, argv, positionalMap)
299     }
300
301     while (optional.length) {
302       const maybe = optional.shift()
303       populatePositional(maybe, argv, positionalMap)
304     }
305
306     argv._ = context.commands.concat(argv._)
307
308     postProcessPositionals(argv, positionalMap, self.cmdToParseOptions(commandHandler.original))
309
310     return positionalMap
311   }
312
313   function populatePositional (positional, argv, positionalMap, parseOptions) {
314     const cmd = positional.cmd[0]
315     if (positional.variadic) {
316       positionalMap[cmd] = argv._.splice(0).map(String)
317     } else {
318       if (argv._.length) positionalMap[cmd] = [String(argv._.shift())]
319     }
320   }
321
322   // we run yargs-parser against the positional arguments
323   // applying the same parsing logic used for flags.
324   function postProcessPositionals (argv, positionalMap, parseOptions) {
325     // combine the parsing hints we've inferred from the command
326     // string with explicitly configured parsing hints.
327     const options = Object.assign({}, yargs.getOptions())
328     options.default = Object.assign(parseOptions.default, options.default)
329     options.alias = Object.assign(parseOptions.alias, options.alias)
330     options.array = options.array.concat(parseOptions.array)
331
332     const unparsed = []
333     Object.keys(positionalMap).forEach((key) => {
334       positionalMap[key].map((value) => {
335         unparsed.push(`--${key}`)
336         unparsed.push(value)
337       })
338     })
339
340     // short-circuit parse.
341     if (!unparsed.length) return
342
343     const parsed = Parser.detailed(unparsed, options)
344
345     if (parsed.error) {
346       yargs.getUsageInstance().fail(parsed.error.message, parsed.error)
347     } else {
348       // only copy over positional keys (don't overwrite
349       // flag arguments that were already parsed).
350       const positionalKeys = Object.keys(positionalMap)
351       Object.keys(positionalMap).forEach((key) => {
352         [].push.apply(positionalKeys, parsed.aliases[key])
353       })
354
355       Object.keys(parsed.argv).forEach((key) => {
356         if (positionalKeys.indexOf(key) !== -1) {
357           argv[key] = parsed.argv[key]
358         }
359       })
360     }
361   }
362
363   self.cmdToParseOptions = function (cmdString) {
364     const parseOptions = {
365       array: [],
366       default: {},
367       alias: {},
368       demand: {}
369     }
370
371     const parsed = self.parseCommand(cmdString)
372     parsed.demanded.forEach((d) => {
373       const cmds = d.cmd.slice(0)
374       const cmd = cmds.shift()
375       if (d.variadic) {
376         parseOptions.array.push(cmd)
377         parseOptions.default[cmd] = []
378       }
379       cmds.forEach((c) => {
380         parseOptions.alias[cmd] = c
381       })
382       parseOptions.demand[cmd] = true
383     })
384
385     parsed.optional.forEach((o) => {
386       const cmds = o.cmd.slice(0)
387       const cmd = cmds.shift()
388       if (o.variadic) {
389         parseOptions.array.push(cmd)
390         parseOptions.default[cmd] = []
391       }
392       cmds.forEach((c) => {
393         parseOptions.alias[cmd] = c
394       })
395     })
396
397     return parseOptions
398   }
399
400   self.reset = () => {
401     handlers = {}
402     aliasMap = {}
403     defaultCommand = undefined
404     return self
405   }
406
407   // used by yargs.parse() to freeze
408   // the state of commands such that
409   // we can apply .parse() multiple times
410   // with the same yargs instance.
411   let frozen
412   self.freeze = () => {
413     frozen = {}
414     frozen.handlers = handlers
415     frozen.aliasMap = aliasMap
416     frozen.defaultCommand = defaultCommand
417   }
418   self.unfreeze = () => {
419     handlers = frozen.handlers
420     aliasMap = frozen.aliasMap
421     defaultCommand = frozen.defaultCommand
422     frozen = undefined
423   }
424
425   return self
426 }