You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
314 lines
7.7 KiB
314 lines
7.7 KiB
// this file handles outputting usage instructions, |
|
// failures, etc. keeps logging in one place. |
|
var cliui = require('cliui'), |
|
decamelize = require('decamelize'), |
|
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 () { |
|
normalizeAliases() |
|
|
|
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 |
|
}, {}) |
|
), |
|
ui = cliui({ |
|
width: wrap, |
|
wrap: !!wrap |
|
}) |
|
|
|
// the usage string. |
|
if (usage) { |
|
var u = usage.replace(/\$0/g, yargs.$0) |
|
ui.div(u + '\n') |
|
} |
|
|
|
// your application's commands, i.e., non-option |
|
// arguments populated in '_'. |
|
if (commands.length) { |
|
ui.div('Commands:') |
|
|
|
commands.forEach(function (command) { |
|
ui.div( |
|
{text: command[0], padding: [0, 2, 0, 2], width: maxWidth(commands) + 4}, |
|
{text: command[1]} |
|
) |
|
}) |
|
|
|
ui.div() |
|
} |
|
|
|
// 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 (options.alias[alias] || []).indexOf(key) === -1 |
|
}) |
|
}) |
|
|
|
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 |
|
}, {}) |
|
|
|
if (keys.length) { |
|
ui.div('Options:') |
|
|
|
keys.forEach(function (key) { |
|
var kswitch = switches[key] |
|
var desc = descriptions[key] || '' |
|
var type = null |
|
|
|
if (~options.boolean.indexOf(key)) type = '[boolean]' |
|
if (~options.count.indexOf(key)) type = '[count]' |
|
if (~options.string.indexOf(key)) type = '[string]' |
|
if (~options.normalize.indexOf(key)) type = '[string]' |
|
if (~options.array.indexOf(key)) type = '[array]' |
|
|
|
var extra = [ |
|
type, |
|
demanded[key] ? '[required]' : null, |
|
defaultString(options.default[key], options.defaultDescription[key]) |
|
].filter(Boolean).join(' ') |
|
|
|
ui.span( |
|
{text: kswitch, padding: [0, 2, 0, 2], width: maxWidth(switches) + 4}, |
|
desc |
|
) |
|
|
|
if (extra) ui.div({text: extra, padding: [0, 0, 0, 2], align: 'right'}) |
|
else ui.div() |
|
}) |
|
|
|
ui.div() |
|
} |
|
|
|
// describe some common use-cases for your application. |
|
if (examples.length) { |
|
ui.div('Examples:') |
|
|
|
examples.forEach(function (example) { |
|
example[0] = example[0].replace(/\$0/g, yargs.$0) |
|
}) |
|
|
|
examples.forEach(function (example) { |
|
ui.div( |
|
{text: example[0], padding: [0, 2, 0, 2], width: maxWidth(examples) + 4}, |
|
example[1] |
|
) |
|
}) |
|
|
|
ui.div() |
|
} |
|
|
|
// the usage string. |
|
if (epilog) { |
|
var e = epilog.replace(/\$0/g, yargs.$0) |
|
ui.div(e + '\n') |
|
} |
|
|
|
return ui.toString() |
|
} |
|
|
|
// return the maximum width of a string |
|
// in the left-hand column of a table. |
|
function maxWidth (table) { |
|
var width = 0 |
|
|
|
// table might be of the form [leftColumn], |
|
// or {key: leftColumn}} |
|
if (!Array.isArray(table)) { |
|
table = Object.keys(table).map(function (key) { |
|
return [table[key]] |
|
}) |
|
} |
|
|
|
table.forEach(function (v) { |
|
width = Math.max(v[0].length, width) |
|
}) |
|
|
|
// if we've enabled 'wrap' we should limit |
|
// the max-width of the left-column. |
|
if (wrap) width = Math.min(width, parseInt(wrap * 0.5, 10)) |
|
|
|
return width |
|
} |
|
|
|
// make sure any options set for aliases, |
|
// are copied to the keys being aliased. |
|
function normalizeAliases () { |
|
var options = yargs.getOptions(), |
|
demanded = yargs.getDemanded() |
|
|
|
;(Object.keys(options.alias) || []).forEach(function (key) { |
|
options.alias[key].forEach(function (alias) { |
|
// copy descriptions. |
|
if (descriptions[alias]) self.describe(key, descriptions[alias]) |
|
// copy demanded. |
|
if (demanded[alias]) yargs.demand(key, demanded[alias].msg) |
|
|
|
// type messages. |
|
if (~options.boolean.indexOf(alias)) yargs.boolean(key) |
|
if (~options.count.indexOf(alias)) yargs.count(key) |
|
if (~options.string.indexOf(alias)) yargs.string(key) |
|
if (~options.normalize.indexOf(alias)) yargs.normalize(key) |
|
if (~options.array.indexOf(alias)) yargs.array(key) |
|
}) |
|
}) |
|
} |
|
|
|
self.showHelp = function (level) { |
|
level = level || 'error' |
|
console[level](self.help()) |
|
} |
|
|
|
self.functionDescription = function (fn, defaultDescription) { |
|
if (defaultDescription) { |
|
return defaultDescription |
|
} |
|
var description = fn.name ? decamelize(fn.name, '-') : 'generated-value' |
|
return ['(', description, ')'].join('') |
|
} |
|
|
|
// 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 'object': |
|
string += JSON.stringify(value) |
|
break |
|
default: |
|
string += value |
|
} |
|
} |
|
|
|
return string + ']' |
|
} |
|
|
|
// guess the width of the console window, max-width 80. |
|
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 |
|
}
|
|
|