usage.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. // this file handles outputting usage instructions,
  2. // failures, etc. keeps logging in one place.
  3. var decamelize = require('decamelize'),
  4. wordwrap = require('wordwrap'),
  5. wsize = require('window-size');
  6. module.exports = function (yargs) {
  7. var self = {};
  8. // methods for ouputting/building failure message.
  9. var fails = [];
  10. self.failFn = function (f) {
  11. fails.push(f);
  12. };
  13. var failMessage = null;
  14. var showHelpOnFail = true;
  15. self.showHelpOnFail = function (enabled, message) {
  16. if (typeof enabled === 'string') {
  17. message = enabled;
  18. enabled = true;
  19. }
  20. else if (typeof enabled === 'undefined') {
  21. enabled = true;
  22. }
  23. failMessage = message;
  24. showHelpOnFail = enabled;
  25. return self;
  26. };
  27. self.fail = function (msg) {
  28. if (fails.length) {
  29. fails.forEach(function (f) {
  30. f(msg);
  31. });
  32. } else {
  33. if (showHelpOnFail) yargs.showHelp("error");
  34. if (msg) console.error(msg);
  35. if (failMessage) {
  36. if (msg) console.error("");
  37. console.error(failMessage);
  38. }
  39. if (yargs.getExitProcess()){
  40. process.exit(1);
  41. }else{
  42. throw new Error(msg);
  43. }
  44. }
  45. };
  46. // methods for ouputting/building help (usage) message.
  47. var usage;
  48. self.usage = function (msg) {
  49. usage = msg;
  50. };
  51. var examples = [];
  52. self.example = function (cmd, description) {
  53. examples.push([cmd, description || '']);
  54. };
  55. var commands = [];
  56. self.command = function (cmd, description) {
  57. commands.push([cmd, description || '']);
  58. };
  59. self.getCommands = function () {
  60. return commands;
  61. };
  62. var descriptions = {};
  63. self.describe = function (key, desc) {
  64. if (typeof key === 'object') {
  65. Object.keys(key).forEach(function (k) {
  66. self.describe(k, key[k]);
  67. });
  68. }
  69. else {
  70. descriptions[key] = desc;
  71. }
  72. };
  73. self.getDescriptions = function() {
  74. return descriptions;
  75. }
  76. var epilog;
  77. self.epilog = function (msg) {
  78. epilog = msg;
  79. };
  80. var wrap = windowWidth();
  81. self.wrap = function (cols) {
  82. wrap = cols;
  83. };
  84. self.help = function () {
  85. var demanded = yargs.getDemanded(),
  86. options = yargs.getOptions(),
  87. keys = Object.keys(
  88. Object.keys(descriptions)
  89. .concat(Object.keys(demanded))
  90. .concat(Object.keys(options.default))
  91. .reduce(function (acc, key) {
  92. if (key !== '_') acc[key] = true;
  93. return acc;
  94. }, {})
  95. );
  96. var help = keys.length ? [ 'Options:' ] : [];
  97. // your application's commands, i.e., non-option
  98. // arguments populated in '_'.
  99. if (commands.length) {
  100. help.unshift('');
  101. var commandsTable = {};
  102. commands.forEach(function(command) {
  103. commandsTable[command[0]] = {
  104. desc: command[1],
  105. extra: ''
  106. };
  107. });
  108. help = ['Commands:'].concat(formatTable(commandsTable, 5), help);
  109. }
  110. // the usage string.
  111. if (usage) {
  112. var u = usage.replace(/\$0/g, yargs.$0);
  113. if (wrap) u = wordwrap(0, wrap)(u);
  114. help.unshift(u, '');
  115. }
  116. // the options table.
  117. var aliasKeys = (Object.keys(options.alias) || [])
  118. .concat(Object.keys(yargs.parsed.newAliases) || []);
  119. keys = keys.filter(function(key) {
  120. return !yargs.parsed.newAliases[key] && aliasKeys.every(function(alias) {
  121. return -1 == (options.alias[alias] || []).indexOf(key);
  122. });
  123. });
  124. var switches = keys.reduce(function (acc, key) {
  125. acc[key] = [ key ].concat(options.alias[key] || [])
  126. .map(function (sw) {
  127. return (sw.length > 1 ? '--' : '-') + sw
  128. })
  129. .join(', ')
  130. ;
  131. return acc;
  132. }, {});
  133. var switchTable = {};
  134. keys.forEach(function (key) {
  135. var kswitch = switches[key];
  136. var desc = descriptions[key] || '';
  137. var type = null;
  138. if (options.boolean[key]) type = '[boolean]';
  139. if (options.count[key]) type = '[count]';
  140. if (options.string[key]) type = '[string]';
  141. if (options.normalize[key]) type = '[string]';
  142. var extra = [
  143. type,
  144. demanded[key]
  145. ? '[required]'
  146. : null
  147. ,
  148. defaultString(options.default[key], options.defaultDescription[key])
  149. ].filter(Boolean).join(' ');
  150. switchTable[kswitch] = {
  151. desc: desc,
  152. extra: extra
  153. };
  154. });
  155. help.push.apply(help, formatTable(switchTable, 3));
  156. if (keys.length) help.push('');
  157. // describe some common use-cases for your application.
  158. if (examples.length) {
  159. examples.forEach(function (example) {
  160. example[0] = example[0].replace(/\$0/g, yargs.$0);
  161. });
  162. var examplesTable = {};
  163. examples.forEach(function(example) {
  164. examplesTable[example[0]] = {
  165. desc: example[1],
  166. extra: ''
  167. };
  168. });
  169. help.push.apply(help, ['Examples:'].concat(formatTable(examplesTable, 5), ''));
  170. }
  171. // the usage string.
  172. if (epilog) {
  173. var e = epilog.replace(/\$0/g, yargs.$0);
  174. if (wrap) e = wordwrap(0, wrap)(e);
  175. help.push(e, '');
  176. }
  177. return help.join('\n');
  178. };
  179. self.showHelp = function (level) {
  180. level = level || 'error';
  181. console[level](self.help());
  182. }
  183. // format the default-value-string displayed in
  184. // the right-hand column.
  185. function defaultString(value, defaultDescription) {
  186. var string = '[default: ';
  187. if (value === undefined) return null;
  188. if (defaultDescription) {
  189. string += defaultDescription;
  190. } else {
  191. switch (typeof value) {
  192. case 'string':
  193. string += JSON.stringify(value);
  194. break;
  195. case 'function':
  196. string += '(' + (value.name.length ? decamelize(value.name, '-') : 'generated-value') + ')'
  197. break;
  198. default:
  199. string += value;
  200. }
  201. }
  202. return string + ']';
  203. }
  204. // word-wrapped two-column layout used by
  205. // examples, options, commands.
  206. function formatTable (table, padding) {
  207. var output = [];
  208. // size of left-hand-column.
  209. var llen = longest(Object.keys(table));
  210. // don't allow the left-column to take up
  211. // more than half of the screen.
  212. if (wrap) {
  213. llen = Math.min(llen, parseInt(wrap / 2));
  214. }
  215. // size of right-column.
  216. var desclen = longest(Object.keys(table).map(function (k) {
  217. return table[k].desc;
  218. }));
  219. Object.keys(table).forEach(function(left) {
  220. var desc = table[left].desc,
  221. extra = table[left].extra,
  222. leftLines = null;
  223. if (wrap) {
  224. desc = wordwrap(llen + padding + 1, wrap)(desc)
  225. .slice(llen + padding + 1);
  226. }
  227. // if we need to wrap the left-hand-column,
  228. // split it on to multiple lines.
  229. if (wrap && left.length > llen) {
  230. leftLines = wordwrap(2, llen)(left.trim()).split('\n');
  231. left = '';
  232. }
  233. var lpadding = new Array(
  234. Math.max(llen - left.length + padding, 0)
  235. ).join(' ');
  236. var dpadding = new Array(
  237. Math.max(desclen - desc.length + 1, 0)
  238. ).join(' ');
  239. if (!wrap && dpadding.length > 0) {
  240. desc += dpadding;
  241. }
  242. var prelude = ' ' + left + lpadding;
  243. var body = [ desc, extra ].filter(Boolean).join(' ');
  244. if (wrap) {
  245. var dlines = desc.split('\n');
  246. var dlen = dlines.slice(-1)[0].length
  247. + (dlines.length === 1 ? prelude.length : 0)
  248. if (extra.length > wrap) {
  249. body = desc + '\n' + wordwrap(llen + 4, wrap)(extra)
  250. } else {
  251. body = desc + (dlen + extra.length > wrap - 2
  252. ? '\n'
  253. + new Array(wrap - extra.length + 1).join(' ')
  254. + extra
  255. : new Array(wrap - extra.length - dlen + 1).join(' ')
  256. + extra
  257. );
  258. }
  259. }
  260. if (leftLines) { // handle word-wrapping the left-hand-column.
  261. var rightLines = body.split('\n'),
  262. firstLine = prelude + rightLines[0],
  263. lineCount = Math.max(leftLines.length, rightLines.length);
  264. for (var i = 0; i < lineCount; i++) {
  265. var left = leftLines[i],
  266. right = i ? rightLines[i] : firstLine;
  267. output.push(strcpy(left, right, firstLine.length));
  268. }
  269. } else {
  270. output.push(prelude + body);
  271. }
  272. });
  273. return output;
  274. }
  275. // find longest string in array of strings.
  276. function longest (xs) {
  277. return Math.max.apply(
  278. null,
  279. xs.map(function (x) { return x.length })
  280. );
  281. }
  282. // copy one string into another, used when
  283. // formatting usage table.
  284. function strcpy (source, destination, width) {
  285. var str = ''
  286. source = source || '';
  287. destination = destination || new Array(width).join(' ');
  288. for (var i = 0; i < destination.length; i++) {
  289. var char = destination.charAt(i);
  290. if (char === ' ') char = source.charAt(i) || char;
  291. str += char;
  292. }
  293. return str;
  294. }
  295. // guess the width of the console window, max-width 100.
  296. function windowWidth() {
  297. return wsize.width ? Math.min(80, wsize.width) : null;
  298. }
  299. // logic for displaying application version.
  300. var version = null;
  301. self.version = function (ver, opt, msg) {
  302. version = ver;
  303. };
  304. self.showVersion = function() {
  305. if (typeof version === 'function') console.log(version());
  306. else console.log(version);
  307. };
  308. return self;
  309. }