Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,29 @@
/// -h, --help Show help information
/// ```
///
/// In any case where the argument type is not `EnumerableOptionValue`, the
/// default implementation will use the `.staticText` case and will print a
/// block of discussion text.
/// Arrays of enumerable types are also supported and will display the same
/// enumerated format:
///
/// ```swift
/// @Option var colors: [Color] = [.red, .blue]
/// ```
///
/// The printed usage would look like the following:
///
/// ```
/// USAGE: example [--colors <colors> ...]
///
/// OPTIONS:
/// --colors <colors> (default: red, blue)
/// red - A red color.
/// blue - A blue color.
/// yellow - A yellow color.
/// -h, --help Show help information
/// ```
///
/// In any case where the argument type is not `EnumerableOptionValue` or an
/// array of `EnumerableOptionValue`, the default implementation will use the
/// `.staticText` case and will print a block of discussion text.
enum ArgumentDiscussion {
case staticText(String)
case enumerated(preamble: String? = nil, any ExpressibleByArgument.Type)
Expand Down
26 changes: 24 additions & 2 deletions Sources/ArgumentParser/Parsable Properties/Option.swift
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,11 @@ extension Option {
/// var chars: [Character] = []
/// ```
///
/// If the element type conforms to `ExpressibleByArgument` and has enumerable
/// value descriptions (via `defaultValueDescription`), the help output will
/// display each possible value with its description, similar to single
/// enumerable options.
///
/// - Parameters:
/// - wrappedValue: A default value to use for this property, provided
/// implicitly by the compiler during property wrapper initialization.
Expand All @@ -745,7 +750,13 @@ extension Option {
container: Array<T>.self,
key: key,
kind: .name(key: key, specification: name),
help: help,
help: .init(
help?.abstract ?? "",
discussion: help?.discussion,
valueName: help?.valueName,
visibility: help?.visibility ?? .default,
argumentType: T.self
),
parsingStrategy: parsingStrategy.base,
initial: wrappedValue,
completion: completion)
Expand All @@ -765,6 +776,11 @@ extension Option {
/// var chars: [Character]
/// ```
///
/// If the element type conforms to `ExpressibleByArgument` and has enumerable
/// value descriptions (via `defaultValueDescription`), the help output will
/// display each possible value with its description, similar to single
/// enumerable options.
///
/// - Parameters:
/// - name: A specification for what names are allowed for this option.
/// - parsingStrategy: The behavior to use when parsing the elements for
Expand All @@ -784,7 +800,13 @@ extension Option {
container: Array<T>.self,
key: key,
kind: .name(key: key, specification: name),
help: help,
help: .init(
help?.abstract ?? "",
discussion: help?.discussion,
valueName: help?.valueName,
visibility: help?.visibility ?? .default,
argumentType: T.self
),
parsingStrategy: parsingStrategy.base,
initial: nil,
completion: completion)
Expand Down
7 changes: 6 additions & 1 deletion Sources/ArgumentParser/Parsing/ArgumentDefinition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,12 @@ where Element: ExpressibleByArgument {
guard !initial.isEmpty else { return nil }
return initial
.lazy
.map { $0.defaultValueDescription }
.map { element in
if let element = element as? (any CaseIterable & RawRepresentable) {
return String(describing: element.rawValue)
}
return element.defaultValueDescription
}
.joined(separator: ", ")
}
}
74 changes: 74 additions & 0 deletions Tests/ArgumentParserUnitTests/HelpGenerationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,80 @@ extension HelpGenerationTests {
""")
}

struct CustomOptionAsListWithSingleDefaultValue: ParsableCommand {
@Option(
help: "An option with enumerable values.")
var opt: [OptionValues] = [.red]
}

func testEnumerableOptionAsListWithSingleDefault() {
AssertHelp(
.default,
for: CustomOptionAsListWithSingleDefaultValue.self,
columns: 100,
equals: """
USAGE: custom-option-as-list-with-single-default-value [--opt <opt> ...]

OPTIONS:
--opt <opt> An option with enumerable values. (default: red)
blue - The color of the sky.
red - The color of a rose.
yellow - The color of the sun.
-h, --help Show help information.

""")
}

struct CustomOptionAsListWithMultipleDefaultValue: ParsableCommand {
@Option(
help: "An option with multiple enumerable values.")
var opt: [OptionValues] = [.red, .blue]
}

func testEnumerableOptionAsListWithMultipleDefault() {
AssertHelp(
.default,
for: CustomOptionAsListWithMultipleDefaultValue.self,
columns: 100,
equals: """
USAGE: custom-option-as-list-with-multiple-default-value [--opt <opt> ...]

OPTIONS:
--opt <opt> An option with multiple enumerable values. (default: red, blue)
blue - The color of the sky.
red - The color of a rose.
yellow - The color of the sun.
-h, --help Show help information.

""")

}

struct CustomOptionAsListWithEmptyArrayAsDefault: ParsableCommand {
@Option(
help: "An option with default value set to empty array.")
var opt: [OptionValues] = []
}

func testEnumerableOptionAsListWithEmptyArrayAsDefault() {
AssertHelp(
.default,
for: CustomOptionAsListWithEmptyArrayAsDefault.self,
columns: 100,
equals: """
USAGE: custom-option-as-list-with-empty-array-as-default [--opt <opt> ...]

OPTIONS:
--opt <opt> An option with default value set to empty array.
blue - The color of the sky.
red - The color of a rose.
yellow - The color of the sun.
-h, --help Show help information.

""")

}

struct CustomOptionWithDefault: ParsableCommand {
@Option(help: "An option with enumerable values and a custom default.")
var opt: OptionValues = .red
Expand Down