index.js 22 KB

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