Skip to content

Commit c8ec27d

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) Updates string diff printer to fix an OOB indexing bug.
1 parent d9c4ebf commit c8ec27d

File tree

7 files changed

+94
-57
lines changed

7 files changed

+94
-57
lines changed

Sources/ArgumentParser/Usage/HelpGenerator.swift

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,22 @@ internal struct HelpGenerator {
152152

153153
let synopsis: String
154154
let description: String
155-
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.
@@ -165,24 +180,20 @@ internal struct HelpGenerator {
165180
.map { $0.synopsisForHelp }
166181
.joined(separator: "/")
167182

168-
let defaultValue = arg.help.defaultValue
169-
.map { "(default: \($0))" } ?? ""
170-
171183
let descriptionString = groupedArgs
172184
.lazy
173185
.map { $0.help.abstract }
174186
.first { !$0.isEmpty }
175187

176-
description = [descriptionString, defaultValue]
188+
description = [descriptionString, allAndDefaultValues]
177189
.lazy
178190
.compactMap { $0 }
179191
.filter { !$0.isEmpty }
180192
.joined(separator: " ")
181193
} else {
182194
synopsis = arg.synopsisForHelp
183195

184-
let defaultValue = arg.help.defaultValue.flatMap { $0.isEmpty ? nil : "(default: \($0))" }
185-
description = [arg.help.abstract, defaultValue]
196+
description = [arg.help.abstract, allAndDefaultValues]
186197
.lazy
187198
.compactMap { $0 }
188199
.filter { !$0.isEmpty }

Sources/ArgumentParserTestHelpers/TestHelpers.swift

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -131,55 +131,73 @@ public func AssertParseCommand<A: ParsableCommand>(_ rootCommand: ParsableComman
131131
}
132132
}
133133

134-
public func AssertEqualStrings(actual: String, expected: String, file: StaticString = #file, line: UInt = #line) {
134+
public func AssertEqualStrings(
135+
actual: String,
136+
expected: String,
137+
file: StaticString = #file,
138+
line: UInt = #line
139+
) {
135140
// If the input strings are not equal, create a simple diff for debugging...
136141
guard actual != expected else {
137142
// Otherwise they are equal, early exit.
138143
return
139144
}
140145

141-
// Split in the inputs into lines.
142-
let actualLines = actual.split(separator: "\n", omittingEmptySubsequences: false)
143-
let expectedLines = expected.split(separator: "\n", omittingEmptySubsequences: false)
146+
let stringComparison: String
144147

145148
// If collectionDifference is available, use it to make a nicer error message.
146149
if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) {
147-
// Compute the changes between the two strings.
148-
let changes = actualLines.difference(from: expectedLines).sorted()
149-
150-
// Render the changes into a diff style string.
151-
var diff = ""
152-
var expectedLines = expectedLines[...]
153-
for change in changes {
154-
if expectedLines.startIndex < change.offset {
155-
for line in expectedLines[..<change.offset] {
156-
diff += " \(line)\n"
157-
}
158-
expectedLines = expectedLines[change.offset...].dropFirst()
159-
}
150+
let actualLines = actual.components(separatedBy: .newlines)
151+
let expectedLines = expected.components(separatedBy: .newlines)
160152

153+
let difference = actualLines.difference(from: expectedLines)
154+
155+
var result = ""
156+
157+
var insertions = [Int: String]()
158+
var removals = [Int: String]()
159+
160+
for change in difference {
161161
switch change {
162-
case .insert(_, let line, _):
163-
diff += "- \(line)\n"
164-
case .remove(_, let line, _):
165-
diff += "+ \(line)\n"
162+
case .insert(let offset, let element, _):
163+
insertions[offset] = element
164+
case .remove(let offset, let element, _):
165+
removals[offset] = element
166166
}
167167
}
168-
for line in expectedLines {
169-
diff += " \(line)\n"
168+
169+
var expectedLine = 0
170+
var actualLine = 0
171+
172+
while expectedLine < expectedLines.count || actualLine < actualLines.count {
173+
if let removal = removals[expectedLine] {
174+
result += "\(removal)\n"
175+
expectedLine += 1
176+
} else if let insertion = insertions[actualLine] {
177+
result += "+\(insertion)\n"
178+
actualLine += 1
179+
} else {
180+
result += " \(expectedLines[expectedLine])\n"
181+
expectedLine += 1
182+
actualLine += 1
183+
}
170184
}
171-
XCTFail("Strings are not equal.\n\(diff)", file: file, line: line)
185+
186+
stringComparison = result
172187
} else {
173-
XCTAssertEqual(
174-
actualLines.count,
175-
expectedLines.count,
176-
"Strings have different numbers of lines.",
177-
file: file,
178-
line: line)
179-
for (actualLine, expectedLine) in zip(actualLines, expectedLines) {
180-
XCTAssertEqual(actualLine, expectedLine, file: file, line: line)
181-
}
188+
stringComparison = """
189+
Expected:
190+
\(expected)
191+
192+
Actual:
193+
\(actual)
194+
"""
182195
}
196+
197+
XCTFail(
198+
"Actual output does not match the expected output:\n\(stringComparison)",
199+
file: file,
200+
line: line)
183201
}
184202

185203
public func AssertHelp<T: ParsableArguments>(

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/DumpHelpGenerationTests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ extension DumpHelpGenerationTests {
245245
},
246246
"serializationVersion" : 0
247247
}
248+
248249
"""
249250

250251
static let bDumpText: String = """
@@ -314,6 +315,7 @@ extension DumpHelpGenerationTests {
314315
},
315316
"serializationVersion" : 0
316317
}
318+
317319
"""
318320

319321
static let mathDumpText: String = """

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)