coa 0.3.7 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
package/.npmignore CHANGED
@@ -1,3 +1,6 @@
1
1
  .idea
2
2
  *.iml
3
- node_modules
3
+ node_modules/
4
+ !node_modules/coa*.js
5
+ lib-cov/
6
+ html-report/
package/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: node_js
2
+
3
+ node_js:
4
+ - "0.6"
5
+ - "0.8"
6
+ - "0.10"
7
+ - "0.11"
8
+
9
+ matrix:
10
+ allow_failures:
11
+ - node_js: 0.11
package/GNUmakefile CHANGED
@@ -1,15 +1,34 @@
1
+ BIN = ./node_modules/.bin
2
+
1
3
  .PHONY: all
2
4
  all: lib
3
5
 
4
6
  lib: $(foreach s,$(wildcard src/*.coffee),$(patsubst src/%.coffee,lib/%.js,$s))
5
7
 
8
+ lib-cov: clean-coverage lib
9
+ $(BIN)/istanbul instrument --output lib-cov --no-compact --variable global.__coverage__ lib
10
+
6
11
  lib/%.js: src/%.coffee
7
- ./node_modules/.bin/coffee -cb -o $(@D) $<
12
+ $(BIN)/coffee -cb -o $(@D) $<
8
13
 
9
14
  .PHONY: test
10
15
  test: lib
11
- ./node_modules/.bin/vows --spec
16
+ $(BIN)/mocha
17
+
18
+ .PHONY: coverage
19
+ coverage: lib-cov
20
+ COVER=1 $(BIN)/mocha --reporter mocha-istanbul
21
+ @echo
22
+ @echo Open html-report/index.html file in your browser
12
23
 
13
24
  .PHONY: watch
14
25
  watch:
15
- coffee --watch --bare --output lib src/*.coffee
26
+ $(BIN)/coffee --watch --bare --output lib src/*.coffee
27
+
28
+ .PHONY: clean
29
+ clean: clean-coverage
30
+
31
+ .PHONY: clean-coverage
32
+ clean-coverage:
33
+ -rm -rf lib-cov
34
+ -rm -rf html-report
package/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Command-Option-Argument
2
+ [![build status](https://secure.travis-ci.org/veged/coa.png)](http://travis-ci.org/veged/coa)
2
3
 
3
4
  ## What is it?
4
5
 
@@ -8,6 +9,7 @@ Once you write definition in terms of commands, options and arguments you automa
8
9
  * Command line help text
9
10
  * Program API for use COA-based programs as modules
10
11
  * Shell completion
12
+ * Subcommand extendibility by external node modules
11
13
 
12
14
  ### Other features
13
15
 
@@ -18,6 +20,8 @@ Once you write definition in terms of commands, options and arguments you automa
18
20
 
19
21
  ### TODO
20
22
 
23
+ * --Subcommand extendibility--
24
+ * Shell completion helpers
21
25
  * Localization
22
26
  * Shell-mode
23
27
  * Configs
@@ -43,14 +47,22 @@ require('coa').Cmd() // main (top level) command declaration
43
47
  .version;
44
48
  })
45
49
  .end() // end option chain and return to main command
46
- .cmd().name('subcommand').apply(require('./subcommand').COA).end() // load subcommand from module
50
+ .cmd()
51
+ .name('subcommand')
52
+ .apply(require('./subcommand')) // load subcommand from module
53
+ .end()
47
54
  .cmd() // inplace subcommand declaration
48
- .name('othercommand').title('Awesome other subcommand').helpful()
55
+ .name('othercommand')
56
+ .title('Awesome other subcommand')
57
+ .helpful()
49
58
  .opt()
50
- .name('input').title('input file, required')
51
- .short('i').long('input')
59
+ .name('input')
60
+ .title('input file, required')
61
+ .short('i')
62
+ .long('input')
52
63
  .val(function(v) { // validator function, also for translate simple values
53
- return require('fs').createReadStream(v) })
64
+ return require('fs').createReadStream(v);
65
+ })
54
66
  .req() // make option required
55
67
  .end() // end option chain and return to command
56
68
  .end() // end subcommand chain and return to parent command
@@ -59,12 +71,14 @@ require('coa').Cmd() // main (top level) command declaration
59
71
 
60
72
  ````javascript
61
73
  // subcommand.js
62
- exports.COA = function() {
74
+ module.exports = function() {
63
75
  this
64
76
  .title('Awesome subcommand').helpful()
65
77
  .opt()
66
- .name('output').title('output file')
67
- .short('o').long('output')
78
+ .name('output')
79
+ .title('output file')
80
+ .short('o')
81
+ .long('output')
68
82
  .output() // use default preset for "output" option declaration
69
83
  .end()
70
84
  };
@@ -139,6 +153,16 @@ Adds shell completion to command, adds "completion" subcommand, that makes all t
139
153
  Must be called only on root command.<br>
140
154
  **@returns** *COA.Cmd* `this` instance (for chainability)
141
155
 
156
+ #### Cmd.extendable
157
+ Adds ability to extend command by external node modules.<br>
158
+ **@param** *String* `[pattern]` Pattern of modules names to search.
159
+ Should be simple string with `%s` placeholder like `coa-program-%s-subcommand`
160
+ or without it — it will be treated as module name prefix then. E.g. `coa-program-`.<br>
161
+ Node module should export function or `Cmd` object. Function will be passed
162
+ to `Cmd.apply()` method of created subcommand object using `Cmd.cmd()` method. And
163
+ `Cmd` object will be passed to `Cmd.cmd()` method.
164
+ **@returns** *COA.Cmd* `this` instance (for chainability)
165
+
142
166
  #### Cmd.usage
143
167
  Build full usage text for current command instance.<br>
144
168
  **@returns** *String* `usage` text
@@ -226,6 +250,11 @@ Default value passed through validation function as ordinary value.<br>
226
250
  **@param** *Object* `_def`<br>
227
251
  **@returns** *COA.Opt* `this` instance (for chainability)
228
252
 
253
+ #### Opt.input
254
+ Make option value inputting stream.
255
+ It's add useful validation and shortcut for STDIN.
256
+ **@returns** *{COA.Opt}* `this` instance (for chainability)
257
+
229
258
  #### Opt.output
230
259
  Make option value outputing stream.<br>
231
260
  It's add useful validation and shortcut for STDOUT.<br>
package/index.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require(process.env.COVER? './lib-cov' : './lib');
package/lib/arg.js CHANGED
@@ -1,4 +1,4 @@
1
- // Generated by CoffeeScript 1.3.3
1
+ // Generated by CoffeeScript 1.6.3
2
2
  var Arg, Cmd, Color, Opt;
3
3
 
4
4
  Color = require('./color').Color;
@@ -160,6 +160,16 @@ exports.Arg = Arg = (function() {
160
160
 
161
161
  Arg.prototype.end = Cmd.prototype.end;
162
162
 
163
+ /**
164
+ Apply function with arguments in context of arg instance.
165
+ @param {Function} fn
166
+ @param {Array} args
167
+ @returns {COA.Arg} this instance (for chainability)
168
+ */
169
+
170
+
171
+ Arg.prototype.apply = Cmd.prototype.apply;
172
+
163
173
  return Arg;
164
174
 
165
175
  })();
