Initial commit
[yaffs-website] / node_modules / yargs / lib / command.js
1 const path = require('path')
2 const inspect = require('util').inspect
3 const requireDirectory = require('require-directory')
4 const whichModule = require('which-module')
5
6 // handles parsing positional arguments,
7 // and populating argv with said positional
8 // arguments.
9 module.exports = function (yargs, usage, validation) {
10   const self = {}
11
12   var handlers = {}
13   self.addHandler = function (cmd, description, builder, handler) {
14     if (typeof cmd === 'object') {
15       const commandString = typeof cmd.command === 'string' ? cmd.command : moduleName(cmd)
16       self.addHandler(commandString, extractDesc(cmd), cmd.builder, cmd.handler)
17       return
18     }
19
20     // allow a module to be provided instead of separate builder and handler
21     if (typeof builder === 'object' && builder.builder && typeof builder.handler === 'function') {
22       self.addHandler(cmd, description, builder.builder, builder.handler)
23       return
24     }
25
26     if (description !== false) {
27       usage.command(cmd, description)
28     }
29
30     // we should not register a handler if no
31     // builder is provided, e.g., user will
32     // handle command themselves with '_'.
33     var parsedCommand = parseCommand(cmd)
34     handlers[parsedCommand.cmd] = {
35       original: cmd,
36       handler: handler,
37       // TODO: default to a noop builder in
38       // yargs@5.x
39       builder: builder,
40       demanded: parsedCommand.demanded,
41       optional: parsedCommand.optional
42     }
43   }
44
45   self.addDirectory = function (dir, context, req, callerFile, opts) {
46     opts = opts || {}
47     // disable recursion to support nested directories of subcommands
48     if (typeof opts.recurse !== 'boolean') opts.recurse = false
49     // exclude 'json', 'coffee' from require-directory defaults
50     if (!Array.isArray(opts.extensions)) opts.extensions = ['js']
51     // allow consumer to define their own visitor function
52     const parentVisit = typeof opts.visit === 'function' ? opts.visit : function (o) { return o }
53     // call addHandler via visitor function
54     opts.visit = function (obj, joined, filename) {
55       const visited = parentVisit(obj, joined, filename)
56       // allow consumer to skip modules with their own visitor
57       if (visited) {
58         // check for cyclic reference
59         // each command file path should only be seen once per execution
60         if (~context.files.indexOf(joined)) return visited
61         // keep track of visited files in context.files
62         context.files.push(joined)
63         self.addHandler(visited)
64       }
65       return visited
66     }
67     requireDirectory({ require: req, filename: callerFile }, dir, opts)
68   }
69
70   // lookup module object from require()d command and derive name
71   // if module was not require()d and no name given, throw error
72   function moduleName (obj) {
73     const mod = whichModule(obj)
74     if (!mod) throw new Error('No command name given for module: ' + inspect(obj))
75     return commandFromFilename(mod.filename)
76   }
77
78   // derive command name from filename
79   function commandFromFilename (filename) {
80     return path.basename(filename, path.extname(filename))
81   }
82
83   function extractDesc (obj) {
84     for (var keys = ['describe', 'description', 'desc'], i = 0, l = keys.length, test; i < l; i++) {
85       test = obj[keys[i]]
86       if (typeof test === 'string' || typeof test === 'boolean') return test
87     }
88     return false
89   }
90
91   function parseCommand (cmd) {
92     var splitCommand = cmd.split(/\s/)
93     var bregex = /\.*[\][<>]/g
94     var parsedCommand = {
95       cmd: (splitCommand.shift()).replace(bregex, ''),
96       demanded: [],
97       optional: []
98     }
99     splitCommand.forEach(function (cmd, i) {
100       var variadic = false
101       if (/\.+[\]>]/.test(cmd) && i === splitCommand.length - 1) variadic = true
102       if (/^\[/.test(cmd)) {
103         parsedCommand.optional.push({
104           cmd: cmd.replace(bregex, ''),
105           variadic: variadic
106         })
107       } else {
108         parsedCommand.demanded.push({
109           cmd: cmd.replace(bregex, ''),
110           variadic: variadic
111         })
112       }
113     })
114     return parsedCommand
115   }
116
117   self.getCommands = function () {
118     return Object.keys(handlers)
119   }
120
121   self.getCommandHandlers = function () {
122     return handlers
123   }
124
125   self.runCommand = function (command, yargs, parsed) {
126     var argv = parsed.argv
127     var commandHandler = handlers[command]
128     var innerArgv = argv
129     var currentContext = yargs.getContext()
130     var parentCommands = currentContext.commands.slice()
131     currentContext.commands.push(command)
132     if (commandHandler.builder && typeof commandHandler.builder === 'function') {
133       // a function can be provided, which interacts which builds
134       // up a yargs chain and returns it.
135       innerArgv = commandHandler.builder(yargs.reset(parsed.aliases))
136       // if the builder function did not yet parse argv with reset yargs
137       // and did not explicitly set a usage() string, then apply the
138       // original command string as usage() for consistent behavior with
139       // options object below
140       if (yargs.parsed === false && typeof yargs.getUsageInstance().getUsage() === 'undefined') {
141         yargs.usage('$0 ' + (parentCommands.length ? parentCommands.join(' ') + ' ' : '') + commandHandler.original)
142       }
143       innerArgv = innerArgv ? innerArgv.argv : argv
144     } else if (commandHandler.builder && typeof commandHandler.builder === 'object') {
145       // as a short hand, an object can instead be provided, specifying
146       // the options that a command takes.
147       innerArgv = yargs.reset(parsed.aliases)
148       innerArgv.usage('$0 ' + (parentCommands.length ? parentCommands.join(' ') + ' ' : '') + commandHandler.original)
149       Object.keys(commandHandler.builder).forEach(function (key) {
150         innerArgv.option(key, commandHandler.builder[key])
151       })
152       innerArgv = innerArgv.argv
153     }
154
155     populatePositional(commandHandler, innerArgv, currentContext)
156
157     if (commandHandler.handler) {
158       commandHandler.handler(innerArgv)
159     }
160     currentContext.commands.pop()
161     return innerArgv
162   }
163
164   function populatePositional (commandHandler, argv, context) {
165     argv._ = argv._.slice(context.commands.length) // nuke the current commands
166     var demanded = commandHandler.demanded.slice(0)
167     var optional = commandHandler.optional.slice(0)
168
169     validation.positionalCount(demanded.length, argv._.length)
170
171     while (demanded.length) {
172       var demand = demanded.shift()
173       if (demand.variadic) argv[demand.cmd] = []
174       if (!argv._.length) break
175       if (demand.variadic) argv[demand.cmd] = argv._.splice(0)
176       else argv[demand.cmd] = argv._.shift()
177     }
178
179     while (optional.length) {
180       var maybe = optional.shift()
181       if (maybe.variadic) argv[maybe.cmd] = []
182       if (!argv._.length) break
183       if (maybe.variadic) argv[maybe.cmd] = argv._.splice(0)
184       else argv[maybe.cmd] = argv._.shift()
185     }
186
187     argv._ = context.commands.concat(argv._)
188   }
189
190   self.reset = function () {
191     handlers = {}
192     return self
193   }
194
195   return self
196 }