coa 0.4.1 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
package/src/arg.coffee DELETED
@@ -1,130 +0,0 @@
1
- Color = require('./color').Color
2
- Cmd = require('./cmd').Cmd
3
- Opt = require('./opt').Opt
4
-
5
- ###*
6
- Argument
7
-
8
- Unnamed entity. From command line arguments passed as list of unnamed values.
9
- @namespace
10
- @class Presents argument
11
- ###
12
- exports.Arg = class Arg
13
-
14
- ###*
15
- @constructs
16
- @param {COA.Cmd} cmd parent command
17
- ###
18
- constructor: (@_cmd) -> @_cmd._args.push @
19
-
20
- ###*
21
- Set a canonical argument identifier to be used anywhere in text messages.
22
- @param {String} _name argument name
23
- @returns {COA.Arg} this instance (for chainability)
24
- ###
25
- name: Opt::name
26
-
27
- ###*
28
- Set a long description for argument to be used anywhere in text messages.
29
- @param {String} _title argument title
30
- @returns {COA.Arg} this instance (for chainability)
31
- ###
32
- title: Cmd::title
33
-
34
- ###*
35
- Makes an argument accepts multiple values.
36
- Otherwise, the value will be used by the latter passed.
37
- @returns {COA.Arg} this instance (for chainability)
38
- ###
39
- arr: Opt::arr
40
-
41
- ###*
42
- Makes an argument required.
43
- @returns {COA.Arg} this instance (for chainability)
44
- ###
45
- req: Opt::req
46
-
47
- ###*
48
- Set a validation (or value) function for argument.
49
- Value from command line passes through before becoming available from API.
50
- Using for validation and convertion simple types to any values.
51
- @param {Function} _val validating function,
52
- invoked in the context of argument instance
53
- and has one parameter with value from command line
54
- @returns {COA.Arg} this instance (for chainability)
55
- ###
56
- val: Opt::val
57
-
58
- ###*
59
- Set a default value for argument.
60
- Default value passed through validation function as ordinary value.
61
- @param {Object} _def
62
- @returns {COA.Arg} this instance (for chainability)
63
- ###
64
- def: Opt::def
65
-
66
- ###*
67
- Set custom additional completion for current argument.
68
- @param {Function} completion generation function,
69
- invoked in the context of argument instance.
70
- Accepts parameters:
71
- - {Object} opts completion options
72
- It can return promise or any other value treated as result.
73
- @returns {COA.Arg} this instance (for chainability)
74
- ###
75
- comp: Cmd::comp
76
-
77
- ###*
78
- Make argument value inputting stream.
79
- It's add useful validation and shortcut for STDIN.
80
- @returns {COA.Arg} this instance (for chainability)
81
- ###
82
- input: Opt::input
83
-
84
- ###*
85
- Make argument value outputing stream.
86
- It's add useful validation and shortcut for STDOUT.
87
- @returns {COA.Arg} this instance (for chainability)
88
- ###
89
- output: Opt::output
90
-
91
- _parse: (arg, args) ->
92
- @_saveVal(args, arg)
93
-
94
- _saveVal: Opt::_saveVal
95
-
96
- _checkParsed: (opts, args) -> not args.hasOwnProperty(@_name)
97
-
98
- _usage: ->
99
- res = []
100
-
101
- res.push Color('lpurple', @_name.toUpperCase()), ' : ', @_title
102
- if @_req then res.push ' ', Color('lred', '(required)')
103
-
104
- res.join ''
105
-
106
- _requiredText: -> 'Missing required argument:\n ' + @_usage()
107
-
108
- ###*
109
- Return rejected promise with error code.
110
- Use in .val() for return with error.
111
- @param {Object} reject reason
112
- You can customize toString() method and exitCode property
113
- of reason object.
114
- @returns {Q.promise} rejected promise
115
- ###
116
- reject: Cmd::reject
117
-
118
- ###*
119
- Finish chain for current option and return parent command instance.
120
- @returns {COA.Cmd} parent command
121
- ###
122
- end: Cmd::end
123
-
124
- ###*
125
- Apply function with arguments in context of arg instance.
126
- @param {Function} fn
127
- @param {Array} args
128
- @returns {COA.Arg} this instance (for chainability)
129
- ###
130
- apply: Cmd::apply
package/src/cmd.coffee DELETED
@@ -1,456 +0,0 @@
1
- UTIL = require 'util'
2
- PATH = require 'path'
3
- Color = require('./color').Color
4
- Q = require('q')
5
-
6
- #inspect = require('eyes').inspector { maxLength: 99999, stream: process.stderr }
7
-
8
- ###*
9
- Command
10
-
11
- Top level entity. Commands may have options and arguments.
12
- @namespace
13
- @class Presents command
14
- ###
15
- exports.Cmd = class Cmd
16
-
17
- ###*
18
- @constructs
19
- @param {COA.Cmd} [cmd] parent command
20
- ###
21
- constructor: (cmd) ->
22
- if this not instanceof Cmd
23
- return new Cmd cmd
24
-
25
- @_parent cmd
26
-
27
- @_cmds = []
28
- @_cmdsByName = {}
29
-
30
- @_opts = []
31
- @_optsByKey = {}
32
-
33
- @_args = []
34
-
35
- @_ext = false
36
-
37
- @get: (propertyName, func) ->
38
- Object.defineProperty @::, propertyName,
39
- configurable: true
40
- enumerable: true
41
- get: func
42
-
43
- ###*
44
- Returns object containing all its subcommands as methods
45
- to use from other programs.
46
- @returns {Object}
47
- ###
48
- @get 'api', () ->
49
- if not @_api
50
- @_api = => @invoke.apply @, arguments
51
- for c of @_cmdsByName
52
- do (c) =>
53
- @_api[c] = @_cmdsByName[c].api
54
- @_api
55
-
56
- _parent: (cmd) ->
57
- @_cmd = cmd or this
58
- if cmd
59
- cmd._cmds.push @
60
- if @_name then @_cmd._cmdsByName[@_name] = @
61
- @
62
-
63
- ###*
64
- Set a canonical command identifier to be used anywhere in the API.
65
- @param {String} _name command name
66
- @returns {COA.Cmd} this instance (for chainability)
67
- ###
68
- name: (@_name) ->
69
- if @_cmd isnt @ then @_cmd._cmdsByName[_name] = @
70
- @
71
-
72
- ###*
73
- Set a long description for command to be used anywhere in text messages.
74
- @param {String} _title command title
75
- @returns {COA.Cmd} this instance (for chainability)
76
- ###
77
- title: (@_title) -> @
78
-
79
- ###*
80
- Create new or add existing subcommand for current command.
81
- @param {COA.Cmd} [cmd] existing command instance
82
- @returns {COA.Cmd} new subcommand instance
83
- ###
84
- cmd: (cmd) ->
85
- if cmd then cmd._parent @
86
- else new Cmd @
87
-
88
- ###*
89
- Create option for current command.
90
- @returns {COA.Opt} new option instance
91
- ###
92
- opt: -> new (require('./opt').Opt) @
93
-
94
- ###*
95
- Create argument for current command.
96
- @returns {COA.Opt} new argument instance
97
- ###
98
- arg: -> new (require('./arg').Arg) @
99
-
100
- ###*
101
- Add (or set) action for current command.
102
- @param {Function} act action function,
103
- invoked in the context of command instance
104
- and has the parameters:
105
- - {Object} opts parsed options
106
- - {Array} args parsed arguments
107
- - {Object} res actions result accumulator
108
- It can return rejected promise by Cmd.reject (in case of error)
109
- or any other value treated as result.
110
- @param {Boolean} [force=false] flag for set action instead add to existings
111
- @returns {COA.Cmd} this instance (for chainability)
112
- ###
113
- act: (act, force) ->
114
- return @ unless act
115
-
116
- if not force and @_act
117
- @_act.push act
118
- else
119
- @_act = [act]
120
-
121
- @
122
-
123
- ###*
124
- Set custom additional completion for current command.
125
- @param {Function} completion generation function,
126
- invoked in the context of command instance.
127
- Accepts parameters:
128
- - {Object} opts completion options
129
- It can return promise or any other value treated as result.
130
- @returns {COA.Cmd} this instance (for chainability)
131
- ###
132
- comp: (@_comp) -> @
133
-
134
- ###*
135
- Apply function with arguments in context of command instance.
136
- @param {Function} fn
137
- @param {Array} args
138
- @returns {COA.Cmd} this instance (for chainability)
139
- ###
140
- apply: (fn, args...) ->
141
- fn.apply this, args
142
- @
143
-
144
- ###*
145
- Make command "helpful", i.e. add -h --help flags for print usage.
146
- @returns {COA.Cmd} this instance (for chainability)
147
- ###
148
- helpful: ->
149
- @opt()
150
- .name('help').title('Help')
151
- .short('h').long('help')
152
- .flag()
153
- .only()
154
- .act ->
155
- return @usage()
156
- .end()
157
-
158
- ###*
159
- Adds shell completion to command, adds "completion" subcommand,
160
- that makes all the magic.
161
- Must be called only on root command.
162
- @returns {COA.Cmd} this instance (for chainability)
163
- ###
164
- completable: ->
165
- @cmd()
166
- .name('completion')
167
- .apply(require './completion')
168
- .end()
169
-
170
- ###*
171
- Allow command to be extendable by external node.js modules.
172
- @param {String} [pattern] Pattern of node.js module to find subcommands at.
173
- @returns {COA.Cmd} this instance (for chainability)
174
- ###
175
- extendable: (pattern) ->
176
- @_ext = pattern or true
177
- @
178
-
179
- _exit: (msg, code) ->
180
- process.once 'exit', ->
181
- if msg then UTIL.error msg
182
- process.exit code or 0
183
-
184
- ###*
185
- Build full usage text for current command instance.
186
- @returns {String} usage text
187
- ###
188
- usage: ->
189
- res = []
190
-
191
- if @_title then res.push @_fullTitle()
192
-
193
- res.push('', 'Usage:')
194
-
195
- if @_cmds.length then res.push(['', '',
196
- Color('lred', @_fullName()),
197
- Color('lblue', 'COMMAND'),
198
- Color('lgreen', '[OPTIONS]'),
199
- Color('lpurple', '[ARGS]')].join ' ')
200
-
201
- if @_opts.length + @_args.length then res.push(['', '',
202
- Color('lred', @_fullName()),
203
- Color('lgreen', '[OPTIONS]'),
204
- Color('lpurple', '[ARGS]')].join ' ')
205
-
206
- res.push(
207
- @_usages(@_cmds, 'Commands'),
208
- @_usages(@_opts, 'Options'),
209
- @_usages(@_args, 'Arguments'))
210
-
211
- res.join '\n'
212
-
213
- _usage: ->
214
- Color('lblue', @_name) + ' : ' + @_title
215
-
216
- _usages: (os, title) ->
217
- unless os.length then return
218
- res = ['', title + ':']
219
- for o in os
220
- res.push ' ' + o._usage()
221
- res.join '\n'
222
-
223
- _fullTitle: ->
224
- (if @_cmd is this then '' else @_cmd._fullTitle() + '\n') + @_title
225
-
226
- _fullName: ->
227
- (if this._cmd is this then '' else @_cmd._fullName() + ' ') + PATH.basename(@_name)
228
-
229
- _ejectOpt: (opts, opt) ->
230
- if (pos = opts.indexOf(opt)) >= 0
231
- if opts[pos]._arr
232
- opts[pos]
233
- else
234
- opts.splice(pos, 1)[0]
235
-
236
- _checkRequired: (opts, args) ->
237
- if not (@_opts.filter (o) -> o._only and o._name of opts).length
238
- all = @_opts.concat @_args
239
- while i = all.shift()
240
- if i._req and i._checkParsed opts, args
241
- return @reject i._requiredText()
242
-
243
- _parseCmd: (argv, unparsed = []) ->
244
- argv = argv.concat()
245
- optSeen = false
246
- while i = argv.shift()
247
- if not i.indexOf '-'
248
- optSeen = true
249
- if not optSeen and /^\w[\w-_]*$/.test(i)
250
- cmd = @_cmdsByName[i]
251
-
252
- if not cmd and @_ext
253
- # construct package name to require
254
- if typeof @_ext is 'string'
255
- if ~@_ext.indexOf('%s')
256
- # use formatted string
257
- pkg = UTIL.format(@_ext, i)
258
- else
259
- # just append subcommand name to the prefix
260
- pkg = @_ext + i
261
- else if @_ext is true
262
- # use default scheme: <command>-<subcommand>-<subcommand> and so on
263
- pkg = i
264
- c = @
265
- loop
266
- pkg = c._name + '-' + pkg
267
- if c._cmd is c then break
268
- c = c._cmd
269
-
270
- try
271
- cmdDesc = require(pkg)
272
- catch e
273
-
274
- if cmdDesc
275
- if typeof cmdDesc == 'function'
276
- # set create subcommand, set its name and apply imported function
277
- @cmd()
278
- .name(i)
279
- .apply(cmdDesc)
280
- .end()
281
- else if typeof cmdDesc == 'object'
282
- # register subcommand
283
- @cmd(cmdDesc)
284
- # set command name
285
- cmdDesc.name(i)
286
- else
287
- throw new Error 'Error: Unsupported command declaration type, ' +
288
- 'should be function or COA.Cmd() object'
289
- cmd = @_cmdsByName[i]
290
- if cmd
291
- return cmd._parseCmd argv, unparsed
292
-
293
- unparsed.push i
294
-
295
- { cmd: @, argv: unparsed }
296
-
297
- _parseOptsAndArgs: (argv) ->
298
- opts = {}
299
- args = {}
300
-
301
- nonParsedOpts = @_opts.concat()
302
- nonParsedArgs = @_args.concat()
303
-
304
- while i = argv.shift()
305
- # opt
306
- if i isnt '--' and not i.indexOf '-'
307
-
308
- if m = i.match /^(--\w[\w-_]*)=(.*)$/
309
- i = m[1]
310
-
311
- # suppress 'unknown argument' error for flag options with values
312
- if not @_optsByKey[i]._flag
313
- argv.unshift m[2]
314
-
315
- if opt = @_ejectOpt nonParsedOpts, @_optsByKey[i]
316
- if Q.isRejected(res = opt._parse argv, opts)
317
- return res
318
- else
319
- return @reject "Unknown option: #{ i }"
320
-
321
- # arg
322
- else
323
- if i is '--'
324
- i = argv.splice(0)
325
-
326
- i = if Array.isArray(i) then i else [i]
327
-
328
- while a = i.shift()
329
- if arg = nonParsedArgs.shift()
330
- if arg._arr then nonParsedArgs.unshift arg
331
- if Q.isRejected(res = arg._parse a, args)
332
- return res
333
- else
334
- return @reject "Unknown argument: #{ a }"
335
-
336
- # set defaults
337
- {
338
- opts: @_setDefaults(opts, nonParsedOpts),
339
- args: @_setDefaults(args, nonParsedArgs)
340
- }
341
-
342
- _setDefaults: (params, desc) ->
343
- for i in desc
344
- if i._name not of params and '_def' of i
345
- i._saveVal params, i._def
346
- params
347
-
348
- _processParams: (params, desc) ->
349
- notExists = []
350
- for i in desc
351
- n = i._name
352
- if n not of params
353
- notExists.push i
354
- continue
355
-
356
- vals = params[n]
357
- delete params[n]
358
- if not Array.isArray vals
359
- vals = [vals]
360
-
361
- for v in vals
362
- if Q.isRejected(res = i._saveVal(params, v))
363
- return res
364
-
365
- # set defaults
366
- @_setDefaults params, notExists
367
-
368
- _parseArr: (argv) ->
369
- Q.when @_parseCmd(argv), (p) ->
370
- Q.when p.cmd._parseOptsAndArgs(p.argv), (r) ->
371
- { cmd: p.cmd, opts: r.opts, args: r.args }
372
-
373
- _do: (input) ->
374
- Q.when input, (input) =>
375
- cmd = input.cmd
376
- [@_checkRequired].concat(cmd._act or []).reduce(
377
- (res, act) ->
378
- Q.when res, (res) ->
379
- act.call(
380
- cmd
381
- input.opts
382
- input.args
383
- res)
384
- undefined
385
- )
386
-
387
- ###*
388
- Parse arguments from simple format like NodeJS process.argv
389
- and run ahead current program, i.e. call process.exit when all actions done.
390
- @param {Array} argv
391
- @returns {COA.Cmd} this instance (for chainability)
392
- ###
393
- run: (argv = process.argv.slice(2)) ->
394
- cb = (code) => (res) =>
395
- if res
396
- @_exit res.stack ? res.toString(), res.exitCode ? code
397
- else
398
- @_exit()
399
- Q.when(@do(argv), cb(0), cb(1)).done()
400
- @
401
-
402
- ###*
403
- Convenient function to run command from tests.
404
- @param {Array} argv
405
- @returns {Q.Promise}
406
- ###
407
- do: (argv) ->
408
- @_do(@_parseArr argv || [])
409
-
410
- ###*
411
- Invoke specified (or current) command using provided
412
- options and arguments.
413
- @param {String|Array} cmds subcommand to invoke (optional)
414
- @param {Object} opts command options (optional)
415
- @param {Object} args command arguments (optional)
416
- @returns {Q.Promise}
417
- ###
418
- invoke: (cmds = [], opts = {}, args = {}) ->
419
- if typeof cmds == 'string'
420
- cmds = cmds.split(' ')
421
-
422
- if arguments.length < 3
423
- if not Array.isArray cmds
424
- args = opts
425
- opts = cmds
426
- cmds = []
427
-
428
- Q.when @_parseCmd(cmds), (p) =>
429
- if p.argv.length
430
- return @reject "Unknown command: " + cmds.join ' '
431
-
432
- Q.all([@_processParams(opts, @_opts), @_processParams(args, @_args)])
433
- .spread (opts, args) =>
434
- @_do({ cmd: p.cmd, opts: opts, args: args })
435
- # catch fails from .only() options
436
- .fail (res) =>
437
- if res and res.exitCode is 0
438
- res.toString()
439
- else
440
- @reject(res)
441
-
442
- ###*
443
- Return reject of actions results promise with error code.
444
- Use in .act() for return with error.
445
- @param {Object} reject reason
446
- You can customize toString() method and exitCode property
447
- of reason object.
448
- @returns {Q.promise} rejected promise
449
- ###
450
- reject: (reason) -> Q.reject(reason)
451
-
452
- ###*
453
- Finish chain for current subcommand and return parent command instance.
454
- @returns {COA.Cmd} parent command
455
- ###
456
- end: -> @_cmd
package/src/color.coffee DELETED
@@ -1,25 +0,0 @@
1
- colors =
2
- black: '30'
3
- dgray: '1;30'
4
- red: '31'
5
- lred: '1;31'
6
- green: '32'
7
- lgreen: '1;32'
8
- brown: '33'
9
- yellow: '1;33'
10
- blue: '34'
11
- lblue: '1;34'
12
- purple: '35'
13
- lpurple: '1;35'
14
- cyan: '36'
15
- lcyan: '1;36'
16
- lgray: '37'
17
- white: '1;37'
18
-
19
- exports.Color = (c, str) ->
20
- # Use \x1B instead of \033 because of CoffeeScript 1.3.x compilation error
21
- [
22
- '\x1B[', colors[c], 'm'
23
- str
24
- '\x1B[m'
25
- ].join ''