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