cmd.js 15 KB

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