123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 |
- // this file handles outputting usage instructions,
- // failures, etc. keeps logging in one place.
- var decamelize = require('decamelize'),
- wordwrap = require('wordwrap'),
- wsize = require('window-size');
- module.exports = function (yargs) {
- var self = {};
- // methods for ouputting/building failure message.
- var fails = [];
- self.failFn = function (f) {
- fails.push(f);
- };
- var failMessage = null;
- var showHelpOnFail = true;
- self.showHelpOnFail = function (enabled, message) {
- if (typeof enabled === 'string') {
- message = enabled;
- enabled = true;
- }
- else if (typeof enabled === 'undefined') {
- enabled = true;
- }
- failMessage = message;
- showHelpOnFail = enabled;
- return self;
- };
- self.fail = function (msg) {
- if (fails.length) {
- fails.forEach(function (f) {
- f(msg);
- });
- } else {
- if (showHelpOnFail) yargs.showHelp("error");
- if (msg) console.error(msg);
- if (failMessage) {
- if (msg) console.error("");
- console.error(failMessage);
- }
- if (yargs.getExitProcess()){
- process.exit(1);
- }else{
- throw new Error(msg);
- }
- }
- };
- // methods for ouputting/building help (usage) message.
- var usage;
- self.usage = function (msg) {
- usage = msg;
- };
- var examples = [];
- self.example = function (cmd, description) {
- examples.push([cmd, description || '']);
- };
- var commands = [];
- self.command = function (cmd, description) {
- commands.push([cmd, description || '']);
- };
- self.getCommands = function () {
- return commands;
- };
- var descriptions = {};
- self.describe = function (key, desc) {
- if (typeof key === 'object') {
- Object.keys(key).forEach(function (k) {
- self.describe(k, key[k]);
- });
- }
- else {
- descriptions[key] = desc;
- }
- };
- self.getDescriptions = function() {
- return descriptions;
- }
- var epilog;
- self.epilog = function (msg) {
- epilog = msg;
- };
- var wrap = windowWidth();
- self.wrap = function (cols) {
- wrap = cols;
- };
- self.help = function () {
- var demanded = yargs.getDemanded(),
- options = yargs.getOptions(),
- keys = Object.keys(
- Object.keys(descriptions)
- .concat(Object.keys(demanded))
- .concat(Object.keys(options.default))
- .reduce(function (acc, key) {
- if (key !== '_') acc[key] = true;
- return acc;
- }, {})
- );
- var help = keys.length ? [ 'Options:' ] : [];
- // your application's commands, i.e., non-option
- // arguments populated in '_'.
- if (commands.length) {
- help.unshift('');
- var commandsTable = {};
- commands.forEach(function(command) {
- commandsTable[command[0]] = {
- desc: command[1],
- extra: ''
- };
- });
- help = ['Commands:'].concat(formatTable(commandsTable, 5), help);
- }
- // the usage string.
- if (usage) {
- var u = usage.replace(/\$0/g, yargs.$0);
- if (wrap) u = wordwrap(0, wrap)(u);
- help.unshift(u, '');
- }
- // the options table.
- var aliasKeys = (Object.keys(options.alias) || [])
- .concat(Object.keys(yargs.parsed.newAliases) || []);
- keys = keys.filter(function(key) {
- return !yargs.parsed.newAliases[key] && aliasKeys.every(function(alias) {
- return -1 == (options.alias[alias] || []).indexOf(key);
- });
- });
- var switches = keys.reduce(function (acc, key) {
- acc[key] = [ key ].concat(options.alias[key] || [])
- .map(function (sw) {
- return (sw.length > 1 ? '--' : '-') + sw
- })
- .join(', ')
- ;
- return acc;
- }, {});
- var switchTable = {};
- keys.forEach(function (key) {
- var kswitch = switches[key];
- var desc = descriptions[key] || '';
- var type = null;
- if (options.boolean[key]) type = '[boolean]';
- if (options.count[key]) type = '[count]';
- if (options.string[key]) type = '[string]';
- if (options.normalize[key]) type = '[string]';
- var extra = [
- type,
- demanded[key]
- ? '[required]'
- : null
- ,
- defaultString(options.default[key], options.defaultDescription[key])
- ].filter(Boolean).join(' ');
- switchTable[kswitch] = {
- desc: desc,
- extra: extra
- };
- });
- help.push.apply(help, formatTable(switchTable, 3));
- if (keys.length) help.push('');
- // describe some common use-cases for your application.
- if (examples.length) {
- examples.forEach(function (example) {
- example[0] = example[0].replace(/\$0/g, yargs.$0);
- });
- var examplesTable = {};
- examples.forEach(function(example) {
- examplesTable[example[0]] = {
- desc: example[1],
- extra: ''
- };
- });
- help.push.apply(help, ['Examples:'].concat(formatTable(examplesTable, 5), ''));
- }
- // the usage string.
- if (epilog) {
- var e = epilog.replace(/\$0/g, yargs.$0);
- if (wrap) e = wordwrap(0, wrap)(e);
- help.push(e, '');
- }
- return help.join('\n');
- };
- self.showHelp = function (level) {
- level = level || 'error';
- console[level](self.help());
- }
- // format the default-value-string displayed in
- // the right-hand column.
- function defaultString(value, defaultDescription) {
- var string = '[default: ';
- if (value === undefined) return null;
- if (defaultDescription) {
- string += defaultDescription;
- } else {
- switch (typeof value) {
- case 'string':
- string += JSON.stringify(value);
- break;
- case 'function':
- string += '(' + (value.name.length ? decamelize(value.name, '-') : 'generated-value') + ')'
- break;
- default:
- string += value;
- }
- }
- return string + ']';
- }
- // word-wrapped two-column layout used by
- // examples, options, commands.
- function formatTable (table, padding) {
- var output = [];
- // size of left-hand-column.
- var llen = longest(Object.keys(table));
- // don't allow the left-column to take up
- // more than half of the screen.
- if (wrap) {
- llen = Math.min(llen, parseInt(wrap / 2));
- }
- // size of right-column.
- var desclen = longest(Object.keys(table).map(function (k) {
- return table[k].desc;
- }));
- Object.keys(table).forEach(function(left) {
- var desc = table[left].desc,
- extra = table[left].extra,
- leftLines = null;
- if (wrap) {
- desc = wordwrap(llen + padding + 1, wrap)(desc)
- .slice(llen + padding + 1);
- }
- // if we need to wrap the left-hand-column,
- // split it on to multiple lines.
- if (wrap && left.length > llen) {
- leftLines = wordwrap(2, llen)(left.trim()).split('\n');
- left = '';
- }
- var lpadding = new Array(
- Math.max(llen - left.length + padding, 0)
- ).join(' ');
- var dpadding = new Array(
- Math.max(desclen - desc.length + 1, 0)
- ).join(' ');
- if (!wrap && dpadding.length > 0) {
- desc += dpadding;
- }
- var prelude = ' ' + left + lpadding;
- var body = [ desc, extra ].filter(Boolean).join(' ');
- if (wrap) {
- var dlines = desc.split('\n');
- var dlen = dlines.slice(-1)[0].length
- + (dlines.length === 1 ? prelude.length : 0)
- if (extra.length > wrap) {
- body = desc + '\n' + wordwrap(llen + 4, wrap)(extra)
- } else {
- body = desc + (dlen + extra.length > wrap - 2
- ? '\n'
- + new Array(wrap - extra.length + 1).join(' ')
- + extra
- : new Array(wrap - extra.length - dlen + 1).join(' ')
- + extra
- );
- }
- }
- if (leftLines) { // handle word-wrapping the left-hand-column.
- var rightLines = body.split('\n'),
- firstLine = prelude + rightLines[0],
- lineCount = Math.max(leftLines.length, rightLines.length);
- for (var i = 0; i < lineCount; i++) {
- var left = leftLines[i],
- right = i ? rightLines[i] : firstLine;
- output.push(strcpy(left, right, firstLine.length));
- }
- } else {
- output.push(prelude + body);
- }
- });
- return output;
- }
- // find longest string in array of strings.
- function longest (xs) {
- return Math.max.apply(
- null,
- xs.map(function (x) { return x.length })
- );
- }
- // copy one string into another, used when
- // formatting usage table.
- function strcpy (source, destination, width) {
- var str = ''
- source = source || '';
- destination = destination || new Array(width).join(' ');
- for (var i = 0; i < destination.length; i++) {
- var char = destination.charAt(i);
- if (char === ' ') char = source.charAt(i) || char;
- str += char;
- }
- return str;
- }
- // guess the width of the console window, max-width 100.
- function windowWidth() {
- return wsize.width ? Math.min(80, wsize.width) : null;
- }
- // logic for displaying application version.
- var version = null;
- self.version = function (ver, opt, msg) {
- version = ver;
- };
- self.showVersion = function() {
- if (typeof version === 'function') console.log(version());
- else console.log(version);
- };
- return self;
- }
|