index.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952
  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. }
  243. else
  244. {
  245. params = !option.defValue;
  246. }
  247. //command.values[option.camelName] = newValue;
  248. resultToken.options.push({
  249. option: option,
  250. value: params
  251. });
  252. }
  253. var resultToken = {
  254. command: command,
  255. args: [],
  256. literalArgs: [],
  257. options: []
  258. };
  259. var result = [resultToken];
  260. var suggestStartsWith = '';
  261. var noSuggestions = false;
  262. var collectArgs = false;
  263. var commandArgs = [];
  264. var noOptionsYet = true;
  265. var option;
  266. commandsPath = [command.name];
  267. for (var i = 0; i < args.length; i++)
  268. {
  269. var suggestPoint = suggest && i == args.length - 1;
  270. var token = args[i];
  271. if (collectArgs)
  272. {
  273. commandArgs.push(token);
  274. continue;
  275. }
  276. if (suggestPoint && (token == '--' || token == '-' || token[0] != '-'))
  277. {
  278. suggestStartsWith = token;
  279. break; // returns long option & command list outside the loop
  280. }
  281. if (token == '--')
  282. {
  283. noOptionsYet = false;
  284. collectArgs = true;
  285. continue;
  286. }
  287. if (token[0] == '-')
  288. {
  289. noOptionsYet = false;
  290. if (commandArgs.length)
  291. {
  292. //command.args_.apply(command, commandArgs);
  293. resultToken.args = commandArgs;
  294. commandArgs = [];
  295. }
  296. if (token[1] == '-')
  297. {
  298. // long option
  299. option = command.long[token.substr(2)];
  300. if (!option)
  301. {
  302. // option doesn't exist
  303. if (suggestPoint)
  304. return findVariants(command, token);
  305. else
  306. throw new SyntaxError('Unknown option: ' + token);
  307. }
  308. // process option
  309. processOption(option, command);
  310. }
  311. else
  312. {
  313. // short flags sequence
  314. if (!/^-[a-zA-Z]+$/.test(token))
  315. throw new SyntaxError('Wrong short option sequence: ' + token);
  316. if (token.length == 2)
  317. {
  318. option = command.short[token[1]];
  319. if (!option)
  320. throw new SyntaxError('Unknown short option name: -' + token[1]);
  321. // single option
  322. processOption(option, command);
  323. }
  324. else
  325. {
  326. // short options sequence
  327. for (var j = 1; j < token.length; j++)
  328. {
  329. option = command.short[token[j]];
  330. if (!option)
  331. throw new SyntaxError('Unknown short option name: -' + token[j]);
  332. if (option.maxArgsCount)
  333. throw new SyntaxError('Non-boolean option -' + token[j] + ' can\'t be used in short option sequence: ' + token);
  334. processOption(option, command);
  335. }
  336. }
  337. }
  338. }
  339. else
  340. {
  341. if (command.commands[token] && (!command.params || commandArgs.length >= command.params.minArgsCount))
  342. {
  343. if (noOptionsYet)
  344. {
  345. resultToken.args = commandArgs;
  346. commandArgs = [];
  347. }
  348. if (command.params && resultToken.args.length < command.params.minArgsCount)
  349. throw new SyntaxError('Missed required argument(s) for command `' + command.name + '`');
  350. // switch control to another command
  351. command = command.commands[token];
  352. noOptionsYet = true;
  353. commandsPath.push(command.name);
  354. resultToken = {
  355. command: command,
  356. args: [],
  357. literalArgs: [],
  358. options: []
  359. };
  360. result.push(resultToken);
  361. }
  362. else
  363. {
  364. if (noOptionsYet && command.params && commandArgs.length < command.params.maxArgsCount)
  365. {
  366. commandArgs.push(token);
  367. continue;
  368. }
  369. if (suggestPoint)
  370. return findVariants(command, token);
  371. else
  372. throw new SyntaxError('Unknown command: ' + token);
  373. }
  374. }
  375. }
  376. if (suggest)
  377. {
  378. if (collectArgs || noSuggestions)
  379. return [];
  380. return findVariants(command, suggestStartsWith);
  381. }
  382. else
  383. {
  384. if (!noOptionsYet)
  385. resultToken.literalArgs = commandArgs;
  386. else
  387. resultToken.args = commandArgs;
  388. if (command.params && resultToken.args.length < command.params.minArgsCount)
  389. throw new SyntaxError('Missed required argument(s) for command `' + command.name + '`');
  390. }
  391. return result;
  392. }
  393. function setFunctionFactory(name){
  394. return function(fn){
  395. var property = name + '_';
  396. if (this[property] !== noop)
  397. throw new SyntaxError('Method `' + name + '` could be invoked only once');
  398. if (typeof fn != 'function')
  399. throw new SyntaxError('Value for `' + name + '` method should be a function');
  400. this[property] = fn;
  401. return this;
  402. }
  403. }
  404. /**
  405. * @class
  406. */
  407. var Command = function(name, params){
  408. this.name = name;
  409. this.params = false;
  410. try {
  411. if (params)
  412. this.params = parseParams(params);
  413. } catch(e) {
  414. throw new SyntaxError('Bad paramenter description in command definition: ' + this.name + ' ' + params);
  415. }
  416. this.commands = {};
  417. this.options = {};
  418. this.short = {};
  419. this.long = {};
  420. this.values = {};
  421. this.defaults_ = {};
  422. this.suggestions = [];
  423. this.option('-h, --help', 'Output usage information', function(){
  424. this.showHelp();
  425. process.exit(0);
  426. }, undefined);
  427. };
  428. Command.prototype = {
  429. params: null,
  430. commands: null,
  431. options: null,
  432. short: null,
  433. long: null,
  434. values: null,
  435. defaults_: null,
  436. suggestions: null,
  437. description_: '',
  438. version_: '',
  439. initContext_: noop,
  440. init_: noop,
  441. delegate_: noop,
  442. action_: noop,
  443. args_: noop,
  444. end_: null,
  445. option: function(usage, description, opt_1, opt_2){
  446. addOptionToCommand(this, createOption.apply(null, arguments));
  447. return this;
  448. },
  449. shortcut: function(usage, description, fn, opt_1, opt_2){
  450. if (typeof fn != 'function')
  451. throw new SyntaxError('fn should be a function');
  452. var command = this;
  453. var option = addOptionToCommand(this, createOption(usage, description, opt_1, opt_2));
  454. var normalize = option.normalize;
  455. option.normalize = function(value){
  456. var values;
  457. value = normalize.call(command, value);
  458. values = fn(value);
  459. for (var name in values)
  460. if (hasOwnProperty.call(values, name))
  461. if (hasOwnProperty.call(command.options, name))
  462. command.setOption(name, values[name]);
  463. else
  464. command.values[name] = values[name];
  465. command.values[option.name] = value;
  466. return value;
  467. };
  468. return this;
  469. },
  470. hasOption: function(name){
  471. return hasOwnProperty.call(this.options, name);
  472. },
  473. hasOptions: function(){
  474. return Object.keys(this.options).length > 0;
  475. },
  476. setOption: function(name, value, isDefault){
  477. if (!this.hasOption(name))
  478. throw new SyntaxError('Option `' + name + '` is not defined');
  479. var option = this.options[name];
  480. var newValue = Array.isArray(value)
  481. ? option.normalize.apply(this, value)
  482. : option.normalize.call(this, value);
  483. this.values[name] = option.maxArgsCount ? newValue : value;
  484. if (isDefault && !hasOwnProperty.call(this.defaults_, name))
  485. this.defaults_[name] = this.values[name];
  486. },
  487. setOptions: function(values){
  488. for (var name in values)
  489. if (hasOwnProperty.call(values, name) && this.hasOption(name))
  490. this.setOption(name, values[name]);
  491. },
  492. reset: function(){
  493. this.values = {};
  494. assign(this.values, this.defaults_);
  495. },
  496. command: function(nameOrCommand, params){
  497. var name;
  498. var command;
  499. if (nameOrCommand instanceof Command)
  500. {
  501. command = nameOrCommand;
  502. name = command.name;
  503. }
  504. else
  505. {
  506. name = nameOrCommand;
  507. if (!/^[a-zA-Z][a-zA-Z0-9\-\_]*$/.test(name))
  508. throw new SyntaxError('Wrong command name: ' + name);
  509. }
  510. // search for existing one
  511. var subcommand = this.commands[name];
  512. if (!subcommand)
  513. {
  514. // create new one if not exists
  515. subcommand = command || new Command(name, params);
  516. subcommand.end_ = this;
  517. this.commands[name] = subcommand;
  518. this.suggestions.push(name);
  519. }
  520. return subcommand;
  521. },
  522. end: function() {
  523. return this.end_;
  524. },
  525. hasCommands: function(){
  526. return Object.keys(this.commands).length > 0;
  527. },
  528. version: function(version, usage, description){
  529. if (this.version_)
  530. throw new SyntaxError('Version for command could be set only once');
  531. this.version_ = version;
  532. this.option(
  533. usage || '-v, --version',
  534. description || 'Output version',
  535. function(){
  536. console.log(this.version_);
  537. process.exit(0);
  538. },
  539. undefined
  540. );
  541. return this;
  542. },
  543. description: function(description){
  544. if (this.description_)
  545. throw new SyntaxError('Description for command could be set only once');
  546. this.description_ = description;
  547. return this;
  548. },
  549. init: setFunctionFactory('init'),
  550. initContext: setFunctionFactory('initContext'),
  551. args: setFunctionFactory('args'),
  552. delegate: setFunctionFactory('delegate'),
  553. action: setFunctionFactory('action'),
  554. extend: function(fn){
  555. fn.apply(null, [this].concat(Array.prototype.slice.call(arguments, 1)));
  556. return this;
  557. },
  558. parse: function(args, suggest){
  559. if (!args)
  560. args = process.argv.slice(2);
  561. if (!errorHandler)
  562. return processArgs(this, args, suggest);
  563. else
  564. try {
  565. return processArgs(this, args, suggest);
  566. } catch(e) {
  567. errorHandler(e.message || e);
  568. }
  569. },
  570. run: function(args, context){
  571. var commands = this.parse(args);
  572. if (!commands)
  573. return;
  574. var prevCommand;
  575. var context = assign({}, context || this.initContext_());
  576. for (var i = 0; i < commands.length; i++)
  577. {
  578. var item = commands[i];
  579. var command = item.command;
  580. // reset command values
  581. command.reset();
  582. command.context = context;
  583. command.root = this;
  584. if (prevCommand)
  585. prevCommand.delegate_(command);
  586. // apply beforeInit options
  587. command.setOptions(
  588. item.options.reduce(function(res, entry){
  589. if (entry.option.beforeInit)
  590. res[entry.option.camelName] = entry.value;
  591. return res;
  592. }, {})
  593. );
  594. command.init_(item.args);
  595. if (item.args.length)
  596. command.args_(item.args);
  597. // apply regular options
  598. command.setOptions(
  599. item.options.reduce(function(res, entry){
  600. if (!entry.option.beforeInit)
  601. res[entry.option.camelName] = entry.value;
  602. return res;
  603. }, {})
  604. );
  605. prevCommand = command;
  606. }
  607. // return last command action result
  608. if (command)
  609. return command.action_(item.args, item.literalArgs);
  610. },
  611. normalize: function(values){
  612. var result = {};
  613. if (!values)
  614. values = {};
  615. for (var name in this.values)
  616. if (hasOwnProperty.call(this.values, name))
  617. result[name] = hasOwnProperty.call(values, name) && hasOwnProperty.call(this.options, name)
  618. ? this.options[name].normalize.call(this, values[name])
  619. : this.values[name];
  620. for (var name in values)
  621. if (hasOwnProperty.call(values, name) && !hasOwnProperty.call(result, name))
  622. result[name] = values[name];
  623. return result;
  624. },
  625. showHelp: function(){
  626. console.log(showCommandHelp(this));
  627. }
  628. };
  629. //
  630. // help
  631. //
  632. /**
  633. * Return program help documentation.
  634. *
  635. * @return {String}
  636. * @api private
  637. */
  638. function showCommandHelp(command){
  639. function breakByLines(str, offset){
  640. var words = str.split(' ');
  641. var maxWidth = MAX_LINE_WIDTH - offset || 0;
  642. var lines = [];
  643. var line = '';
  644. while (words.length)
  645. {
  646. var word = words.shift();
  647. if (!line || (line.length + word.length + 1) < maxWidth)
  648. {
  649. line += (line ? ' ' : '') + word;
  650. }
  651. else
  652. {
  653. lines.push(line);
  654. words.unshift(word);
  655. line = '';
  656. }
  657. }
  658. lines.push(line);
  659. return lines.map(function(line, idx){
  660. return (idx && offset ? pad(offset, '') : '') + line;
  661. }).join('\n');
  662. }
  663. function args(command){
  664. return command.params.args.map(function(arg){
  665. return arg.required
  666. ? '<' + arg.name + '>'
  667. : '[' + arg.name + ']';
  668. }).join(' ');
  669. }
  670. function commandsHelp(){
  671. if (!command.hasCommands())
  672. return '';
  673. var maxNameLength = MIN_OFFSET - 2;
  674. var lines = Object.keys(command.commands).sort().map(function(name){
  675. var subcommand = command.commands[name];
  676. var line = {
  677. name: chalk.green(name) + chalk.gray(
  678. (subcommand.params ? ' ' + args(subcommand) : '')
  679. // (subcommand.hasOptions() ? ' [options]' : '')
  680. ),
  681. description: subcommand.description_ || ''
  682. };
  683. maxNameLength = Math.max(maxNameLength, stringLength(line.name));
  684. return line;
  685. });
  686. return [
  687. '',
  688. 'Commands:',
  689. '',
  690. lines.map(function(line){
  691. return ' ' + pad(maxNameLength, line.name) + ' ' + breakByLines(line.description, maxNameLength + 4);
  692. }).join('\n'),
  693. ''
  694. ].join('\n');
  695. }
  696. function optionsHelp(){
  697. if (!command.hasOptions())
  698. return '';
  699. var hasShortOptions = Object.keys(command.short).length > 0;
  700. var maxNameLength = MIN_OFFSET - 2;
  701. var lines = Object.keys(command.long).sort().map(function(name){
  702. var option = command.long[name];
  703. var line = {
  704. name: option.usage
  705. .replace(/^(?:-., |)/, function(m){
  706. return m || (hasShortOptions ? ' ' : '');
  707. })
  708. .replace(/(^|\s)(-[^\s,]+)/ig, function(m, p, flag){
  709. return p + chalk.yellow(flag);
  710. }),
  711. description: option.description
  712. };
  713. maxNameLength = Math.max(maxNameLength, stringLength(line.name));
  714. return line;
  715. });
  716. // Prepend the help information
  717. return [
  718. '',
  719. 'Options:',
  720. '',
  721. lines.map(function(line){
  722. return ' ' + pad(maxNameLength, line.name) + ' ' + breakByLines(line.description, maxNameLength + 4);
  723. }).join('\n'),
  724. ''
  725. ].join('\n');
  726. }
  727. var output = [];
  728. var chalk = require('chalk');
  729. chalk.enabled = module.exports.color && process.stdout.isTTY;
  730. if (command.description_)
  731. output.push(command.description_ + '\n');
  732. output.push(
  733. 'Usage:\n\n ' +
  734. chalk.cyan(commandsPath ? commandsPath.join(' ') : command.name) +
  735. (command.params ? ' ' + chalk.magenta(args(command)) : '') +
  736. (command.hasOptions() ? ' [' + chalk.yellow('options') + ']' : '') +
  737. (command.hasCommands() ? ' [' + chalk.green('command') + ']' : ''),
  738. commandsHelp() +
  739. optionsHelp()
  740. );
  741. return output.join('\n');
  742. };
  743. //
  744. // export
  745. //
  746. module.exports = {
  747. color: true,
  748. Error: SyntaxError,
  749. Argument: Argument,
  750. Command: Command,
  751. Option: Option,
  752. error: function(fn){
  753. if (errorHandler)
  754. throw new SyntaxError('Error handler should be set only once');
  755. if (typeof fn != 'function')
  756. throw new SyntaxError('Error handler should be a function');
  757. errorHandler = fn;
  758. return this;
  759. },
  760. create: function(name, params){
  761. return new Command(name || require('path').basename(process.argv[1]) || 'cli', params);
  762. },
  763. confirm: function(message, fn){
  764. process.stdout.write(message);
  765. process.stdin.setEncoding('utf8');
  766. process.stdin.once('data', function(val){
  767. process.stdin.pause();
  768. fn(/^y|yes|ok|true$/i.test(val.trim()));
  769. });
  770. process.stdin.resume();
  771. }
  772. };