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.
504 lines
12 KiB
504 lines
12 KiB
var assert = require('assert'), |
|
path = require('path'), |
|
Completion = require('./lib/completion'), |
|
Parser = require('./lib/parser'), |
|
Usage = require('./lib/usage'), |
|
Validation = require('./lib/validation') |
|
|
|
Argv(process.argv.slice(2)) |
|
|
|
var exports = module.exports = Argv |
|
function Argv (processArgs, cwd) { |
|
processArgs = processArgs || [] // handle calling yargs(). |
|
|
|
var self = {} |
|
var completion = null |
|
var usage = null |
|
var validation = null |
|
|
|
if (!cwd) cwd = process.cwd() |
|
|
|
self.$0 = process.argv |
|
.slice(0, 2) |
|
.map(function (x, i) { |
|
// ignore the node bin, specify this in your |
|
// bin file with #!/usr/bin/env node |
|
if (i === 0 && /\b(node|iojs)$/.test(x)) return |
|
var b = rebase(cwd, x) |
|
return x.match(/^\//) && b.length < x.length |
|
? b : x |
|
}) |
|
.join(' ').trim() |
|
|
|
if (process.env._ !== undefined && process.argv[1] === process.env._) { |
|
self.$0 = process.env._.replace( |
|
path.dirname(process.execPath) + '/', '' |
|
) |
|
} |
|
|
|
var options |
|
self.resetOptions = self.reset = function () { |
|
// put yargs back into its initial |
|
// state, this is useful for creating a |
|
// nested CLI. |
|
options = { |
|
array: [], |
|
boolean: [], |
|
string: [], |
|
narg: {}, |
|
key: {}, |
|
alias: {}, |
|
default: {}, |
|
defaultDescription: {}, |
|
requiresArg: [], |
|
count: [], |
|
normalize: [], |
|
config: [] |
|
} |
|
|
|
usage = Usage(self) // handle usage output. |
|
validation = Validation(self, usage) // handle arg validation. |
|
completion = Completion(self, usage) |
|
|
|
demanded = {} |
|
|
|
exitProcess = true |
|
strict = false |
|
helpOpt = null |
|
versionOpt = null |
|
completionOpt = null |
|
commandHandlers = {} |
|
self.parsed = false |
|
|
|
return self |
|
} |
|
self.resetOptions() |
|
|
|
self.boolean = function (bools) { |
|
options.boolean.push.apply(options.boolean, [].concat(bools)) |
|
return self |
|
} |
|
|
|
self.array = function (arrays) { |
|
options.array.push.apply(options.array, [].concat(arrays)) |
|
return self |
|
} |
|
|
|
self.nargs = function (key, n) { |
|
if (typeof key === 'object') { |
|
Object.keys(key).forEach(function (k) { |
|
self.nargs(k, key[k]) |
|
}) |
|
} else { |
|
options.narg[key] = n |
|
} |
|
return self |
|
} |
|
|
|
self.normalize = function (strings) { |
|
options.normalize.push.apply(options.normalize, [].concat(strings)) |
|
return self |
|
} |
|
|
|
self.config = function (configs) { |
|
options.config.push.apply(options.config, [].concat(configs)) |
|
return self |
|
} |
|
|
|
self.example = function (cmd, description) { |
|
usage.example(cmd, description) |
|
return self |
|
} |
|
|
|
self.command = function (cmd, description, fn) { |
|
usage.command(cmd, description) |
|
if (fn) commandHandlers[cmd] = fn |
|
return self |
|
} |
|
|
|
var commandHandlers = {} |
|
self.getCommandHandlers = function () { |
|
return commandHandlers |
|
} |
|
|
|
self.string = function (strings) { |
|
options.string.push.apply(options.string, [].concat(strings)) |
|
return self |
|
} |
|
|
|
self.default = function (key, value, defaultDescription) { |
|
if (typeof key === 'object') { |
|
Object.keys(key).forEach(function (k) { |
|
self.default(k, key[k]) |
|
}) |
|
} else { |
|
if (typeof value === 'function') { |
|
defaultDescription = usage.functionDescription(value, defaultDescription) |
|
value = value.call() |
|
} |
|
options.defaultDescription[key] = defaultDescription |
|
options.default[key] = value |
|
} |
|
return self |
|
} |
|
|
|
self.alias = function (x, y) { |
|
if (typeof x === 'object') { |
|
Object.keys(x).forEach(function (key) { |
|
self.alias(key, x[key]) |
|
}) |
|
} else { |
|
options.alias[x] = (options.alias[x] || []).concat(y) |
|
} |
|
return self |
|
} |
|
|
|
self.count = function (counts) { |
|
options.count.push.apply(options.count, [].concat(counts)) |
|
return self |
|
} |
|
|
|
var demanded = {} |
|
self.demand = self.required = self.require = function (keys, msg) { |
|
if (typeof keys === 'number') { |
|
if (!demanded._) demanded._ = { count: 0, msg: null } |
|
demanded._.count += keys |
|
demanded._.msg = msg |
|
} else if (Array.isArray(keys)) { |
|
keys.forEach(function (key) { |
|
self.demand(key, msg) |
|
}) |
|
} else { |
|
if (typeof msg === 'string') { |
|
demanded[keys] = { msg: msg } |
|
} else if (msg === true || typeof msg === 'undefined') { |
|
demanded[keys] = { msg: undefined } |
|
} |
|
} |
|
|
|
return self |
|
} |
|
self.getDemanded = function () { |
|
return demanded |
|
} |
|
|
|
self.requiresArg = function (requiresArgs) { |
|
options.requiresArg.push.apply(options.requiresArg, [].concat(requiresArgs)) |
|
return self |
|
} |
|
|
|
self.implies = function (key, value) { |
|
validation.implies(key, value) |
|
return self |
|
} |
|
|
|
self.usage = function (msg, opts) { |
|
if (!opts && typeof msg === 'object') { |
|
opts = msg |
|
msg = null |
|
} |
|
|
|
usage.usage(msg) |
|
|
|
if (opts) self.options(opts) |
|
|
|
return self |
|
} |
|
|
|
self.epilogue = self.epilog = function (msg) { |
|
usage.epilog(msg) |
|
return self |
|
} |
|
|
|
self.fail = function (f) { |
|
usage.failFn(f) |
|
return self |
|
} |
|
|
|
self.check = function (f) { |
|
validation.check(f) |
|
return self |
|
} |
|
|
|
self.defaults = self.default |
|
|
|
self.describe = function (key, desc) { |
|
options.key[key] = true |
|
usage.describe(key, desc) |
|
return self |
|
} |
|
|
|
self.parse = function (args) { |
|
return parseArgs(args) |
|
} |
|
|
|
self.option = self.options = function (key, opt) { |
|
if (typeof key === 'object') { |
|
Object.keys(key).forEach(function (k) { |
|
self.options(k, key[k]) |
|
}) |
|
} else { |
|
assert(typeof opt === 'object', 'second argument to option must be an object') |
|
|
|
options.key[key] = true // track manually set keys. |
|
|
|
if (opt.alias) self.alias(key, opt.alias) |
|
|
|
var demand = opt.demand || opt.required || opt.require |
|
|
|
if (demand) { |
|
self.demand(key, demand) |
|
} if ('default' in opt) { |
|
self.default(key, opt.default) |
|
} if ('nargs' in opt) { |
|
self.nargs(key, opt.nargs) |
|
} if (opt.boolean || opt.type === 'boolean') { |
|
self.boolean(key) |
|
if (opt.alias) self.boolean(opt.alias) |
|
} if (opt.array || opt.type === 'array') { |
|
self.array(key) |
|
if (opt.alias) self.array(opt.alias) |
|
} if (opt.string || opt.type === 'string') { |
|
self.string(key) |
|
if (opt.alias) self.string(opt.alias) |
|
} if (opt.count || opt.type === 'count') { |
|
self.count(key) |
|
} |
|
|
|
var desc = opt.describe || opt.description || opt.desc |
|
if (desc) { |
|
self.describe(key, desc) |
|
} |
|
|
|
if (opt.requiresArg) { |
|
self.requiresArg(key) |
|
} |
|
} |
|
|
|
return self |
|
} |
|
self.getOptions = function () { |
|
return options |
|
} |
|
|
|
self.wrap = function (cols) { |
|
usage.wrap(cols) |
|
return self |
|
} |
|
|
|
var strict = false |
|
self.strict = function () { |
|
strict = true |
|
return self |
|
} |
|
self.getStrict = function () { |
|
return strict |
|
} |
|
|
|
self.showHelp = function (level) { |
|
if (!self.parsed) parseArgs(processArgs) // run parser, if it has not already been executed. |
|
usage.showHelp(level) |
|
return self |
|
} |
|
|
|
var versionOpt = null |
|
self.version = function (ver, opt, msg) { |
|
versionOpt = opt || 'version' |
|
usage.version(ver) |
|
self.boolean(versionOpt) |
|
self.describe(versionOpt, msg || 'Show version number') |
|
return self |
|
} |
|
|
|
var helpOpt = null |
|
self.addHelpOpt = function (opt, msg) { |
|
helpOpt = opt |
|
self.boolean(opt) |
|
self.describe(opt, msg || 'Show help') |
|
return self |
|
} |
|
|
|
self.showHelpOnFail = function (enabled, message) { |
|
usage.showHelpOnFail(enabled, message) |
|
return self |
|
} |
|
|
|
var exitProcess = true |
|
self.exitProcess = function (enabled) { |
|
if (typeof enabled !== 'boolean') { |
|
enabled = true |
|
} |
|
exitProcess = enabled |
|
return self |
|
} |
|
self.getExitProcess = function () { |
|
return exitProcess |
|
} |
|
|
|
self.help = function () { |
|
if (arguments.length > 0) return self.addHelpOpt.apply(self, arguments) |
|
|
|
if (!self.parsed) parseArgs(processArgs) // run parser, if it has not already been executed. |
|
|
|
return usage.help() |
|
} |
|
|
|
var completionOpt = null, |
|
completionCommand = null |
|
self.completion = function (cmd, desc, fn) { |
|
// a function to execute when generating |
|
// completions can be provided as the second |
|
// or third argument to completion. |
|
if (typeof desc === 'function') { |
|
fn = desc |
|
desc = null |
|
} |
|
|
|
// register the completion command. |
|
completionCommand = cmd |
|
completionOpt = completion.completionKey |
|
self.command(completionCommand, desc || 'generate bash completion script') |
|
|
|
// a function can be provided |
|
if (fn) completion.registerFunction(fn) |
|
|
|
return self |
|
} |
|
|
|
self.showCompletionScript = function ($0) { |
|
$0 = $0 || self.$0 |
|
console.log(completion.generateCompletionScript($0)) |
|
return self |
|
} |
|
|
|
self.getUsageInstance = function () { |
|
return usage |
|
} |
|
|
|
self.getValidationInstance = function () { |
|
return validation |
|
} |
|
|
|
self.terminalWidth = function () { |
|
return require('window-size').width |
|
} |
|
|
|
Object.defineProperty(self, 'argv', { |
|
get: function () { |
|
var args = null |
|
|
|
try { |
|
args = parseArgs(processArgs) |
|
} catch (err) { |
|
usage.fail(err.message) |
|
} |
|
|
|
return args |
|
}, |
|
enumerable: true |
|
}) |
|
|
|
function parseArgs (args) { |
|
var parsed = Parser(args, options), |
|
argv = parsed.argv, |
|
aliases = parsed.aliases |
|
|
|
argv.$0 = self.$0 |
|
|
|
self.parsed = parsed |
|
|
|
// generate a completion script for adding to ~/.bashrc. |
|
if (completionCommand && ~argv._.indexOf(completionCommand)) { |
|
self.showCompletionScript() |
|
if (exitProcess) { |
|
process.exit(0) |
|
} |
|
} |
|
|
|
// if there's a handler associated with a |
|
// command defer processing to it. |
|
var handlerKeys = Object.keys(self.getCommandHandlers()) |
|
for (var i = 0, command; (command = handlerKeys[i]) !== undefined; i++) { |
|
if (~argv._.indexOf(command)) { |
|
self.getCommandHandlers()[command](self.reset()) |
|
return self.argv |
|
} |
|
} |
|
|
|
Object.keys(argv).forEach(function (key) { |
|
if (key === helpOpt && argv[key]) { |
|
self.showHelp('log') |
|
if (exitProcess) { |
|
process.exit(0) |
|
} |
|
} else if (key === versionOpt && argv[key]) { |
|
usage.showVersion() |
|
if (exitProcess) { |
|
process.exit(0) |
|
} |
|
} else if (key === completionOpt) { |
|
// we allow for asynchronous completions, |
|
// e.g., loading in a list of commands from an API. |
|
completion.getCompletion(function (completions) { |
|
;(completions || []).forEach(function (completion) { |
|
console.log(completion) |
|
}) |
|
|
|
if (exitProcess) { |
|
process.exit(0) |
|
} |
|
}) |
|
return |
|
} |
|
}) |
|
|
|
validation.nonOptionCount(argv) |
|
validation.missingArgumentValue(argv) |
|
validation.requiredArguments(argv) |
|
|
|
if (strict) { |
|
validation.unknownArguments(argv, aliases) |
|
} |
|
|
|
validation.customChecks(argv, aliases) |
|
validation.implications(argv) |
|
setPlaceholderKeys(argv) |
|
|
|
return argv |
|
} |
|
|
|
function setPlaceholderKeys (argv) { |
|
Object.keys(options.key).forEach(function (key) { |
|
if (typeof argv[key] === 'undefined') argv[key] = undefined |
|
}) |
|
} |
|
|
|
sigletonify(self) |
|
return self |
|
} |
|
|
|
// rebase an absolute path to a relative one with respect to a base directory |
|
// exported for tests |
|
exports.rebase = rebase |
|
function rebase (base, dir) { |
|
return path.relative(base, dir) |
|
} |
|
|
|
/* Hack an instance of Argv with process.argv into Argv |
|
so people can do |
|
require('yargs')(['--beeble=1','-z','zizzle']).argv |
|
to parse a list of args and |
|
require('yargs').argv |
|
to get a parsed version of process.argv. |
|
*/ |
|
function sigletonify (inst) { |
|
Object.keys(inst).forEach(function (key) { |
|
if (key === 'argv') { |
|
Argv.__defineGetter__(key, inst.__lookupGetter__(key)) |
|
} else { |
|
Argv[key] = typeof inst[key] === 'function' |
|
? inst[key].bind(inst) |
|
: inst[key] |
|
} |
|
}) |
|
}
|
|
|