[SignalingServer] Optimize dependent modules
[platform/framework/web/wrtjs.git] / signaling_server / service / node_modules / yargs / lib / usage.js
1 'use strict'
2 // this file handles outputting usage instructions,
3 // failures, etc. keeps logging in one place.
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   // methods for ouputting/building failure message.
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       // don't output failure message more than once
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   // methods for ouputting/building help (usage) message.
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     // the last default wins, so cancel out any previously set default
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 epilog
126   self.epilog = (msg) => {
127     epilog = 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     normalizeAliases()
152
153     // handle old demanded API
154     const base$0 = path.basename(yargs.$0)
155     const demandedOptions = yargs.getDemandedOptions()
156     const demandedCommands = yargs.getDemandedCommands()
157     const groups = yargs.getGroups()
158     const options = yargs.getOptions()
159
160     let keys = []
161     keys = keys.concat(Object.keys(descriptions))
162     keys = keys.concat(Object.keys(demandedOptions))
163     keys = keys.concat(Object.keys(demandedCommands))
164     keys = keys.concat(Object.keys(options.default))
165     keys = keys.filter(filterHiddenOptions)
166     keys = Object.keys(keys.reduce((acc, key) => {
167       if (key !== '_') acc[key] = true
168       return acc
169     }, {}))
170
171     const theWrap = getWrap()
172     const ui = require('cliui')({
173       width: theWrap,
174       wrap: !!theWrap
175     })
176
177     // the usage string.
178     if (!usageDisabled) {
179       if (usages.length) {
180         // user-defined usage.
181         usages.forEach((usage) => {
182           ui.div(`${usage[0].replace(/\$0/g, base$0)}`)
183           if (usage[1]) {
184             ui.div({ text: `${usage[1]}`, padding: [1, 0, 0, 0] })
185           }
186         })
187         ui.div()
188       } else if (commands.length) {
189         let u = null
190         // demonstrate how commands are used.
191         if (demandedCommands._) {
192           u = `${base$0} <${__('command')}>\n`
193         } else {
194           u = `${base$0} [${__('command')}]\n`
195         }
196         ui.div(`${u}`)
197       }
198     }
199
200     // your application's commands, i.e., non-option
201     // arguments populated in '_'.
202     if (commands.length) {
203       ui.div(__('Commands:'))
204
205       const context = yargs.getContext()
206       const parentCommands = context.commands.length ? `${context.commands.join(' ')} ` : ''
207
208       if (yargs.getParserConfiguration()['sort-commands'] === true) {
209         commands = commands.sort((a, b) => a[0].localeCompare(b[0]))
210       }
211
212       commands.forEach((command) => {
213         const commandString = `${base$0} ${parentCommands}${command[0].replace(/^\$0 ?/, '')}` // drop $0 from default commands.
214         ui.span(
215           {
216             text: commandString,
217             padding: [0, 2, 0, 2],
218             width: maxWidth(commands, theWrap, `${base$0}${parentCommands}`) + 4
219           },
220           { text: command[1] }
221         )
222         const hints = []
223         if (command[2]) hints.push(`[${__('default:').slice(0, -1)}]`) // TODO hacking around i18n here
224         if (command[3] && command[3].length) {
225           hints.push(`[${__('aliases:')} ${command[3].join(', ')}]`)
226         }
227         if (hints.length) {
228           ui.div({ text: hints.join(' '), padding: [0, 0, 0, 2], align: 'right' })
229         } else {
230           ui.div()
231         }
232       })
233
234       ui.div()
235     }
236
237     // perform some cleanup on the keys array, making it
238     // only include top-level keys not their aliases.
239     const aliasKeys = (Object.keys(options.alias) || [])
240       .concat(Object.keys(yargs.parsed.newAliases) || [])
241
242     keys = keys.filter(key => !yargs.parsed.newAliases[key] && aliasKeys.every(alias => (options.alias[alias] || []).indexOf(key) === -1))
243
244     // populate 'Options:' group with any keys that have not
245     // explicitly had a group set.
246     if (!groups[defaultGroup]) groups[defaultGroup] = []
247     addUngroupedKeys(keys, options.alias, groups)
248
249     // display 'Options:' table along with any custom tables:
250     Object.keys(groups).forEach((groupName) => {
251       if (!groups[groupName].length) return
252
253       // if we've grouped the key 'f', but 'f' aliases 'foobar',
254       // normalizedKeys should contain only 'foobar'.
255       const normalizedKeys = groups[groupName].filter(filterHiddenOptions).map((key) => {
256         if (~aliasKeys.indexOf(key)) return key
257         for (let i = 0, aliasKey; (aliasKey = aliasKeys[i]) !== undefined; i++) {
258           if (~(options.alias[aliasKey] || []).indexOf(key)) return aliasKey
259         }
260         return key
261       })
262
263       if (normalizedKeys.length < 1) return
264
265       ui.div(__(groupName))
266
267       // actually generate the switches string --foo, -f, --bar.
268       const switches = normalizedKeys.reduce((acc, key) => {
269         acc[key] = [ key ].concat(options.alias[key] || [])
270           .map(sw => {
271             // for the special positional group don't
272             // add '--' or '-' prefix.
273             if (groupName === self.getPositionalGroupName()) return sw
274             else return (sw.length > 1 ? '--' : '-') + sw
275           })
276           .join(', ')
277
278         return acc
279       }, {})
280
281       normalizedKeys.forEach((key) => {
282         const kswitch = switches[key]
283         let desc = descriptions[key] || ''
284         let type = null
285
286         if (~desc.lastIndexOf(deferY18nLookupPrefix)) desc = __(desc.substring(deferY18nLookupPrefix.length))
287
288         if (~options.boolean.indexOf(key)) type = `[${__('boolean')}]`
289         if (~options.count.indexOf(key)) type = `[${__('count')}]`
290         if (~options.string.indexOf(key)) type = `[${__('string')}]`
291         if (~options.normalize.indexOf(key)) type = `[${__('string')}]`
292         if (~options.array.indexOf(key)) type = `[${__('array')}]`
293         if (~options.number.indexOf(key)) type = `[${__('number')}]`
294
295         const extra = [
296           type,
297           (key in demandedOptions) ? `[${__('required')}]` : null,
298           options.choices && options.choices[key] ? `[${__('choices:')} ${
299             self.stringifiedValues(options.choices[key])}]` : null,
300           defaultString(options.default[key], options.defaultDescription[key])
301         ].filter(Boolean).join(' ')
302
303         ui.span(
304           { text: kswitch, padding: [0, 2, 0, 2], width: maxWidth(switches, theWrap) + 4 },
305           desc
306         )
307
308         if (extra) ui.div({ text: extra, padding: [0, 0, 0, 2], align: 'right' })
309         else ui.div()
310       })
311
312       ui.div()
313     })
314
315     // describe some common use-cases for your application.
316     if (examples.length) {
317       ui.div(__('Examples:'))
318
319       examples.forEach((example) => {
320         example[0] = example[0].replace(/\$0/g, base$0)
321       })
322
323       examples.forEach((example) => {
324         if (example[1] === '') {
325           ui.div(
326             {
327               text: example[0],
328               padding: [0, 2, 0, 2]
329             }
330           )
331         } else {
332           ui.div(
333             {
334               text: example[0],
335               padding: [0, 2, 0, 2],
336               width: maxWidth(examples, theWrap) + 4
337             }, {
338               text: example[1]
339             }
340           )
341         }
342       })
343
344       ui.div()
345     }
346
347     // the usage string.
348     if (epilog) {
349       const e = epilog.replace(/\$0/g, base$0)
350       ui.div(`${e}\n`)
351     }
352
353     // Remove the trailing white spaces
354     return ui.toString().replace(/\s*$/, '')
355   }
356
357   // return the maximum width of a string
358   // in the left-hand column of a table.
359   function maxWidth (table, theWrap, modifier) {
360     let width = 0
361
362     // table might be of the form [leftColumn],
363     // or {key: leftColumn}
364     if (!Array.isArray(table)) {
365       table = Object.keys(table).map(key => [table[key]])
366     }
367
368     table.forEach((v) => {
369       width = Math.max(
370         stringWidth(modifier ? `${modifier} ${v[0]}` : v[0]),
371         width
372       )
373     })
374
375     // if we've enabled 'wrap' we should limit
376     // the max-width of the left-column.
377     if (theWrap) width = Math.min(width, parseInt(theWrap * 0.5, 10))
378
379     return width
380   }
381
382   // make sure any options set for aliases,
383   // are copied to the keys being aliased.
384   function normalizeAliases () {
385     // handle old demanded API
386     const demandedOptions = yargs.getDemandedOptions()
387     const options = yargs.getOptions()
388
389     ;(Object.keys(options.alias) || []).forEach((key) => {
390       options.alias[key].forEach((alias) => {
391         // copy descriptions.
392         if (descriptions[alias]) self.describe(key, descriptions[alias])
393         // copy demanded.
394         if (alias in demandedOptions) yargs.demandOption(key, demandedOptions[alias])
395         // type messages.
396         if (~options.boolean.indexOf(alias)) yargs.boolean(key)
397         if (~options.count.indexOf(alias)) yargs.count(key)
398         if (~options.string.indexOf(alias)) yargs.string(key)
399         if (~options.normalize.indexOf(alias)) yargs.normalize(key)
400         if (~options.array.indexOf(alias)) yargs.array(key)
401         if (~options.number.indexOf(alias)) yargs.number(key)
402       })
403     })
404   }
405
406   // given a set of keys, place any keys that are
407   // ungrouped under the 'Options:' grouping.
408   function addUngroupedKeys (keys, aliases, groups) {
409     let groupedKeys = []
410     let toCheck = null
411     Object.keys(groups).forEach((group) => {
412       groupedKeys = groupedKeys.concat(groups[group])
413     })
414
415     keys.forEach((key) => {
416       toCheck = [key].concat(aliases[key])
417       if (!toCheck.some(k => groupedKeys.indexOf(k) !== -1)) {
418         groups[defaultGroup].push(key)
419       }
420     })
421     return groupedKeys
422   }
423
424   function filterHiddenOptions (key) {
425     return yargs.getOptions().hiddenOptions.indexOf(key) < 0 || yargs.parsed.argv[yargs.getOptions().showHiddenOpt]
426   }
427
428   self.showHelp = (level) => {
429     const logger = yargs._getLoggerInstance()
430     if (!level) level = 'error'
431     const emit = typeof level === 'function' ? level : logger[level]
432     emit(self.help())
433   }
434
435   self.functionDescription = (fn) => {
436     const description = fn.name ? decamelize(fn.name, '-') : __('generated-value')
437     return ['(', description, ')'].join('')
438   }
439
440   self.stringifiedValues = function stringifiedValues (values, separator) {
441     let string = ''
442     const sep = separator || ', '
443     const array = [].concat(values)
444
445     if (!values || !array.length) return string
446
447     array.forEach((value) => {
448       if (string.length) string += sep
449       string += JSON.stringify(value)
450     })
451
452     return string
453   }
454
455   // format the default-value-string displayed in
456   // the right-hand column.
457   function defaultString (value, defaultDescription) {
458     let string = `[${__('default:')} `
459
460     if (value === undefined && !defaultDescription) return null
461
462     if (defaultDescription) {
463       string += defaultDescription
464     } else {
465       switch (typeof value) {
466         case 'string':
467           string += `"${value}"`
468           break
469         case 'object':
470           string += JSON.stringify(value)
471           break
472         default:
473           string += value
474       }
475     }
476
477     return `${string}]`
478   }
479
480   // guess the width of the console window, max-width 80.
481   function windowWidth () {
482     const maxWidth = 80
483     if (typeof process === 'object' && process.stdout && process.stdout.columns) {
484       return Math.min(maxWidth, process.stdout.columns)
485     } else {
486       return maxWidth
487     }
488   }
489
490   // logic for displaying application version.
491   let version = null
492   self.version = (ver) => {
493     version = ver
494   }
495
496   self.showVersion = () => {
497     const logger = yargs._getLoggerInstance()
498     logger.log(version)
499   }
500
501   self.reset = function reset (localLookup) {
502     // do not reset wrap here
503     // do not reset fails here
504     failMessage = null
505     failureOutput = false
506     usages = []
507     usageDisabled = false
508     epilog = undefined
509     examples = []
510     commands = []
511     descriptions = objFilter(descriptions, (k, v) => !localLookup[k])
512     return self
513   }
514
515   let frozen
516   self.freeze = function freeze () {
517     frozen = {}
518     frozen.failMessage = failMessage
519     frozen.failureOutput = failureOutput
520     frozen.usages = usages
521     frozen.usageDisabled = usageDisabled
522     frozen.epilog = epilog
523     frozen.examples = examples
524     frozen.commands = commands
525     frozen.descriptions = descriptions
526   }
527   self.unfreeze = function unfreeze () {
528     failMessage = frozen.failMessage
529     failureOutput = frozen.failureOutput
530     usages = frozen.usages
531     usageDisabled = frozen.usageDisabled
532     epilog = frozen.epilog
533     examples = frozen.examples
534     commands = frozen.commands
535     descriptions = frozen.descriptions
536     frozen = undefined
537   }
538
539   return self
540 }