index.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948
  1. var MAX_LINE_WIDTH = process.stdout.columns || 200;
  2. var MIN_OFFSET = 25;
  3. var errorHandler;
  4. var commandsPath;
  5. var reAstral = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
  6. var ansiRegex = /\x1B\[([0-9]{1,3}(;[0-9]{1,3})*)?[m|K]/g;
  7. var hasOwnProperty = Object.prototype.hasOwnProperty;
  8. function stringLength(str){
  9. return str
  10. .replace(ansiRegex, '')
  11. .replace(reAstral, ' ')
  12. .length;
  13. }
  14. function camelize(name){
  15. return name.replace(/-(.)/g, function(m, ch){
  16. return ch.toUpperCase();
  17. });
  18. }
  19. function assign(dest, source){
  20. for (var key in source)
  21. if (hasOwnProperty.call(source, key))
  22. dest[key] = source[key];
  23. return dest;
  24. }
  25. function returnFirstArg(value){
  26. return value;
  27. }
  28. function pad(width, str){
  29. return str + Array(Math.max(0, width - stringLength(str)) + 1).join(' ');
  30. }
  31. function noop(){
  32. // nothing todo
  33. }
  34. function parseParams(str){
  35. // params [..<required>] [..[optional]]
  36. // <foo> - require
  37. // [foo] - optional
  38. var tmp;
  39. var left = str.trim();
  40. var result = {
  41. minArgsCount: 0,
  42. maxArgsCount: 0,
  43. args: []
  44. };
  45. do {
  46. tmp = left;
  47. left = left.replace(/^<([a-zA-Z][a-zA-Z0-9\-\_]*)>\s*/, function(m, name){
  48. result.args.push(new Argument(name, true));
  49. result.minArgsCount++;
  50. result.maxArgsCount++;
  51. return '';
  52. });
  53. }
  54. while (tmp != left);
  55. do {
  56. tmp = left;
  57. left = left.replace(/^\[([a-zA-Z][a-zA-Z0-9\-\_]*)\]\s*/, function(m, name){
  58. result.args.push(new Argument(name, false));
  59. result.maxArgsCount++;
  60. return '';
  61. });
  62. }
  63. while (tmp != left);
  64. if (left)
  65. throw new SyntaxError('Bad parameter description: ' + str);
  66. return result.args.length ? result : false;
  67. }
  68. /**
  69. * @class
  70. */
  71. var SyntaxError = function(message){
  72. this.message = message;
  73. };
  74. SyntaxError.prototype = Object.create(Error.prototype);
  75. SyntaxError.prototype.name = 'SyntaxError';
  76. SyntaxError.prototype.clap = true;
  77. /**
  78. * @class
  79. */
  80. var Argument = function(name, required){
  81. this.name = name;
  82. this.required = required;
  83. };
  84. Argument.prototype = {
  85. required: false,
  86. name: '',
  87. normalize: returnFirstArg,
  88. suggest: function(){
  89. return [];
  90. }
  91. };
  92. /**
  93. * @class
  94. * @param {string} usage
  95. * @param {string} description
  96. */
  97. var Option = function(usage, description){
  98. var self = this;
  99. var params;
  100. var left = usage.trim()
  101. // short usage
  102. // -x
  103. .replace(/^-([a-zA-Z])(?:\s*,\s*|\s+)/, function(m, name){
  104. self.short = name;
  105. return '';
  106. })
  107. // long usage
  108. // --flag
  109. // --no-flag - invert value if flag is boolean
  110. .replace(/^--([a-zA-Z][a-zA-Z0-9\-\_]+)\s*/, function(m, name){
  111. self.long = name;
  112. self.name = name.replace(/(^|-)no-/, '$1');
  113. self.defValue = self.name != self.long;
  114. return '';
  115. });
  116. if (!this.long)
  117. throw new SyntaxError('Usage has no long name: ' + usage);
  118. try {
  119. params = parseParams(left);
  120. } catch(e) {
  121. throw new SyntaxError('Bad paramenter description in usage for option: ' + usage, e);
  122. }
  123. if (params)
  124. {
  125. left = '';
  126. this.name = this.long;
  127. this.defValue = undefined;
  128. assign(this, params);
  129. }
  130. if (left)
  131. throw new SyntaxError('Bad usage description for option: ' + usage);
  132. if (!this.name)
  133. this.name = this.long;
  134. this.description = description || '';
  135. this.usage = usage.trim();
  136. this.camelName = camelize(this.name);
  137. };
  138. Option.prototype = {
  139. name: '',
  140. description: '',
  141. short: '',
  142. long: '',
  143. beforeInit: false,
  144. required: false,
  145. minArgsCount: 0,
  146. maxArgsCount: 0,
  147. args: null,
  148. defValue: undefined,
  149. normalize: returnFirstArg
  150. };
  151. //
  152. // Command
  153. //
  154. function createOption(usage, description, opt_1, opt_2){
  155. var option = new Option(usage, description);
  156. // if (option.bool && arguments.length > 2)
  157. // throw new SyntaxError('bool flags can\'t has default value or validator');
  158. if (arguments.length == 3)
  159. {
  160. if (opt_1 && opt_1.constructor === Object)
  161. {
  162. for (var key in opt_1)
  163. if (key == 'normalize' ||
  164. key == 'defValue' ||
  165. key == 'beforeInit')
  166. option[key] = opt_1[key];
  167. // old name for `beforeInit` setting is `hot`
  168. if (opt_1.hot)
  169. option.beforeInit = true;
  170. }
  171. else
  172. {
  173. if (typeof opt_1 == 'function')
  174. option.normalize = opt_1;
  175. else
  176. option.defValue = opt_1;
  177. }
  178. }
  179. if (arguments.length == 4)
  180. {
  181. if (typeof opt_1 == 'function')
  182. option.normalize = opt_1;
  183. option.defValue = opt_2;
  184. }
  185. return option;
  186. }
  187. function addOptionToCommand(command, option){
  188. var commandOption;
  189. // short
  190. if (option.short)
  191. {
  192. commandOption = command.short[option.short];
  193. if (commandOption)
  194. throw new SyntaxError('Short option name -' + option.short + ' already in use by ' + commandOption.usage + ' ' + commandOption.description);
  195. command.short[option.short] = option;
  196. }
  197. // long
  198. commandOption = command.long[option.long];
  199. if (commandOption)
  200. throw new SyntaxError('Long option --' + option.long + ' already in use by ' + commandOption.usage + ' ' + commandOption.description);
  201. command.long[option.long] = option;
  202. // camel
  203. commandOption = command.options[option.camelName];
  204. if (commandOption)
  205. throw new SyntaxError('Name option ' + option.camelName + ' already in use by ' + commandOption.usage + ' ' + commandOption.description);
  206. command.options[option.camelName] = option;
  207. // set default value
  208. if (typeof option.defValue != 'undefined')
  209. command.setOption(option.camelName, option.defValue, true);
  210. // add to suggestions
  211. command.suggestions.push('--' + option.long);
  212. return option;
  213. }
  214. function findVariants(obj, entry){
  215. return obj.suggestions.filter(function(item){
  216. return item.substr(0, entry.length) == entry;
  217. });
  218. }
  219. function processArgs(command, args, suggest){
  220. function processOption(option, command){
  221. var params = [];
  222. if (option.maxArgsCount)
  223. {
  224. for (var j = 0; j < option.maxArgsCount; j++)
  225. {
  226. var suggestPoint = suggest && i + 1 + j >= args.length - 1;
  227. var nextToken = args[i + 1];
  228. // TODO: suggestions for options
  229. if (suggestPoint)
  230. {
  231. // search for suggest
  232. noSuggestions = true;
  233. i = args.length;
  234. return;
  235. }
  236. if (!nextToken || nextToken[0] == '-')
  237. break;
  238. params.push(args[++i]);
  239. }
  240. if (params.length < option.minArgsCount)
  241. throw new SyntaxError('Option ' + token + ' should be used with at least ' + option.minArgsCount + ' argument(s)\nUsage: ' + option.usage);
  242. if (option.maxArgsCount == 1)
  243. params = params[0];
  244. }
  245. else
  246. {
  247. params = !option.defValue;
  248. }
  249. //command.values[option.camelName] = newValue;
  250. resultToken.options.push({
  251. option: option,
  252. value: params
  253. });
  254. }
  255. var resultToken = {
  256. command: command,
  257. args: [],
  258. literalArgs: [],
  259. options: []
  260. };
  261. var result = [resultToken];
  262. var suggestStartsWith = '';
  263. var noSuggestions = false;
  264. var collectArgs = false;
  265. var commandArgs = [];
  266. var noOptionsYet = true;
  267. var option;
  268. commandsPath = [command.name];
  269. for (var i = 0; i < args.length; i++)
  270. {
  271. var suggestPoint = suggest && i == args.length - 1;
  272. var token = args[i];
  273. if (collectArgs)
  274. {
  275. commandArgs.push(token);
  276. continue;
  277. }
  278. if (suggestPoint && (token == '--' || token == '-' || token[0] != '-'))
  279. {
  280. suggestStartsWith = token;
  281. break; // returns long option & command list outside the loop
  282. }
  283. if (token == '--')
  284. {
  285. noOptionsYet = false;
  286. collectArgs = true;
  287. continue;
  288. }
  289. if (token[0] == '-')
  290. {
  291. noOptionsYet = false;
  292. if (commandArgs.length)
  293. {
  294. //command.args_.apply(command, commandArgs);
  295. resultToken.args = commandArgs;
  296. commandArgs = [];
  297. }
  298. if (token[1] == '-')
  299. {
  300. // long option
  301. option = command.long[token.substr(2)];
  302. if (!option)
  303. {
  304. // option doesn't exist
  305. if (suggestPoint)
  306. return findVariants(command, token);
  307. else
  308. throw new SyntaxError('Unknown option: ' + token);
  309. }
  310. // process option
  311. processOption(option, command);
  312. }
  313. else
  314. {
  315. // short flags sequence
  316. if (!/^-[a-zA-Z]+$/.test(token))
  317. throw new SyntaxError('Wrong short option sequence: ' + token);
  318. if (token.length == 2)
  319. {
  320. option = command.short[token[1]];
  321. if (!option)
  322. throw new SyntaxError('Unknown short option name: -' + token[1]);
  323. // single option
  324. processOption(option, command);
  325. }
  326. else
  327. {
  328. // short options sequence
  329. for (var j = 1; j < token.length; j++)
  330. {
  331. option = command.short[token[j]];
  332. if (!option)
  333. throw new SyntaxError('Unknown short option name: -' + token[j]);
  334. if (option.maxArgsCount)
  335. throw new SyntaxError('Non-boolean option -' + token[j] + ' can\'t be used in short option sequence: ' + token);
  336. processOption(option, command);
  337. }
  338. }
  339. }
  340. }
  341. else
  342. {
  343. if (command.commands[token] && (!command.params || commandArgs.length >= command.params.minArgsCount))
  344. {
  345. if (noOptionsYet)
  346. {
  347. resultToken.args = commandArgs;
  348. commandArgs = [];
  349. }
  350. if (command.params && resultToken.args.length < command.params.minArgsCount)
  351. throw new SyntaxError('Missed required argument(s) for command `' + command.name + '`');
  352. // switch control to another command
  353. command = command.commands[token];
  354. noOptionsYet = true;
  355. commandsPath.push(command.name);
  356. resultToken = {
  357. command: command,
  358. args: [],
  359. literalArgs: [],
  360. options: []
  361. };
  362. result.push(resultToken);
  363. }
  364. else
  365. {
  366. if (noOptionsYet && command.params && commandArgs.length < command.params.maxArgsCount)
  367. {
  368. commandArgs.push(token);
  369. continue;
  370. }
  371. if (suggestPoint)
  372. return findVariants(command, token);
  373. else
  374. throw new SyntaxError('Unknown command: ' + token);
  375. }
  376. }
  377. }
  378. if (suggest)
  379. {
  380. if (collectArgs || noSuggestions)
  381. return [];
  382. return findVariants(command, suggestStartsWith);
  383. }
  384. else
  385. {
  386. if (!noOptionsYet)
  387. resultToken.literalArgs = commandArgs;
  388. else
  389. resultToken.args = commandArgs;
  390. if (command.params && resultToken.args.length < command.params.minArgsCount)
  391. throw new SyntaxError('Missed required argument(s) for command `' + command.name + '`');
  392. }
  393. return result;
  394. }
  395. function setFunctionFactory(name){
  396. return function(fn){
  397. var property = name + '_';
  398. if (this[property] !== noop)
  399. throw new SyntaxError('Method `' + name + '` could be invoked only once');
  400. if (typeof fn != 'function')
  401. throw new SyntaxError('Value for `' + name + '` method should be a function');
  402. this[property] = fn;
  403. return this;
  404. }
  405. }
  406. /**
  407. * @class
  408. */
  409. var Command = function(name, params){
  410. this.name = name;
  411. this.params = false;
  412. try {
  413. if (params)
  414. this.params = parseParams(params);
  415. } catch(e) {
  416. throw new SyntaxError('Bad paramenter description in command definition: ' + this.name + ' ' + params);
  417. }
  418. this.commands = {};
  419. this.options = {};
  420. this.short = {};
  421. this.long = {};
  422. this.values = {};
  423. this.defaults_ = {};
  424. this.suggestions = [];
  425. this.option('-h, --help', 'Output usage information', function(){
  426. this.showHelp();
  427. process.exit(0);
  428. }, undefined);
  429. };
  430. Command.prototype = {
  431. params: null,
  432. commands: null,
  433. options: null,
  434. short: null,
  435. long: null,
  436. values: null,
  437. defaults_: null,
  438. suggestions: null,
  439. description_: '',
  440. version_: '',
  441. initContext_: noop,
  442. init_: noop,
  443. delegate_: noop,
  444. action_: noop,
  445. args_: noop,
  446. end_: null,
  447. option: function(usage, description, opt_1, opt_2){
  448. addOptionToCommand(this, createOption.apply(null, arguments));
  449. return this;
  450. },
  451. shortcut: function(usage, description, fn, opt_1, opt_2){
  452. if (typeof fn != 'function')
  453. throw new SyntaxError('fn should be a function');
  454. var command = this;
  455. var option = addOptionToCommand(this, createOption(usage, description, opt_1, opt_2));
  456. var normalize = option.normalize;
  457. option.normalize = function(value){
  458. var values;
  459. value = normalize.call(command, value);
  460. values = fn(value);
  461. for (var name in values)
  462. if (hasOwnProperty.call(values, name))
  463. if (hasOwnProperty.call(command.options, name))
  464. command.setOption(name, values[name]);
  465. else
  466. command.values[name] = values[name];
  467. command.values[option.name] = value;
  468. return value;
  469. };
  470. return this;
  471. },
  472. hasOption: function(name){
  473. return hasOwnProperty.call(this.options, name);
  474. },
  475. hasOptions: function(){
  476. return Object.keys(this.options).length > 0;
  477. },
  478. setOption: function(name, value, isDefault){
  479. if (!this.hasOption(name))
  480. throw new SyntaxError('Option `' + name + '` is not defined');
  481. var option = this.options[name];
  482. var oldValue = this.values[name];
  483. var newValue = option.normalize.call(this, value, oldValue);
  484. this.values[name] = option.maxArgsCount ? newValue : value;
  485. if (isDefault && !hasOwnProperty.call(this.defaults_, name))
  486. this.defaults_[name] = this.values[name];
  487. },
  488. setOptions: function(values){
  489. for (var name in values)
  490. if (hasOwnProperty.call(values, name) && this.hasOption(name))
  491. this.setOption(name, values[name]);
  492. },
  493. reset: function(){
  494. this.values = {};
  495. assign(this.values, this.defaults_);
  496. },
  497. command: function(nameOrCommand, params){
  498. var name;
  499. var command;
  500. if (nameOrCommand instanceof Command)
  501. {
  502. command = nameOrCommand;
  503. name = command.name;
  504. }
  505. else
  506. {
  507. name = nameOrCommand;
  508. if (!/^[a-zA-Z][a-zA-Z0-9\-\_]*$/.test(name))
  509. throw new SyntaxError('Wrong command name: ' + name);
  510. }
  511. // search for existing one
  512. var subcommand = this.commands[name];
  513. if (!subcommand)
  514. {
  515. // create new one if not exists
  516. subcommand = command || new Command(name, params);
  517. subcommand.end_ = this;
  518. this.commands[name] = subcommand;
  519. this.suggestions.push(name);
  520. }
  521. return subcommand;
  522. },
  523. end: function() {
  524. return this.end_;
  525. },
  526. hasCommands: function(){
  527. return Object.keys(this.commands).length > 0;
  528. },
  529. version: function(version, usage, description){
  530. if (this.version_)
  531. throw new SyntaxError('Version for command could be set only once');
  532. this.version_ = version;
  533. this.option(
  534. usage || '-v, --version',
  535. description || 'Output version',
  536. function(){
  537. console.log(this.version_);
  538. process.exit(0);
  539. },
  540. undefined
  541. );
  542. return this;
  543. },
  544. description: function(description){
  545. if (this.description_)
  546. throw new SyntaxError('Description for command could be set only once');
  547. this.description_ = description;
  548. return this;
  549. },
  550. init: setFunctionFactory('init'),
  551. initContext: setFunctionFactory('initContext'),
  552. args: setFunctionFactory('args'),
  553. delegate: setFunctionFactory('delegate'),
  554. action: setFunctionFactory('action'),
  555. extend: function(fn){
  556. fn.apply(null, [this].concat(Array.prototype.slice.call(arguments, 1)));
  557. return this;
  558. },
  559. parse: function(args, suggest){
  560. if (!args)
  561. args = process.argv.slice(2);
  562. if (!errorHandler)
  563. return processArgs(this, args, suggest);
  564. else
  565. try {
  566. return processArgs(this, args, suggest);
  567. } catch(e) {
  568. errorHandler(e.message || e);
  569. }
  570. },
  571. run: function(args, context){
  572. var commands = this.parse(args);
  573. if (!commands)
  574. return;
  575. var prevCommand;
  576. var context = assign({}, context || this.initContext_());
  577. for (var i = 0; i < commands.length; i++)
  578. {
  579. var item = commands[i];
  580. var command = item.command;
  581. // reset command values
  582. command.reset();
  583. command.context = context;
  584. command.root = this;
  585. if (prevCommand)
  586. prevCommand.delegate_(command);
  587. // apply beforeInit options
  588. item.options.forEach(function(entry){
  589. if (entry.option.beforeInit)
  590. command.setOption(entry.option.camelName, entry.value);
  591. });
  592. command.init_(item.args);
  593. if (item.args.length)
  594. command.args_(item.args);
  595. // apply regular options
  596. item.options.forEach(function(entry){
  597. if (!entry.option.beforeInit)
  598. command.setOption(entry.option.camelName, entry.value);
  599. });
  600. prevCommand = command;
  601. }
  602. // return last command action result
  603. if (command)
  604. return command.action_(item.args, item.literalArgs);
  605. },
  606. normalize: function(values){
  607. var result = {};
  608. if (!values)
  609. values = {};
  610. for (var name in this.values)
  611. if (hasOwnProperty.call(this.values, name))
  612. result[name] = hasOwnProperty.call(values, name) && hasOwnProperty.call(this.options, name)
  613. ? this.options[name].normalize.call(this, values[name])
  614. : this.values[name];
  615. for (var name in values)
  616. if (hasOwnProperty.call(values, name) && !hasOwnProperty.call(result, name))
  617. result[name] = values[name];
  618. return result;
  619. },
  620. showHelp: function(){
  621. console.log(showCommandHelp(this));
  622. }
  623. };
  624. //
  625. // help
  626. //
  627. /**
  628. * Return program help documentation.
  629. *
  630. * @return {String}
  631. * @api private
  632. */
  633. function showCommandHelp(command){
  634. function breakByLines(str, offset){
  635. var words = str.split(' ');
  636. var maxWidth = MAX_LINE_WIDTH - offset || 0;
  637. var lines = [];
  638. var line = '';
  639. while (words.length)
  640. {
  641. var word = words.shift();
  642. if (!line || (line.length + word.length + 1) < maxWidth)
  643. {
  644. line += (line ? ' ' : '') + word;
  645. }
  646. else
  647. {
  648. lines.push(line);
  649. words.unshift(word);
  650. line = '';
  651. }
  652. }
  653. lines.push(line);
  654. return lines.map(function(line, idx){
  655. return (idx && offset ? pad(offset, '') : '') + line;
  656. }).join('\n');
  657. }
  658. function args(command){
  659. return command.params.args.map(function(arg){
  660. return arg.required
  661. ? '<' + arg.name + '>'
  662. : '[' + arg.name + ']';
  663. }).join(' ');
  664. }
  665. function commandsHelp(){
  666. if (!command.hasCommands())
  667. return '';
  668. var maxNameLength = MIN_OFFSET - 2;
  669. var lines = Object.keys(command.commands).sort().map(function(name){
  670. var subcommand = command.commands[name];
  671. var line = {
  672. name: chalk.green(name) + chalk.gray(
  673. (subcommand.params ? ' ' + args(subcommand) : '')
  674. // (subcommand.hasOptions() ? ' [options]' : '')
  675. ),
  676. description: subcommand.description_ || ''
  677. };
  678. maxNameLength = Math.max(maxNameLength, stringLength(line.name));
  679. return line;
  680. });
  681. return [
  682. '',
  683. 'Commands:',
  684. '',
  685. lines.map(function(line){
  686. return ' ' + pad(maxNameLength, line.name) + ' ' + breakByLines(line.description, maxNameLength + 4);
  687. }).join('\n'),
  688. ''
  689. ].join('\n');
  690. }
  691. function optionsHelp(){
  692. if (!command.hasOptions())
  693. return '';
  694. var hasShortOptions = Object.keys(command.short).length > 0;
  695. var maxNameLength = MIN_OFFSET - 2;
  696. var lines = Object.keys(command.long).sort().map(function(name){
  697. var option = command.long[name];
  698. var line = {
  699. name: option.usage
  700. .replace(/^(?:-., |)/, function(m){
  701. return m || (hasShortOptions ? ' ' : '');
  702. })
  703. .replace(/(^|\s)(-[^\s,]+)/ig, function(m, p, flag){
  704. return p + chalk.yellow(flag);
  705. }),
  706. description: option.description
  707. };
  708. maxNameLength = Math.max(maxNameLength, stringLength(line.name));
  709. return line;
  710. });
  711. // Prepend the help information
  712. return [
  713. '',
  714. 'Options:',
  715. '',
  716. lines.map(function(line){
  717. return ' ' + pad(maxNameLength, line.name) + ' ' + breakByLines(line.description, maxNameLength + 4);
  718. }).join('\n'),
  719. ''
  720. ].join('\n');
  721. }
  722. var output = [];
  723. var chalk = require('chalk');
  724. chalk.enabled = module.exports.color && process.stdout.isTTY;
  725. if (command.description_)
  726. output.push(command.description_ + '\n');
  727. output.push(
  728. 'Usage:\n\n ' +
  729. chalk.cyan(commandsPath ? commandsPath.join(' ') : command.name) +
  730. (command.params ? ' ' + chalk.magenta(args(command)) : '') +
  731. (command.hasOptions() ? ' [' + chalk.yellow('options') + ']' : '') +
  732. (command.hasCommands() ? ' [' + chalk.green('command') + ']' : ''),
  733. commandsHelp() +
  734. optionsHelp()
  735. );
  736. return output.join('\n');
  737. };
  738. //
  739. // export
  740. //
  741. module.exports = {
  742. color: true,
  743. Error: SyntaxError,
  744. Argument: Argument,
  745. Command: Command,
  746. Option: Option,
  747. error: function(fn){
  748. if (errorHandler)
  749. throw new SyntaxError('Error handler should be set only once');
  750. if (typeof fn != 'function')
  751. throw new SyntaxError('Error handler should be a function');
  752. errorHandler = fn;
  753. return this;
  754. },
  755. create: function(name, params){
  756. return new Command(name || require('path').basename(process.argv[1]) || 'cli', params);
  757. },
  758. confirm: function(message, fn){
  759. process.stdout.write(message);
  760. process.stdin.setEncoding('utf8');
  761. process.stdin.once('data', function(val){
  762. process.stdin.pause();
  763. fn(/^y|yes|ok|true$/i.test(val.trim()));
  764. });
  765. process.stdin.resume();
  766. }
  767. };