Skip to content

Commit 84babd6

Browse files
authored
Display help of array list of enum items (#821)
Adding an `@Option` that is an Array of Enum flag did not display the help message correctly. Fixes: #818
1 parent 04695ec commit 84babd6

File tree

4 files changed

+127
-6
lines changed

4 files changed

+127
-6
lines changed

Sources/ArgumentParser/Parsable Properties/ArgumentDiscussion.swift

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,29 @@
7777
/// -h, --help Show help information
7878
/// ```
7979
///
80-
/// In any case where the argument type is not `EnumerableOptionValue`, the
81-
/// default implementation will use the `.staticText` case and will print a
82-
/// block of discussion text.
80+
/// Arrays of enumerable types are also supported and will display the same
81+
/// enumerated format:
82+
///
83+
/// ```swift
84+
/// @Option var colors: [Color] = [.red, .blue]
85+
/// ```
86+
///
87+
/// The printed usage would look like the following:
88+
///
89+
/// ```
90+
/// USAGE: example [--colors <colors> ...]
91+
///
92+
/// OPTIONS:
93+
/// --colors <colors> (default: red, blue)
94+
/// red - A red color.
95+
/// blue - A blue color.
96+
/// yellow - A yellow color.
97+
/// -h, --help Show help information
98+
/// ```
99+
///
100+
/// In any case where the argument type is not `EnumerableOptionValue` or an
101+
/// array of `EnumerableOptionValue`, the default implementation will use the
102+
/// `.staticText` case and will print a block of discussion text.
83103
enum ArgumentDiscussion {
84104
case staticText(String)
85105
case enumerated(preamble: String? = nil, any ExpressibleByArgument.Type)

Sources/ArgumentParser/Parsable Properties/Option.swift

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,11 @@ extension Option {
721721
/// var chars: [Character] = []
722722
/// ```
723723
///
724+
/// If the element type conforms to `ExpressibleByArgument` and has enumerable
725+
/// value descriptions (via `defaultValueDescription`), the help output will
726+
/// display each possible value with its description, similar to single
727+
/// enumerable options.
728+
///
724729
/// - Parameters:
725730
/// - wrappedValue: A default value to use for this property, provided
726731
/// implicitly by the compiler during property wrapper initialization.
@@ -745,7 +750,13 @@ extension Option {
745750
container: Array<T>.self,
746751
key: key,
747752
kind: .name(key: key, specification: name),
748-
help: help,
753+
help: .init(
754+
help?.abstract ?? "",
755+
discussion: help?.discussion,
756+
valueName: help?.valueName,
757+
visibility: help?.visibility ?? .default,
758+
argumentType: T.self
759+
),
749760
parsingStrategy: parsingStrategy.base,
750761
initial: wrappedValue,
751762
completion: completion)
@@ -765,6 +776,11 @@ extension Option {
765776
/// var chars: [Character]
766777
/// ```
767778
///
779+
/// If the element type conforms to `ExpressibleByArgument` and has enumerable
780+
/// value descriptions (via `defaultValueDescription`), the help output will
781+
/// display each possible value with its description, similar to single
782+
/// enumerable options.
783+
///
768784
/// - Parameters:
769785
/// - name: A specification for what names are allowed for this option.
770786
/// - parsingStrategy: The behavior to use when parsing the elements for
@@ -784,7 +800,13 @@ extension Option {
784800
container: Array<T>.self,
785801
key: key,
786802
kind: .name(key: key, specification: name),
787-
help: help,
803+
help: .init(
804+
help?.abstract ?? "",
805+
discussion: help?.discussion,
806+
valueName: help?.valueName,
807+
visibility: help?.visibility ?? .default,
808+
argumentType: T.self
809+
),
788810
parsingStrategy: parsingStrategy.base,
789811
initial: nil,
790812
completion: completion)

Sources/ArgumentParser/Parsing/ArgumentDefinition.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,12 @@ where Element: ExpressibleByArgument {
447447
guard !initial.isEmpty else { return nil }
448448
return initial
449449
.lazy
450-
.map { $0.defaultValueDescription }
450+
.map { element in
451+
if let element = element as? (any CaseIterable & RawRepresentable) {
452+
return String(describing: element.rawValue)
453+
}
454+
return element.defaultValueDescription
455+
}
451456
.joined(separator: ", ")
452457
}
453458
}

Tests/ArgumentParserUnitTests/HelpGenerationTests.swift

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,6 +1095,80 @@ extension HelpGenerationTests {
10951095
""")
10961096
}
10971097

1098+
struct CustomOptionAsListWithSingleDefaultValue: ParsableCommand {
1099+
@Option(
1100+
help: "An option with enumerable values.")
1101+
var opt: [OptionValues] = [.red]
1102+
}
1103+
1104+
func testEnumerableOptionAsListWithSingleDefault() {
1105+
AssertHelp(
1106+
.default,
1107+
for: CustomOptionAsListWithSingleDefaultValue.self,
1108+
columns: 100,
1109+
equals: """
1110+
USAGE: custom-option-as-list-with-single-default-value [--opt <opt> ...]
1111+
1112+
OPTIONS:
1113+
--opt <opt> An option with enumerable values. (default: red)
1114+
blue - The color of the sky.
1115+
red - The color of a rose.
1116+
yellow - The color of the sun.
1117+
-h, --help Show help information.
1118+
1119+
""")
1120+
}
1121+
1122+
struct CustomOptionAsListWithMultipleDefaultValue: ParsableCommand {
1123+
@Option(
1124+
help: "An option with multiple enumerable values.")
1125+
var opt: [OptionValues] = [.red, .blue]
1126+
}
1127+
1128+
func testEnumerableOptionAsListWithMultipleDefault() {
1129+
AssertHelp(
1130+
.default,
1131+
for: CustomOptionAsListWithMultipleDefaultValue.self,
1132+
columns: 100,
1133+
equals: """
1134+
USAGE: custom-option-as-list-with-multiple-default-value [--opt <opt> ...]
1135+
1136+
OPTIONS:
1137+
--opt <opt> An option with multiple enumerable values. (default: red, blue)
1138+
blue - The color of the sky.
1139+
red - The color of a rose.
1140+
yellow - The color of the sun.
1141+
-h, --help Show help information.
1142+
1143+
""")
1144+
1145+
}
1146+
1147+
struct CustomOptionAsListWithEmptyArrayAsDefault: ParsableCommand {
1148+
@Option(
1149+
help: "An option with default value set to empty array.")
1150+
var opt: [OptionValues] = []
1151+
}
1152+
1153+
func testEnumerableOptionAsListWithEmptyArrayAsDefault() {
1154+
AssertHelp(
1155+
.default,
1156+
for: CustomOptionAsListWithEmptyArrayAsDefault.self,
1157+
columns: 100,
1158+
equals: """
1159+
USAGE: custom-option-as-list-with-empty-array-as-default [--opt <opt> ...]
1160+
1161+
OPTIONS:
1162+
--opt <opt> An option with default value set to empty array.
1163+
blue - The color of the sky.
1164+
red - The color of a rose.
1165+
yellow - The color of the sun.
1166+
-h, --help Show help information.
1167+
1168+
""")
1169+
1170+
}
1171+
10981172
struct CustomOptionWithDefault: ParsableCommand {
10991173
@Option(help: "An option with enumerable values and a custom default.")
11001174
var opt: OptionValues = .red

0 commit comments

Comments
 (0)