Welcome to mirror list, hosted at ThFree Co, Russian Federation.

command.js « lib « yargs « node_modules « cli « @iarna « node_modules - github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 3567cf95329d1aced41fa5cb7e093a3820def9de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
const path = require('path')
const inspect = require('util').inspect
const camelCase = require('camelcase')

const DEFAULT_MARKER = '*'

// handles parsing positional arguments,
// and populating argv with said positional
// arguments.
module.exports = function (yargs, usage, validation) {
  const self = {}

  var handlers = {}
  var aliasMap = {}
  var defaultCommand
  self.addHandler = function (cmd, description, builder, handler) {
    var aliases = []
    handler = handler || function () {}

    if (Array.isArray(cmd)) {
      aliases = cmd.slice(1)
      cmd = cmd[0]
    } else if (typeof cmd === 'object') {
      var 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)
      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)
      return
    }

    // parse positionals out of cmd string
    var parsedCommand = self.parseCommand(cmd)

    // remove positional args from aliases only
    aliases = aliases.map(function (alias) {
      return self.parseCommand(alias).cmd
    })

    // check for default and filter out '*''
    var isDefault = false
    var parsedAliases = [parsedCommand.cmd].concat(aliases).filter(function (c) {
      if (c === DEFAULT_MARKER) {
        isDefault = true
        return false
      }
      return true
    })

    // short-circuit if default with no aliases
    if (isDefault && parsedAliases.length === 0) {
      defaultCommand = {
        original: cmd.replace(DEFAULT_MARKER, '').trim(),
        handler: handler,
        builder: builder || {},
        demanded: parsedCommand.demanded,
        optional: parsedCommand.optional
      }
      return
    }

    // 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(function (alias) {
      aliasMap[alias] = parsedCommand.cmd
    })

    if (description !== false) {
      usage.command(cmd, description, isDefault, aliases)
    }

    handlers[parsedCommand.cmd] = {
      original: cmd,
      handler: handler,
      builder: builder || {},
      demanded: parsedCommand.demanded,
      optional: parsedCommand.optional
    }

    if (isDefault) defaultCommand = handlers[parsedCommand.cmd]
  }

  self.addDirectory = function (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 : function (o) { return o }
    // call addHandler via visitor function
    opts.visit = function (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 (var 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 (cmd) {
    var extraSpacesStrippedCommand = cmd.replace(/\s{2,}/g, ' ')
    var splitCommand = extraSpacesStrippedCommand.split(/\s+(?![^[]*]|[^<]*>)/)
    var bregex = /\.*[\][<>]/g
    var parsedCommand = {
      cmd: (splitCommand.shift()).replace(bregex, ''),
      demanded: [],
      optional: []
    }
    splitCommand.forEach(function (cmd, i) {
      var 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: variadic
        })
      } else {
        parsedCommand.demanded.push({
          cmd: cmd.replace(bregex, '').split('|'),
          variadic: variadic
        })
      }
    })
    return parsedCommand
  }

  self.getCommands = function () {
    return Object.keys(handlers).concat(Object.keys(aliasMap))
  }

  self.getCommandHandlers = function () {
    return handlers
  }

  self.hasDefaultCommand = function () {
    return !!defaultCommand
  }

  self.runCommand = function (command, yargs, parsed, commandIndex) {
    var aliases = parsed.aliases
    var commandHandler = handlers[command] || handlers[aliasMap[command]] || defaultCommand
    var currentContext = yargs.getContext()
    var numFiles = currentContext.files.length
    var parentCommands = currentContext.commands.slice()

    // what does yargs look like after the buidler is run?
    var innerArgv = parsed.argv
    var innerYargs = null
    var positionalMap = {}

    if (command) currentContext.commands.push(command)
    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 (typeof yargs.getUsageInstance().getUsage() === 'undefined') {
          yargs.usage('$0 ' + (parentCommands.length ? parentCommands.join(' ') + ' ' : '') + commandHandler.original)
        }
        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)
      innerYargs.usage('$0 ' + (parentCommands.length ? parentCommands.join(' ') + ' ' : '') + commandHandler.original)
      Object.keys(commandHandler.builder).forEach(function (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)
    }

    // 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()
      commandHandler.handler(innerArgv)
    }

    if (command) currentContext.commands.pop()
    numFiles = currentContext.files.length - numFiles
    if (numFiles > 0) currentContext.files.splice(numFiles * -1, numFiles)

    return innerArgv
  }

  // 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
    var demanded = commandHandler.demanded.slice(0)
    var optional = commandHandler.optional.slice(0)
    var positionalMap = {}

    validation.positionalCount(demanded.length, argv._.length)

    while (demanded.length) {
      var demand = demanded.shift()
      populatePositional(demand, argv, yargs, positionalMap)
    }

    while (optional.length) {
      var maybe = optional.shift()
      populatePositional(maybe, argv, yargs, positionalMap)
    }

    argv._ = context.commands.concat(argv._)
    return positionalMap
  }

  // populate a single positional argument and its
  // aliases onto argv.
  function populatePositional (positional, argv, yargs, positionalMap) {
    // "positional" consists of the positional.cmd, an array representing
    // the positional's name and aliases, and positional.variadic
    // indicating whether or not it is a variadic array.
    var variadics = null
    var value = null
    for (var i = 0, cmd; (cmd = positional.cmd[i]) !== undefined; i++) {
      if (positional.variadic) {
        if (variadics) argv[cmd] = variadics.slice(0)
        else argv[cmd] = variadics = argv._.splice(0)
      } else {
        if (!value && !argv._.length) continue
        if (value) argv[cmd] = value
        else argv[cmd] = value = argv._.shift()
      }
      positionalMap[cmd] = true
      postProcessPositional(yargs, argv, cmd)
      addCamelCaseExpansions(argv, cmd)
    }
  }

  // TODO move positional arg logic to yargs-parser and remove this duplication
  function postProcessPositional (yargs, argv, key) {
    var coerce = yargs.getOptions().coerce[key]
    if (typeof coerce === 'function') {
      try {
        argv[key] = coerce(argv[key])
      } catch (err) {
        yargs.getUsageInstance().fail(err.message, err)
      }
    }
  }

  function addCamelCaseExpansions (argv, option) {
    if (/-/.test(option)) {
      const cc = camelCase(option)
      if (typeof argv[option] === 'object') argv[cc] = argv[option].slice(0)
      else argv[cc] = argv[option]
    }
  }

  self.reset = function () {
    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.
  var frozen
  self.freeze = function () {
    frozen = {}
    frozen.handlers = handlers
    frozen.aliasMap = aliasMap
    frozen.defaultCommand = defaultCommand
  }
  self.unfreeze = function () {
    handlers = frozen.handlers
    aliasMap = frozen.aliasMap
    defaultCommand = frozen.defaultCommand
    frozen = undefined
  }

  return self
}