Skip to content

Commit 501bf60

Browse files
authored
Display help when no arguments results in error (#140)
If a command cannot successfully run with zero arguments, print the error and the full help message instead of the short usage message. This closes #134.
1 parent 7fc05c0 commit 501bf60

File tree

6 files changed

+41
-2
lines changed

6 files changed

+41
-2
lines changed

Sources/ArgumentParser/Parsing/CommandParser.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ extension CommandParser {
214214
} catch let error as CommandError {
215215
return .failure(error)
216216
} catch let error as ParserError {
217+
let error = arguments.isEmpty ? ParserError.noArguments(error) : error
217218
return .failure(CommandError(commandStack: commandStack, parserError: error))
218219
} catch is HelpRequested {
219220
return .success(HelpCommand(commandStack: commandStack))

Sources/ArgumentParser/Parsing/ParserError.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ enum ParserError: Error {
2828
case unableToParseValue(InputOrigin, Name?, String, forKey: InputKey, originalError: Error? = nil)
2929
case missingSubcommand
3030
case userValidationError(Error)
31+
case noArguments(Error)
3132
}
3233

3334
/// These are errors used internally to the parsing, and will not be exposed to the help generation.

Sources/ArgumentParser/Usage/MessageInfo.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ enum MessageInfo {
8080
self = .other(message: String(describing: error), exitCode: EXIT_FAILURE)
8181
}
8282
} else if let parserError = parserError {
83+
let usage: String = {
84+
guard case ParserError.noArguments = parserError else { return usage }
85+
return "\n" + HelpGenerator(commandStack: [type.asCommand]).rendered
86+
}()
8387
let message = ArgumentSet(commandStack.last!).helpMessage(for: parserError)
8488
self = .validation(message: message, usage: usage)
8589
} else {

Sources/ArgumentParser/Usage/UsageGenerator.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,15 @@ extension ErrorMessageGenerator {
212212
default:
213213
return String(describing: error)
214214
}
215+
case .noArguments(let error):
216+
switch error {
217+
case let error as ParserError:
218+
return ErrorMessageGenerator(arguments: self.arguments, error: error).makeErrorMessage()
219+
case let error as LocalizedError:
220+
return error.errorDescription
221+
default:
222+
return String(describing: error)
223+
}
215224
}
216225
}
217226
}

Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ fileprivate struct Foo: ParsableArguments {
3131
static var usageString: String = """
3232
Usage: foo [--count <count>] [<names> ...] [--version] [--throw]
3333
"""
34+
35+
static var helpString: String = """
36+
USAGE: foo [--count <count>] [<names> ...] [--version] [--throw]
37+
38+
ARGUMENTS:
39+
<names>
40+
41+
OPTIONS:
42+
--count <count>
43+
--version
44+
--throw
45+
-h, --help Show help information.
46+
"""
3447

3548
@Option()
3649
var count: Int?
@@ -106,7 +119,9 @@ extension ValidationEndToEndTests {
106119
AssertErrorMessage(Foo.self, [], "Must specify at least one name.")
107120
AssertFullErrorMessage(Foo.self, [], """
108121
Error: Must specify at least one name.
109-
\(Foo.usageString)
122+
123+
\(Foo.helpString)
124+
110125
""")
111126

112127
AssertErrorMessage(Foo.self, ["--count", "3", "Joe"], """

Tests/ArgumentParserExampleTests/RepeatExampleTests.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,16 @@ final class RepeatExampleTests: XCTestCase {
4747
command: "repeat",
4848
expected: """
4949
Error: Missing expected argument '<phrase>'
50-
Usage: repeat [--count <count>] [--include-counter] <phrase>
50+
51+
USAGE: repeat [--count <count>] [--include-counter] <phrase>
52+
53+
ARGUMENTS:
54+
<phrase> The phrase to repeat.
55+
56+
OPTIONS:
57+
--count <count> The number of times to repeat 'phrase'.
58+
--include-counter Include a counter with each repetition.
59+
-h, --help Show help information.
5160
""",
5261
exitCode: .validationFailure)
5362

0 commit comments

Comments
 (0)