Skip to content

Commit

Permalink
Fix cacjs#37: better help output for negated options
Browse files Browse the repository at this point in the history
Not only doesn't show the `(default: true)` for negated options, which
was quite confusing, but merges them with the line describing the
affirmative case.

Added a new test.  Since it relies on help output, it is a bit
brittle, like the other "snapshot"-based ones.  I didn't the same
execa snapshot for this because it would make a test take a lot longer
to run (becasue spawns a process).

* README.md (Options): Mention [no-]foo syntax.

* src/Command.ts (outputHelp): Consider options with negated
counterparts when printing.

* src/__test__/index.test.ts ('negated option help output'): New test.
  • Loading branch information
joaotavora committed Feb 19, 2021
1 parent de2edee commit 436eebe
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 7 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <path> Disable config file/Use a custom config file
-h, --help Display this message
-v, --version Display version number
```

### Variadic Arguments

Expand Down
34 changes: 27 additions & 7 deletions src/Command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
})
}

Expand Down
28 changes: 28 additions & 0 deletions src/__test__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,34 @@ test('negated option validation', () => {
expect(options.config).toBe(false)
})

test('negated option help output', () => {
const cli = cac()

cli.option('--config <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:
$ <command> [options]
Options:
--[no-]config <config> Skip/Use custom config file `
)
} finally {
console.log = saved
}
})

test('array types without transformFunction', () => {
const cli = cac()

Expand Down

0 comments on commit 436eebe

Please sign in to comment.