1 | 'use strict'
|
2 |
|
3 |
|
4 | const decamelize = require('decamelize')
|
5 | const stringWidth = require('string-width')
|
6 | const objFilter = require('./obj-filter')
|
7 | const path = require('path')
|
8 | const setBlocking = require('set-blocking')
|
9 | const YError = require('./yerror')
|
10 |
|
11 | module.exports = function usage (yargs, y18n) {
|
12 | const __ = y18n.__
|
13 | const self = {}
|
14 |
|
15 |
|
16 | const fails = []
|
17 | self.failFn = function failFn (f) {
|
18 | fails.push(f)
|
19 | }
|
20 |
|
21 | let failMessage = null
|
22 | let showHelpOnFail = true
|
23 | self.showHelpOnFail = function showHelpOnFailFn (enabled, message) {
|
24 | if (typeof enabled === 'string') {
|
25 | message = enabled
|
26 | enabled = true
|
27 | } else if (typeof enabled === 'undefined') {
|
28 | enabled = true
|
29 | }
|
30 | failMessage = message
|
31 | showHelpOnFail = enabled
|
32 | return self
|
33 | }
|
34 |
|
35 | let failureOutput = false
|
36 | self.fail = function fail (msg, err) {
|
37 | const logger = yargs._getLoggerInstance()
|
38 |
|
39 | if (fails.length) {
|
40 | for (let i = fails.length - 1; i >= 0; --i) {
|
41 | fails[i](msg, err, self)
|
42 | }
|
43 | } else {
|
44 | if (yargs.getExitProcess()) setBlocking(true)
|
45 |
|
46 |
|
47 | if (!failureOutput) {
|
48 | failureOutput = true
|
49 | if (showHelpOnFail) {
|
50 | yargs.showHelp('error')
|
51 | logger.error()
|
52 | }
|
53 | if (msg || err) logger.error(msg || err)
|
54 | if (failMessage) {
|
55 | if (msg || err) logger.error('')
|
56 | logger.error(failMessage)
|
57 | }
|
58 | }
|
59 |
|
60 | err = err || new YError(msg)
|
61 | if (yargs.getExitProcess()) {
|
62 | return yargs.exit(1)
|
63 | } else if (yargs._hasParseCallback()) {
|
64 | return yargs.exit(1, err)
|
65 | } else {
|
66 | throw err
|
67 | }
|
68 | }
|
69 | }
|
70 |
|
71 |
|
72 | let usages = []
|
73 | let usageDisabled = false
|
74 | self.usage = (msg, description) => {
|
75 | if (msg === null) {
|
76 | usageDisabled = true
|
77 | usages = []
|
78 | return
|
79 | }
|
80 | usageDisabled = false
|
81 | usages.push([msg, description || ''])
|
82 | return self
|
83 | }
|
84 | self.getUsage = () => {
|
85 | return usages
|
86 | }
|
87 | self.getUsageDisabled = () => {
|
88 | return usageDisabled
|
89 | }
|
90 |
|
91 | self.getPositionalGroupName = () => {
|
92 | return __('Positionals:')
|
93 | }
|
94 |
|
95 | let examples = []
|
96 | self.example = (cmd, description) => {
|
97 | examples.push([cmd, description || ''])
|
98 | }
|
99 |
|
100 | let commands = []
|
101 | self.command = function command (cmd, description, isDefault, aliases) {
|
102 |
|
103 | if (isDefault) {
|
104 | commands = commands.map((cmdArray) => {
|
105 | cmdArray[2] = false
|
106 | return cmdArray
|
107 | })
|
108 | }
|
109 | commands.push([cmd, description || '', isDefault, aliases])
|
110 | }
|
111 | self.getCommands = () => commands
|
112 |
|
113 | let descriptions = {}
|
114 | self.describe = function describe (key, desc) {
|
115 | if (typeof key === 'object') {
|
116 | Object.keys(key).forEach((k) => {
|
117 | self.describe(k, key[k])
|
118 | })
|
119 | } else {
|
120 | descriptions[key] = desc
|
121 | }
|
122 | }
|
123 | self.getDescriptions = () => descriptions
|
124 |
|
125 | let epilogs = []
|
126 | self.epilog = (msg) => {
|
127 | epilogs.push(msg)
|
128 | }
|
129 |
|
130 | let wrapSet = false
|
131 | let wrap
|
132 | self.wrap = (cols) => {
|
133 | wrapSet = true
|
134 | wrap = cols
|
135 | }
|
136 |
|
137 | function getWrap () {
|
138 | if (!wrapSet) {
|
139 | wrap = windowWidth()
|
140 | wrapSet = true
|
141 | }
|
142 |
|
143 | return wrap
|
144 | }
|
145 |
|
146 | const deferY18nLookupPrefix = '__yargsString__:'
|
147 | self.deferY18nLookup = str => deferY18nLookupPrefix + str
|
148 |
|
149 | const defaultGroup = 'Options:'
|
150 | self.help = function help () {
|
151 | if (cachedHelpMessage) return cachedHelpMessage
|
152 | normalizeAliases()
|
153 |
|
154 |
|
155 | const base$0 = yargs.customScriptName ? yargs.$0 : path.basename(yargs.$0)
|
156 | const demandedOptions = yargs.getDemandedOptions()
|
157 | const demandedCommands = yargs.getDemandedCommands()
|
158 | const groups = yargs.getGroups()
|
159 | const options = yargs.getOptions()
|
160 |
|
161 | let keys = []
|
162 | keys = keys.concat(Object.keys(descriptions))
|
163 | keys = keys.concat(Object.keys(demandedOptions))
|
164 | keys = keys.concat(Object.keys(demandedCommands))
|
165 | keys = keys.concat(Object.keys(options.default))
|
166 | keys = keys.filter(filterHiddenOptions)
|
167 | keys = Object.keys(keys.reduce((acc, key) => {
|
168 | if (key !== '_') acc[key] = true
|
169 | return acc
|
170 | }, {}))
|
171 |
|
172 | const theWrap = getWrap()
|
173 | const ui = require('cliui')({
|
174 | width: theWrap,
|
175 | wrap: !!theWrap
|
176 | })
|
177 |
|
178 |
|
179 | if (!usageDisabled) {
|
180 | if (usages.length) {
|
181 |
|
182 | usages.forEach((usage) => {
|
183 | ui.div(`${usage[0].replace(/\$0/g, base$0)}`)
|
184 | if (usage[1]) {
|
185 | ui.div({ text: `${usage[1]}`, padding: [1, 0, 0, 0] })
|
186 | }
|
187 | })
|
188 | ui.div()
|
189 | } else if (commands.length) {
|
190 | let u = null
|
191 |
|
192 | if (demandedCommands._) {
|
193 | u = `${base$0} <${__('command')}>\n`
|
194 | } else {
|
195 | u = `${base$0} [${__('command')}]\n`
|
196 | }
|
197 | ui.div(`${u}`)
|
198 | }
|
199 | }
|
200 |
|
201 |
|
202 |
|
203 | if (commands.length) {
|
204 | ui.div(__('Commands:'))
|
205 |
|
206 | const context = yargs.getContext()
|
207 | const parentCommands = context.commands.length ? `${context.commands.join(' ')} ` : ''
|
208 |
|
209 | if (yargs.getParserConfiguration()['sort-commands'] === true) {
|
210 | commands = commands.sort((a, b) => a[0].localeCompare(b[0]))
|
211 | }
|
212 |
|
213 | commands.forEach((command) => {
|
214 | const commandString = `${base$0} ${parentCommands}${command[0].replace(/^\$0 ?/, '')}`
|
215 | ui.span(
|
216 | {
|
217 | text: commandString,
|
218 | padding: [0, 2, 0, 2],
|
219 | width: maxWidth(commands, theWrap, `${base$0}${parentCommands}`) + 4
|
220 | },
|
221 | { text: command[1] }
|
222 | )
|
223 | const hints = []
|
224 | if (command[2]) hints.push(`[${__('default:').slice(0, -1)}]`)
|
225 | if (command[3] && command[3].length) {
|
226 | hints.push(`[${__('aliases:')} ${command[3].join(', ')}]`)
|
227 | }
|
228 | if (hints.length) {
|
229 | ui.div({ text: hints.join(' '), padding: [0, 0, 0, 2], align: 'right' })
|
230 | } else {
|
231 | ui.div()
|
232 | }
|
233 | })
|
234 |
|
235 | ui.div()
|
236 | }
|
237 |
|
238 |
|
239 |
|
240 | const aliasKeys = (Object.keys(options.alias) || [])
|
241 | .concat(Object.keys(yargs.parsed.newAliases) || [])
|
242 |
|
243 | keys = keys.filter(key => !yargs.parsed.newAliases[key] && aliasKeys.every(alias => (options.alias[alias] || []).indexOf(key) === -1))
|
244 |
|
245 |
|
246 |
|
247 | if (!groups[defaultGroup]) groups[defaultGroup] = []
|
248 | addUngroupedKeys(keys, options.alias, groups)
|
249 |
|
250 |
|
251 | Object.keys(groups).forEach((groupName) => {
|
252 | if (!groups[groupName].length) return
|
253 |
|
254 |
|
255 |
|
256 | const normalizedKeys = groups[groupName].filter(filterHiddenOptions).map((key) => {
|
257 | if (~aliasKeys.indexOf(key)) return key
|
258 | for (let i = 0, aliasKey; (aliasKey = aliasKeys[i]) !== undefined; i++) {
|
259 | if (~(options.alias[aliasKey] || []).indexOf(key)) return aliasKey
|
260 | }
|
261 | return key
|
262 | })
|
263 |
|
264 | if (normalizedKeys.length < 1) return
|
265 |
|
266 | ui.div(__(groupName))
|
267 |
|
268 |
|
269 | const switches = normalizedKeys.reduce((acc, key) => {
|
270 | acc[key] = [ key ].concat(options.alias[key] || [])
|
271 | .map(sw => {
|
272 |
|
273 |
|
274 | if (groupName === self.getPositionalGroupName()) return sw
|
275 | else return (sw.length > 1 ? '--' : '-') + sw
|
276 | })
|
277 | .join(', ')
|
278 |
|
279 | return acc
|
280 | }, {})
|
281 |
|
282 | normalizedKeys.forEach((key) => {
|
283 | const kswitch = switches[key]
|
284 | let desc = descriptions[key] || ''
|
285 | let type = null
|
286 |
|
287 | if (~desc.lastIndexOf(deferY18nLookupPrefix)) desc = __(desc.substring(deferY18nLookupPrefix.length))
|
288 |
|
289 | if (~options.boolean.indexOf(key)) type = `[${__('boolean')}]`
|
290 | if (~options.count.indexOf(key)) type = `[${__('count')}]`
|
291 | if (~options.string.indexOf(key)) type = `[${__('string')}]`
|
292 | if (~options.normalize.indexOf(key)) type = `[${__('string')}]`
|
293 | if (~options.array.indexOf(key)) type = `[${__('array')}]`
|
294 | if (~options.number.indexOf(key)) type = `[${__('number')}]`
|
295 |
|
296 | const extra = [
|
297 | type,
|
298 | (key in demandedOptions) ? `[${__('required')}]` : null,
|
299 | options.choices && options.choices[key] ? `[${__('choices:')} ${
|
300 | self.stringifiedValues(options.choices[key])}]` : null,
|
301 | defaultString(options.default[key], options.defaultDescription[key])
|
302 | ].filter(Boolean).join(' ')
|
303 |
|
304 | ui.span(
|
305 | { text: kswitch, padding: [0, 2, 0, 2], width: maxWidth(switches, theWrap) + 4 },
|
306 | desc
|
307 | )
|
308 |
|
309 | if (extra) ui.div({ text: extra, padding: [0, 0, 0, 2], align: 'right' })
|
310 | else ui.div()
|
311 | })
|
312 |
|
313 | ui.div()
|
314 | })
|
315 |
|
316 |
|
317 | if (examples.length) {
|
318 | ui.div(__('Examples:'))
|
319 |
|
320 | examples.forEach((example) => {
|
321 | example[0] = example[0].replace(/\$0/g, base$0)
|
322 | })
|
323 |
|
324 | examples.forEach((example) => {
|
325 | if (example[1] === '') {
|
326 | ui.div(
|
327 | {
|
328 | text: example[0],
|
329 | padding: [0, 2, 0, 2]
|
330 | }
|
331 | )
|
332 | } else {
|
333 | ui.div(
|
334 | {
|
335 | text: example[0],
|
336 | padding: [0, 2, 0, 2],
|
337 | width: maxWidth(examples, theWrap) + 4
|
338 | }, {
|
339 | text: example[1]
|
340 | }
|
341 | )
|
342 | }
|
343 | })
|
344 |
|
345 | ui.div()
|
346 | }
|
347 |
|
348 |
|
349 | if (epilogs.length > 0) {
|
350 | const e = epilogs.map(epilog => epilog.replace(/\$0/g, base$0)).join('\n')
|
351 | ui.div(`${e}\n`)
|
352 | }
|
353 |
|
354 |
|
355 | return ui.toString().replace(/\s*$/, '')
|
356 | }
|
357 |
|
358 |
|
359 |
|
360 | function maxWidth (table, theWrap, modifier) {
|
361 | let width = 0
|
362 |
|
363 |
|
364 |
|
365 | if (!Array.isArray(table)) {
|
366 | table = Object.keys(table).map(key => [table[key]])
|
367 | }
|
368 |
|
369 | table.forEach((v) => {
|
370 | width = Math.max(
|
371 | stringWidth(modifier ? `${modifier} ${v[0]}` : v[0]),
|
372 | width
|
373 | )
|
374 | })
|
375 |
|
376 |
|
377 |
|
378 | if (theWrap) width = Math.min(width, parseInt(theWrap * 0.5, 10))
|
379 |
|
380 | return width
|
381 | }
|
382 |
|
383 |
|
384 |
|
385 | function normalizeAliases () {
|
386 |
|
387 | const demandedOptions = yargs.getDemandedOptions()
|
388 | const options = yargs.getOptions()
|
389 |
|
390 | ;(Object.keys(options.alias) || []).forEach((key) => {
|
391 | options.alias[key].forEach((alias) => {
|
392 |
|
393 | if (descriptions[alias]) self.describe(key, descriptions[alias])
|
394 |
|
395 | if (alias in demandedOptions) yargs.demandOption(key, demandedOptions[alias])
|
396 |
|
397 | if (~options.boolean.indexOf(alias)) yargs.boolean(key)
|
398 | if (~options.count.indexOf(alias)) yargs.count(key)
|
399 | if (~options.string.indexOf(alias)) yargs.string(key)
|
400 | if (~options.normalize.indexOf(alias)) yargs.normalize(key)
|
401 | if (~options.array.indexOf(alias)) yargs.array(key)
|
402 | if (~options.number.indexOf(alias)) yargs.number(key)
|
403 | })
|
404 | })
|
405 | }
|
406 |
|
407 |
|
408 |
|
409 | let cachedHelpMessage
|
410 | self.cacheHelpMessage = function () {
|
411 | cachedHelpMessage = this.help()
|
412 | }
|
413 |
|
414 |
|
415 |
|
416 | function addUngroupedKeys (keys, aliases, groups) {
|
417 | let groupedKeys = []
|
418 | let toCheck = null
|
419 | Object.keys(groups).forEach((group) => {
|
420 | groupedKeys = groupedKeys.concat(groups[group])
|
421 | })
|
422 |
|
423 | keys.forEach((key) => {
|
424 | toCheck = [key].concat(aliases[key])
|
425 | if (!toCheck.some(k => groupedKeys.indexOf(k) !== -1)) {
|
426 | groups[defaultGroup].push(key)
|
427 | }
|
428 | })
|
429 | return groupedKeys
|
430 | }
|
431 |
|
432 | function filterHiddenOptions (key) {
|
433 | return yargs.getOptions().hiddenOptions.indexOf(key) < 0 || yargs.parsed.argv[yargs.getOptions().showHiddenOpt]
|
434 | }
|
435 |
|
436 | self.showHelp = (level) => {
|
437 | const logger = yargs._getLoggerInstance()
|
438 | if (!level) level = 'error'
|
439 | const emit = typeof level === 'function' ? level : logger[level]
|
440 | emit(self.help())
|
441 | }
|
442 |
|
443 | self.functionDescription = (fn) => {
|
444 | const description = fn.name ? decamelize(fn.name, '-') : __('generated-value')
|
445 | return ['(', description, ')'].join('')
|
446 | }
|
447 |
|
448 | self.stringifiedValues = function stringifiedValues (values, separator) {
|
449 | let string = ''
|
450 | const sep = separator || ', '
|
451 | const array = [].concat(values)
|
452 |
|
453 | if (!values || !array.length) return string
|
454 |
|
455 | array.forEach((value) => {
|
456 | if (string.length) string += sep
|
457 | string += JSON.stringify(value)
|
458 | })
|
459 |
|
460 | return string
|
461 | }
|
462 |
|
463 |
|
464 |
|
465 | function defaultString (value, defaultDescription) {
|
466 | let string = `[${__('default:')} `
|
467 |
|
468 | if (value === undefined && !defaultDescription) return null
|
469 |
|
470 | if (defaultDescription) {
|
471 | string += defaultDescription
|
472 | } else {
|
473 | switch (typeof value) {
|
474 | case 'string':
|
475 | string += `"${value}"`
|
476 | break
|
477 | case 'object':
|
478 | string += JSON.stringify(value)
|
479 | break
|
480 | default:
|
481 | string += value
|
482 | }
|
483 | }
|
484 |
|
485 | return `${string}]`
|
486 | }
|
487 |
|
488 |
|
489 | function windowWidth () {
|
490 | const maxWidth = 80
|
491 | if (typeof process === 'object' && process.stdout && process.stdout.columns) {
|
492 | return Math.min(maxWidth, process.stdout.columns)
|
493 | } else {
|
494 | return maxWidth
|
495 | }
|
496 | }
|
497 |
|
498 |
|
499 | let version = null
|
500 | self.version = (ver) => {
|
501 | version = ver
|
502 | }
|
503 |
|
504 | self.showVersion = () => {
|
505 | const logger = yargs._getLoggerInstance()
|
506 | logger.log(version)
|
507 | }
|
508 |
|
509 | self.reset = function reset (localLookup) {
|
510 |
|
511 |
|
512 | failMessage = null
|
513 | failureOutput = false
|
514 | usages = []
|
515 | usageDisabled = false
|
516 | epilogs = []
|
517 | examples = []
|
518 | commands = []
|
519 | descriptions = objFilter(descriptions, (k, v) => !localLookup[k])
|
520 | return self
|
521 | }
|
522 |
|
523 | let frozens = []
|
524 | self.freeze = function freeze () {
|
525 | let frozen = {}
|
526 | frozens.push(frozen)
|
527 | frozen.failMessage = failMessage
|
528 | frozen.failureOutput = failureOutput
|
529 | frozen.usages = usages
|
530 | frozen.usageDisabled = usageDisabled
|
531 | frozen.epilogs = epilogs
|
532 | frozen.examples = examples
|
533 | frozen.commands = commands
|
534 | frozen.descriptions = descriptions
|
535 | }
|
536 | self.unfreeze = function unfreeze () {
|
537 | let frozen = frozens.pop()
|
538 | failMessage = frozen.failMessage
|
539 | failureOutput = frozen.failureOutput
|
540 | usages = frozen.usages
|
541 | usageDisabled = frozen.usageDisabled
|
542 | epilogs = frozen.epilogs
|
543 | examples = frozen.examples
|
544 | commands = frozen.commands
|
545 | descriptions = frozen.descriptions
|
546 | }
|
547 |
|
548 | return self
|
549 | }
|