Comparing version 1.0.4 to 2.0.0
@@ -1,1 +0,1 @@ | ||
module.exports = require(process.env.COVER? './lib-cov' : './lib'); | ||
module.exports = require('./lib'); |
203
lib/arg.js
@@ -1,175 +0,58 @@ | ||
// Generated by CoffeeScript 1.6.3 | ||
var Arg, Cmd, Color, Opt; | ||
'use strict'; | ||
Color = require('./color').Color; | ||
const | ||
CoaParam = require('./coaparam'), | ||
Color = require('./color'); | ||
Cmd = require('./cmd').Cmd; | ||
Opt = require('./opt').Opt; | ||
/** | ||
Argument | ||
* Argument | ||
* | ||
* Unnamed entity. From command line arguments passed as list of unnamed values. | ||
* | ||
* @class Arg | ||
* @extends CoaParam | ||
*/ | ||
module.exports = class Arg extends CoaParam { | ||
/** | ||
* @constructs | ||
* @param {COA.Cmd} cmd - parent command | ||
*/ | ||
constructor(cmd) { | ||
super(cmd); | ||
Unnamed entity. From command line arguments passed as list of unnamed values. | ||
@namespace | ||
@class Presents argument | ||
*/ | ||
this._cmd._args.push(this); | ||
} | ||
_saveVal(args, val) { | ||
this._val && (val = this._val(val)); | ||
exports.Arg = Arg = (function() { | ||
/** | ||
@constructs | ||
@param {COA.Cmd} cmd parent command | ||
*/ | ||
const name = this._name; | ||
this._arr | ||
? (args[name] || (args[name] = [])).push(val) | ||
: (args[name] = val); | ||
function Arg(_cmd) { | ||
this._cmd = _cmd; | ||
this._cmd._args.push(this); | ||
} | ||
return val; | ||
} | ||
/** | ||
Set a canonical argument identifier to be used anywhere in text messages. | ||
@param {String} _name argument name | ||
@returns {COA.Arg} this instance (for chainability) | ||
*/ | ||
_parse(arg, args) { | ||
return this._saveVal(args, arg); | ||
} | ||
_checkParsed(opts, args) { | ||
return !args.hasOwnProperty(this._name); | ||
} | ||
Arg.prototype.name = Opt.prototype.name; | ||
_usage() { | ||
const res = []; | ||
/** | ||
Set a long description for argument to be used anywhere in text messages. | ||
@param {String} _title argument title | ||
@returns {COA.Arg} this instance (for chainability) | ||
*/ | ||
res.push(Color('lpurple', this._name.toUpperCase()), ' : ', this._title); | ||
this._req && res.push(' ', Color('lred', '(required)')); | ||
Arg.prototype.title = Cmd.prototype.title; | ||
return res.join(''); | ||
} | ||
/** | ||
Makes an argument accepts multiple values. | ||
Otherwise, the value will be used by the latter passed. | ||
@returns {COA.Arg} this instance (for chainability) | ||
*/ | ||
Arg.prototype.arr = Opt.prototype.arr; | ||
/** | ||
Makes an argument required. | ||
@returns {COA.Arg} this instance (for chainability) | ||
*/ | ||
Arg.prototype.req = Opt.prototype.req; | ||
/** | ||
Set a validation (or value) function for argument. | ||
Value from command line passes through before becoming available from API. | ||
Using for validation and convertion simple types to any values. | ||
@param {Function} _val validating function, | ||
invoked in the context of argument instance | ||
and has one parameter with value from command line | ||
@returns {COA.Arg} this instance (for chainability) | ||
*/ | ||
Arg.prototype.val = Opt.prototype.val; | ||
/** | ||
Set a default value for argument. | ||
Default value passed through validation function as ordinary value. | ||
@param {Object} _def | ||
@returns {COA.Arg} this instance (for chainability) | ||
*/ | ||
Arg.prototype.def = Opt.prototype.def; | ||
/** | ||
Set custom additional completion for current argument. | ||
@param {Function} completion generation function, | ||
invoked in the context of argument instance. | ||
Accepts parameters: | ||
- {Object} opts completion options | ||
It can return promise or any other value treated as result. | ||
@returns {COA.Arg} this instance (for chainability) | ||
*/ | ||
Arg.prototype.comp = Cmd.prototype.comp; | ||
/** | ||
Make argument value inputting stream. | ||
It's add useful validation and shortcut for STDIN. | ||
@returns {COA.Arg} this instance (for chainability) | ||
*/ | ||
Arg.prototype.input = Opt.prototype.input; | ||
/** | ||
Make argument value outputing stream. | ||
It's add useful validation and shortcut for STDOUT. | ||
@returns {COA.Arg} this instance (for chainability) | ||
*/ | ||
Arg.prototype.output = Opt.prototype.output; | ||
Arg.prototype._parse = function(arg, args) { | ||
return this._saveVal(args, arg); | ||
}; | ||
Arg.prototype._saveVal = Opt.prototype._saveVal; | ||
Arg.prototype._checkParsed = function(opts, args) { | ||
return !args.hasOwnProperty(this._name); | ||
}; | ||
Arg.prototype._usage = function() { | ||
var res; | ||
res = []; | ||
res.push(Color('lpurple', this._name.toUpperCase()), ' : ', this._title); | ||
if (this._req) { | ||
res.push(' ', Color('lred', '(required)')); | ||
_requiredText() { | ||
return `Missing required argument:\n ${this._usage()}`; | ||
} | ||
return res.join(''); | ||
}; | ||
Arg.prototype._requiredText = function() { | ||
return 'Missing required argument:\n ' + this._usage(); | ||
}; | ||
/** | ||
Return rejected promise with error code. | ||
Use in .val() for return with error. | ||
@param {Object} reject reason | ||
You can customize toString() method and exitCode property | ||
of reason object. | ||
@returns {Q.promise} rejected promise | ||
*/ | ||
Arg.prototype.reject = Cmd.prototype.reject; | ||
/** | ||
Finish chain for current option and return parent command instance. | ||
@returns {COA.Cmd} parent command | ||
*/ | ||
Arg.prototype.end = Cmd.prototype.end; | ||
/** | ||
Apply function with arguments in context of arg instance. | ||
@param {Function} fn | ||
@param {Array} args | ||
@returns {COA.Arg} this instance (for chainability) | ||
*/ | ||
Arg.prototype.apply = Cmd.prototype.apply; | ||
return Arg; | ||
})(); | ||
}; |
915
lib/cmd.js
@@ -1,605 +0,492 @@ | ||
// Generated by CoffeeScript 1.6.3 | ||
var Cmd, Color, PATH, Q, UTIL, | ||
__slice = [].slice; | ||
'use strict'; | ||
UTIL = require('util'); | ||
const | ||
UTIL = require('util'), | ||
PATH = require('path'), | ||
EOL = require('os').EOL, | ||
PATH = require('path'); | ||
Q = require('q'), | ||
Color = require('./color').Color; | ||
CoaObject = require('./coaobject'), | ||
Color = require('./color'), | ||
Opt = require('./opt'), | ||
Arg = require('./arg'), | ||
completion = require('./completion'); | ||
Q = require('q'); | ||
/** | ||
Command | ||
* Command | ||
* | ||
* Top level entity. Commands may have options and arguments. | ||
* | ||
* @namespace | ||
* @class Cmd | ||
* @extends CoaObject | ||
*/ | ||
class Cmd extends CoaObject { | ||
/** | ||
* @constructs | ||
* @param {COA.Cmd} [cmd] parent command | ||
*/ | ||
constructor(cmd) { | ||
super(cmd); | ||
Top level entity. Commands may have options and arguments. | ||
@namespace | ||
@class Presents command | ||
*/ | ||
this._parent(cmd); | ||
this._cmds = []; | ||
this._cmdsByName = {}; | ||
this._opts = []; | ||
this._optsByKey = {}; | ||
this._args = []; | ||
this._api = null; | ||
this._ext = false; | ||
} | ||
static create(cmd) { | ||
return new Cmd(cmd); | ||
} | ||
exports.Cmd = Cmd = (function() { | ||
/** | ||
@constructs | ||
@param {COA.Cmd} [cmd] parent command | ||
*/ | ||
/** | ||
* Returns object containing all its subcommands as methods | ||
* to use from other programs. | ||
* | ||
* @returns {Object} | ||
*/ | ||
get api() { | ||
// Need _this here because of passed arguments into _api | ||
const _this = this; | ||
this._api || (this._api = function () { | ||
return _this.invoke.apply(_this, arguments); | ||
}); | ||
function Cmd(cmd) { | ||
if (!(this instanceof Cmd)) { | ||
return new Cmd(cmd); | ||
const cmds = this._cmdsByName; | ||
Object.keys(cmds).forEach(cmd => { this._api[cmd] = cmds[cmd].api; }); | ||
return this._api; | ||
} | ||
this._parent(cmd); | ||
this._cmds = []; | ||
this._cmdsByName = {}; | ||
this._opts = []; | ||
this._optsByKey = {}; | ||
this._args = []; | ||
this._ext = false; | ||
} | ||
Cmd.get = function(propertyName, func) { | ||
return Object.defineProperty(this.prototype, propertyName, { | ||
configurable: true, | ||
enumerable: true, | ||
get: func | ||
}); | ||
}; | ||
_parent(cmd) { | ||
this._cmd = cmd || this; | ||
/** | ||
Returns object containing all its subcommands as methods | ||
to use from other programs. | ||
@returns {Object} | ||
*/ | ||
this.isRootCmd || | ||
cmd._cmds.push(this) && | ||
this._name && | ||
(this._cmd._cmdsByName[this._name] = this); | ||
Cmd.get('api', function() { | ||
var c, _fn, | ||
_this = this; | ||
if (!this._api) { | ||
this._api = function() { | ||
return _this.invoke.apply(_this, arguments); | ||
}; | ||
return this; | ||
} | ||
_fn = function(c) { | ||
return _this._api[c] = _this._cmdsByName[c].api; | ||
}; | ||
for (c in this._cmdsByName) { | ||
_fn(c); | ||
} | ||
return this._api; | ||
}); | ||
Cmd.prototype._parent = function(cmd) { | ||
this._cmd = cmd || this; | ||
if (cmd) { | ||
cmd._cmds.push(this); | ||
if (this._name) { | ||
this._cmd._cmdsByName[this._name] = this; | ||
} | ||
get isRootCmd() { | ||
return this._cmd === this; | ||
} | ||
return this; | ||
}; | ||
/** | ||
Set a canonical command identifier to be used anywhere in the API. | ||
@param {String} _name command name | ||
@returns {COA.Cmd} this instance (for chainability) | ||
*/ | ||
/** | ||
* 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) { | ||
super.name(name); | ||
this.isRootCmd || | ||
(this._cmd._cmdsByName[name] = this); | ||
Cmd.prototype.name = function(_name) { | ||
this._name = _name; | ||
if (this._cmd !== this) { | ||
this._cmd._cmdsByName[_name] = this; | ||
return this; | ||
} | ||
return this; | ||
}; | ||
/** | ||
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) | ||
*/ | ||
/** | ||
* Create new or add existing subcommand for current command. | ||
* | ||
* @param {COA.Cmd} [cmd] existing command instance | ||
* @returns {COA.Cmd} new subcommand instance | ||
*/ | ||
cmd(cmd) { | ||
return cmd? | ||
cmd._parent(this) | ||
: new Cmd(this); | ||
} | ||
/** | ||
* Create option for current command. | ||
* | ||
* @returns {COA.Opt} new option instance | ||
*/ | ||
opt() { | ||
return new Opt(this); | ||
} | ||
Cmd.prototype.title = function(_title) { | ||
this._title = _title; | ||
return this; | ||
}; | ||
/** | ||
* Create argument for current command. | ||
* | ||
* @returns {COA.Opt} new argument instance | ||
*/ | ||
arg() { | ||
return new Arg(this); | ||
} | ||
/** | ||
Create new or add existing subcommand for current command. | ||
@param {COA.Cmd} [cmd] existing command instance | ||
@returns {COA.Cmd} new subcommand instance | ||
*/ | ||
/** | ||
* 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 | ||
* - {String[]} 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) { | ||
if(!act) return this; | ||
(!this._act || force) && (this._act = []); | ||
this._act.push(act); | ||
Cmd.prototype.cmd = function(cmd) { | ||
if (cmd) { | ||
return cmd._parent(this); | ||
} else { | ||
return new Cmd(this); | ||
return this; | ||
} | ||
}; | ||
/** | ||
Create option for current command. | ||
@returns {COA.Opt} new option instance | ||
*/ | ||
/** | ||
* Make command "helpful", i.e. add -h --help flags for print usage. | ||
* | ||
* @returns {COA.Cmd} - this instance (for chainability) | ||
*/ | ||
helpful() { | ||
return this.opt() | ||
.name('help') | ||
.title('Help') | ||
.short('h') | ||
.long('help') | ||
.flag() | ||
.only() | ||
.act(function() { | ||
return this.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() { | ||
return this.cmd() | ||
.name('completion') | ||
.apply(completion) | ||
.end(); | ||
} | ||
Cmd.prototype.opt = function() { | ||
return new (require('./opt').Opt)(this); | ||
}; | ||
/** | ||
* 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) { | ||
this._ext = pattern || true; | ||
return this; | ||
} | ||
/** | ||
Create argument for current command. | ||
@returns {COA.Opt} new argument instance | ||
*/ | ||
_exit(msg, code) { | ||
return process.once('exit', function() { | ||
msg && console[code === 0 ? 'log' : 'error'](msg); | ||
process.exit(code || 0); | ||
}); | ||
} | ||
/** | ||
* Build full usage text for current command instance. | ||
* | ||
* @returns {String} usage text | ||
*/ | ||
usage() { | ||
const res = []; | ||
Cmd.prototype.arg = function() { | ||
return new (require('./arg').Arg)(this); | ||
}; | ||
this._title && res.push(this._fullTitle()); | ||
/** | ||
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) | ||
*/ | ||
res.push('', 'Usage:'); | ||
this._cmds.length | ||
&& res.push([ | ||
'', '', Color('lred', this._fullName()), Color('lblue', 'COMMAND'), | ||
Color('lgreen', '[OPTIONS]'), Color('lpurple', '[ARGS]') | ||
].join(' ')); | ||
Cmd.prototype.act = function(act, force) { | ||
if (!act) { | ||
return this; | ||
} | ||
if (!force && this._act) { | ||
this._act.push(act); | ||
} else { | ||
this._act = [act]; | ||
} | ||
return this; | ||
}; | ||
(this._opts.length + this._args.length) | ||
&& res.push([ | ||
'', '', Color('lred', this._fullName()), | ||
Color('lgreen', '[OPTIONS]'), Color('lpurple', '[ARGS]') | ||
].join(' ')); | ||
/** | ||
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) | ||
*/ | ||
res.push( | ||
this._usages(this._cmds, 'Commands'), | ||
this._usages(this._opts, 'Options'), | ||
this._usages(this._args, 'Arguments') | ||
); | ||
return res.join(EOL); | ||
} | ||
Cmd.prototype.comp = function(_comp) { | ||
this._comp = _comp; | ||
return this; | ||
}; | ||
_usage() { | ||
return Color('lblue', this._name) + ' : ' + this._title; | ||
} | ||
/** | ||
Apply function with arguments in context of command instance. | ||
@param {Function} fn | ||
@param {Array} args | ||
@returns {COA.Cmd} this instance (for chainability) | ||
*/ | ||
_usages(os, title) { | ||
if(!os.length) return; | ||
return ['', title + ':'] | ||
.concat(os.map(o => ` ${o._usage()}`)) | ||
.join(EOL); | ||
} | ||
Cmd.prototype.apply = function() { | ||
var args, fn; | ||
fn = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; | ||
fn.apply(this, args); | ||
return this; | ||
}; | ||
_fullTitle() { | ||
return `${this.isRootCmd? '' : this._cmd._fullTitle() + EOL}${this._title}`; | ||
} | ||
/** | ||
Make command "helpful", i.e. add -h --help flags for print usage. | ||
@returns {COA.Cmd} this instance (for chainability) | ||
*/ | ||
_fullName() { | ||
return `${this.isRootCmd? '' : this._cmd._fullName() + ' '}${PATH.basename(this._name)}`; | ||
} | ||
_ejectOpt(opts, opt) { | ||
const pos = opts.indexOf(opt); | ||
if(pos === -1) return; | ||
Cmd.prototype.helpful = function() { | ||
return this.opt().name('help').title('Help').short('h').long('help').flag().only().act(function() { | ||
return this.usage(); | ||
}).end(); | ||
}; | ||
return opts[pos]._arr? | ||
opts[pos] : | ||
opts.splice(pos, 1)[0]; | ||
} | ||
/** | ||
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) | ||
*/ | ||
_checkRequired(opts, args) { | ||
if(this._opts.some(opt => opt._only && opts.hasOwnProperty(opt._name))) return; | ||
const all = this._opts.concat(this._args); | ||
let i; | ||
while(i = all.shift()) | ||
if(i._req && i._checkParsed(opts, args)) | ||
return this.reject(i._requiredText()); | ||
} | ||
Cmd.prototype.completable = function() { | ||
return this.cmd().name('completion').apply(require('./completion')).end(); | ||
}; | ||
_parseCmd(argv, unparsed) { | ||
unparsed || (unparsed = []); | ||
/** | ||
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) | ||
*/ | ||
let i, | ||
optSeen = false; | ||
while(i = argv.shift()) { | ||
i.indexOf('-') || (optSeen = true); | ||
if(optSeen || !/^\w[\w-_]*$/.test(i)) { | ||
unparsed.push(i); | ||
continue; | ||
} | ||
Cmd.prototype.extendable = function(pattern) { | ||
this._ext = pattern || true; | ||
return this; | ||
}; | ||
let pkg, cmd = this._cmdsByName[i]; | ||
if(!cmd && this._ext) { | ||
if(this._ext === true) { | ||
pkg = i; | ||
let c = this; | ||
while(true) { // eslint-disable-line | ||
pkg = c._name + '-' + pkg; | ||
if(c.isRootCmd) break; | ||
c = c._cmd; | ||
} | ||
} else if(typeof this._ext === 'string') | ||
pkg = ~this._ext.indexOf('%s')? | ||
UTIL.format(this._ext, i) : | ||
this._ext + i; | ||
Cmd.prototype._exit = function(msg, code) { | ||
return process.once('exit', function() { | ||
if (msg) { | ||
console.error(msg); | ||
} | ||
return process.exit(code || 0); | ||
}); | ||
}; | ||
let cmdDesc; | ||
try { | ||
cmdDesc = require(pkg); | ||
} catch(e) { | ||
// Dummy | ||
} | ||
/** | ||
Build full usage text for current command instance. | ||
@returns {String} usage text | ||
*/ | ||
if(cmdDesc) { | ||
if(typeof cmdDesc === 'function') { | ||
this.cmd().name(i).apply(cmdDesc).end(); | ||
} else if(typeof cmdDesc === 'object') { | ||
this.cmd(cmdDesc); | ||
cmdDesc.name(i); | ||
} else throw new Error('Error: Unsupported command declaration type, ' | ||
+ 'should be a function or COA.Cmd() object'); | ||
cmd = this._cmdsByName[i]; | ||
} | ||
} | ||
Cmd.prototype.usage = function() { | ||
var res; | ||
res = []; | ||
if (this._title) { | ||
res.push(this._fullTitle()); | ||
} | ||
res.push('', 'Usage:'); | ||
if (this._cmds.length) { | ||
res.push(['', '', Color('lred', this._fullName()), Color('lblue', 'COMMAND'), Color('lgreen', '[OPTIONS]'), Color('lpurple', '[ARGS]')].join(' ')); | ||
} | ||
if (this._opts.length + this._args.length) { | ||
res.push(['', '', Color('lred', this._fullName()), Color('lgreen', '[OPTIONS]'), Color('lpurple', '[ARGS]')].join(' ')); | ||
} | ||
res.push(this._usages(this._cmds, 'Commands'), this._usages(this._opts, 'Options'), this._usages(this._args, 'Arguments')); | ||
return res.join('\n'); | ||
}; | ||
if(cmd) return cmd._parseCmd(argv, unparsed); | ||
Cmd.prototype._usage = function() { | ||
return Color('lblue', this._name) + ' : ' + this._title; | ||
}; | ||
unparsed.push(i); | ||
} | ||
Cmd.prototype._usages = function(os, title) { | ||
var o, res, _i, _len; | ||
if (!os.length) { | ||
return; | ||
return { cmd : this, argv : unparsed }; | ||
} | ||
res = ['', title + ':']; | ||
for (_i = 0, _len = os.length; _i < _len; _i++) { | ||
o = os[_i]; | ||
res.push(' ' + o._usage()); | ||
} | ||
return res.join('\n'); | ||
}; | ||
Cmd.prototype._fullTitle = function() { | ||
return (this._cmd === this ? '' : this._cmd._fullTitle() + '\n') + this._title; | ||
}; | ||
_parseOptsAndArgs(argv) { | ||
const opts = {}, | ||
args = {}, | ||
nonParsedOpts = this._opts.concat(), | ||
nonParsedArgs = this._args.concat(); | ||
Cmd.prototype._fullName = function() { | ||
return (this._cmd === this ? '' : this._cmd._fullName() + ' ') + PATH.basename(this._name); | ||
}; | ||
let res, i; | ||
while(i = argv.shift()) { | ||
if(i !== '--' && i[0] === '-') { | ||
const m = i.match(/^(--\w[\w-_]*)=(.*)$/); | ||
if(m) { | ||
i = m[1]; | ||
this._optsByKey[i]._flag || argv.unshift(m[2]); | ||
} | ||
Cmd.prototype._ejectOpt = function(opts, opt) { | ||
var pos; | ||
if ((pos = opts.indexOf(opt)) >= 0) { | ||
if (opts[pos]._arr) { | ||
return opts[pos]; | ||
} else { | ||
return opts.splice(pos, 1)[0]; | ||
} | ||
} | ||
}; | ||
const opt = this._ejectOpt(nonParsedOpts, this._optsByKey[i]); | ||
if(!opt) return this.reject(`Unknown option: ${i}`); | ||
Cmd.prototype._checkRequired = function(opts, args) { | ||
var all, i; | ||
if (!(this._opts.filter(function(o) { | ||
return o._only && o._name in opts; | ||
})).length) { | ||
all = this._opts.concat(this._args); | ||
while (i = all.shift()) { | ||
if (i._req && i._checkParsed(opts, args)) { | ||
return this.reject(i._requiredText()); | ||
} | ||
} | ||
} | ||
}; | ||
if(Q.isRejected(res = opt._parse(argv, opts))) return res; | ||
Cmd.prototype._parseCmd = function(argv, unparsed) { | ||
var c, cmd, cmdDesc, e, i, optSeen, pkg; | ||
if (unparsed == null) { | ||
unparsed = []; | ||
} | ||
argv = argv.concat(); | ||
optSeen = false; | ||
while (i = argv.shift()) { | ||
if (!i.indexOf('-')) { | ||
optSeen = true; | ||
} | ||
if (!optSeen && /^\w[\w-_]*$/.test(i)) { | ||
cmd = this._cmdsByName[i]; | ||
if (!cmd && this._ext) { | ||
if (typeof this._ext === 'string') { | ||
if (~this._ext.indexOf('%s')) { | ||
pkg = UTIL.format(this._ext, i); | ||
} else { | ||
pkg = this._ext + i; | ||
continue; | ||
} | ||
} else if (this._ext === true) { | ||
pkg = i; | ||
c = this; | ||
while (true) { | ||
pkg = c._name + '-' + pkg; | ||
if (c._cmd === c) { | ||
break; | ||
} | ||
c = c._cmd; | ||
} | ||
} | ||
try { | ||
cmdDesc = require(pkg); | ||
} catch (_error) { | ||
e = _error; | ||
} | ||
if (cmdDesc) { | ||
if (typeof cmdDesc === 'function') { | ||
this.cmd().name(i).apply(cmdDesc).end(); | ||
} else if (typeof cmdDesc === 'object') { | ||
this.cmd(cmdDesc); | ||
cmdDesc.name(i); | ||
} else { | ||
throw new Error('Error: Unsupported command declaration type, ' + 'should be function or COA.Cmd() object'); | ||
} | ||
cmd = this._cmdsByName[i]; | ||
} | ||
} | ||
if (cmd) { | ||
return cmd._parseCmd(argv, unparsed); | ||
} | ||
} | ||
unparsed.push(i); | ||
} | ||
return { | ||
cmd: this, | ||
argv: unparsed | ||
}; | ||
}; | ||
Cmd.prototype._parseOptsAndArgs = function(argv) { | ||
var a, arg, args, i, m, nonParsedArgs, nonParsedOpts, opt, opts, res; | ||
opts = {}; | ||
args = {}; | ||
nonParsedOpts = this._opts.concat(); | ||
nonParsedArgs = this._args.concat(); | ||
while (i = argv.shift()) { | ||
if (i !== '--' && !i.indexOf('-')) { | ||
if (m = i.match(/^(--\w[\w-_]*)=(.*)$/)) { | ||
i = m[1]; | ||
if (!this._optsByKey[i]._flag) { | ||
argv.unshift(m[2]); | ||
} | ||
} | ||
if (opt = this._ejectOpt(nonParsedOpts, this._optsByKey[i])) { | ||
if (Q.isRejected(res = opt._parse(argv, opts))) { | ||
return res; | ||
} | ||
} else { | ||
return this.reject("Unknown option: " + i); | ||
} | ||
} else { | ||
if (i === '--') { | ||
i = argv.splice(0); | ||
} | ||
i = Array.isArray(i) ? i : [i]; | ||
while (a = i.shift()) { | ||
if (arg = nonParsedArgs.shift()) { | ||
if (arg._arr) { | ||
nonParsedArgs.unshift(arg); | ||
} | ||
if (Q.isRejected(res = arg._parse(a, args))) { | ||
return res; | ||
} | ||
} else { | ||
return this.reject("Unknown argument: " + a); | ||
} | ||
} | ||
} | ||
} | ||
return { | ||
opts: this._setDefaults(opts, nonParsedOpts), | ||
args: this._setDefaults(args, nonParsedArgs) | ||
}; | ||
}; | ||
i === '--' && (i = argv.splice(0)); | ||
Array.isArray(i) || (i = [i]); | ||
Cmd.prototype._setDefaults = function(params, desc) { | ||
var i, _i, _len; | ||
for (_i = 0, _len = desc.length; _i < _len; _i++) { | ||
i = desc[_i]; | ||
if (!(i._name in params) && '_def' in i) { | ||
i._saveVal(params, i._def); | ||
} | ||
} | ||
return params; | ||
}; | ||
let a; | ||
while(a = i.shift()) { | ||
let arg = nonParsedArgs.shift(); | ||
if(!arg) return this.reject(`Unknown argument: ${a}`); | ||
Cmd.prototype._processParams = function(params, desc) { | ||
var i, n, notExists, res, v, vals, _i, _j, _len, _len1; | ||
notExists = []; | ||
for (_i = 0, _len = desc.length; _i < _len; _i++) { | ||
i = desc[_i]; | ||
n = i._name; | ||
if (!(n in params)) { | ||
notExists.push(i); | ||
continue; | ||
} | ||
vals = params[n]; | ||
delete params[n]; | ||
if (!Array.isArray(vals)) { | ||
vals = [vals]; | ||
} | ||
for (_j = 0, _len1 = vals.length; _j < _len1; _j++) { | ||
v = vals[_j]; | ||
if (Q.isRejected(res = i._saveVal(params, v))) { | ||
return res; | ||
arg._arr && nonParsedArgs.unshift(arg); | ||
if(Q.isRejected(res = arg._parse(a, args))) return res; | ||
} | ||
} | ||
} | ||
} | ||
return this._setDefaults(params, notExists); | ||
}; | ||
Cmd.prototype._parseArr = function(argv) { | ||
return Q.when(this._parseCmd(argv), function(p) { | ||
return Q.when(p.cmd._parseOptsAndArgs(p.argv), function(r) { | ||
return { | ||
cmd: p.cmd, | ||
opts: r.opts, | ||
args: r.args | ||
opts : this._setDefaults(opts, nonParsedOpts), | ||
args : this._setDefaults(args, nonParsedArgs) | ||
}; | ||
}); | ||
}); | ||
}; | ||
} | ||
Cmd.prototype._do = function(input) { | ||
var _this = this; | ||
return Q.when(input, function(input) { | ||
var cmd; | ||
cmd = input.cmd; | ||
return [_this._checkRequired].concat(cmd._act || []).reduce(function(res, act) { | ||
return Q.when(res, function(res) { | ||
return act.call(cmd, input.opts, input.args, res); | ||
}); | ||
}, void 0); | ||
}); | ||
}; | ||
_setDefaults(params, desc) { | ||
for(const item of desc) | ||
item._def && | ||
!params.hasOwnProperty(item._name) && | ||
item._saveVal(params, item._def); | ||
/** | ||
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) { | ||
var cb, | ||
_this = this; | ||
if (argv == null) { | ||
argv = process.argv.slice(2); | ||
return params; | ||
} | ||
cb = function(code) { | ||
return function(res) { | ||
var _ref, _ref1; | ||
if (res) { | ||
return _this._exit((_ref = res.stack) != null ? _ref : res.toString(), (_ref1 = res.exitCode) != null ? _ref1 : code); | ||
} else { | ||
return _this._exit(); | ||
} | ||
}; | ||
}; | ||
Q.when(this["do"](argv), cb(0), cb(1)).done(); | ||
return this; | ||
}; | ||
/** | ||
Convenient function to run command from tests. | ||
@param {Array} argv | ||
@returns {Q.Promise} | ||
*/ | ||
_processParams(params, desc) { | ||
const notExists = []; | ||
for(const item of desc) { | ||
const n = item._name; | ||
Cmd.prototype["do"] = function(argv) { | ||
return this._do(this._parseArr(argv || [])); | ||
}; | ||
if(!params.hasOwnProperty(n)) { | ||
notExists.push(item); | ||
continue; | ||
} | ||
/** | ||
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} | ||
*/ | ||
const vals = Array.isArray(params[n])? params[n] : [params[n]]; | ||
delete params[n]; | ||
let res; | ||
for(const v of vals) | ||
if(Q.isRejected(res = item._saveVal(params, v))) | ||
return res; | ||
} | ||
Cmd.prototype.invoke = function(cmds, opts, args) { | ||
var _this = this; | ||
if (cmds == null) { | ||
cmds = []; | ||
return this._setDefaults(params, notExists); | ||
} | ||
if (opts == null) { | ||
opts = {}; | ||
_parseArr(argv) { | ||
return Q.when(this._parseCmd(argv), p => | ||
Q.when(p.cmd._parseOptsAndArgs(p.argv), r => ({ | ||
cmd : p.cmd, | ||
opts : r.opts, | ||
args : r.args | ||
}))); | ||
} | ||
if (args == null) { | ||
args = {}; | ||
_do(inputPromise) { | ||
return Q.when(inputPromise, input => { | ||
return [this._checkRequired] | ||
.concat(input.cmd._act || []) | ||
.reduce((res, act) => | ||
Q.when(res, prev => act.call(input.cmd, input.opts, input.args, prev)), | ||
undefined); | ||
}); | ||
} | ||
if (typeof cmds === 'string') { | ||
cmds = cmds.split(' '); | ||
} | ||
if (arguments.length < 3) { | ||
if (!Array.isArray(cmds)) { | ||
args = opts; | ||
opts = cmds; | ||
cmds = []; | ||
} | ||
} | ||
return Q.when(this._parseCmd(cmds), function(p) { | ||
if (p.argv.length) { | ||
return _this.reject("Unknown command: " + cmds.join(' ')); | ||
} | ||
return Q.all([_this._processParams(opts, _this._opts), _this._processParams(args, _this._args)]).spread(function(opts, args) { | ||
return _this._do({ | ||
cmd: p.cmd, | ||
opts: opts, | ||
args: args | ||
}).fail(function(res) { | ||
if (res && res.exitCode === 0) { | ||
return res.toString(); | ||
} else { | ||
return _this.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 | ||
*/ | ||
/** | ||
* Parse arguments from simple format like NodeJS process.argv | ||
* and run ahead current program, i.e. call process.exit when all actions done. | ||
* | ||
* @param {String[]} argv - arguments | ||
* @returns {COA.Cmd} - this instance (for chainability) | ||
*/ | ||
run(argv) { | ||
argv || (argv = process.argv.slice(2)); | ||
const cb = code => | ||
res => res? | ||
this._exit(res.stack || res.toString(), (res.hasOwnProperty('exitCode')? res.exitCode : code) || 0) : | ||
this._exit(); | ||
Cmd.prototype.reject = function(reason) { | ||
return Q.reject(reason); | ||
}; | ||
Q.when(this.do(argv), cb(0), cb(1)).done(); | ||
/** | ||
Finish chain for current subcommand and return parent command instance. | ||
@returns {COA.Cmd} parent command | ||
*/ | ||
return this; | ||
} | ||
/** | ||
* Invoke specified (or current) command using provided | ||
* options and arguments. | ||
* | ||
* @param {String|String[]} [cmds] - subcommand to invoke (optional) | ||
* @param {Object} [opts] - command options (optional) | ||
* @param {Object} [args] - command arguments (optional) | ||
* @returns {Q.Promise} | ||
*/ | ||
invoke(cmds, opts, args) { | ||
cmds || (cmds = []); | ||
opts || (opts = {}); | ||
args || (args = {}); | ||
typeof cmds === 'string' && (cmds = cmds.split(' ')); | ||
Cmd.prototype.end = function() { | ||
return this._cmd; | ||
}; | ||
if(arguments.length < 3 && !Array.isArray(cmds)) { | ||
args = opts; | ||
opts = cmds; | ||
cmds = []; | ||
} | ||
return Cmd; | ||
return Q.when(this._parseCmd(cmds), p => { | ||
if(p.argv.length) | ||
return this.reject(`Unknown command: ${cmds.join(' ')}`); | ||
})(); | ||
return Q.all([ | ||
this._processParams(opts, this._opts), | ||
this._processParams(args, this._args) | ||
]).spread((_opts, _args) => | ||
this._do({ | ||
cmd : p.cmd, | ||
opts : _opts, | ||
args : _args | ||
}) | ||
.fail(res => (res && res.exitCode === 0)? | ||
res.toString() : | ||
this.reject(res))); | ||
}); | ||
} | ||
} | ||
/** | ||
* Convenient function to run command from tests. | ||
* | ||
* @param {String[]} argv - arguments | ||
* @returns {Q.Promise} | ||
*/ | ||
Cmd.prototype.do = function(argv) { | ||
return this._do(this._parseArr(argv || [])); | ||
}; | ||
module.exports = Cmd; |
@@ -1,25 +0,22 @@ | ||
// Generated by CoffeeScript 1.6.3 | ||
var colors; | ||
'use strict'; | ||
colors = { | ||
black: '30', | ||
dgray: '1;30', | ||
red: '31', | ||
lred: '1;31', | ||
green: '32', | ||
lgreen: '1;32', | ||
brown: '33', | ||
yellow: '1;33', | ||
blue: '34', | ||
lblue: '1;34', | ||
purple: '35', | ||
lpurple: '1;35', | ||
cyan: '36', | ||
lcyan: '1;36', | ||
lgray: '37', | ||
white: '1;37' | ||
const colors = { | ||
black : '30', | ||
dgray : '1;30', | ||
red : '31', | ||
lred : '1;31', | ||
green : '32', | ||
lgreen : '1;32', | ||
brown : '33', | ||
yellow : '1;33', | ||
blue : '34', | ||
lblue : '1;34', | ||
purple : '35', | ||
lpurple : '1;35', | ||
cyan : '36', | ||
lcyan : '1;36', | ||
lgray : '37', | ||
white : '1;37' | ||
}; | ||
exports.Color = function(c, str) { | ||
return ['\x1B[', colors[c], 'm', str, '\x1B[m'].join(''); | ||
}; | ||
module.exports = (c, str) => `\x1B[${colors[c]}m${str}\x1B[m`; |
@@ -1,134 +0,176 @@ | ||
// Generated by CoffeeScript 1.6.3 | ||
'use strict'; | ||
const constants = require('constants'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const Q = require('q'); | ||
const shell = require('./shell'); | ||
const escape = shell.escape; | ||
const unescape = shell.unescape; | ||
/** | ||
Most of the code adopted from the npm package shell completion code. | ||
See https://github.com/isaacs/npm/blob/master/lib/completion.js | ||
*/ | ||
* Most of the code adopted from the npm package shell completion code. | ||
* See https://github.com/isaacs/npm/blob/master/lib/completion.js | ||
* | ||
* @returns {COA.CoaObject} | ||
*/ | ||
module.exports = function completion() { | ||
return this | ||
.title('Shell completion') | ||
.helpful() | ||
.arg() | ||
.name('raw') | ||
.title('Completion words') | ||
.arr() | ||
.end() | ||
.act((opts, args) => { | ||
if(process.platform === 'win32') { | ||
const e = new Error('shell completion not supported on windows'); | ||
e.code = 'ENOTSUP'; | ||
e.errno = constants.ENOTSUP; | ||
return this.reject(e); | ||
} | ||
var Q, complete, dumpScript, escape, getOpts, unescape; | ||
// if the COMP_* isn't in the env, then just dump the script | ||
if((process.env.COMP_CWORD == null) | ||
|| (process.env.COMP_LINE == null) | ||
|| (process.env.COMP_POINT == null)) { | ||
return dumpScript(this._cmd._name); | ||
} | ||
Q = require('q'); | ||
console.error('COMP_LINE: %s', process.env.COMP_LINE); | ||
console.error('COMP_CWORD: %s', process.env.COMP_CWORD); | ||
console.error('COMP_POINT: %s', process.env.COMP_POINT); | ||
console.error('args: %j', args.raw); | ||
escape = require('./shell').escape; | ||
// completion opts | ||
opts = getOpts(args.raw); | ||
unescape = require('./shell').unescape; | ||
// cmd | ||
const parsed = this._cmd._parseCmd(opts.partialWords); | ||
return Q.when(complete(parsed.cmd, parsed.opts), compls => { | ||
console.error('filtered: %j', compls); | ||
return console.log(compls.map(escape).join('\n')); | ||
}); | ||
}); | ||
}; | ||
module.exports = function() { | ||
return this.title('Shell completion').helpful().arg().name('raw').title('Completion words').arr().end().act(function(opts, args) { | ||
var argv, cmd, e, _ref; | ||
if (process.platform === 'win32') { | ||
e = new Error('shell completion not supported on windows'); | ||
e.code = 'ENOTSUP'; | ||
e.errno = require('constants').ENOTSUP; | ||
return this.reject(e); | ||
} | ||
if ((process.env.COMP_CWORD == null) || (process.env.COMP_LINE == null) || (process.env.COMP_POINT == null)) { | ||
return dumpScript(this._cmd._name); | ||
} | ||
console.error('COMP_LINE: %s', process.env.COMP_LINE); | ||
console.error('COMP_CWORD: %s', process.env.COMP_CWORD); | ||
console.error('COMP_POINT: %s', process.env.COMP_POINT); | ||
console.error('args: %j', args.raw); | ||
opts = getOpts(args.raw); | ||
_ref = this._cmd._parseCmd(opts.partialWords), cmd = _ref.cmd, argv = _ref.argv; | ||
return Q.when(complete(cmd, opts), function(compls) { | ||
console.error('filtered: %j', compls); | ||
return console.log(compls.map(escape).join('\n')); | ||
function dumpScript(name) { | ||
const defer = Q.defer(); | ||
fs.readFile(path.resolve(__dirname, 'completion.sh'), 'utf8', function(err, d) { | ||
if(err) return defer.reject(err); | ||
d = d.replace(/{{cmd}}/g, path.basename(name)).replace(/^\#\!.*?\n/, ''); | ||
process.stdout.on('error', onError); | ||
process.stdout.write(d, () => defer.resolve()); | ||
}); | ||
}); | ||
}; | ||
dumpScript = function(name) { | ||
var defer, fs, path; | ||
fs = require('fs'); | ||
path = require('path'); | ||
defer = Q.defer(); | ||
fs.readFile(path.resolve(__dirname, 'completion.sh'), 'utf8', function(err, d) { | ||
var onError; | ||
if (err) { | ||
return defer.reject(err); | ||
} | ||
d = d.replace(/{{cmd}}/g, path.basename(name)).replace(/^\#\!.*?\n/, ''); | ||
onError = function(err) { | ||
if (err.errno === require('constants').EPIPE) { | ||
return defer.promise; | ||
function onError(err) { | ||
// Darwin is a real dick sometimes. | ||
// | ||
// This is necessary because the "source" or "." program in | ||
// bash on OS X closes its file argument before reading | ||
// from it, meaning that you get exactly 1 write, which will | ||
// work most of the time, and will always raise an EPIPE. | ||
// | ||
// Really, one should not be tossing away EPIPE errors, or any | ||
// errors, so casually. But, without this, `. <(cmd completion)` | ||
// can never ever work on OS X. | ||
if(err.errno !== constants.EPIPE) return defer.reject(err); | ||
process.stdout.removeListener('error', onError); | ||
return defer.resolve(); | ||
} else { | ||
return defer.reject(err); | ||
} | ||
} | ||
} | ||
function getOpts(argv) { | ||
// get the partial line and partial word, if the point isn't at the end | ||
// ie, tabbing at: cmd foo b|ar | ||
const line = process.env.COMP_LINE; | ||
const w = +process.env.COMP_CWORD; | ||
const point = +process.env.COMP_POINT; | ||
const words = argv.map(unescape); | ||
const word = words[w]; | ||
const partialLine = line.substr(0, point); | ||
const partialWords = words.slice(0, w); | ||
// figure out where in that last word the point is | ||
let partialWord = argv[w] || ''; | ||
let i = partialWord.length; | ||
while(partialWord.substr(0, i) !== partialLine.substr(-1 * i) && i > 0) i--; | ||
partialWord = unescape(partialWord.substr(0, i)); | ||
partialWord && partialWords.push(partialWord); | ||
return { | ||
line, | ||
w, | ||
point, | ||
words, | ||
word, | ||
partialLine, | ||
partialWords, | ||
partialWord | ||
}; | ||
process.stdout.on('error', onError); | ||
return process.stdout.write(d, function() { | ||
return defer.resolve(); | ||
}); | ||
}); | ||
return defer.promise; | ||
}; | ||
} | ||
getOpts = function(argv) { | ||
var i, line, partialLine, partialWord, partialWords, point, w, word, words; | ||
line = process.env.COMP_LINE; | ||
w = +process.env.COMP_CWORD; | ||
point = +process.env.COMP_POINT; | ||
words = argv.map(unescape); | ||
word = words[w]; | ||
partialLine = line.substr(0, point); | ||
partialWords = words.slice(0, w); | ||
partialWord = argv[w] || ''; | ||
i = partialWord.length; | ||
while (partialWord.substr(0, i) !== partialLine.substr(-1 * i) && i > 0) { | ||
i--; | ||
} | ||
partialWord = unescape(partialWord.substr(0, i)); | ||
if (partialWord) { | ||
partialWords.push(partialWord); | ||
} | ||
return { | ||
line: line, | ||
w: w, | ||
point: point, | ||
words: words, | ||
word: word, | ||
partialLine: partialLine, | ||
partialWords: partialWords, | ||
partialWord: partialWord | ||
}; | ||
}; | ||
function complete(cmd, opts) { | ||
let optWord, optPrefix, | ||
compls = []; | ||
complete = function(cmd, opts) { | ||
var compls, m, o, opt, optPrefix, optWord; | ||
compls = []; | ||
if (opts.partialWord.indexOf('-')) { | ||
compls = Object.keys(cmd._cmdsByName); | ||
} else { | ||
if (m = opts.partialWord.match(/^(--\w[\w-_]*)=(.*)$/)) { | ||
optWord = m[1]; | ||
optPrefix = optWord + '='; | ||
} else { | ||
compls = Object.keys(cmd._optsByKey); | ||
// Complete on cmds | ||
if(opts.partialWord.indexOf('-')) | ||
compls = Object.keys(cmd._cmdsByName); | ||
// Complete on required opts without '-' in last partial word | ||
// (if required not already specified) | ||
// | ||
// Commented out because of uselessness: | ||
// -b, --block suggest results in '-' on cmd line; | ||
// next completion suggest all options, because of '-' | ||
//.concat Object.keys(cmd._optsByKey).filter (v) -> cmd._optsByKey[v]._req | ||
else { | ||
// complete on opt values: --opt=| case | ||
const m = opts.partialWord.match(/^(--\w[\w-_]*)=(.*)$/); | ||
if(m) { | ||
optWord = m[1]; | ||
optPrefix = optWord + '='; | ||
} else | ||
// complete on opts | ||
// don't complete on opts in case of --opt=val completion | ||
// TODO: don't complete on opts in case of unknown arg after commands | ||
// TODO: complete only on opts with arr() or not already used | ||
// TODO: complete only on full opts? | ||
compls = Object.keys(cmd._optsByKey); | ||
} | ||
} | ||
if (!(o = opts.partialWords[opts.w - 1]).indexOf('-')) { | ||
optWord = o; | ||
} | ||
if (optWord && (opt = cmd._optsByKey[optWord])) { | ||
if (!opt._flag && opt._comp) { | ||
compls = Q.join(compls, Q.when(opt._comp(opts), function(c, o) { | ||
return c.concat(o.map(function(v) { | ||
return (optPrefix || '') + v; | ||
})); | ||
})); | ||
} | ||
} | ||
if (cmd._comp) { | ||
compls = Q.join(compls, Q.when(cmd._comp(opts)), function(c, o) { | ||
return c.concat(o); | ||
// complete on opt values: next arg case | ||
opts.partialWords[opts.w - 1].indexOf('-') || (optWord = opts.partialWords[opts.w - 1]); | ||
// complete on opt values: completion | ||
let opt; | ||
optWord | ||
&& (opt = cmd._optsByKey[optWord]) | ||
&& !opt._flag | ||
&& opt._comp | ||
&& (compls = Q.join(compls, | ||
Q.when(opt._comp(opts), | ||
(c, o) => c.concat(o.map(v => (optPrefix || '') + v))))); | ||
// TODO: complete on args values (context aware, custom completion?) | ||
// custom completion on cmds | ||
cmd._comp && (compls = Q.join(compls, Q.when(cmd._comp(opts)), (c, o) => c.concat(o))); | ||
// TODO: context aware custom completion on cmds, opts and args | ||
// (can depend on already entered values, especially options) | ||
return Q.when(compls, complitions => { | ||
console.error('partialWord: %s', opts.partialWord); | ||
console.error('compls: %j', complitions); | ||
return compls.filter(c => c.indexOf(opts.partialWord) === 0); | ||
}); | ||
} | ||
return Q.when(compls, function(compls) { | ||
console.error('partialWord: %s', opts.partialWord); | ||
console.error('compls: %j', compls); | ||
return compls.filter(function(c) { | ||
return c.indexOf(opts.partialWord) === 0; | ||
}); | ||
}); | ||
}; | ||
} |
@@ -1,10 +0,14 @@ | ||
// Generated by CoffeeScript 1.6.3 | ||
exports.Cmd = require('./cmd').Cmd; | ||
const | ||
Cmd = require('./cmd'), | ||
Opt = require('./opt'), | ||
Arg = require('./arg'), | ||
shell = require('./shell'); | ||
exports.Opt = require('./cmd').Opt; | ||
exports.Arg = require('./cmd').Arg; | ||
exports.shell = require('./shell'); | ||
exports.require = require; | ||
module.exports = { | ||
Cmd : Cmd.create, | ||
Opt : Opt.create, | ||
Arg : Arg.create, | ||
classes : { Cmd, Opt, Arg }, | ||
shell, | ||
require | ||
}; |
435
lib/opt.js
@@ -1,338 +0,155 @@ | ||
// Generated by CoffeeScript 1.6.3 | ||
var Cmd, Color, Opt, Q, fs; | ||
'use strict'; | ||
fs = require('fs'); | ||
const | ||
Q = require('q'), | ||
Q = require('q'); | ||
CoaParam = require('./coaparam'), | ||
Color = require('./color'); | ||
Color = require('./color').Color; | ||
Cmd = require('./cmd').Cmd; | ||
/** | ||
Option | ||
* Option | ||
* | ||
* Named entity. Options may have short and long keys for use from command line. | ||
* | ||
* @namespace | ||
* @class Opt | ||
* @extends CoaParam | ||
*/ | ||
module.exports = class Opt extends CoaParam { | ||
/** | ||
* @constructs | ||
* @param {COA.Cmd} cmd - parent command | ||
*/ | ||
constructor(cmd) { | ||
super(cmd); | ||
Named entity. Options may have short and long keys for use from command line. | ||
@namespace | ||
@class Presents option | ||
*/ | ||
this._short = null; | ||
this._long = null; | ||
this._flag = false; | ||
this._only = false; | ||
this._cmd._opts.push(this); | ||
} | ||
/** | ||
* Set a short key for option to be used with one hyphen from command line. | ||
* | ||
* @param {String} short - short name | ||
* @returns {COA.Opt} - this instance (for chainability) | ||
*/ | ||
short(short) { | ||
this._short = short; | ||
this._cmd._optsByKey[`-${short}`] = this; | ||
return this; | ||
} | ||
exports.Opt = Opt = (function() { | ||
/** | ||
@constructs | ||
@param {COA.Cmd} cmd parent command | ||
*/ | ||
/** | ||
* Set a short key for option to be used with double hyphens from command line. | ||
* | ||
* @param {String} long - long name | ||
* @returns {COA.Opt} - this instance (for chainability) | ||
*/ | ||
long(long) { | ||
this._long = long; | ||
this._cmd._optsByKey[`--${long}`] = this; | ||
return this; | ||
} | ||
function Opt(_cmd) { | ||
this._cmd = _cmd; | ||
this._cmd._opts.push(this); | ||
} | ||
/** | ||
* Make an option boolean, i.e. option without value. | ||
* | ||
* @returns {COA.Opt} - this instance (for chainability) | ||
*/ | ||
flag() { | ||
this._flag = true; | ||
return this; | ||
} | ||
/** | ||
Set a canonical option identifier to be used anywhere in the API. | ||
@param {String} _name option name | ||
@returns {COA.Opt} this instance (for chainability) | ||
*/ | ||
/** | ||
* Makes an option to act as a command, | ||
* i.e. program will exit just after option action. | ||
* | ||
* @returns {COA.Opt} - this instance (for chainability) | ||
*/ | ||
only() { | ||
this._only = true; | ||
return this; | ||
} | ||
/** | ||
* Add action for current option command. | ||
* This action is performed if the current option | ||
* is present in parsed options (with any value). | ||
* | ||
* @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. | ||
* @returns {COA.Opt} - this instance (for chainability) | ||
*/ | ||
act(act) { | ||
// Need function here for arguments | ||
const opt = this; | ||
this._cmd.act(function(opts) { | ||
if(!opts.hasOwnProperty(opt._name)) return; | ||
Opt.prototype.name = function(_name) { | ||
this._name = _name; | ||
return this; | ||
}; | ||
const res = act.apply(this, arguments); | ||
if(!opt._only) return res; | ||
/** | ||
Set a long description for option to be used anywhere in text messages. | ||
@param {String} _title option title | ||
@returns {COA.Opt} this instance (for chainability) | ||
*/ | ||
return Q.when(res, out => this.reject({ | ||
toString : () => out.toString(), | ||
exitCode : 0 | ||
})); | ||
}); | ||
return this; | ||
} | ||
Opt.prototype.title = Cmd.prototype.title; | ||
_saveVal(opts, val) { | ||
this._val && (val = this._val(val)); | ||
/** | ||
Set a short key for option to be used with one hyphen from command line. | ||
@param {String} _short | ||
@returns {COA.Opt} this instance (for chainability) | ||
*/ | ||
const name = this._name; | ||
this._arr | ||
? (opts[name] || (opts[name] = [])).push(val) | ||
: (opts[name] = val); | ||
return val; | ||
} | ||
Opt.prototype.short = function(_short) { | ||
this._short = _short; | ||
return this._cmd._optsByKey['-' + _short] = this; | ||
}; | ||
_parse(argv, opts) { | ||
return this._saveVal(opts, this._flag ? true : argv.shift()); | ||
} | ||
/** | ||
Set a short key for option to be used with double hyphens from command line. | ||
@param {String} _long | ||
@returns {COA.Opt} this instance (for chainability) | ||
*/ | ||
_checkParsed(opts) { | ||
return !opts.hasOwnProperty(this._name); | ||
} | ||
_usage() { | ||
const res = [], | ||
nameStr = this._name.toUpperCase(); | ||
Opt.prototype.long = function(_long) { | ||
this._long = _long; | ||
return this._cmd._optsByKey['--' + _long] = this; | ||
}; | ||
/** | ||
Make an option boolean, i.e. option without value. | ||
@returns {COA.Opt} this instance (for chainability) | ||
*/ | ||
Opt.prototype.flag = function() { | ||
this._flag = true; | ||
return this; | ||
}; | ||
/** | ||
Makes an option accepts multiple values. | ||
Otherwise, the value will be used by the latter passed. | ||
@returns {COA.Opt} this instance (for chainability) | ||
*/ | ||
Opt.prototype.arr = function() { | ||
this._arr = true; | ||
return this; | ||
}; | ||
/** | ||
Makes an option required. | ||
@returns {COA.Opt} this instance (for chainability) | ||
*/ | ||
Opt.prototype.req = function() { | ||
this._req = true; | ||
return this; | ||
}; | ||
/** | ||
Makes an option to act as a command, | ||
i.e. program will exit just after option action. | ||
@returns {COA.Opt} this instance (for chainability) | ||
*/ | ||
Opt.prototype.only = function() { | ||
this._only = true; | ||
return this; | ||
}; | ||
/** | ||
Set a validation (or value) function for option. | ||
Value from command line passes through before becoming available from API. | ||
Using for validation and convertion simple types to any values. | ||
@param {Function} _val validating function, | ||
invoked in the context of option instance | ||
and has one parameter with value from command line | ||
@returns {COA.Opt} this instance (for chainability) | ||
*/ | ||
Opt.prototype.val = function(_val) { | ||
this._val = _val; | ||
return this; | ||
}; | ||
/** | ||
Set a default value for option. | ||
Default value passed through validation function as ordinary value. | ||
@param {Object} _def | ||
@returns {COA.Opt} this instance (for chainability) | ||
*/ | ||
Opt.prototype.def = function(_def) { | ||
this._def = _def; | ||
return this; | ||
}; | ||
/** | ||
Make option value inputting stream. | ||
It's add useful validation and shortcut for STDIN. | ||
@returns {COA.Opt} this instance (for chainability) | ||
*/ | ||
Opt.prototype.input = function() { | ||
process.stdin.pause(); | ||
return this.def(process.stdin).val(function(v) { | ||
var s; | ||
if (typeof v === 'string') { | ||
if (v === '-') { | ||
return process.stdin; | ||
} else { | ||
s = fs.createReadStream(v, { | ||
encoding: 'utf8' | ||
}); | ||
s.pause(); | ||
return s; | ||
if(this._short) { | ||
res.push('-', Color('lgreen', this._short)); | ||
this._flag || res.push(' ' + nameStr); | ||
res.push(', '); | ||
} | ||
} else { | ||
return v; | ||
} | ||
}); | ||
}; | ||
/** | ||
Make option value outputing stream. | ||
It's add useful validation and shortcut for STDOUT. | ||
@returns {COA.Opt} this instance (for chainability) | ||
*/ | ||
Opt.prototype.output = function() { | ||
return this.def(process.stdout).val(function(v) { | ||
if (typeof v === 'string') { | ||
if (v === '-') { | ||
return process.stdout; | ||
} else { | ||
return fs.createWriteStream(v, { | ||
encoding: 'utf8' | ||
}); | ||
if(this._long) { | ||
res.push('--', Color('green', this._long)); | ||
this._flag || res.push('=' + nameStr); | ||
} | ||
} else { | ||
return v; | ||
} | ||
}); | ||
}; | ||
/** | ||
Add action for current option command. | ||
This action is performed if the current option | ||
is present in parsed options (with any value). | ||
@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. | ||
@returns {COA.Opt} this instance (for chainability) | ||
*/ | ||
res.push(' : ', this._title); | ||
this._req && res.push(' ', Color('lred', '(required)')); | ||
Opt.prototype.act = function(act) { | ||
var name, opt; | ||
opt = this; | ||
name = this._name; | ||
this._cmd.act(function(opts) { | ||
var res, | ||
_this = this; | ||
if (name in opts) { | ||
res = act.apply(this, arguments); | ||
if (opt._only) { | ||
return Q.when(res, function(res) { | ||
return _this.reject({ | ||
toString: function() { | ||
return res.toString(); | ||
}, | ||
exitCode: 0 | ||
}); | ||
}); | ||
} else { | ||
return res; | ||
} | ||
} | ||
}); | ||
return this; | ||
}; | ||
/** | ||
Set custom additional completion for current option. | ||
@param {Function} completion generation function, | ||
invoked in the context of option instance. | ||
Accepts parameters: | ||
- {Object} opts completion options | ||
It can return promise or any other value treated as result. | ||
@returns {COA.Opt} this instance (for chainability) | ||
*/ | ||
Opt.prototype.comp = Cmd.prototype.comp; | ||
Opt.prototype._saveVal = function(opts, val) { | ||
var _name; | ||
if (this._val) { | ||
val = this._val(val); | ||
return res.join(''); | ||
} | ||
if (this._arr) { | ||
(opts[_name = this._name] || (opts[_name] = [])).push(val); | ||
} else { | ||
opts[this._name] = val; | ||
} | ||
return val; | ||
}; | ||
Opt.prototype._parse = function(argv, opts) { | ||
return this._saveVal(opts, this._flag ? true : argv.shift()); | ||
}; | ||
Opt.prototype._checkParsed = function(opts, args) { | ||
return !opts.hasOwnProperty(this._name); | ||
}; | ||
Opt.prototype._usage = function() { | ||
var nameStr, res; | ||
res = []; | ||
nameStr = this._name.toUpperCase(); | ||
if (this._short) { | ||
res.push('-', Color('lgreen', this._short)); | ||
if (!this._flag) { | ||
res.push(' ' + nameStr); | ||
} | ||
res.push(', '); | ||
_requiredText() { | ||
return `Missing required option:\n ${this._usage()}`; | ||
} | ||
if (this._long) { | ||
res.push('--', Color('green', this._long)); | ||
if (!this._flag) { | ||
res.push('=' + nameStr); | ||
} | ||
} | ||
res.push(' : ', this._title); | ||
if (this._req) { | ||
res.push(' ', Color('lred', '(required)')); | ||
} | ||
return res.join(''); | ||
}; | ||
Opt.prototype._requiredText = function() { | ||
return 'Missing required option:\n ' + this._usage(); | ||
}; | ||
/** | ||
Return rejected promise with error code. | ||
Use in .val() for return with error. | ||
@param {Object} reject reason | ||
You can customize toString() method and exitCode property | ||
of reason object. | ||
@returns {Q.promise} rejected promise | ||
*/ | ||
Opt.prototype.reject = Cmd.prototype.reject; | ||
/** | ||
Finish chain for current option and return parent command instance. | ||
@returns {COA.Cmd} parent command | ||
*/ | ||
Opt.prototype.end = Cmd.prototype.end; | ||
/** | ||
Apply function with arguments in context of option instance. | ||
@param {Function} fn | ||
@param {Array} args | ||
@returns {COA.Opt} this instance (for chainability) | ||
*/ | ||
Opt.prototype.apply = Cmd.prototype.apply; | ||
return Opt; | ||
})(); | ||
}; |
@@ -1,14 +0,14 @@ | ||
// Generated by CoffeeScript 1.6.3 | ||
exports.unescape = function(w) { | ||
w = w.charAt(0) === '"' ? w.replace(/^"|([^\\])"$/g, '$1') : w.replace(/\\ /g, ' '); | ||
return w.replace(/\\("|'|\$|`|\\)/g, '$1'); | ||
}; | ||
module.exports = { escape, unescape }; | ||
exports.escape = function(w) { | ||
w = w.replace(/(["'$`\\])/g, '\\$1'); | ||
if (w.match(/\s+/)) { | ||
return '"' + w + '"'; | ||
} else { | ||
return w; | ||
} | ||
}; | ||
function unescape(w) { | ||
w = w.charAt(0) === '"' | ||
? w.replace(/^"|([^\\])"$/g, '$1') | ||
: w.replace(/\\ /g, ' '); | ||
return w.replace(/\\("|'|\$|`|\\)/g, '$1'); | ||
} | ||
function escape(w) { | ||
w = w.replace(/(["'$`\\])/g,'\\$1'); | ||
return w.match(/\s+/) ? `"${w}"` : w; | ||
} |
{ | ||
"name": "coa", | ||
"description": "Command-Option-Argument: Yet another parser for command line options.", | ||
"version": "1.0.4", | ||
"version": "2.0.0", | ||
"homepage": "http://github.com/veged/coa", | ||
@@ -14,2 +14,7 @@ "author": "Sergey Berezhnoy <veged@ya.ru> (http://github.com/veged)", | ||
], | ||
"files": [ | ||
"lib/", | ||
"index.js", | ||
"README.ru.md" | ||
], | ||
"repository": { | ||
@@ -26,21 +31,21 @@ "type": "git", | ||
"devDependencies": { | ||
"coffee-script": "~1.6.3", | ||
"istanbul": "~0.1.40", | ||
"mocha-istanbul": "*", | ||
"chai": "~1.7.2", | ||
"coveralls": "^2.11.16", | ||
"eslint": "^3.15.0", | ||
"eslint-config-pedant": "^0.8.0", | ||
"mocha": "~1.21.4", | ||
"chai": "~1.7.2" | ||
"nyc": "^10.1.2" | ||
}, | ||
"scripts": { | ||
"test": "make test", | ||
"coverage": "make coverage" | ||
"clean": "rm -r .nyc_output coverage", | ||
"coverage": "nyc --reporter=text --reporter=html mocha; echo; echo 'Open coverage/index.html file in your browser'", | ||
"coveralls": "nyc report --reporter=text-lcov | coveralls", | ||
"lint": "eslint .", | ||
"pretest": "npm run lint", | ||
"test": "nyc mocha" | ||
}, | ||
"engines": { | ||
"node": ">= 0.8.0" | ||
"node": ">= 4.0" | ||
}, | ||
"licenses": [ | ||
{ | ||
"type": "MIT" | ||
} | ||
], | ||
"optionalDependencies": {} | ||
"license": "MIT" | ||
} |
# Command-Option-Argument | ||
[![build status](https://secure.travis-ci.org/veged/coa.png)](http://travis-ci.org/veged/coa) | ||
Yet another parser for command line options. | ||
[![NPM Status][npm-img]][npm] | ||
[![Travis Status][test-img]][travis] | ||
[![AppVeyor Status][appveyor-img]][appveyor] | ||
[![Coverage Status][coverage-img]][coveralls] | ||
[![Dependency Status][dependency-img]][david] | ||
[npm]: https://www.npmjs.org/package/coa | ||
[npm-img]: https://img.shields.io/npm/v/coa.svg | ||
[travis]: https://travis-ci.org/veged/coa | ||
[test-img]: https://img.shields.io/travis/veged/coa.svg | ||
[appveyor]: https://ci.appveyor.com/project/zxqfox/coa | ||
[appveyor-img]: https://ci.appveyor.com/api/projects/status/github/veged/coa?svg=true | ||
[coveralls]: https://coveralls.io/r/veged/coa | ||
[coverage-img]: https://img.shields.io/coveralls/veged/coa.svg | ||
[david]: https://david-dm.org/veged/coa | ||
[dependency-img]: http://img.shields.io/david/veged/coa.svg | ||
## What is it? | ||
@@ -5,0 +23,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
341
13
70491
6
15
985