Skip to content

Commit cffff99

Browse files
committed
Display possible option values in help
Updates HelpGenerator to print possible value options as a suffix to the user defined help string. In practice this looks like: > Set diagnostic level to report public declarations without an > availability attribute. (values: error, warn, ignore; default: warn)
1 parent ecac862 commit cffff99

File tree

8 files changed

+143
-46
lines changed

8 files changed

+143
-46
lines changed

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,15 @@ Add new items at the end of the relevant section under **Unreleased**.
66

77
## [Unreleased]
88

9-
*No changes yet.*
9+
### Additions
10+
11+
- Help screens now include possible options for `ExpressibleByArgument` types
12+
with non empty `allValueStrings`. Types also conforming to `CaseIterable` do
13+
not need to manually implement `allValueStrings`, instead it is derived from
14+
`allCases`. ([#594])
15+
16+
<!-- Add: "Don't remove nested option group titles (#592)" -->
17+
<!-- Add: "Document ability to skip unknown parameters (#572)" -->
1018

1119
---
1220

@@ -891,6 +899,7 @@ This changelog's format is based on [Keep a Changelog](https://keepachangelog.co
891899
[#554]: https://github.com/apple/swift-argument-parser/pull/554
892900
[#574]: https://github.com/apple/swift-argument-parser/pull/574
893901
[#579]: https://github.com/apple/swift-argument-parser/pull/579
902+
[#579]: https://github.com/apple/swift-argument-parser/pull/594
894903

895904
<!-- Link references for contributors -->
896905

Sources/ArgumentParser/Documentation.docc/Articles/CustomizingHelp.md

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,82 @@ OPTIONS:
7474
-h, --help Show help information.
7575
```
7676

77+
## Enumerating Possible Values
78+
79+
When an argument or option has a fixed set of possible values, listing these values in the help screen can simplify use of your tool. You can customize the displayed set of values for custom ``ExpressibleByArgument`` types by implementing ``ExpressibleByArgument/allValueStrings``. Despite the name, ``ExpressibleByArgument/allValueStrings`` does _not_ need to be an exhaustive list of possible values.
80+
81+
```swift
82+
enum Fruit: String, ExpressibleByArgument {
83+
case apple
84+
case banana
85+
case coconut
86+
case dragonFruit = "dragon-fruit"
87+
88+
static var allValueStrings: [String] {
89+
["apple", "banana", "coconut", "dragon-fruit"]
90+
}
91+
}
92+
93+
struct FruitStore: ParsableCommand {
94+
@Argument(help: "The fruit to purchase")
95+
var fruit: Fruit
96+
97+
@Option(help: "The number of fruit to purchase")
98+
var quantity: Int = 1
99+
}
100+
```
101+
102+
The help screen includes the list of values in the description of the `<fruit>` argument:
103+
104+
```
105+
USAGE: fruit-store <fruit> [--quantity <quantity>]
106+
107+
ARGUMENTS:
108+
<fruit> The fruit to purchase (values: apple, banana,
109+
coconut, dragon-fruit)
110+
111+
OPTIONS:
112+
--quantity <quantity> The number of fruit to purchase (default: 1)
113+
-h, --help Show help information.
114+
```
115+
116+
### Deriving Possible Values
117+
118+
ExpressibleByArgument types that conform to ``CaseIterable`` do not need to manually specify ``ExpressibleByArgument/allValueStrings``. Instead, a list of possible values is derived from the type's cases, as in this updated example:
119+
120+
```swift
121+
enum Fruit: String, CaseIterable, ExpressibleByArgument {
122+
case apple
123+
case banana
124+
case coconut
125+
case dragonFruit = "dragon-fruit"
126+
}
127+
128+
struct FruitStore: ParsableCommand {
129+
@Argument(help: "The fruit to purchase")
130+
var fruit: Fruit
131+
132+
@Option(help: "The number of fruit to purchase")
133+
var quantity: Int = 1
134+
}
135+
```
136+
137+
The help screen still contains all the possible values.
138+
139+
```
140+
USAGE: fruit-store <fruit> [--quantity <quantity>]
141+
142+
ARGUMENTS:
143+
<fruit> The fruit to purchase (values: apple, banana,
144+
coconut, dragon-fruit)
145+
146+
OPTIONS:
147+
--quantity <quantity> The number of fruit to purchase (default: 1)
148+
-h, --help Show help information.
149+
```
150+
151+
For an ``ExpressibleByArgument`` and ``CaseIterable`` type with many cases, you may still want to implement ``ExpressibleByArgument/allValueStrings`` to avoid an overly long list of values appearing in the help screen. For these types it is recommended to include the most common possible values.
152+
77153
## Controlling Argument Visibility
78154

79155
You can specify the visibility of any argument, option, or flag.
@@ -144,10 +220,7 @@ OPTIONS:
144220

145221
## Grouping Arguments in the Help Screen
146222

147-
When you provide a title in an `@OptionGroup` declaration, that type's
148-
properties are grouped together under your title in the help screen.
149-
For example, this command bundles similar arguments together under a
150-
"Build Options" title:
223+
When you provide a title in an `@OptionGroup` declaration, that type's properties are grouped together under your title in the help screen. For example, this command bundles similar arguments together under a "Build Options" title:
151224

152225
```swift
153226
struct BuildOptions: ParsableArguments {

Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ public protocol ExpressibleByArgument {
2020
var defaultValueDescription: String { get }
2121

2222
/// An array of all possible strings that can convert to a value of this
23-
/// type.
23+
/// type, for display in the help screen.
2424
///
25-
/// The default implementation of this property returns an empty array.
25+
/// The default implementation of this property returns an empty array. If the
26+
/// conforming type is also `CaseIterable`, the default implementation returns
27+
/// an array with a value for each case.
2628
static var allValueStrings: [String] { get }
2729

2830
/// The completion kind to use for options or arguments of this type that

Sources/ArgumentParser/Usage/HelpGenerator.swift

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,23 @@ internal struct HelpGenerator {
151151
assert(arg.help.visibility.isAtLeastAsVisible(as: visibility))
152152

153153
let synopsis: String
154-
let description: String
155-
154+
let abstract: String
155+
156+
let allValueStrings = arg.help.allValueStrings.filter { !$0.isEmpty }
157+
let defaultValue = arg.help.defaultValue ?? ""
158+
159+
let allAndDefaultValues: String
160+
switch (!allValueStrings.isEmpty, !defaultValue.isEmpty) {
161+
case (false, false):
162+
allAndDefaultValues = ""
163+
case (true, false):
164+
allAndDefaultValues = "(values: \(allValueStrings.joined(separator: ", ")))"
165+
case (false, true):
166+
allAndDefaultValues = "(default: \(defaultValue))"
167+
case (true, true):
168+
allAndDefaultValues = "(values: \(allValueStrings.joined(separator: ", ")); default: \(defaultValue))"
169+
}
170+
156171
if arg.help.isComposite {
157172
// If this argument is composite, we have a group of arguments to
158173
// output together.
@@ -164,32 +179,24 @@ internal struct HelpGenerator {
164179
.lazy
165180
.map { $0.synopsisForHelp }
166181
.joined(separator: "/")
167-
168-
let defaultValue = arg.help.defaultValue
169-
.map { "(default: \($0))" } ?? ""
170-
171-
let descriptionString = groupedArgs
182+
abstract = groupedArgs
172183
.lazy
173184
.map { $0.help.abstract }
174-
.first { !$0.isEmpty }
175-
176-
description = [descriptionString, defaultValue]
177-
.lazy
178-
.compactMap { $0 }
179-
.filter { !$0.isEmpty }
180-
.joined(separator: " ")
185+
.first { !$0.isEmpty } ?? ""
181186
} else {
182187
synopsis = arg.synopsisForHelp
183-
184-
let defaultValue = arg.help.defaultValue.flatMap { $0.isEmpty ? nil : "(default: \($0))" }
185-
description = [arg.help.abstract, defaultValue]
186-
.lazy
187-
.compactMap { $0 }
188-
.filter { !$0.isEmpty }
189-
.joined(separator: " ")
188+
abstract = arg.help.abstract
190189
}
191190

192-
let element = Section.Element(label: synopsis, abstract: description, discussion: arg.help.discussion)
191+
let description = [abstract, allAndDefaultValues]
192+
.lazy
193+
.filter { !$0.isEmpty }
194+
.joined(separator: " ")
195+
196+
let element = Section.Element(
197+
label: synopsis,
198+
abstract: description,
199+
discussion: arg.help.discussion)
193200
switch (arg.kind, arg.help.parentTitle) {
194201
case (_, let sectionTitle) where !sectionTitle.isEmpty:
195202
if !titledSections.keys.contains(sectionTitle) {

Tests/ArgumentParserExampleTests/MathExampleTests.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ final class MathExampleTests: XCTestCase {
7777
<values> A group of floating-point values to operate on.
7878
7979
OPTIONS:
80-
--kind <kind> The kind of average to provide. (default: mean)
80+
--kind <kind> The kind of average to provide. (values: mean,
81+
median, mode; default: mean)
8182
--version Show the version.
8283
-h, --help Show help information.
8384
"""

Tests/ArgumentParserUnitTests/HelpGenerationTests+AtArgument.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ extension HelpGenerationTests {
251251
USAGE: bare-no-default <arg0>
252252
253253
ARGUMENTS:
254-
<arg0> example
254+
<arg0> example (values: A())
255255
256256
OPTIONS:
257257
-h, --help Show help information.
@@ -267,7 +267,7 @@ extension HelpGenerationTests {
267267
USAGE: bare-default [<arg0>]
268268
269269
ARGUMENTS:
270-
<arg0> example (default: A())
270+
<arg0> example (values: A(); default: A())
271271
272272
OPTIONS:
273273
-h, --help Show help information.
@@ -283,7 +283,7 @@ extension HelpGenerationTests {
283283
USAGE: optional-no-default [<arg0>]
284284
285285
ARGUMENTS:
286-
<arg0> example
286+
<arg0> example (values: A())
287287
288288
OPTIONS:
289289
-h, --help Show help information.
@@ -299,7 +299,7 @@ extension HelpGenerationTests {
299299
USAGE: optional-default-nil [<arg0>]
300300
301301
ARGUMENTS:
302-
<arg0> example
302+
<arg0> example (values: A())
303303
304304
OPTIONS:
305305
-h, --help Show help information.
@@ -315,7 +315,7 @@ extension HelpGenerationTests {
315315
USAGE: array-no-default <arg0> ...
316316
317317
ARGUMENTS:
318-
<arg0> example
318+
<arg0> example (values: A())
319319
320320
OPTIONS:
321321
-h, --help Show help information.
@@ -331,7 +331,7 @@ extension HelpGenerationTests {
331331
USAGE: array-default-empty [<arg0> ...]
332332
333333
ARGUMENTS:
334-
<arg0> example
334+
<arg0> example (values: A())
335335
336336
OPTIONS:
337337
-h, --help Show help information.
@@ -347,7 +347,7 @@ extension HelpGenerationTests {
347347
USAGE: array-default [<arg0> ...]
348348
349349
ARGUMENTS:
350-
<arg0> example (default: A())
350+
<arg0> example (values: A(); default: A())
351351
352352
OPTIONS:
353353
-h, --help Show help information.

Tests/ArgumentParserUnitTests/HelpGenerationTests+AtOption.swift

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ extension HelpGenerationTests {
155155
enum AtOptionEBA {
156156
// ExpressibleByArgument
157157
struct A: ExpressibleByArgument {
158+
static var allValueStrings: [String] { ["A()"] }
159+
var defaultValueDescription: String { "A()" }
158160
init() { }
159161
init?(argument: String) { self.init() }
160162
}
@@ -206,7 +208,7 @@ extension HelpGenerationTests {
206208
USAGE: bare-no-default --arg0 <arg0>
207209
208210
OPTIONS:
209-
--arg0 <arg0> example
211+
--arg0 <arg0> example (values: A())
210212
-h, --help Show help information.
211213
212214
""")
@@ -217,7 +219,7 @@ extension HelpGenerationTests {
217219
USAGE: bare-default [--arg0 <arg0>]
218220
219221
OPTIONS:
220-
--arg0 <arg0> example (default: A())
222+
--arg0 <arg0> example (values: A(); default: A())
221223
-h, --help Show help information.
222224
223225
""")
@@ -228,7 +230,7 @@ extension HelpGenerationTests {
228230
USAGE: optional-no-default [--arg0 <arg0>]
229231
230232
OPTIONS:
231-
--arg0 <arg0> example
233+
--arg0 <arg0> example (values: A())
232234
-h, --help Show help information.
233235
234236
""")
@@ -239,7 +241,7 @@ extension HelpGenerationTests {
239241
USAGE: optional-default-nil [--arg0 <arg0>]
240242
241243
OPTIONS:
242-
--arg0 <arg0> example
244+
--arg0 <arg0> example (values: A())
243245
-h, --help Show help information.
244246
245247
""")
@@ -250,7 +252,7 @@ extension HelpGenerationTests {
250252
USAGE: array-no-default --arg0 <arg0> ...
251253
252254
OPTIONS:
253-
--arg0 <arg0> example
255+
--arg0 <arg0> example (values: A())
254256
-h, --help Show help information.
255257
256258
""")
@@ -261,7 +263,7 @@ extension HelpGenerationTests {
261263
USAGE: array-default-empty [--arg0 <arg0> ...]
262264
263265
OPTIONS:
264-
--arg0 <arg0> example
266+
--arg0 <arg0> example (values: A())
265267
-h, --help Show help information.
266268
267269
""")
@@ -272,7 +274,7 @@ extension HelpGenerationTests {
272274
USAGE: array-default [--arg0 <arg0> ...]
273275
274276
OPTIONS:
275-
--arg0 <arg0> example (default: A())
277+
--arg0 <arg0> example (values: A(); default: A())
276278
-h, --help Show help information.
277279
278280
""")
@@ -283,6 +285,8 @@ extension HelpGenerationTests {
283285
enum AtOptionEBATransform {
284286
// ExpressibleByArgument with Transform
285287
struct A: ExpressibleByArgument {
288+
static var allValueStrings: [String] { ["A()"] }
289+
var defaultValueDescription: String { "A()" }
286290
init() { }
287291
init?(argument: String) { self.init() }
288292
}

Tests/ArgumentParserUnitTests/HelpGenerationTests.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,9 @@ extension HelpGenerationTests {
211211
--degree <degree> Your degree.
212212
--directory <directory> Directory. (default: current directory)
213213
--manual <manual> Manual Option. (default: default-value)
214-
--unspecial <unspecial> Unspecialized Synthesized (default: 0)
215-
--special <special> Specialized Synthesized (default: Apple)
214+
--unspecial <unspecial> Unspecialized Synthesized (values: 0, 1; default: 0)
215+
--special <special> Specialized Synthesized (values: Apple, Banana;
216+
default: Apple)
216217
-h, --help Show help information.
217218
218219
""")

0 commit comments

Comments
 (0)