You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
373 lines
11 KiB
JavaScript
373 lines
11 KiB
JavaScript
7 years ago
|
// 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;
|
||
|
}
|