Skip to content
Browse files

First turn for APIization feature. Now parsing input, doing actions a…

…nd program control are splited into different parts. All actions now async (powered by Q promises module). Rename: .parse() -> .run()
  • Loading branch information...
1 parent 0788d70 commit d60f9a618d07e21a823fbbc8f313123fb8ac4ee1 @veged committed
Showing with 166 additions and 104 deletions.
  1. +18 −15 README.md
  2. +70 −41 lib/cmd.js
  3. +3 −0 lib/opt.js
  4. +2 −1 package.json
  5. +60 −36 src/cmd.coffee
  6. +4 −1 src/opt.coffee
  7. +1 −1 tests/a.js
  8. +1 −1 tests/h.js
  9. +1 −1 tests/l.js
  10. +1 −1 tests/req.js
  11. +4 −5 tests/v.js
  12. +1 −1 tests/val.js
View
33 README.md
@@ -18,9 +18,9 @@ require('coa').Cmd() // main (top level) command declaration
.long('version') // long key: --version
.type(Boolean) // type Boolean for options without value
.act(function(opts) { // add action for option
- this.exit( // exit program with code 0 and text message
- JSON.parse(require('fs').readFileSync(__dirname + '/package.json'))
- .version);
+ // return message as result of action
+ return JSON.parse(require('fs').readFileSync(__dirname + '/package.json'))
+ .version;
})
.end() // end option chain and return to main command
.cmd().name('subcommand').apply(require('./subcommand').COA).end() // load subcommand from module
@@ -34,7 +34,7 @@ require('coa').Cmd() // main (top level) command declaration
.req() // make option required
.end() // end option chain and return to command
.end() // end subcommand chain and return to parent command
- .parse(process.argv.slice(2)); // parse and run on process.argv
+ .run(process.argv.slice(2)); // parse and run on process.argv
````
````javascript
@@ -84,6 +84,9 @@ Add (or set) action for current command.<br>
and has the parameters:<br>
- *Object* `opts` parsed options<br>
- *Array* `args` parsed arguments<br>
+ - *Object* `res` actions result accumulator<br>
+ It can return rejected promise by Cmd.reject (in case of error)
+ or any other value treated as result.<br>
**@param** *{Boolean}* [force=false] flag for set action instead add to existings<br>
**@returns** *COA.Cmd* `this` instance (for chainability)
@@ -97,24 +100,21 @@ Apply function with arguments in context of command instance.<br>
Make command "helpful", i.e. add -h --help flags for print usage.<br>
**@returns** *COA.Cmd* `this` instance (for chainability)
-#### Cmd.errorExit
-Terminate program with error code 1.
-**@param** *String* `msg` message for print to STDERR<br>
-**@param** *{Object}* [o] optional object for print with message
-
-#### Cmd.exit
-Terminate program with error code 0.<br>
-**@param** *String* `msg` message for print to STDERR
-
#### Cmd.usage
Build full usage text for current command instance.<br>
**@returns** *String* `usage` text
-#### Cmd.parse
-Parse arguments from simple format like NodeJS process.argv.<br>
+#### Cmd.run
+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`<br>
**@returns** *COA.Cmd* `this` instance (for chainability)
+#### Cmd.reject
+Return reject of actions results promise.<br>
+Use in .act() for return with error.<br>
+**@returns** *Q.promise* rejected promise
+
#### Cmd.end
Finish chain for current subcommand and return parent command instance.<br>
**@returns** *COA.Cmd* `parent` command
@@ -187,6 +187,9 @@ is present in parsed options (with any value).<br>
and has the parameters:<br>
- *Object* `opts` parsed options<br>
- *Array* `args` parsed arguments<br>
+ - *Object* `res` actions result accumulator<br>
+ It can return rejected promise by Cmd.reject (in case of error)
+ or any other value treated as result.<br>
**@returns** *COA.Opt* `this` instance (for chainability)
#### Opt.end
View
111 lib/cmd.js
@@ -1,8 +1,9 @@
-var Cmd, Color, path, sys;
-var __slice = Array.prototype.slice;
+var Cmd, Color, Q, path, sys;
+var __slice = Array.prototype.slice, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
sys = require('sys');
path = require('path');
Color = require('./color').Color;
+Q = require('q');
/**
## Command
Top level entity. Commands may have options and arguments.
@@ -73,22 +74,20 @@ exports.Cmd = Cmd = (function() {
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)
*/
Cmd.prototype.act = function(act, force) {
- var oldAct;
if (!act) {
- return this._act;
+ return this;
}
if (!force && this._act) {
- oldAct = this._act;
- this._act = function() {
- oldAct.apply(this, arguments);
- return act.apply(this, arguments);
- };
+ this._act.push(act);
} else {
- this._act = act;
+ this._act = [act];
}
return this;
};
@@ -110,30 +109,15 @@ exports.Cmd = Cmd = (function() {
*/
Cmd.prototype.helpful = function() {
this._helpful = true;
- return this.opt().name('help').title('Help').short('h').long('help').flag().act(function(opts, args) {
- return this.exit(this.usage());
+ return this.opt().name('help').title('Help').short('h').long('help').flag().act(function() {
+ return this.usage();
}).end();
};
- /**
- Terminate program with error code 1.
- @param {String} msg message for print to STDERR
- @param {Object} [o] optional object for print with message
- */
- Cmd.prototype.errorExit = function(msg, o) {
- if (msg) {
- sys.error(msg + (o ? ': ' + o : ''));
- }
- return process.exit(1);
- };
- /**
- Terminate program with error code 0.
- @param {String} msg message for print to STDERR
- */
- Cmd.prototype.exit = function(msg) {
+ Cmd.prototype._exit = function(msg, code) {
if (msg) {
sys.error(msg);
}
- return process.exit();
+ return process.exit(code || 0);
};
/**
Build full usage text for current command instance.
@@ -186,12 +170,7 @@ exports.Cmd = Cmd = (function() {
}
}
};
- /**
- Parse arguments from simple format like NodeJS process.argv.
- @param {Array} argv
- @returns {COA.Cmd} this instance (for chainability)
- */
- Cmd.prototype.parse = function(argv) {
+ Cmd.prototype._parseArr = function(argv) {
var arg, args, cmd, i, m, nonParsed, nonParsedArgs, nonParsedOpts, opt, opts;
opts = {};
args = {};
@@ -206,12 +185,12 @@ exports.Cmd = Cmd = (function() {
if (opt = this._ejectOpt(nonParsedOpts, this._optsByKey[i])) {
opt._parse(argv, opts);
} else {
- this.errorExit('Unknown option', i);
+ return this.reject("Unknown option: " + i);
}
} else if (!nonParsedArgs && /^\w[\w-_]*$/.test(i)) {
cmd = this._cmdsByName[i];
if (cmd) {
- cmd.parse(argv);
+ cmd._parseArr(argv);
} else {
nonParsedArgs = this._args.concat();
argv.unshift(i);
@@ -223,7 +202,7 @@ exports.Cmd = Cmd = (function() {
}
arg._parse(i, args);
} else {
- this.errorExit('Unknown argument', i);
+ return this.reject("Unknown argument: " + i);
}
}
}
@@ -232,19 +211,69 @@ exports.Cmd = Cmd = (function() {
nonParsed = nonParsedOpts.concat(nonParsedArgs);
while (i = nonParsed.shift()) {
if (i._req && i._checkParsed(opts, args)) {
- this.errorExit(i._requiredText());
+ return this.reject(i._requiredText());
}
if ('_def' in i) {
i._saveVal(opts, i._def);
}
}
}
- if (typeof this._act === "function") {
- this._act(opts, args);
+ return {
+ opts: opts,
+ args: args
+ };
+ };
+ Cmd.prototype._do = function(input, succ, err) {
+ var defer, _ref;
+ defer = Q.defer();
+ if ((_ref = this._act) != null) {
+ _ref.reduce(__bind(function(res, act) {
+ return res.then(__bind(function(params) {
+ var actRes, _ref2;
+ actRes = act.call(this, params.opts, params.args, params.res);
+ if (Q.isPromise(actRes)) {
+ return actRes;
+ } else {
+ if ((_ref2 = params.res) == null) {
+ params.res = actRes;
+ }
+ return params;
+ }
+ }, this));
+ }, this), defer.promise).fail(__bind(function(res) {
+ return err.call(this, res);
+ }, this)).then(__bind(function(res) {
+ return succ.call(this, res.res);
+ }, this));
}
+ return defer.resolve(this._parseArr(input));
+ };
+ /**
+ 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)
+ */
+ Cmd.prototype.run = function(argv) {
+ if (argv == null) {
+ argv = process.argv;
+ }
+ this._do(argv, function(res) {
+ return this._exit(res.toString(), 0);
+ }, function(res) {
+ return this._exit(res.toString(), 1);
+ });
return this;
};
/**
+ Return reject of actions results promise.
+ Use in .act() for return with error.
+ @returns {Q.promise} rejected promise
+ */
+ Cmd.prototype.reject = function(reason) {
+ return Q.reject(reason);
+ };
+ /**
Finish chain for current subcommand and return parent command instance.
@returns {COA.Cmd} parent command
*/
View
3 lib/opt.js
@@ -126,6 +126,9 @@ exports.Opt = Opt = (function() {
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.
@returns {COA.Opt} this instance (for chainability)
*/
Opt.prototype.act = function(act) {
View
3 package.json
@@ -1,7 +1,7 @@
{
"name" : "coa",
"description" : "Command-Option-Argument: Yet another parser for command line options.",
- "version" : "0.0.5",
+ "version" : "0.0.6",
"homepage" : "http://github.com/veged/coa",
"author" : {
"name": "Sergey Berezhnoy",
@@ -17,6 +17,7 @@
"directories" : { "lib" : "./lib" },
"main" : "./lib/coa.js",
"dependencies" : {
+ "q": ">=0.7.1"
},
"engines" : [ "node ~0.4.0" ],
"licenses" : [ { "type" : "AS IS" } ]
View
96 src/cmd.coffee
@@ -1,6 +1,7 @@
sys = require 'sys'
path = require 'path'
Color = require('./color').Color
+Q = require('q')
#inspect = require('eyes').inspector { maxLength: 99999, stream: process.stderr }
@@ -70,19 +71,19 @@ exports.Cmd = class Cmd
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 @_act unless act
+ return @ unless act
if not force and @_act
- oldAct = @_act
- @_act = ->
- oldAct.apply @, arguments
- act.apply @, arguments
+ @_act.push act
else
- @_act = act
+ @_act = [act]
@
@@ -106,26 +107,14 @@ exports.Cmd = class Cmd
.name('help').title('Help')
.short('h').long('help')
.flag()
- .act (opts, args) ->
- @exit @usage()
+ .act ->
+ return @usage()
.end()
- ###*
- Terminate program with error code 1.
- @param {String} msg message for print to STDERR
- @param {Object} [o] optional object for print with message
- ###
- errorExit: (msg, o) ->
- if msg then sys.error msg + (if o then ': ' + o else '')
- process.exit 1
- ###*
- Terminate program with error code 0.
- @param {String} msg message for print to STDERR
- ###
- exit: (msg) ->
+ _exit: (msg, code) ->
if msg then sys.error msg
- process.exit()
+ process.exit code or 0
###*
Build full usage text for current command instance.
@@ -179,12 +168,7 @@ exports.Cmd = class Cmd
else
opts.splice(pos, 1)[0]
- ###*
- Parse arguments from simple format like NodeJS process.argv.
- @param {Array} argv
- @returns {COA.Cmd} this instance (for chainability)
- ###
- parse: (argv) ->
+ _parseArr: (argv) ->
opts = {}
args = {}
nonParsedOpts = @_opts.concat()
@@ -202,16 +186,16 @@ exports.Cmd = class Cmd
if opt = @_ejectOpt nonParsedOpts, @_optsByKey[i]
opt._parse argv, opts
else
- @errorExit('Unknown option', i)
+ return @reject "Unknown option: #{ i }"
# cmd
else if not nonParsedArgs and /^\w[\w-_]*$/.test i
cmd = @_cmdsByName[i]
if cmd
- cmd.parse(argv)
+ cmd._parseArr argv
else
nonParsedArgs = @_args.concat()
- argv.unshift(i)
+ argv.unshift i
# arg
else
@@ -219,7 +203,7 @@ exports.Cmd = class Cmd
if arg._arr then nonParsedArgs.unshift arg
arg._parse i, args
else
- @errorExit 'Unknown argument', i
+ return @reject "Unknown argument: #{ i }"
nonParsedArgs or= @_args.concat()
@@ -227,16 +211,56 @@ exports.Cmd = class Cmd
nonParsed = nonParsedOpts.concat nonParsedArgs
while i = nonParsed.shift()
if i._req and i._checkParsed opts, args
- @errorExit i._requiredText()
+ return @reject i._requiredText()
if '_def' of i
i._saveVal opts, i._def
- #console.log opts, args
- @_act? opts, args
- #@exit()
+ { opts: opts, args: args }
+
+ _do: (input, succ, err) ->
+ defer = Q.defer()
+ @_act?.reduce(
+ (res, act) =>
+ res.then (params) =>
+ actRes = act.call(@
+ params.opts
+ params.args
+ params.res)
+
+ if Q.isPromise actRes
+ actRes
+ else
+ params.res ?= actRes
+ params
+ defer.promise
+ )
+ .fail((res) => err.call @, res)
+ .then((res) => succ.call @, res.res)
+
+ defer.resolve @_parseArr input
+
+ ###*
+ 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) ->
+ @_do(
+ argv
+ (res) -> @_exit res.toString(), 0
+ (res) -> @_exit res.toString(), 1
+ )
@
###*
+ Return reject of actions results promise.
+ Use in .act() for return with error.
+ @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
###
View
5 src/opt.coffee
@@ -113,13 +113,16 @@ exports.Opt = class Opt
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.
@returns {COA.Opt} this instance (for chainability)
###
act: (act) ->
name = @_name
@_cmd.act (opts) ->
if name of opts
- act.apply @, arguments
+ return act.apply @, arguments
@
_saveVal: (opts, val) ->
View
2 tests/a.js
@@ -10,4 +10,4 @@ require('../lib/coa').Cmd()
console.log(opts.arr);
})
.end()
- .parse(['-a', '1', '-a', '2']);
+ .run(['-a', '1', '-a', '2']);
View
2 tests/h.js
@@ -2,4 +2,4 @@ require('../lib/coa').Cmd()
.name('bla')
.title('Bla bla bla')
.helpful()
- .parse(['-h']);
+ .run(['-h']);
View
2 tests/l.js
@@ -17,4 +17,4 @@ require('../lib/coa').Cmd()
})
.end()
.act(function(opts) { console.log(opts) })
- .parse(['--long1', '111', '--long2', '222']);
+ .run(['--long1', '111', '--long2', '222']);
View
2 tests/req.js
@@ -7,4 +7,4 @@ require('../lib/coa').Cmd()
.short('b').long('bla')
.req()
.end()
- .parse([]);
+ .run([]);
View
9 tests/v.js
@@ -6,10 +6,9 @@ require('../lib/coa').Cmd()
.name('version').title('Version')
.short('v').long('version')
.flag()
- .act(function(opts) {
- this.exit(
- JSON.parse(require('fs').readFileSync(__dirname + '/../package.json'))
- .version);
+ .act(function(opts, args) {
+ return JSON.parse(require('fs').readFileSync(__dirname + '/../package.json'))
+ .version
})
.end()
- .parse(['-v']);
+ .run(['-v']);
View
2 tests/val.js
@@ -10,4 +10,4 @@ require('../lib/coa').Cmd()
console.log(opts.bla);
})
.end()
- .parse(['--bla=blabla']);
+ .run(['--bla=blabla']);

0 comments on commit d60f9a6

Please sign in to comment.
Something went wrong with that request. Please try again.