yargs.js 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127
  1. const argsert = require('./lib/argsert')
  2. const assign = require('./lib/assign')
  3. const Command = require('./lib/command')
  4. const Completion = require('./lib/completion')
  5. const Parser = require('yargs-parser')
  6. const path = require('path')
  7. const Usage = require('./lib/usage')
  8. const Validation = require('./lib/validation')
  9. const Y18n = require('y18n')
  10. const objFilter = require('./lib/obj-filter')
  11. const setBlocking = require('set-blocking')
  12. const applyExtends = require('./lib/apply-extends')
  13. const YError = require('./lib/yerror')
  14. var exports = module.exports = Yargs
  15. function Yargs (processArgs, cwd, parentRequire) {
  16. processArgs = processArgs || [] // handle calling yargs().
  17. const self = {}
  18. var command = null
  19. var completion = null
  20. var groups = {}
  21. var output = ''
  22. var preservedGroups = {}
  23. var usage = null
  24. var validation = null
  25. const y18n = Y18n({
  26. directory: path.resolve(__dirname, './locales'),
  27. updateFiles: false
  28. })
  29. if (!cwd) cwd = process.cwd()
  30. self.$0 = process.argv
  31. .slice(0, 2)
  32. .map(function (x, i) {
  33. // ignore the node bin, specify this in your
  34. // bin file with #!/usr/bin/env node
  35. if (i === 0 && /\b(node|iojs)(\.exe)?$/.test(x)) return
  36. var b = rebase(cwd, x)
  37. return x.match(/^(\/|([a-zA-Z]:)?\\)/) && b.length < x.length ? b : x
  38. })
  39. .join(' ').trim()
  40. if (process.env._ !== undefined && process.argv[1] === process.env._) {
  41. self.$0 = process.env._.replace(
  42. path.dirname(process.execPath) + '/', ''
  43. )
  44. }
  45. // use context object to keep track of resets, subcommand execution, etc
  46. // submodules should modify and check the state of context as necessary
  47. const context = { resets: -1, commands: [], files: [] }
  48. self.getContext = function () {
  49. return context
  50. }
  51. // puts yargs back into an initial state. any keys
  52. // that have been set to "global" will not be reset
  53. // by this action.
  54. var options
  55. self.resetOptions = self.reset = function (aliases) {
  56. context.resets++
  57. aliases = aliases || {}
  58. options = options || {}
  59. // put yargs back into an initial state, this
  60. // logic is used to build a nested command
  61. // hierarchy.
  62. var tmpOptions = {}
  63. tmpOptions.local = options.local ? options.local : []
  64. tmpOptions.configObjects = options.configObjects ? options.configObjects : []
  65. // if a key has been explicitly set as local,
  66. // we should reset it before passing options to command.
  67. var localLookup = {}
  68. tmpOptions.local.forEach(function (l) {
  69. localLookup[l] = true
  70. ;(aliases[l] || []).forEach(function (a) {
  71. localLookup[a] = true
  72. })
  73. })
  74. // preserve all groups not set to local.
  75. preservedGroups = Object.keys(groups).reduce(function (acc, groupName) {
  76. var keys = groups[groupName].filter(function (key) {
  77. return !(key in localLookup)
  78. })
  79. if (keys.length > 0) {
  80. acc[groupName] = keys
  81. }
  82. return acc
  83. }, {})
  84. // groups can now be reset
  85. groups = {}
  86. var arrayOptions = [
  87. 'array', 'boolean', 'string', 'requiresArg', 'skipValidation',
  88. 'count', 'normalize', 'number'
  89. ]
  90. var objectOptions = [
  91. 'narg', 'key', 'alias', 'default', 'defaultDescription',
  92. 'config', 'choices', 'demandedOptions', 'demandedCommands', 'coerce'
  93. ]
  94. arrayOptions.forEach(function (k) {
  95. tmpOptions[k] = (options[k] || []).filter(function (k) {
  96. return !localLookup[k]
  97. })
  98. })
  99. objectOptions.forEach(function (k) {
  100. tmpOptions[k] = objFilter(options[k], function (k, v) {
  101. return !localLookup[k]
  102. })
  103. })
  104. tmpOptions.envPrefix = options.envPrefix
  105. options = tmpOptions
  106. // if this is the first time being executed, create
  107. // instances of all our helpers -- otherwise just reset.
  108. usage = usage ? usage.reset(localLookup) : Usage(self, y18n)
  109. validation = validation ? validation.reset(localLookup) : Validation(self, usage, y18n)
  110. command = command ? command.reset() : Command(self, usage, validation)
  111. if (!completion) completion = Completion(self, usage, command)
  112. completionCommand = null
  113. output = ''
  114. exitError = null
  115. hasOutput = false
  116. self.parsed = false
  117. return self
  118. }
  119. self.resetOptions()
  120. // temporary hack: allow "freezing" of reset-able state for parse(msg, cb)
  121. var frozen
  122. function freeze () {
  123. frozen = {}
  124. frozen.options = options
  125. frozen.configObjects = options.configObjects.slice(0)
  126. frozen.exitProcess = exitProcess
  127. frozen.groups = groups
  128. usage.freeze()
  129. validation.freeze()
  130. command.freeze()
  131. frozen.strict = strict
  132. frozen.completionCommand = completionCommand
  133. frozen.output = output
  134. frozen.exitError = exitError
  135. frozen.hasOutput = hasOutput
  136. frozen.parsed = self.parsed
  137. }
  138. function unfreeze () {
  139. options = frozen.options
  140. options.configObjects = frozen.configObjects
  141. exitProcess = frozen.exitProcess
  142. groups = frozen.groups
  143. output = frozen.output
  144. exitError = frozen.exitError
  145. hasOutput = frozen.hasOutput
  146. self.parsed = frozen.parsed
  147. usage.unfreeze()
  148. validation.unfreeze()
  149. command.unfreeze()
  150. strict = frozen.strict
  151. completionCommand = frozen.completionCommand
  152. parseFn = null
  153. parseContext = null
  154. frozen = undefined
  155. }
  156. self.boolean = function (keys) {
  157. argsert('<array|string>', [keys], arguments.length)
  158. populateParserHintArray('boolean', keys)
  159. return self
  160. }
  161. self.array = function (keys) {
  162. argsert('<array|string>', [keys], arguments.length)
  163. populateParserHintArray('array', keys)
  164. return self
  165. }
  166. self.number = function (keys) {
  167. argsert('<array|string>', [keys], arguments.length)
  168. populateParserHintArray('number', keys)
  169. return self
  170. }
  171. self.normalize = function (keys) {
  172. argsert('<array|string>', [keys], arguments.length)
  173. populateParserHintArray('normalize', keys)
  174. return self
  175. }
  176. self.count = function (keys) {
  177. argsert('<array|string>', [keys], arguments.length)
  178. populateParserHintArray('count', keys)
  179. return self
  180. }
  181. self.string = function (keys) {
  182. argsert('<array|string>', [keys], arguments.length)
  183. populateParserHintArray('string', keys)
  184. return self
  185. }
  186. self.requiresArg = function (keys) {
  187. argsert('<array|string>', [keys], arguments.length)
  188. populateParserHintArray('requiresArg', keys)
  189. return self
  190. }
  191. self.skipValidation = function (keys) {
  192. argsert('<array|string>', [keys], arguments.length)
  193. populateParserHintArray('skipValidation', keys)
  194. return self
  195. }
  196. function populateParserHintArray (type, keys, value) {
  197. keys = [].concat(keys)
  198. keys.forEach(function (key) {
  199. options[type].push(key)
  200. })
  201. }
  202. self.nargs = function (key, value) {
  203. argsert('<string|object|array> [number]', [key, value], arguments.length)
  204. populateParserHintObject(self.nargs, false, 'narg', key, value)
  205. return self
  206. }
  207. self.choices = function (key, value) {
  208. argsert('<object|string|array> [string|array]', [key, value], arguments.length)
  209. populateParserHintObject(self.choices, true, 'choices', key, value)
  210. return self
  211. }
  212. self.alias = function (key, value) {
  213. argsert('<object|string|array> [string|array]', [key, value], arguments.length)
  214. populateParserHintObject(self.alias, true, 'alias', key, value)
  215. return self
  216. }
  217. // TODO: actually deprecate self.defaults.
  218. self.default = self.defaults = function (key, value, defaultDescription) {
  219. argsert('<object|string|array> [*] [string]', [key, value, defaultDescription], arguments.length)
  220. if (defaultDescription) options.defaultDescription[key] = defaultDescription
  221. if (typeof value === 'function') {
  222. if (!options.defaultDescription[key]) options.defaultDescription[key] = usage.functionDescription(value)
  223. value = value.call()
  224. }
  225. populateParserHintObject(self.default, false, 'default', key, value)
  226. return self
  227. }
  228. self.describe = function (key, desc) {
  229. argsert('<object|string|array> [string]', [key, desc], arguments.length)
  230. populateParserHintObject(self.describe, false, 'key', key, true)
  231. usage.describe(key, desc)
  232. return self
  233. }
  234. self.demandOption = function (keys, msg) {
  235. argsert('<object|string|array> [string]', [keys, msg], arguments.length)
  236. populateParserHintObject(self.demandOption, false, 'demandedOptions', keys, msg)
  237. return self
  238. }
  239. self.coerce = function (keys, value) {
  240. argsert('<object|string|array> [function]', [keys, value], arguments.length)
  241. populateParserHintObject(self.coerce, false, 'coerce', keys, value)
  242. return self
  243. }
  244. function populateParserHintObject (builder, isArray, type, key, value) {
  245. if (Array.isArray(key)) {
  246. // an array of keys with one value ['x', 'y', 'z'], function parse () {}
  247. var temp = {}
  248. key.forEach(function (k) {
  249. temp[k] = value
  250. })
  251. builder(temp)
  252. } else if (typeof key === 'object') {
  253. // an object of key value pairs: {'x': parse () {}, 'y': parse() {}}
  254. Object.keys(key).forEach(function (k) {
  255. builder(k, key[k])
  256. })
  257. } else {
  258. // a single key value pair 'x', parse() {}
  259. if (isArray) {
  260. options[type][key] = (options[type][key] || []).concat(value)
  261. } else {
  262. options[type][key] = value
  263. }
  264. }
  265. }
  266. self.config = function (key, msg, parseFn) {
  267. argsert('[object|string] [string|function] [function]', [key, msg, parseFn], arguments.length)
  268. // allow a config object to be provided directly.
  269. if (typeof key === 'object') {
  270. key = applyExtends(key, cwd)
  271. options.configObjects = (options.configObjects || []).concat(key)
  272. return self
  273. }
  274. // allow for a custom parsing function.
  275. if (typeof msg === 'function') {
  276. parseFn = msg
  277. msg = null
  278. }
  279. key = key || 'config'
  280. self.describe(key, msg || usage.deferY18nLookup('Path to JSON config file'))
  281. ;(Array.isArray(key) ? key : [key]).forEach(function (k) {
  282. options.config[k] = parseFn || true
  283. })
  284. return self
  285. }
  286. self.example = function (cmd, description) {
  287. argsert('<string> [string]', [cmd, description], arguments.length)
  288. usage.example(cmd, description)
  289. return self
  290. }
  291. self.command = function (cmd, description, builder, handler) {
  292. argsert('<string|array|object> [string|boolean] [function|object] [function]', [cmd, description, builder, handler], arguments.length)
  293. command.addHandler(cmd, description, builder, handler)
  294. return self
  295. }
  296. self.commandDir = function (dir, opts) {
  297. argsert('<string> [object]', [dir, opts], arguments.length)
  298. const req = parentRequire || require
  299. command.addDirectory(dir, self.getContext(), req, require('get-caller-file')(), opts)
  300. return self
  301. }
  302. // TODO: deprecate self.demand in favor of
  303. // .demandCommand() .demandOption().
  304. self.demand = self.required = self.require = function (keys, max, msg) {
  305. // you can optionally provide a 'max' key,
  306. // which will raise an exception if too many '_'
  307. // options are provided.
  308. if (Array.isArray(max)) {
  309. max.forEach(function (key) {
  310. self.demandOption(key, msg)
  311. })
  312. max = Infinity
  313. } else if (typeof max !== 'number') {
  314. msg = max
  315. max = Infinity
  316. }
  317. if (typeof keys === 'number') {
  318. self.demandCommand(keys, max, msg, msg)
  319. } else if (Array.isArray(keys)) {
  320. keys.forEach(function (key) {
  321. self.demandOption(key, msg)
  322. })
  323. } else {
  324. if (typeof msg === 'string') {
  325. self.demandOption(keys, msg)
  326. } else if (msg === true || typeof msg === 'undefined') {
  327. self.demandOption(keys)
  328. }
  329. }
  330. return self
  331. }
  332. self.demandCommand = function (min, max, minMsg, maxMsg) {
  333. argsert('[number] [number|string] [string|null] [string|null]', [min, max, minMsg, maxMsg], arguments.length)
  334. if (typeof min === 'undefined') min = 1
  335. if (typeof max !== 'number') {
  336. minMsg = max
  337. max = Infinity
  338. }
  339. self.global('_', false)
  340. options.demandedCommands._ = {
  341. min: min,
  342. max: max,
  343. minMsg: minMsg,
  344. maxMsg: maxMsg
  345. }
  346. return self
  347. }
  348. self.getDemandedOptions = function () {
  349. argsert([], 0)
  350. return options.demandedOptions
  351. }
  352. self.getDemandedCommands = function () {
  353. argsert([], 0)
  354. return options.demandedCommands
  355. }
  356. self.implies = function (key, value) {
  357. argsert('<string|object> [string]', [key, value], arguments.length)
  358. validation.implies(key, value)
  359. return self
  360. }
  361. self.conflicts = function (key1, key2) {
  362. argsert('<string|object> [string]', [key1, key2], arguments.length)
  363. validation.conflicts(key1, key2)
  364. return self
  365. }
  366. self.usage = function (msg, opts) {
  367. argsert('<string|null|object> [object]', [msg, opts], arguments.length)
  368. if (!opts && typeof msg === 'object') {
  369. opts = msg
  370. msg = null
  371. }
  372. usage.usage(msg)
  373. if (opts) self.options(opts)
  374. return self
  375. }
  376. self.epilogue = self.epilog = function (msg) {
  377. argsert('<string>', [msg], arguments.length)
  378. usage.epilog(msg)
  379. return self
  380. }
  381. self.fail = function (f) {
  382. argsert('<function>', [f], arguments.length)
  383. usage.failFn(f)
  384. return self
  385. }
  386. self.check = function (f, _global) {
  387. argsert('<function> [boolean]', [f, _global], arguments.length)
  388. validation.check(f, _global !== false)
  389. return self
  390. }
  391. self.global = function (globals, global) {
  392. argsert('<string|array> [boolean]', [globals, global], arguments.length)
  393. globals = [].concat(globals)
  394. if (global !== false) {
  395. options.local = options.local.filter(function (l) {
  396. return globals.indexOf(l) === -1
  397. })
  398. } else {
  399. globals.forEach(function (g) {
  400. if (options.local.indexOf(g) === -1) options.local.push(g)
  401. })
  402. }
  403. return self
  404. }
  405. self.pkgConf = function (key, path) {
  406. argsert('<string> [string]', [key, path], arguments.length)
  407. var conf = null
  408. // prefer cwd to require-main-filename in this method
  409. // since we're looking for e.g. "nyc" config in nyc consumer
  410. // rather than "yargs" config in nyc (where nyc is the main filename)
  411. var obj = pkgUp(path || cwd)
  412. // If an object exists in the key, add it to options.configObjects
  413. if (obj[key] && typeof obj[key] === 'object') {
  414. conf = applyExtends(obj[key], path || cwd)
  415. options.configObjects = (options.configObjects || []).concat(conf)
  416. }
  417. return self
  418. }
  419. var pkgs = {}
  420. function pkgUp (path) {
  421. var npath = path || '*'
  422. if (pkgs[npath]) return pkgs[npath]
  423. const readPkgUp = require('read-pkg-up')
  424. var obj = {}
  425. try {
  426. obj = readPkgUp.sync({
  427. cwd: path || require('require-main-filename')(parentRequire || require),
  428. normalize: false
  429. })
  430. } catch (noop) {}
  431. pkgs[npath] = obj.pkg || {}
  432. return pkgs[npath]
  433. }
  434. var parseFn = null
  435. var parseContext = null
  436. self.parse = function (args, shortCircuit, _parseFn) {
  437. argsert('<string|array> [function|boolean|object] [function]', [args, shortCircuit, _parseFn], arguments.length)
  438. // a context object can optionally be provided, this allows
  439. // additional information to be passed to a command handler.
  440. if (typeof shortCircuit === 'object') {
  441. parseContext = shortCircuit
  442. shortCircuit = _parseFn
  443. }
  444. // by providing a function as a second argument to
  445. // parse you can capture output that would otherwise
  446. // default to printing to stdout/stderr.
  447. if (typeof shortCircuit === 'function') {
  448. parseFn = shortCircuit
  449. shortCircuit = null
  450. }
  451. // completion short-circuits the parsing process,
  452. // skipping validation, etc.
  453. if (!shortCircuit) processArgs = args
  454. freeze()
  455. if (parseFn) exitProcess = false
  456. var parsed = self._parseArgs(args, shortCircuit)
  457. if (parseFn) parseFn(exitError, parsed, output)
  458. unfreeze()
  459. return parsed
  460. }
  461. self._getParseContext = function () {
  462. return parseContext || {}
  463. }
  464. self._hasParseCallback = function () {
  465. return !!parseFn
  466. }
  467. self.option = self.options = function (key, opt) {
  468. argsert('<string|object> [object]', [key, opt], arguments.length)
  469. if (typeof key === 'object') {
  470. Object.keys(key).forEach(function (k) {
  471. self.options(k, key[k])
  472. })
  473. } else {
  474. if (typeof opt !== 'object') {
  475. opt = {}
  476. }
  477. options.key[key] = true // track manually set keys.
  478. if (opt.alias) self.alias(key, opt.alias)
  479. var demand = opt.demand || opt.required || opt.require
  480. // deprecated, use 'demandOption' instead
  481. if (demand) {
  482. self.demand(key, demand)
  483. }
  484. if (opt.demandOption) {
  485. self.demandOption(key, typeof opt.demandOption === 'string' ? opt.demandOption : undefined)
  486. }
  487. if ('config' in opt) {
  488. self.config(key, opt.configParser)
  489. }
  490. if ('conflicts' in opt) {
  491. self.conflicts(key, opt.conflicts)
  492. }
  493. if ('default' in opt) {
  494. self.default(key, opt.default)
  495. }
  496. if ('implies' in opt) {
  497. self.implies(key, opt.implies)
  498. }
  499. if ('nargs' in opt) {
  500. self.nargs(key, opt.nargs)
  501. }
  502. if ('normalize' in opt) {
  503. self.normalize(key)
  504. }
  505. if ('choices' in opt) {
  506. self.choices(key, opt.choices)
  507. }
  508. if ('coerce' in opt) {
  509. self.coerce(key, opt.coerce)
  510. }
  511. if ('group' in opt) {
  512. self.group(key, opt.group)
  513. }
  514. if (opt.boolean || opt.type === 'boolean') {
  515. self.boolean(key)
  516. if (opt.alias) self.boolean(opt.alias)
  517. }
  518. if (opt.array || opt.type === 'array') {
  519. self.array(key)
  520. if (opt.alias) self.array(opt.alias)
  521. }
  522. if (opt.number || opt.type === 'number') {
  523. self.number(key)
  524. if (opt.alias) self.number(opt.alias)
  525. }
  526. if (opt.string || opt.type === 'string') {
  527. self.string(key)
  528. if (opt.alias) self.string(opt.alias)
  529. }
  530. if (opt.count || opt.type === 'count') {
  531. self.count(key)
  532. }
  533. if (typeof opt.global === 'boolean') {
  534. self.global(key, opt.global)
  535. }
  536. if (opt.defaultDescription) {
  537. options.defaultDescription[key] = opt.defaultDescription
  538. }
  539. if (opt.skipValidation) {
  540. self.skipValidation(key)
  541. }
  542. var desc = opt.describe || opt.description || opt.desc
  543. if (desc) {
  544. self.describe(key, desc)
  545. }
  546. if (opt.requiresArg) {
  547. self.requiresArg(key)
  548. }
  549. }
  550. return self
  551. }
  552. self.getOptions = function () {
  553. return options
  554. }
  555. self.group = function (opts, groupName) {
  556. argsert('<string|array> <string>', [opts, groupName], arguments.length)
  557. var existing = preservedGroups[groupName] || groups[groupName]
  558. if (preservedGroups[groupName]) {
  559. // we now only need to track this group name in groups.
  560. delete preservedGroups[groupName]
  561. }
  562. var seen = {}
  563. groups[groupName] = (existing || []).concat(opts).filter(function (key) {
  564. if (seen[key]) return false
  565. return (seen[key] = true)
  566. })
  567. return self
  568. }
  569. self.getGroups = function () {
  570. // combine explicit and preserved groups. explicit groups should be first
  571. return assign(groups, preservedGroups)
  572. }
  573. // as long as options.envPrefix is not undefined,
  574. // parser will apply env vars matching prefix to argv
  575. self.env = function (prefix) {
  576. argsert('[string|boolean]', [prefix], arguments.length)
  577. if (prefix === false) options.envPrefix = undefined
  578. else options.envPrefix = prefix || ''
  579. return self
  580. }
  581. self.wrap = function (cols) {
  582. argsert('<number|null>', [cols], arguments.length)
  583. usage.wrap(cols)
  584. return self
  585. }
  586. var strict = false
  587. self.strict = function (enabled) {
  588. argsert('[boolean]', [enabled], arguments.length)
  589. strict = enabled !== false
  590. return self
  591. }
  592. self.getStrict = function () {
  593. return strict
  594. }
  595. self.showHelp = function (level) {
  596. argsert('[string|function]', [level], arguments.length)
  597. if (!self.parsed) self._parseArgs(processArgs) // run parser, if it has not already been executed.
  598. usage.showHelp(level)
  599. return self
  600. }
  601. var versionOpt = null
  602. self.version = function (opt, msg, ver) {
  603. argsert('[string|function] [string|function] [string]', [opt, msg, ver], arguments.length)
  604. if (arguments.length === 0) {
  605. ver = guessVersion()
  606. opt = 'version'
  607. } else if (arguments.length === 1) {
  608. ver = opt
  609. opt = 'version'
  610. } else if (arguments.length === 2) {
  611. ver = msg
  612. msg = null
  613. }
  614. versionOpt = opt
  615. msg = msg || usage.deferY18nLookup('Show version number')
  616. usage.version(ver || undefined)
  617. self.boolean(versionOpt)
  618. self.describe(versionOpt, msg)
  619. return self
  620. }
  621. function guessVersion () {
  622. var obj = pkgUp()
  623. return obj.version || 'unknown'
  624. }
  625. var helpOpt = null
  626. var useHelpOptAsCommand = false // a call to .help() will enable this
  627. self.addHelpOpt = self.help = function (opt, msg, addImplicitCmd) {
  628. argsert('[string|boolean] [string|boolean] [boolean]', [opt, msg, addImplicitCmd], arguments.length)
  629. // argument shuffle
  630. if (arguments.length === 0) {
  631. useHelpOptAsCommand = true
  632. } else if (arguments.length === 1) {
  633. if (typeof opt === 'boolean') {
  634. useHelpOptAsCommand = opt
  635. opt = null
  636. } else {
  637. useHelpOptAsCommand = true
  638. }
  639. } else if (arguments.length === 2) {
  640. if (typeof msg === 'boolean') {
  641. useHelpOptAsCommand = msg
  642. msg = null
  643. } else {
  644. useHelpOptAsCommand = true
  645. }
  646. } else {
  647. useHelpOptAsCommand = Boolean(addImplicitCmd)
  648. }
  649. // use arguments, fallback to defaults for opt and msg
  650. helpOpt = opt || 'help'
  651. self.boolean(helpOpt)
  652. self.describe(helpOpt, msg || usage.deferY18nLookup('Show help'))
  653. return self
  654. }
  655. self.showHelpOnFail = function (enabled, message) {
  656. argsert('[boolean|string] [string]', [enabled, message], arguments.length)
  657. usage.showHelpOnFail(enabled, message)
  658. return self
  659. }
  660. var exitProcess = true
  661. self.exitProcess = function (enabled) {
  662. argsert('[boolean]', [enabled], arguments.length)
  663. if (typeof enabled !== 'boolean') {
  664. enabled = true
  665. }
  666. exitProcess = enabled
  667. return self
  668. }
  669. self.getExitProcess = function () {
  670. return exitProcess
  671. }
  672. var completionCommand = null
  673. self.completion = function (cmd, desc, fn) {
  674. argsert('[string] [string|boolean|function] [function]', [cmd, desc, fn], arguments.length)
  675. // a function to execute when generating
  676. // completions can be provided as the second
  677. // or third argument to completion.
  678. if (typeof desc === 'function') {
  679. fn = desc
  680. desc = null
  681. }
  682. // register the completion command.
  683. completionCommand = cmd || 'completion'
  684. if (!desc && desc !== false) {
  685. desc = 'generate bash completion script'
  686. }
  687. self.command(completionCommand, desc)
  688. // a function can be provided
  689. if (fn) completion.registerFunction(fn)
  690. return self
  691. }
  692. self.showCompletionScript = function ($0) {
  693. argsert('[string]', [$0], arguments.length)
  694. $0 = $0 || self.$0
  695. _logger.log(completion.generateCompletionScript($0))
  696. return self
  697. }
  698. self.getCompletion = function (args, done) {
  699. argsert('<array> <function>', [args, done], arguments.length)
  700. completion.getCompletion(args, done)
  701. }
  702. self.locale = function (locale) {
  703. argsert('[string]', [locale], arguments.length)
  704. if (arguments.length === 0) {
  705. guessLocale()
  706. return y18n.getLocale()
  707. }
  708. detectLocale = false
  709. y18n.setLocale(locale)
  710. return self
  711. }
  712. self.updateStrings = self.updateLocale = function (obj) {
  713. argsert('<object>', [obj], arguments.length)
  714. detectLocale = false
  715. y18n.updateLocale(obj)
  716. return self
  717. }
  718. var detectLocale = true
  719. self.detectLocale = function (detect) {
  720. argsert('<boolean>', [detect], arguments.length)
  721. detectLocale = detect
  722. return self
  723. }
  724. self.getDetectLocale = function () {
  725. return detectLocale
  726. }
  727. var hasOutput = false
  728. var exitError = null
  729. // maybe exit, always capture
  730. // context about why we wanted to exit.
  731. self.exit = function (code, err) {
  732. hasOutput = true
  733. exitError = err
  734. if (exitProcess) process.exit(code)
  735. }
  736. // we use a custom logger that buffers output,
  737. // so that we can print to non-CLIs, e.g., chat-bots.
  738. var _logger = {
  739. log: function () {
  740. const args = []
  741. for (var i = 0; i < arguments.length; i++) args.push(arguments[i])
  742. if (!self._hasParseCallback()) console.log.apply(console, args)
  743. hasOutput = true
  744. if (output.length) output += '\n'
  745. output += args.join(' ')
  746. },
  747. error: function () {
  748. const args = []
  749. for (var i = 0; i < arguments.length; i++) args.push(arguments[i])
  750. if (!self._hasParseCallback()) console.error.apply(console, args)
  751. hasOutput = true
  752. if (output.length) output += '\n'
  753. output += args.join(' ')
  754. }
  755. }
  756. self._getLoggerInstance = function () {
  757. return _logger
  758. }
  759. // has yargs output an error our help
  760. // message in the current execution context.
  761. self._hasOutput = function () {
  762. return hasOutput
  763. }
  764. self._setHasOutput = function () {
  765. hasOutput = true
  766. }
  767. var recommendCommands
  768. self.recommendCommands = function (recommend) {
  769. argsert('[boolean]', [recommend], arguments.length)
  770. recommendCommands = typeof recommend === 'boolean' ? recommend : true
  771. return self
  772. }
  773. self.getUsageInstance = function () {
  774. return usage
  775. }
  776. self.getValidationInstance = function () {
  777. return validation
  778. }
  779. self.getCommandInstance = function () {
  780. return command
  781. }
  782. self.terminalWidth = function () {
  783. argsert([], 0)
  784. return typeof process.stdout.columns !== 'undefined' ? process.stdout.columns : null
  785. }
  786. Object.defineProperty(self, 'argv', {
  787. get: function () {
  788. return self._parseArgs(processArgs)
  789. },
  790. enumerable: true
  791. })
  792. self._parseArgs = function (args, shortCircuit, _skipValidation, commandIndex) {
  793. var skipValidation = !!_skipValidation
  794. args = args || processArgs
  795. options.__ = y18n.__
  796. options.configuration = pkgUp()['yargs'] || {}
  797. const parsed = Parser.detailed(args, options)
  798. var argv = parsed.argv
  799. if (parseContext) argv = assign(argv, parseContext)
  800. var aliases = parsed.aliases
  801. argv.$0 = self.$0
  802. self.parsed = parsed
  803. try {
  804. guessLocale() // guess locale lazily, so that it can be turned off in chain.
  805. // while building up the argv object, there
  806. // are two passes through the parser. If completion
  807. // is being performed short-circuit on the first pass.
  808. if (shortCircuit) {
  809. return argv
  810. }
  811. if (argv._.length) {
  812. // check for helpOpt in argv._ before running commands
  813. // assumes helpOpt must be valid if useHelpOptAsCommand is true
  814. if (useHelpOptAsCommand) {
  815. // consider any multi-char helpOpt alias as a valid help command
  816. // unless all helpOpt aliases are single-char
  817. // note that parsed.aliases is a normalized bidirectional map :)
  818. var helpCmds = [helpOpt].concat(aliases[helpOpt] || [])
  819. var multiCharHelpCmds = helpCmds.filter(function (k) {
  820. return k.length > 1
  821. })
  822. if (multiCharHelpCmds.length) helpCmds = multiCharHelpCmds
  823. // look for and strip any helpCmds from argv._
  824. argv._ = argv._.filter(function (cmd) {
  825. if (~helpCmds.indexOf(cmd)) {
  826. argv[helpOpt] = true
  827. return false
  828. }
  829. return true
  830. })
  831. }
  832. // if there's a handler associated with a
  833. // command defer processing to it.
  834. var handlerKeys = command.getCommands()
  835. if (handlerKeys.length) {
  836. var firstUnknownCommand
  837. for (var i = (commandIndex || 0), cmd; argv._[i] !== undefined; i++) {
  838. cmd = String(argv._[i])
  839. if (~handlerKeys.indexOf(cmd) && cmd !== completionCommand) {
  840. setPlaceholderKeys(argv)
  841. // commands are executed using a recursive algorithm that executes
  842. // the deepest command first; we keep track of the position in the
  843. // argv._ array that is currently being executed.
  844. return command.runCommand(cmd, self, parsed, i + 1)
  845. } else if (!firstUnknownCommand && cmd !== completionCommand) {
  846. firstUnknownCommand = cmd
  847. break
  848. }
  849. }
  850. // run the default command, if defined
  851. if (command.hasDefaultCommand() && !argv[helpOpt]) {
  852. setPlaceholderKeys(argv)
  853. return command.runCommand(null, self, parsed)
  854. }
  855. // recommend a command if recommendCommands() has
  856. // been enabled, and no commands were found to execute
  857. if (recommendCommands && firstUnknownCommand && !argv[helpOpt]) {
  858. validation.recommendCommands(firstUnknownCommand, handlerKeys)
  859. }
  860. }
  861. // generate a completion script for adding to ~/.bashrc.
  862. if (completionCommand && ~argv._.indexOf(completionCommand) && !argv[completion.completionKey]) {
  863. if (exitProcess) setBlocking(true)
  864. self.showCompletionScript()
  865. self.exit(0)
  866. }
  867. } else if (command.hasDefaultCommand() && !argv[helpOpt]) {
  868. setPlaceholderKeys(argv)
  869. return command.runCommand(null, self, parsed)
  870. }
  871. // we must run completions first, a user might
  872. // want to complete the --help or --version option.
  873. if (completion.completionKey in argv) {
  874. if (exitProcess) setBlocking(true)
  875. // we allow for asynchronous completions,
  876. // e.g., loading in a list of commands from an API.
  877. var completionArgs = args.slice(args.indexOf('--' + completion.completionKey) + 1)
  878. completion.getCompletion(completionArgs, function (completions) {
  879. ;(completions || []).forEach(function (completion) {
  880. _logger.log(completion)
  881. })
  882. self.exit(0)
  883. })
  884. return setPlaceholderKeys(argv)
  885. }
  886. // Handle 'help' and 'version' options
  887. // if we haven't already output help!
  888. if (!hasOutput) {
  889. Object.keys(argv).forEach(function (key) {
  890. if (key === helpOpt && argv[key]) {
  891. if (exitProcess) setBlocking(true)
  892. skipValidation = true
  893. self.showHelp('log')
  894. self.exit(0)
  895. } else if (key === versionOpt && argv[key]) {
  896. if (exitProcess) setBlocking(true)
  897. skipValidation = true
  898. usage.showVersion()
  899. self.exit(0)
  900. }
  901. })
  902. }
  903. // Check if any of the options to skip validation were provided
  904. if (!skipValidation && options.skipValidation.length > 0) {
  905. skipValidation = Object.keys(argv).some(function (key) {
  906. return options.skipValidation.indexOf(key) >= 0 && argv[key] === true
  907. })
  908. }
  909. // If the help or version options where used and exitProcess is false,
  910. // or if explicitly skipped, we won't run validations.
  911. if (!skipValidation) {
  912. if (parsed.error) throw new YError(parsed.error.message)
  913. // if we're executed via bash completion, don't
  914. // bother with validation.
  915. if (!argv[completion.completionKey]) {
  916. self._runValidation(argv, aliases, {}, parsed.error)
  917. }
  918. }
  919. } catch (err) {
  920. if (err instanceof YError) usage.fail(err.message, err)
  921. else throw err
  922. }
  923. return setPlaceholderKeys(argv)
  924. }
  925. self._runValidation = function (argv, aliases, positionalMap, parseErrors) {
  926. if (parseErrors) throw new YError(parseErrors.message)
  927. validation.nonOptionCount(argv)
  928. validation.missingArgumentValue(argv)
  929. validation.requiredArguments(argv)
  930. if (strict) validation.unknownArguments(argv, aliases, positionalMap)
  931. validation.customChecks(argv, aliases)
  932. validation.limitedChoices(argv)
  933. validation.implications(argv)
  934. validation.conflicting(argv)
  935. }
  936. function guessLocale () {
  937. if (!detectLocale) return
  938. try {
  939. const osLocale = require('os-locale')
  940. self.locale(osLocale.sync({ spawn: false }))
  941. } catch (err) {
  942. // if we explode looking up locale just noop
  943. // we'll keep using the default language 'en'.
  944. }
  945. }
  946. function setPlaceholderKeys (argv) {
  947. Object.keys(options.key).forEach(function (key) {
  948. // don't set placeholder keys for dot
  949. // notation options 'foo.bar'.
  950. if (~key.indexOf('.')) return
  951. if (typeof argv[key] === 'undefined') argv[key] = undefined
  952. })
  953. return argv
  954. }
  955. return self
  956. }
  957. // rebase an absolute path to a relative one with respect to a base directory
  958. // exported for tests
  959. exports.rebase = rebase
  960. function rebase (base, dir) {
  961. return path.relative(base, dir)
  962. }