// Generated by CoffeeScript 1.6.3 var Cmd, Color, PATH, Q, UTIL, __slice = [].slice; UTIL = require('util'); PATH = require('path'); Color = require('./color').Color; Q = require('q'); /** Command Top level entity. Commands may have options and arguments. @namespace @class Presents command */ exports.Cmd = Cmd = (function() { /** @constructs @param {COA.Cmd} [cmd] parent command */ function Cmd(cmd) { if (!(this instanceof Cmd)) { return new Cmd(cmd); } 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 }); }; /** Returns object containing all its subcommands as methods to use from other programs. @returns {Object} */ Cmd.get('api', function() { var c, _fn, _this = this; if (!this._api) { this._api = function() { return _this.invoke.apply(_this, arguments); }; } _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; } } 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) */ Cmd.prototype.name = function(_name) { this._name = _name; if (this._cmd !== this) { this._cmd._cmdsByName[_name] = 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) */ Cmd.prototype.title = function(_title) { this._title = _title; return this; }; /** Create new or add existing subcommand for current command. @param {COA.Cmd} [cmd] existing command instance @returns {COA.Cmd} new subcommand instance */ Cmd.prototype.cmd = function(cmd) { if (cmd) { return cmd._parent(this); } else { return new Cmd(this); } }; /** Create option for current command. @returns {COA.Opt} new option instance */ Cmd.prototype.opt = function() { return new (require('./opt').Opt)(this); }; /** Create argument for current command. @returns {COA.Opt} new argument instance */ Cmd.prototype.arg = function() { return new (require('./arg').Arg)(this); }; /** 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) */ Cmd.prototype.act = function(act, force) { if (!act) { return this; } if (!force && this._act) { this._act.push(act); } else { this._act = [act]; } return this; }; /** 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) */ Cmd.prototype.comp = function(_comp) { this._comp = _comp; return this; }; /** Apply function with arguments in context of command instance. @param {Function} fn @param {Array} args @returns {COA.Cmd} this instance (for chainability) */ Cmd.prototype.apply = function() { var args, fn; fn = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; fn.apply(this, args); return this; }; /** Make command "helpful", i.e. add -h --help flags for print usage. @returns {COA.Cmd} this instance (for chainability) */ Cmd.prototype.helpful = function() { 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) */ Cmd.prototype.completable = function() { return this.cmd().name('completion').apply(require('./completion')).end(); }; /** Allow command to be extendable by external node.js modules. @param {String} [pattern] Pattern of node.js module to find subcommands at. @returns {COA.Cmd} this instance (for chainability) */ Cmd.prototype.extendable = function(pattern) { this._ext = pattern || true; return this; }; Cmd.prototype._exit = function(msg, code) { return process.once('exit', function() { if (msg) { console.error(msg); } return process.exit(code || 0); }); }; /** Build full usage text for current command instance. @returns {String} usage text */ 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'); }; Cmd.prototype._usage = function() { return Color('lblue', this._name) + ' : ' + this._title; }; Cmd.prototype._usages = function(os, title) { var o, res, _i, _len; if (!os.length) { return; } 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; }; Cmd.prototype._fullName = function() { return (this._cmd === this ? '' : this._cmd._fullName() + ' ') + PATH.basename(this._name); }; 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]; } } }; 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()); } } } }; 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; } } 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) }; }; 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; }; 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; } } } 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 }; }); }); }; 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); }); }; /** 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); } 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} */ Cmd.prototype["do"] = function(argv) { return this._do(this._parseArr(argv || [])); }; /** Invoke specified (or current) command using provided options and arguments. @param {String|Array} cmds subcommand to invoke (optional) @param {Object} opts command options (optional) @param {Object} args command arguments (optional) @returns {Q.Promise} */ Cmd.prototype.invoke = function(cmds, opts, args) { var _this = this; if (cmds == null) { cmds = []; } if (opts == null) { opts = {}; } if (args == null) { args = {}; } 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 */ Cmd.prototype.reject = function(reason) { return Q.reject(reason); }; /** Finish chain for current subcommand and return parent command instance. @returns {COA.Cmd} parent command */ Cmd.prototype.end = function() { return this._cmd; }; return Cmd; })();