Skip to content

Commit

Permalink
Add ParsableArguments/Command.usageString (#634)
Browse files Browse the repository at this point in the history
Adds a new API to ParsableArguments and ParsableCommand for getting the
usage string. This allows clients to use argument-parser in a more
piecemeal way to construct their own error screens.
  • Loading branch information
rauhul committed May 1, 2024
1 parent 2b96293 commit cfac15f
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 22 deletions.
12 changes: 12 additions & 0 deletions Sources/ArgumentParser/Parsable Types/ParsableArguments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,18 @@ extension ParsableArguments {
exit(withError: error)
}
}

/// Returns the usage text for this type.
///
/// - Parameters:
/// - includeHidden: Include hidden help information in the generated
/// message.
/// - Returns: The usage text for this type.
public static func usageString(
includeHidden: Bool = false
) -> String {
HelpGenerator(self, visibility: includeHidden ? .hidden : .default).usage
}
}

/// Unboxes the given value if it is a `nil` value.
Expand Down
16 changes: 16 additions & 0 deletions Sources/ArgumentParser/Parsable Types/ParsableCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,22 @@ extension ParsableCommand {
.rendered(screenWidth: columns)
}

/// Returns the usage text for the given subcommand of this command.
///
/// - Parameters:
/// - includeHidden: Include hidden help information in the generated
/// message.
/// - Returns: The usage text for this type.
public static func usageString(
for subcommand: ParsableCommand.Type,
includeHidden: Bool = false
) -> String {
HelpGenerator(
commandStack: CommandParser(self).commandStack(for: subcommand),
visibility: includeHidden ? .hidden : .default)
.usage
}

