import * as os from 'os'; import { CliBaseCommandDefinition, CliCommandDefinition, CliCommandDefinitionOption, ParsedCliCommand } from './types'; import { dashCase } from './utils'; const EOL = os.EOL; const GAP = 4; const __ = ' '; // @see https://stackoverflow.com/a/18914855/3577474 export const sentences = (str = '') => { return str.replace(/([.?!])\s*(?=[A-Z])/g, '$1|').split('|'); }; const noop = (s: string): string => { return s; }; const maxLen = (arr: string[]): number => { let c = 0; let d = 0; let l = 0; let i = arr.length; if (i) { while (i--) { d = arr[i].length; if (d > c) { l = i; c = d; } } } return arr[l].length; }; const formatTable = (arr: string[][]): any => { if (!arr.length) { return ''; } if (Array.isArray(arr)) { const len = maxLen(arr.map(x => x[0])) + GAP; const join = a => a[0] + ' '.repeat(len - a[0].length) + a[1] + (a[2] == null ? '' : ` ${a[2]}`); return arr.map(join); } }; export const section = (str: string, content: string[] | string, fn?: (s: string) => string) => { const arr = Array.isArray(content) ? content : [content]; fn = fn || noop; if (!arr || !arr.length) { return ''; } let i = 0; let out = ''; out += EOL + __ + str; for (; i < arr.length; i++) { out += EOL + __ + __ + fn(arr[i]); } return out + EOL; }; const buildUsage = (parsed: ParsedCliCommand): string => { let args = []; if (parsed.parsedCommandName) { args = (parsed.command.arguments || []).map(a => { return `${a.isOptional ? '[' : '<'}${a.name}${a.isOptional ? ']' : '>'}`; }); return `${parsed.parsedCommandName}${args.length > 0 ? ` ${args.join(' ')}` : ''} [options]`; } else { return ' [args] [options]'; } }; export const buildUsageSection = (parsed: ParsedCliCommand): string => { const pfx = `$ ${parsed.program.name}`; const prefix = s => `${pfx} ${s}`; return section('Usage', `${buildUsage(parsed)}`, prefix); }; export const helpFormatter = (parsed: ParsedCliCommand): string => { const rootDef = parsed.program; const cmdDefs = rootDef.commands; let out = ''; const pfx = `$ ${rootDef.name}`; const prefix = s => `${pfx} ${s}`; // Description Section out += section('Description', parsed.parsedCommandName ? parsed.command.description : rootDef.description); // Usage Section out += buildUsageSection(parsed); let argumentsContent: string[][] = []; if (!parsed.parsedCommandName) { // Global Command List Section const cmdAndDesc = cmdDefs.map(def => [def.name, def.description || '']); out += section('Available Commands', formatTable(cmdAndDesc)); out += EOL + __ + 'For more info, run any command with the `--help` flag'; cmdDefs.forEach(def => { out += EOL + __ + __ + `${pfx} ${def.name} --help`; }); out += EOL; argumentsContent = [ [ 'command', `Command Name${rootDef.defaultCommandName ? ` (default: '${rootDef.defaultCommandName}')` : ''}`, ], ]; } else { // Global Command List Section argumentsContent = parsed.command.arguments.map(a => [ `${a.name} ${a.isOptional ? '(optional)' : '(required)'}`, `${a.description || ''}${a.default ? ` (default: '${a.default}')` : ''}`, ]); } const cmdDef: CliBaseCommandDefinition = parsed.parsedCommandName ? parsed.command : rootDef; // Arguments Section out += section('Arguments', formatTable(argumentsContent)); // Global Options Section const globalOptionsContent = rootDef.options.map(o => [ `--${dashCase(o.name)}${o.flag ? `, -${o.flag}` : ''}`, o.description || '', ]); out += section('Global Options', formatTable(globalOptionsContent)); // Command Options Section if (parsed.parsedCommandName) { const cmdOptionsContent = parsed.command.options.map(o => [ `--${dashCase(o.name)}${o.flag ? `, -${o.flag}` : ''}`, o.description || '', ]); out += section(`${parsed.command.name} Command Options`, formatTable(cmdOptionsContent)); } // Examples Section out += section('Examples', (cmdDef.examples || []).map(prefix)); return out; };