123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778 |
- const assert = require('assert')
- const assign = require('lodash.assign')
- const Command = require('./lib/command')
- const Completion = require('./lib/completion')
- const Parser = require('yargs-parser')
- const path = require('path')
- const Usage = require('./lib/usage')
- const Validation = require('./lib/validation')
- const Y18n = require('y18n')
- const requireMainFilename = require('require-main-filename')
- const objFilter = require('./lib/obj-filter')
- const setBlocking = require('set-blocking')
- var exports = module.exports = Yargs
- function Yargs (processArgs, cwd, parentRequire) {
- processArgs = processArgs || [] // handle calling yargs().
- const self = {}
- var command = null
- var completion = null
- var groups = {}
- var preservedGroups = {}
- var usage = null
- var validation = null
- const y18n = Y18n({
- directory: path.resolve(__dirname, './locales'),
- updateFiles: false
- })
- 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)(\.exe)?$/.test(x)) return
- var b = rebase(cwd, x)
- return x.match(/^(\/|([a-zA-Z]:)?\\)/) && 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) + '/', ''
- )
- }
- // use context object to keep track of resets, subcommand execution, etc
- // submodules should modify and check the state of context as necessary
- const context = { resets: -1, commands: [], files: [] }
- self.getContext = function () {
- return context
- }
- // puts yargs back into an initial state. any keys
- // that have been set to "global" will not be reset
- // by this action.
- var options
- self.resetOptions = self.reset = function (aliases) {
- context.resets++
- aliases = aliases || {}
- options = options || {}
- // put yargs back into an initial state, this
- // logic is used to build a nested command
- // hierarchy.
- var tmpOptions = {}
- tmpOptions.global = options.global ? options.global : []
- // if a key has been set as a global, we
- // do not want to reset it or its aliases.
- var globalLookup = {}
- tmpOptions.global.forEach(function (g) {
- globalLookup[g] = true
- ;(aliases[g] || []).forEach(function (a) {
- globalLookup[a] = true
- })
- })
- // preserve groups containing global keys
- preservedGroups = Object.keys(groups).reduce(function (acc, groupName) {
- var keys = groups[groupName].filter(function (key) {
- return key in globalLookup
- })
- if (keys.length > 0) {
- acc[groupName] = keys
- }
- return acc
- }, {})
- // groups can now be reset
- groups = {}
- var arrayOptions = [
- 'array', 'boolean', 'string', 'requiresArg', 'skipValidation',
- 'count', 'normalize', 'number'
- ]
- var objectOptions = [
- 'narg', 'key', 'alias', 'default', 'defaultDescription',
- 'config', 'choices', 'demanded'
- ]
- arrayOptions.forEach(function (k) {
- tmpOptions[k] = (options[k] || []).filter(function (k) {
- return globalLookup[k]
- })
- })
- objectOptions.forEach(function (k) {
- tmpOptions[k] = objFilter(options[k], function (k, v) {
- return globalLookup[k]
- })
- })
- tmpOptions.envPrefix = undefined
- options = tmpOptions
- // if this is the first time being executed, create
- // instances of all our helpers -- otherwise just reset.
- usage = usage ? usage.reset(globalLookup) : Usage(self, y18n)
- validation = validation ? validation.reset(globalLookup) : Validation(self, usage, y18n)
- command = command ? command.reset() : Command(self, usage, validation)
- if (!completion) completion = Completion(self, usage, command)
- exitProcess = true
- strict = false
- completionCommand = null
- 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.number = function (numbers) {
- options.number.push.apply(options.number, [].concat(numbers))
- return self
- }
- self.choices = function (key, values) {
- if (typeof key === 'object') {
- Object.keys(key).forEach(function (k) {
- self.choices(k, key[k])
- })
- } else {
- options.choices[key] = (options.choices[key] || []).concat(values)
- }
- return self
- }
- self.normalize = function (strings) {
- options.normalize.push.apply(options.normalize, [].concat(strings))
- return self
- }
- self.config = function (key, msg, parseFn) {
- // allow to pass a configuration object
- if (typeof key === 'object') {
- options.configObjects = (options.configObjects || []).concat(key)
- return self
- }
- // allow to provide a parsing function
- if (typeof msg === 'function') {
- parseFn = msg
- msg = null
- }
- key = key || 'config'
- self.describe(key, msg || usage.deferY18nLookup('Path to JSON config file'))
- ;(Array.isArray(key) ? key : [key]).forEach(function (k) {
- options.config[k] = parseFn || true
- })
- return self
- }
- self.example = function (cmd, description) {
- usage.example(cmd, description)
- return self
- }
- self.command = function (cmd, description, builder, handler) {
- command.addHandler(cmd, description, builder, handler)
- return self
- }
- self.commandDir = function (dir, opts) {
- const req = parentRequire || require
- command.addDirectory(dir, self.getContext(), req, require('get-caller-file')(), opts)
- return self
- }
- self.string = function (strings) {
- options.string.push.apply(options.string, [].concat(strings))
- return self
- }
- // The 'defaults' alias is deprecated. It will be removed in the next major version.
- self.default = self.defaults = function (key, value, defaultDescription) {
- if (typeof key === 'object') {
- Object.keys(key).forEach(function (k) {
- self.default(k, key[k])
- })
- } else {
- if (defaultDescription) options.defaultDescription[key] = defaultDescription
- if (typeof value === 'function') {
- if (!options.defaultDescription[key]) options.defaultDescription[key] = usage.functionDescription(value)
- value = value.call()
- }
- 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
- }
- self.demand = self.required = self.require = function (keys, max, msg) {
- // you can optionally provide a 'max' key,
- // which will raise an exception if too many '_'
- // options are provided.
- if (Array.isArray(max)) {
- max.forEach(function (key) {
- self.demand(key, msg)
- })
- max = Infinity
- } else if (typeof max !== 'number') {
- msg = max
- max = Infinity
- }
- if (typeof keys === 'number') {
- if (!options.demanded._) options.demanded._ = { count: 0, msg: null, max: max }
- options.demanded._.count = keys
- options.demanded._.msg = msg
- } else if (Array.isArray(keys)) {
- keys.forEach(function (key) {
- self.demand(key, msg)
- })
- } else {
- if (typeof msg === 'string') {
- options.demanded[keys] = { msg: msg }
- } else if (msg === true || typeof msg === 'undefined') {
- options.demanded[keys] = { msg: undefined }
- }
- }
- return self
- }
- self.getDemanded = function () {
- return options.demanded
- }
- self.requiresArg = function (requiresArgs) {
- options.requiresArg.push.apply(options.requiresArg, [].concat(requiresArgs))
- return self
- }
- self.skipValidation = function (skipValidations) {
- options.skipValidation.push.apply(options.skipValidation, [].concat(skipValidations))
- 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.describe = function (key, desc) {
- options.key[key] = true
- usage.describe(key, desc)
- return self
- }
- self.global = function (globals) {
- options.global.push.apply(options.global, [].concat(globals))
- return self
- }
- self.pkgConf = function (key, path) {
- var conf = null
- var obj = pkgUp(path)
- // If an object exists in the key, add it to options.configObjects
- if (obj[key] && typeof obj[key] === 'object') {
- conf = obj[key]
- options.configObjects = (options.configObjects || []).concat(conf)
- }
- return self
- }
- var pkgs = {}
- function pkgUp (path) {
- var npath = path || '*'
- if (pkgs[npath]) return pkgs[npath]
- const readPkgUp = require('read-pkg-up')
- var obj = {}
- try {
- obj = readPkgUp.sync({
- cwd: path || requireMainFilename(parentRequire || require)
- })
- } catch (noop) {}
- pkgs[npath] = obj.pkg || {}
- return pkgs[npath]
- }
- self.parse = function (args, shortCircuit) {
- if (!shortCircuit) processArgs = args
- return parseArgs(args, shortCircuit)
- }
- 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 ('config' in opt) {
- self.config(key, opt.configParser)
- } if ('default' in opt) {
- self.default(key, opt.default)
- } if ('nargs' in opt) {
- self.nargs(key, opt.nargs)
- } if ('normalize' in opt) {
- self.normalize(key)
- } if ('choices' in opt) {
- self.choices(key, opt.choices)
- } if ('group' in opt) {
- self.group(key, opt.group)
- } if (opt.global) {
- self.global(key)
- } 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.number || opt.type === 'number') {
- self.number(key)
- if (opt.alias) self.number(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)
- } if (opt.defaultDescription) {
- options.defaultDescription[key] = opt.defaultDescription
- } if (opt.skipValidation) {
- self.skipValidation(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.group = function (opts, groupName) {
- var existing = preservedGroups[groupName] || groups[groupName]
- if (preservedGroups[groupName]) {
- // the preserved group will be moved to the set of explicitly declared
- // groups
- delete preservedGroups[groupName]
- }
- var seen = {}
- groups[groupName] = (existing || []).concat(opts).filter(function (key) {
- if (seen[key]) return false
- return (seen[key] = true)
- })
- return self
- }
- self.getGroups = function () {
- // combine explicit and preserved groups. explicit groups should be first
- return assign({}, groups, preservedGroups)
- }
- // as long as options.envPrefix is not undefined,
- // parser will apply env vars matching prefix to argv
- self.env = function (prefix) {
- if (prefix === false) options.envPrefix = undefined
- else options.envPrefix = prefix || ''
- return self
- }
- 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 (opt, msg, ver) {
- if (arguments.length === 0) {
- ver = guessVersion()
- opt = 'version'
- } else if (arguments.length === 1) {
- ver = opt
- opt = 'version'
- } else if (arguments.length === 2) {
- ver = msg
- }
- versionOpt = opt
- msg = msg || usage.deferY18nLookup('Show version number')
- usage.version(ver || undefined)
- self.boolean(versionOpt)
- self.global(versionOpt)
- self.describe(versionOpt, msg)
- return self
- }
- function guessVersion () {
- var obj = pkgUp()
- return obj.version || 'unknown'
- }
- var helpOpt = null
- self.addHelpOpt = self.help = function (opt, msg) {
- opt = opt || 'help'
- helpOpt = opt
- self.boolean(opt)
- self.global(opt)
- self.describe(opt, msg || usage.deferY18nLookup('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
- }
- var 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 || 'completion'
- if (!desc && desc !== false) {
- desc = 'generate bash completion script'
- }
- self.command(completionCommand, desc)
- // 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.getCompletion = function (args, done) {
- completion.getCompletion(args, done)
- }
- self.locale = function (locale) {
- if (arguments.length === 0) {
- guessLocale()
- return y18n.getLocale()
- }
- detectLocale = false
- y18n.setLocale(locale)
- return self
- }
- self.updateStrings = self.updateLocale = function (obj) {
- detectLocale = false
- y18n.updateLocale(obj)
- return self
- }
- var detectLocale = true
- self.detectLocale = function (detect) {
- detectLocale = detect
- return self
- }
- self.getDetectLocale = function () {
- return detectLocale
- }
- self.getUsageInstance = function () {
- return usage
- }
- self.getValidationInstance = function () {
- return validation
- }
- self.getCommandInstance = function () {
- return command
- }
- 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, err)
- }
- return args
- },
- enumerable: true
- })
- function parseArgs (args, shortCircuit) {
- options.__ = y18n.__
- options.configuration = pkgUp(cwd)['yargs'] || {}
- const parsed = Parser.detailed(args, options)
- const argv = parsed.argv
- var aliases = parsed.aliases
- argv.$0 = self.$0
- self.parsed = parsed
- guessLocale() // guess locale lazily, so that it can be turned off in chain.
- // while building up the argv object, there
- // are two passes through the parser. If completion
- // is being performed short-circuit on the first pass.
- if (shortCircuit) {
- return argv
- }
- // if there's a handler associated with a
- // command defer processing to it.
- var handlerKeys = command.getCommands()
- for (var i = 0, cmd; (cmd = argv._[i]) !== undefined; i++) {
- if (~handlerKeys.indexOf(cmd) && cmd !== completionCommand) {
- setPlaceholderKeys(argv)
- return command.runCommand(cmd, self, parsed)
- }
- }
- // generate a completion script for adding to ~/.bashrc.
- if (completionCommand && ~argv._.indexOf(completionCommand) && !argv[completion.completionKey]) {
- if (exitProcess) setBlocking(true)
- self.showCompletionScript()
- if (exitProcess) {
- process.exit(0)
- }
- }
- // we must run completions first, a user might
- // want to complete the --help or --version option.
- if (completion.completionKey in argv) {
- if (exitProcess) setBlocking(true)
- // we allow for asynchronous completions,
- // e.g., loading in a list of commands from an API.
- var completionArgs = args.slice(args.indexOf('--' + completion.completionKey) + 1)
- completion.getCompletion(completionArgs, function (completions) {
- ;(completions || []).forEach(function (completion) {
- console.log(completion)
- })
- if (exitProcess) {
- process.exit(0)
- }
- })
- return
- }
- var skipValidation = false
- // Handle 'help' and 'version' options
- Object.keys(argv).forEach(function (key) {
- if (key === helpOpt && argv[key]) {
- if (exitProcess) setBlocking(true)
- skipValidation = true
- self.showHelp('log')
- if (exitProcess) {
- process.exit(0)
- }
- } else if (key === versionOpt && argv[key]) {
- if (exitProcess) setBlocking(true)
- skipValidation = true
- usage.showVersion()
- if (exitProcess) {
- process.exit(0)
- }
- }
- })
- // Check if any of the options to skip validation were provided
- if (!skipValidation && options.skipValidation.length > 0) {
- skipValidation = Object.keys(argv).some(function (key) {
- return options.skipValidation.indexOf(key) >= 0
- })
- }
- // If the help or version options where used and exitProcess is false,
- // or if explicitly skipped, we won't run validations
- if (!skipValidation) {
- if (parsed.error) throw parsed.error
- // if we're executed via bash completion, don't
- // bother with validation.
- if (!argv[completion.completionKey]) {
- validation.nonOptionCount(argv)
- validation.missingArgumentValue(argv)
- validation.requiredArguments(argv)
- if (strict) validation.unknownArguments(argv, aliases)
- validation.customChecks(argv, aliases)
- validation.limitedChoices(argv)
- validation.implications(argv)
- }
- }
- setPlaceholderKeys(argv)
- return argv
- }
- function guessLocale () {
- if (!detectLocale) return
- try {
- const osLocale = require('os-locale')
- self.locale(osLocale.sync({ spawn: false }))
- } catch (err) {
- // if we explode looking up locale just noop
- // we'll keep using the default language 'en'.
- }
- }
- function setPlaceholderKeys (argv) {
- Object.keys(options.key).forEach(function (key) {
- // don't set placeholder keys for dot
- // notation options 'foo.bar'.
- if (~key.indexOf('.')) return
- if (typeof argv[key] === 'undefined') argv[key] = undefined
- })
- }
- 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)
- }
|