/// Executes this command, or one of its subcommands, with the given
/// arguments.
///
Expand Down
143 changes: 121 additions & 22 deletions Tests/ArgumentParserUnitTests/HelpGenerationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ extension HelpGenerationTests {
static let configuration = CommandConfiguration(
commandName: "parserBug",
subcommands: [Sub.self])

struct CommonOptions: ParsableCommand {
@Flag(help: "example flag")
var example: Bool = false
Expand All @@ -689,12 +689,12 @@ extension HelpGenerationTests {
struct Sub: ParsableCommand {
@OptionGroup()
var commonOptions: CommonOptions

@Argument(help: "Non-mandatory argument")
var argument: String?
}
}

func testIssue278() {
AssertHelp(.default, for: ParserBug.Sub.self, root: ParserBug.self, equals: """
USAGE: parserBug sub [--example] [<argument>]
Expand All @@ -708,42 +708,87 @@ extension HelpGenerationTests {
""")
}
}

extension HelpGenerationTests {
struct NonCustomUsage: ParsableCommand {
struct ExampleSubcommand: ParsableCommand {
static let configuration = CommandConfiguration()
@Argument var output: String
}

static let configuration = CommandConfiguration(
subcommands: [ExampleSubcommand.self])

@Argument var file: String
@Flag var verboseMode = false
}

struct CustomUsageShort: ParsableCommand {
static var configuration: CommandConfiguration {
CommandConfiguration(usage: """
static let configuration = CommandConfiguration(
usage: """
example [--verbose] <file-name>
""")
}


@Argument var file: String
@Flag var verboseMode = false
}

struct CustomUsageLong: ParsableCommand {
static var configuration: CommandConfiguration {
CommandConfiguration(usage: """
static let configuration = CommandConfiguration(
usage: """
example <file-name>
example --verbose <file-name>
example --help
""")
}


@Argument var file: String
@Flag var verboseMode = false
}

struct CustomUsageHidden: ParsableCommand {
static var configuration: CommandConfiguration {
CommandConfiguration(usage: "")
}
static let configuration = CommandConfiguration(usage: "")

@Argument var file: String
@Flag var verboseMode = false
}

func testCustomUsageHelp() {
XCTAssertEqual(CustomUsageShort.helpMessage(columns: 80), """
func test_usageCustomization_helpMessage() {
AssertEqualStrings(
actual: NonCustomUsage.helpMessage(columns: 80),
expected: """
USAGE: non-custom-usage <file> [--verbose-mode] <subcommand>
ARGUMENTS:
<file>
OPTIONS:
--verbose-mode
-h, --help Show help information.
SUBCOMMANDS:
example-subcommand
See 'non-custom-usage help <subcommand>' for detailed help.
""")

AssertEqualStrings(
actual: NonCustomUsage.helpMessage(
for: NonCustomUsage.ExampleSubcommand.self, columns: 80),
expected: """
USAGE: non-custom-usage example-subcommand <output>
ARGUMENTS:
<output>
OPTIONS:
-h, --help Show help information.
""")

AssertEqualStrings(
actual: CustomUsageShort.helpMessage(columns: 80),
expected: """
USAGE: example [--verbose] <file-name>
ARGUMENTS:
Expand All @@ -755,7 +800,9 @@ extension HelpGenerationTests {
""")

XCTAssertEqual(CustomUsageLong.helpMessage(columns: 80), """
AssertEqualStrings(
actual: CustomUsageLong.helpMessage(columns: 80),
expected: """
USAGE: example <file-name>
example --verbose <file-name>
example --help
Expand All @@ -769,7 +816,9 @@ extension HelpGenerationTests {
""")

XCTAssertEqual(CustomUsageHidden.helpMessage(columns: 80), """
AssertEqualStrings(
actual: CustomUsageHidden.helpMessage(columns: 80),
expected: """
ARGUMENTS:
<file>
Expand All @@ -780,24 +829,74 @@ extension HelpGenerationTests {
""")
}

func testCustomUsageError() {
XCTAssertEqual(CustomUsageShort.fullMessage(for: ValidationError("Test")), """
func test_usageCustomization_fullMessage() {
AssertEqualStrings(
actual: NonCustomUsage.fullMessage(for: ValidationError("Test")),
expected: """
Error: Test
Usage: non-custom-usage <file> [--verbose-mode] <subcommand>
See 'non-custom-usage --help' for more information.
""")

AssertEqualStrings(
actual: CustomUsageShort.fullMessage(for: ValidationError("Test")),
expected: """
Error: Test
Usage: example [--verbose] <file-name>
See 'custom-usage-short --help' for more information.
""")
XCTAssertEqual(CustomUsageLong.fullMessage(for: ValidationError("Test")), """

AssertEqualStrings(
actual: CustomUsageLong.fullMessage(for: ValidationError("Test")),
expected: """
Error: Test
Usage: example <file-name>
example --verbose <file-name>
example --help
See 'custom-usage-long --help' for more information.
""")
XCTAssertEqual(CustomUsageHidden.fullMessage(for: ValidationError("Test")), """

AssertEqualStrings(
actual: CustomUsageHidden.fullMessage(for: ValidationError("Test")),
expected: """
Error: Test
See 'custom-usage-hidden --help' for more information.
""")
}

func test_usageCustomization_usageString() {
AssertEqualStrings(
actual: NonCustomUsage.usageString(),
expected: """
non-custom-usage <file> [--verbose-mode] <subcommand>
""")

AssertEqualStrings(
actual: NonCustomUsage.usageString(
for: NonCustomUsage.ExampleSubcommand.self),
expected: """
non-custom-usage example-subcommand <output>
""")

AssertEqualStrings(
actual: CustomUsageShort.usageString(),
expected: """
example [--verbose] <file-name>
""")

AssertEqualStrings(
actual: CustomUsageLong.usageString(),
expected: """
example <file-name>
example --verbose <file-name>
example --help
""")

AssertEqualStrings(
actual: CustomUsageHidden.usageString(),
expected: """
""")
}
}

extension HelpGenerationTests {
Expand Down

0 comments on commit cfac15f

Please sign in to comment.