package/lib/cmd.js CHANGED
@@ -1,4 +1,4 @@
1
- // Generated by CoffeeScript 1.3.3
1
+ // Generated by CoffeeScript 1.6.3
2
2
  var Cmd, Color, PATH, Q, UTIL,
3
3
  __slice = [].slice;
4
4
 
@@ -35,6 +35,7 @@ exports.Cmd = Cmd = (function() {
35
35
  this._opts = [];
36
36
  this._optsByKey = {};
37
37
  this._args = [];
38
+ this._ext = false;
38
39
  }
39
40
 
40
41
  Cmd.get = function(propertyName, func) {
@@ -70,10 +71,13 @@ exports.Cmd = Cmd = (function() {
70
71
  });
71
72
 
72
73
  Cmd.prototype._parent = function(cmd) {
74
+ this._cmd = cmd || this;
73
75
  if (cmd) {
74
76
  cmd._cmds.push(this);
77
+ if (this._name) {
78
+ this._cmd._cmdsByName[this._name] = this;
79
+ }
75
80
  }
76
- this._cmd = cmd || this;
77
81
  return this;
78
82
  };
79
83
 
@@ -221,6 +225,18 @@ exports.Cmd = Cmd = (function() {
221
225
  return this.cmd().name('completion').apply(require('./completion')).end();
222
226
  };
223
227
 
228
+ /**
229
+ Allow command to be extendable by external node.js modules.
230
+ @param {String} [pattern] Pattern of node.js module to find subcommands at.
231
+ @returns {COA.Cmd} this instance (for chainability)
232
+ */
233
+
234
+
235
+ Cmd.prototype.extendable = function(pattern) {
236
+ this._ext = pattern || true;
237
+ return this;
238
+ };
239
+
224
240
  Cmd.prototype._exit = function(msg, code) {
225
241
  return process.once('exit', function() {
226
242
  if (msg) {
@@ -304,7 +320,7 @@ exports.Cmd = Cmd = (function() {
304
320
  };
305
321
 
306
322
  Cmd.prototype._parseCmd = function(argv, unparsed) {
307
- var cmd, i, optSeen;
323
+ var c, cmd, cmdDesc, e, i, optSeen, pkg;
308
324
  if (unparsed == null) {
309
325
  unparsed = [];
310
326
  }
@@ -314,8 +330,46 @@ exports.Cmd = Cmd = (function() {
314
330
  if (!i.indexOf('-')) {
315
331
  optSeen = true;
316
332
  }
317
- if (!optSeen && /^\w[\w-_]*$/.test(i) && (cmd = this._cmdsByName[i])) {
318
- return cmd._parseCmd(argv, unparsed);
333
+ if (!optSeen && /^\w[\w-_]*$/.test(i)) {
334
+ cmd = this._cmdsByName[i];
335
+ if (!cmd && this._ext) {
336
+ if (typeof this._ext === 'string') {
337
+ if (~this._ext.indexOf('%s')) {
338
+ pkg = UTIL.format(this._ext, i);
339
+ } else {
340
+ pkg = this._ext + i;
341
+ }
342
+ } else if (this._ext === true) {
343
+ pkg = i;
344
+ c = this;
345
+ while (true) {
346
+ pkg = c._name + '-' + pkg;
347
+ if (c._cmd === c) {
348
+ break;
349
+ }
350
+ c = c._cmd;
351
+ }
352
+ }
353
+ try {
354
+ cmdDesc = require(pkg);
355
+ } catch (_error) {
356
+ e = _error;
357
+ }
358
+ if (cmdDesc) {
359
+ if (typeof cmdDesc === 'function') {
360
+ this.cmd().name(i).apply(cmdDesc).end();
361
+ } else if (typeof cmdDesc === 'object') {
362
+ this.cmd(cmdDesc);
363
+ cmdDesc.name(i);
364
+ } else {
365
+ throw new Error('Error: Unsupported command declaration type, ' + 'should be function or COA.Cmd() object');
366
+ }
367
+ cmd = this._cmdsByName[i];
368
+ }
369
+ }
370
+ if (cmd) {
371
+ return cmd._parseCmd(argv, unparsed);
372
+ }
319
373
  }
320
374
  unparsed.push(i);
321
375
  }
@@ -456,10 +510,21 @@ exports.Cmd = Cmd = (function() {
456
510
  }
457
511
  };
458
512
  };
459
- Q.when(this._do(this._parseArr(argv)), cb(0), cb(1)).end();
513
+ Q.when(this["do"](argv), cb(0), cb(1)).done();
460
514
  return this;
461
515
  };
462
516
 
517
+ /**
518
+ Convenient function to run command from tests.
519
+ @param {Array} argv
520
+ @returns {Q.Promise}
521
+ */
522
+
523
+
524
+ Cmd.prototype["do"] = function(argv) {
525
+ return this._do(this._parseArr(argv || []));
526
+ };
527
+
463
528
  /**
464
529
  Invoke specified (or current) command using provided
465
530
  options and arguments.
package/lib/color.js CHANGED
@@ -1,4 +1,4 @@
1
- // Generated by CoffeeScript 1.3.3
1
+ // Generated by CoffeeScript 1.6.3
2
2
  var colors;
3
3
 
4
4
  colors = {
package/lib/completion.js CHANGED
@@ -1,4 +1,4 @@
1
- // Generated by CoffeeScript 1.3.3
1
+ // Generated by CoffeeScript 1.6.3
2
2
  /**
3
3
  Most of the code adopted from the npm package shell completion code.
4
4
  See https://github.com/isaacs/npm/blob/master/lib/completion.js
@@ -21,7 +21,7 @@ module.exports = function() {
21
21
  e.errno = require('constants').ENOTSUP;
22
22
  return this.reject(e);
23
23
  }
24
- if (!(process.env.COMP_CWORD != null) || !(process.env.COMP_LINE != null) || !(process.env.COMP_POINT != null)) {
24
+ if ((process.env.COMP_CWORD == null) || (process.env.COMP_LINE == null) || (process.env.COMP_POINT == null)) {
25
25
  return dumpScript(this._cmd._name);
26
26
  }
27
27
  console.error('COMP_LINE: %s', process.env.COMP_LINE);
@@ -112,15 +112,15 @@ complete = function(cmd, opts) {
112
112
  }
113
113
  if (optWord && (opt = cmd._optsByKey[optWord])) {
114
114
  if (!opt._flag && opt._comp) {
115
- compls = Q.join(compls, Q.when(opt._comp(opts), function(c, o) {
115
+ compls = Q.all([compls, opt._comp(opts)]).spread(function(c, o) {
116
116
  return c.concat(o.map(function(v) {
117
117
  return (optPrefix || '') + v;
118
118
  }));
119
- }));
119
+ });
120
120
  }
121
121
  }
122
122
  if (cmd._comp) {
123
- compls = Q.join(compls, Q.when(cmd._comp(opts)), function(c, o) {
123
+ compls = Q.all([compls, cmd._comp(opts)]).spread(function(c, o) {
124
124
  return c.concat(o);
125
125
  });
126
126
  }
package/lib/index.js ADDED
@@ -0,0 +1,10 @@
1
+ // Generated by CoffeeScript 1.6.3
2
+ exports.Cmd = require('./cmd').Cmd;
3
+
4
+ exports.Opt = require('./cmd').Opt;
5
+
6
+ exports.Arg = require('./cmd').Arg;
7
+
8
+ exports.shell = require('./shell');
9
+
10
+ exports.require = require;
package/lib/opt.js CHANGED
@@ -1,4 +1,4 @@
1
- // Generated by CoffeeScript 1.3.3
1
+ // Generated by CoffeeScript 1.6.3
2
2
  var Cmd, Color, Opt, Q, fs;
3
3
 
4
4
  fs = require('fs');
@@ -323,6 +323,16 @@ exports.Opt = Opt = (function() {
323
323
 
324
324
  Opt.prototype.end = Cmd.prototype.end;
325
325
 
326
+ /**
327
+ Apply function with arguments in context of option instance.
328
+ @param {Function} fn
329
+ @param {Array} args
330
+ @returns {COA.Opt} this instance (for chainability)
331
+ */
332
+
333
+
334
+ Opt.prototype.apply = Cmd.prototype.apply;
335
+
326
336
  return Opt;
327
337
 
328
338
  })();
package/lib/shell.js CHANGED
@@ -1,5 +1,4 @@
1
- // Generated by CoffeeScript 1.3.3
2
-
1
+ // Generated by CoffeeScript 1.6.3
3
2
  exports.unescape = function(w) {
4
3
  w = w.charAt(0) === '"' ? w.replace(/^"|([^\\])"$/g, '$1') : w.replace(/\\ /g, ' ');
5
4
  return w.replace(/\\("|'|\$|`|\\)/g, '$1');
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "coa",
3
3
  "description": "Command-Option-Argument: Yet another parser for command line options.",
4
- "version": "0.3.7",
4
+ "version": "0.4.1",
5
5
  "homepage": "http://github.com/veged/coa",
6
6
  "author": "Sergey Berezhnoy <veged@ya.ru> (http://github.com/veged)",
7
7
  "maintainers": [
@@ -18,13 +18,20 @@
18
18
  "directories": {
19
19
  "lib": "./lib"
20
20
  },
21
- "main": "./lib/coa.js",
22
21
  "dependencies": {
23
- "q": "~0.8.8"
22
+ "q": "~0.9.6"
24
23
  },
25
24
  "devDependencies": {
26
- "coffee-script": "~1.3.3",
27
- "vows": "~0.6.4"
25
+ "coffee-script": "~1.6.3",
26
+ "istanbul": "~0.1.40",
27
+ "mocha-as-promised": "*",
28
+ "mocha-istanbul": "*",
29
+ "mocha": "~1.12.0",
30
+ "chai": "~1.7.2"
31
+ },
32
+ "scripts": {
33
+ "test": "make test",
34
+ "coverage": "make coverage"
28
35
  },
29
36
  "engines": {
30
37
  "node": ">= 0.6.0"
package/src/arg.coffee CHANGED
@@ -120,3 +120,11 @@ exports.Arg = class Arg
120
120
  @returns {COA.Cmd} parent command
121
121
  ###
122
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 CHANGED
@@ -32,6 +32,8 @@ exports.Cmd = class Cmd
32
32
 
33
33
  @_args = []
34
34
 
35
+ @_ext = false
36
+
35
37
  @get: (propertyName, func) ->
36
38
  Object.defineProperty @::, propertyName,
37
39
  configurable: true
@@ -52,8 +54,10 @@ exports.Cmd = class Cmd
52
54
  @_api
53
55
 
54
56
  _parent: (cmd) ->
55
- if cmd then cmd._cmds.push @
56
57
  @_cmd = cmd or this
58
+ if cmd
59
+ cmd._cmds.push @
60
+ if @_name then @_cmd._cmdsByName[@_name] = @
57
61
  @
58
62
 
59
63
  ###*
@@ -163,6 +167,15 @@ exports.Cmd = class Cmd
163
167
  .apply(require './completion')
164
168
  .end()
165
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
+
166
179
  _exit: (msg, code) ->
167
180
  process.once 'exit', ->
168
181
  if msg then UTIL.error msg
@@ -233,8 +246,49 @@ exports.Cmd = class Cmd
233
246
  while i = argv.shift()
234
247
  if not i.indexOf '-'
235
248
  optSeen = true
236
- if not optSeen and /^\w[\w-_]*$/.test(i) and cmd = @_cmdsByName[i]
237
- return cmd._parseCmd argv, unparsed
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
238
292
 
239
293
  unparsed.push i
240
294
 
@@ -342,9 +396,17 @@ exports.Cmd = class Cmd
342
396
  @_exit res.stack ? res.toString(), res.exitCode ? code
343
397
  else
344
398
  @_exit()
345
- Q.when(@_do(@_parseArr argv), cb(0), cb(1)).end()
399
+ Q.when(@do(argv), cb(0), cb(1)).done()
346
400
  @
347
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
+
348
410
  ###*
349
411
  Invoke specified (or current) command using provided
350
412
  options and arguments.
@@ -137,15 +137,17 @@ complete = (cmd, opts) ->
137
137
  # complete on opt values: completion
138
138
  if optWord and opt = cmd._optsByKey[optWord]
139
139
  if not opt._flag and opt._comp
140
- compls = Q.join compls, Q.when opt._comp(opts), (c, o) ->
141
- c.concat o.map (v) -> (optPrefix or '') + v
140
+ compls = Q.all([compls, opt._comp(opts)])
141
+ .spread (c, o) ->
142
+ c.concat o.map (v) -> (optPrefix or '') + v
142
143
 
143
144
  # TODO: complete on args values (context aware, custom completion?)
144
145
 
145
146
  # custom completion on cmds
146
147
  if cmd._comp
147
- compls = Q.join compls, Q.when(cmd._comp(opts)), (c, o) ->
148
- c.concat o
148
+ compls = Q.all([compls, cmd._comp(opts)])
149
+ .spread (c, o) ->
150
+ c.concat o
149
151
 
150
152
  # TODO: context aware custom completion on cmds, opts and args
151
153
  # (can depend on already entered values, especially options)
@@ -0,0 +1,5 @@
1
+ exports.Cmd = require('./cmd').Cmd
2
+ exports.Opt = require('./cmd').Opt
3
+ exports.Arg = require('./cmd').Arg
4
+ exports.shell = require('./shell')
5
+ exports.require = require;
package/src/opt.coffee CHANGED
@@ -233,3 +233,11 @@ exports.Opt = class Opt
233
233
  @returns {COA.Cmd} parent command
234
234
  ###
235
235
  end: Cmd::end
236
+
237
+ ###*
238
+ Apply function with arguments in context of option instance.
239
+ @param {Function} fn
240
+ @param {Array} args
241
+ @returns {COA.Opt} this instance (for chainability)
242
+ ###
243
+ apply: Cmd::apply