diff --git a/README.md b/README.md index 2933e4be..57e7622b 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,17 @@ cli ``` This will let CAC set the default value of `config` to true, and you can use `--no-config` flag to set it to `false`. +This also results in the familiar `[no-]foo` syntax in `cli.help()` output: + +``` +Usage: + $ foo build [project] + +Options: + --[no-]config Disable config file/Use a custom config file + -h, --help Display this message + -v, --version Display version number +``` ### Variadic Arguments diff --git a/src/Command.ts b/src/Command.ts index 11ee87eb..862aa461 100644 --- a/src/Command.ts +++ b/src/Command.ts @@ -184,22 +184,42 @@ class Command { ? globalOptions : [...this.options, ...(globalOptions || [])] if (options.length > 0) { - const longestOptionName = findLongest( - options.map((option) => option.rawName) - ) + const negatedOpts: { [key: string]: Option } = {} + // O(n2), let's hope there aren't a zillion options + for (const o1 of options) { + if (!o1.negated) continue + for (const o2 of options) { + if (o1.name == o2.name) negatedOpts[o2.name] = o1 + } + } + const longest = options + .map((o) => { + if (o.negated) return 0 + const l = o.rawName.length + // '5' is the length of '[no-]'. As to the 1, I have no clue... + return negatedOpts[o.name] ? l + 5 + 1 : l + }) + .reduce((acc, l) => Math.max(acc, l), 0) sections.push({ title: 'Options', body: options .map((option) => { - return ` ${padRight(option.rawName, longestOptionName.length)} ${ - option.description - } ${ + if (option.negated) return + let desc = option.description + let name = option.rawName + if (negatedOpts[option.name]) { + const ndesc = negatedOpts[option.name].description + if (ndesc) desc = `${ndesc}/${desc}` + name = name.replace(/^(--?)/, '$1[no-]') + } + + return ` ${padRight(name, longest)} ${desc} ${ option.config.default === undefined ? '' : `(default: ${option.config.default})` }` }) - .join('\n'), + .filter(Boolean).join('\n'), }) } diff --git a/src/__test__/index.test.ts b/src/__test__/index.test.ts index eccbca1e..db1098ce 100644 --- a/src/__test__/index.test.ts +++ b/src/__test__/index.test.ts @@ -103,6 +103,34 @@ test('negated option validation', () => { expect(options.config).toBe(false) }) +test('negated option help output', () => { + const cli = cac() + + cli.option('--config ', 'Use custom config file') + cli.option('--no-config', 'Skip') + + const saved = console.log + let output = '' + try { + console.log = (msg, more) => { + if (more) throw new Error('Unexpected multi-arg call to console.log') + output += msg + } + cli.outputHelp() + expect(output).toBe( + ` + +Usage: + $ [options] + +Options: + --[no-]config Skip/Use custom config file ` + ) + } finally { + console.log = saved + } +}) + test('array types without transformFunction', () => { const cli = cac() diff --git a/src/utils.ts b/src/utils.ts index a564e16c..187c10f9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -79,9 +79,7 @@ export const getMriOptions = (options: Option[]) => { } export const findLongest = (arr: string[]) => { - return arr.sort((a, b) => { - return a.length > b.length ? -1 : 1 - })[0] + return arr.reduce((acc, a) => (a.length > acc.length ? a : acc)) } export const padRight = (str: string, length: number) => {