Blame view

node_modules/coa/src/cmd.coffee 13.5 KB
6a9ffbcc   liuqimichale   地图点击事件
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
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
  UTIL = require 'util'
  PATH = require 'path'
  Color = require('./color').Color
  Q = require('q')
  
  #inspect = require('eyes').inspector { maxLength: 99999, stream: process.stderr }
  
  ###*
  Command
  
  Top level entity. Commands may have options and arguments.
  @namespace
  @class Presents command
  ###
  exports.Cmd = class Cmd
  
      ###*
      @constructs
      @param {COA.Cmd} [cmd] parent command
      ###
      constructor: (cmd) ->
          if this not instanceof Cmd
              return new Cmd cmd
  
          @_parent cmd
  
          @_cmds = []
          @_cmdsByName = {}
  
          @_opts = []
          @_optsByKey = {}
  
          @_args = []
  
          @_ext = false
  
      @get: (propertyName, func) ->
          Object.defineProperty @::, propertyName,
              configurable: true
              enumerable: true
              get: func
  
      ###*
      Returns object containing all its subcommands as methods
      to use from other programs.
      @returns {Object}
      ###
      @get 'api', () ->
          if not @_api
              @_api = => @invoke.apply @, arguments
          for c of @_cmdsByName
              do (c) =>
                  @_api[c] = @_cmdsByName[c].api
          @_api
  
      _parent: (cmd) ->
          @_cmd = cmd or this
          if cmd
              cmd._cmds.push @
              if @_name then @_cmd._cmdsByName[@_name] = @
          @
  
      ###*
      Set a canonical command identifier to be used anywhere in the API.
      @param {String} _name command name
      @returns {COA.Cmd} this instance (for chainability)
      ###
      name: (@_name) ->
          if @_cmd isnt @ then @_cmd._cmdsByName[_name] = @
          @
  
      ###*
      Set a long description for command to be used anywhere in text messages.
      @param {String} _title command title
      @returns {COA.Cmd} this instance (for chainability)
      ###
      title: (@_title) -> @
  
      ###*
      Create new or add existing subcommand for current command.
      @param {COA.Cmd} [cmd] existing command instance
      @returns {COA.Cmd} new subcommand instance
      ###
      cmd: (cmd) ->
          if cmd then cmd._parent @
          else new Cmd @
  
      ###*
      Create option for current command.
      @returns {COA.Opt} new option instance
      ###
      opt: -> new (require('./opt').Opt) @
  
      ###*
      Create argument for current command.
      @returns {COA.Opt} new argument instance
      ###
      arg: -> new (require('./arg').Arg) @
  
      ###*
      Add (or set) action for current command.
      @param {Function} act action function,
          invoked in the context of command instance
          and has the parameters:
              - {Object} opts parsed options
              - {Array} args parsed arguments
              - {Object} res actions result accumulator
          It can return rejected promise by Cmd.reject (in case of error)
          or any other value treated as result.
      @param {Boolean} [force=false] flag for set action instead add to existings
      @returns {COA.Cmd} this instance (for chainability)
      ###
      act: (act, force) ->
          return @ unless act
  
          if not force and @_act
              @_act.push act
          else
              @_act = [act]
  
          @
  
      ###*
      Set custom additional completion for current command.
      @param {Function} completion generation function,
          invoked in the context of command instance.
          Accepts parameters:
              - {Object} opts completion options
          It can return promise or any other value treated as result.
      @returns {COA.Cmd} this instance (for chainability)
      ###
      comp: (@_comp) -> @
  
      ###*
      Apply function with arguments in context of command instance.
      @param {Function} fn
      @param {Array} args
      @returns {COA.Cmd} this instance (for chainability)
      ###
      apply: (fn, args...) ->
          fn.apply this, args
          @
  
      ###*
      Make command "helpful", i.e. add -h --help flags for print usage.
      @returns {COA.Cmd} this instance (for chainability)
      ###
      helpful: ->
          @opt()
              .name('help').title('Help')
              .short('h').long('help')
              .flag()
              .only()
              .act ->
                  return @usage()
              .end()
  
      ###*
      Adds shell completion to command, adds "completion" subcommand,
      that makes all the magic.
      Must be called only on root command.
      @returns {COA.Cmd} this instance (for chainability)
      ###
      completable: ->
          @cmd()
              .name('completion')
              .apply(require './completion')
              .end()
  
      ###*
      Allow command to be extendable by external node.js modules.
      @param {String} [pattern]  Pattern of node.js module to find subcommands at.
      @returns {COA.Cmd} this instance (for chainability)
      ###
      extendable: (pattern) ->
          @_ext = pattern or true
          @
  
      _exit: (msg, code) ->
          process.once 'exit', ->
              if msg then console.error msg
              process.exit code or 0
  
      ###*
      Build full usage text for current command instance.
      @returns {String} usage text
      ###
      usage: ->
          res = []
  
          if @_title then res.push @_fullTitle()
  
          res.push('', 'Usage:')
  
          if @_cmds.length then res.push(['', '',
              Color('lred', @_fullName()),
              Color('lblue', 'COMMAND'),
              Color('lgreen', '[OPTIONS]'),
              Color('lpurple', '[ARGS]')].join ' ')
  
          if @_opts.length + @_args.length then res.push(['', '',
              Color('lred', @_fullName()),
              Color('lgreen', '[OPTIONS]'),
              Color('lpurple', '[ARGS]')].join ' ')
  
          res.push(
              @_usages(@_cmds, 'Commands'),
              @_usages(@_opts, 'Options'),
              @_usages(@_args, 'Arguments'))
  
          res.join '\n'
  
      _usage: ->
          Color('lblue', @_name) + ' : ' + @_title
  
      _usages: (os, title) ->
          unless os.length then return
          res = ['', title + ':']
          for o in os
              res.push '  ' + o._usage()
          res.join '\n'
  
      _fullTitle: ->
          (if @_cmd is this then '' else @_cmd._fullTitle() + '\n') + @_title
  
      _fullName: ->
          (if this._cmd is this then '' else @_cmd._fullName() + ' ') + PATH.basename(@_name)
  
      _ejectOpt: (opts, opt) ->
          if (pos = opts.indexOf(opt)) >= 0
              if opts[pos]._arr
                  opts[pos]
              else
                  opts.splice(pos, 1)[0]
  
      _checkRequired: (opts, args) ->
          if not (@_opts.filter (o) -> o._only and o._name of opts).length
              all = @_opts.concat @_args
              while i = all.shift()
                  if i._req and i._checkParsed opts, args
                      return @reject i._requiredText()
  
      _parseCmd: (argv, unparsed = []) ->
          argv = argv.concat()
          optSeen = false
          while i = argv.shift()
              if not i.indexOf '-'
                  optSeen = true
              if not optSeen and /^\w[\w-_]*$/.test(i)
                  cmd = @_cmdsByName[i]
  
                  if not cmd and @_ext
                      # construct package name to require
                      if typeof @_ext is 'string'
                          if ~@_ext.indexOf('%s')
                              # use formatted string
                              pkg = UTIL.format(@_ext, i)
                          else
                              # just append subcommand name to the prefix
                              pkg = @_ext + i
                      else if @_ext is true
                          # use default scheme: <command>-<subcommand>-<subcommand> and so on
                          pkg = i
                          c = @
                          loop
                              pkg = c._name + '-' + pkg
                              if c._cmd is c then break
                              c = c._cmd
  
                      try
                          cmdDesc = require(pkg)
                      catch e
  
                      if cmdDesc
                          if typeof cmdDesc == 'function'
                              # set create subcommand, set its name and apply imported function
                              @cmd()
                                  .name(i)
                                  .apply(cmdDesc)
                                  .end()
                          else if typeof cmdDesc == 'object'
                              # register subcommand
                              @cmd(cmdDesc)
                              # set command name
                              cmdDesc.name(i)
                          else
                              throw new Error 'Error: Unsupported command declaration type, ' +
                                  'should be function or COA.Cmd() object'
                          cmd = @_cmdsByName[i]
                  if cmd
                      return cmd._parseCmd argv, unparsed
  
              unparsed.push i
  
          { cmd: @, argv: unparsed }
  
      _parseOptsAndArgs: (argv) ->
          opts = {}
          args = {}
  
          nonParsedOpts = @_opts.concat()
          nonParsedArgs = @_args.concat()
  
          while i = argv.shift()
              # opt
              if i isnt '--' and not i.indexOf '-'
  
                  if m = i.match /^(--\w[\w-_]*)=(.*)$/
                      i = m[1]
  
                      # suppress 'unknown argument' error for flag options with values
                      if not @_optsByKey[i]._flag
                          argv.unshift m[2]
  
                  if opt = @_ejectOpt nonParsedOpts, @_optsByKey[i]
                      if Q.isRejected(res = opt._parse argv, opts)
                          return res
                  else
                      return @reject "Unknown option: #{ i }"
  
              # arg
              else
                  if i is '--'
                      i = argv.splice(0)
  
                  i = if Array.isArray(i) then i else [i]
  
                  while a = i.shift()
                      if arg = nonParsedArgs.shift()
                          if arg._arr then nonParsedArgs.unshift arg
                          if Q.isRejected(res = arg._parse a, args)
                              return res
                      else
                          return @reject "Unknown argument: #{ a }"
  
          # set defaults
          {
              opts: @_setDefaults(opts, nonParsedOpts),
              args: @_setDefaults(args, nonParsedArgs)
          }
  
      _setDefaults: (params, desc) ->
          for i in desc
              if i._name not of params and '_def' of i
                  i._saveVal params, i._def
          params
  
      _processParams: (params, desc) ->
          notExists = []
          for i in desc
              n = i._name
              if n not of params
                  notExists.push i
                  continue
  
              vals = params[n]
              delete params[n]
              if not Array.isArray vals
                  vals = [vals]
  
              for v in vals
                  if Q.isRejected(res = i._saveVal(params, v))
                      return res
  
          # set defaults
          @_setDefaults params, notExists
  
      _parseArr: (argv) ->
          Q.when @_parseCmd(argv), (p) ->
              Q.when p.cmd._parseOptsAndArgs(p.argv), (r) ->
                  { cmd: p.cmd, opts: r.opts, args: r.args }
  
      _do: (input) ->
          Q.when input, (input) =>
              cmd = input.cmd
              [@_checkRequired].concat(cmd._act or []).reduce(
                  (res, act) ->
                      Q.when res, (res) ->
                          act.call(
                              cmd
                              input.opts
                              input.args
                              res)
                  undefined
              )
  
      ###*
      Parse arguments from simple format like NodeJS process.argv
      and run ahead current program, i.e. call process.exit when all actions done.
      @param {Array} argv
      @returns {COA.Cmd} this instance (for chainability)
      ###
      run: (argv = process.argv.slice(2)) ->
          cb = (code) => (res) =>
              if res
                  @_exit res.stack ? res.toString(), res.exitCode ? code
              else
                  @_exit()
          Q.when(@do(argv), cb(0), cb(1)).done()
          @
  
      ###*
      Convenient function to run command from tests.
      @param {Array} argv
      @returns {Q.Promise}
      ###
      do: (argv) ->
          @_do(@_parseArr argv || [])
  
      ###*
      Invoke specified (or current) command using provided
      options and arguments.
      @param {String|Array} cmds  subcommand to invoke (optional)
      @param {Object} opts  command options (optional)
      @param {Object} args  command arguments (optional)
      @returns {Q.Promise}
      ###
      invoke: (cmds = [], opts = {}, args = {}) ->
          if typeof cmds == 'string'
              cmds = cmds.split(' ')
  
          if arguments.length < 3
              if not Array.isArray cmds
                  args = opts
                  opts = cmds
                  cmds = []
  
          Q.when @_parseCmd(cmds), (p) =>
              if p.argv.length
                  return @reject "Unknown command: " + cmds.join ' '
  
              Q.all([@_processParams(opts, @_opts), @_processParams(args, @_args)])
                  .spread (opts, args) =>
                      @_do({ cmd: p.cmd, opts: opts, args: args })
                          # catch fails from .only() options
                          .fail (res) =>
                              if res and res.exitCode is 0
                                  res.toString()
                              else
                                  @reject(res)
  
      ###*
      Return reject of actions results promise with error code.
      Use in .act() for return with error.
      @param {Object} reject reason
          You can customize toString() method and exitCode property
          of reason object.
      @returns {Q.promise} rejected promise
      ###
      reject: (reason) -> Q.reject(reason)
  
      ###*
      Finish chain for current subcommand and return parent command instance.
      @returns {COA.Cmd} parent command
      ###
      end: -> @_cmd