Skip to content

Commit b5426db

Browse files
authored
Fix duplicate options showing up in command help output (#68)
When using option groups to bring options into subcommands that are also used in parent commands, the help system duplicates the options in the generated output. For instance, in the `SubcommandEndToEndTest` example, the `--name` argument is listed twice for both the subcommands `CommandA` and `CommandB`, and expected by the test assertion. It is annoying in a simple case like this but causes a lot of unhelpful noise in help output when you share more and more options across a command heirarchy. To solve this we need to keep track of what `HelpGenerator.Section.Element` values have already been processed from parent commands in the command stack. I acheived this by making `Element` conform to `Hashable` to track in a `Set`. This assumes that we don't need to support re-using command names up and down a command heirarchy. It doesn't work at all today (you get an error when trying to use the exact same option in a parent and child command). If we choose to support this eventually then we'll need to augment this solution to keep track of where the `Element` was generated in the heirarchy. This also updates the test to properly assert that the `--name` option is only output once for the subcommands in `SubcommandEndToEndTest`.
1 parent 7f5984a commit b5426db

File tree

2 files changed

+11
-8
lines changed

2 files changed

+11
-8
lines changed

Sources/ArgumentParser/Usage/HelpGenerator.swift

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ internal struct HelpGenerator {
2828
}
2929

3030
struct Section {
31-
struct Element {
31+
struct Element: Hashable {
3232
var label: String
3333
var abstract: String = ""
3434
var discussion: String = ""
@@ -125,7 +125,9 @@ internal struct HelpGenerator {
125125
static func generateSections(commandStack: [ParsableCommand.Type]) -> [Section] {
126126
var positionalElements: [Section.Element] = []
127127
var optionElements: [Section.Element] = []
128-
128+
/// Used to keep track of elements already seen from parent commands.
129+
var alreadySeenElements = Set<Section.Element>()
130+
129131
for commandType in commandStack {
130132
let args = Array(ArgumentSet(commandType))
131133

@@ -174,10 +176,13 @@ internal struct HelpGenerator {
174176
}
175177

176178
let element = Section.Element(label: synopsis, abstract: description, discussion: arg.help.help?.discussion ?? "")
177-
if case .positional = arg.kind {
178-
positionalElements.append(element)
179-
} else {
180-
optionElements.append(element)
179+
if !alreadySeenElements.contains(element) {
180+
alreadySeenElements.insert(element)
181+
if case .positional = arg.kind {
182+
positionalElements.append(element)
183+
} else {
184+
optionElements.append(element)
185+
}
181186
}
182187
}
183188
}

Tests/EndToEndTests/SubcommandEndToEndTests.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ extension SubcommandEndToEndTests {
8686
USAGE: foo a --name <name> --bar <bar>
8787
8888
OPTIONS:
89-
--name <name>
9089
--name <name>
9190
--bar <bar>
9291
-h, --help Show help information.
@@ -96,7 +95,6 @@ extension SubcommandEndToEndTests {
9695
USAGE: foo b --name <name> --baz <baz>
9796
9897
OPTIONS:
99-
--name <name>
10098
--name <name>
10199
--baz <baz>
102100
-h, --help Show help information.

0 commit comments

Comments
 (0)