Skip to content

parseAllAsRoot() for list of parsed commands #797

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
17 changes: 17 additions & 0 deletions Sources/ArgumentParser/Parsable Types/ParsableCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,23 @@ extension ParsableCommand {
return try parser.parse(arguments: arguments).get()
}

/// Parses an instance of this type, from command-line arguments
/// and provide all of the commands in order from this one to possible
/// subcommands to a final leaf command to be run.
///
/// - Parameter arguments: An array of arguments to use for parsing. If
/// `arguments` is `nil`, this uses the program's command-line arguments.
/// - Returns: A new instance of this type, one of its subcommands, or a
/// command type internal to the `ArgumentParser` library.
/// - Throws: If parsing fails.
public static func parseAllAsRoot(
_ arguments: [String]? = nil
) throws -> [ParsableCommand] {
var parser = CommandParser(self)
let arguments = arguments ?? Array(CommandLine._staticArguments.dropFirst())
return try parser.parseAll(arguments: arguments).get()
}

/// Returns the text of the help screen for the given subcommand of this
/// command.
///
Expand Down
39 changes: 28 additions & 11 deletions Sources/ArgumentParser/Parsing/CommandParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,12 @@ extension CommandParser {
}
}

/// Returns the last parsed value if there are no remaining unused arguments.
/// Returns the parsed values if there are no remaining unused arguments.
///
/// If there are remaining arguments or if no commands have been parsed,
/// this throws an error.
fileprivate func extractLastParsedValue(_ split: SplitArguments) throws
-> ParsableCommand
fileprivate func extractParsedValues(_ split: SplitArguments) throws
-> [ParsableCommand]
{
try checkForBuiltInFlags(split)

Expand All @@ -172,12 +172,13 @@ extension CommandParser {
}

guard
let lastCommand = decodedArguments.lazy.compactMap({ $0.command }).last
case let parsedCommands = decodedArguments.lazy.compactMap({ $0.command }),
parsedCommands.count > 0
else {
throw ParserError.invalidState
}

return lastCommand
return [ParsableCommand](parsedCommands)
}

/// Extracts the current command from `split`, throwing if decoding isn't
Expand Down Expand Up @@ -282,6 +283,20 @@ extension CommandParser {
mutating func parse(
arguments: [String]
) -> Result<ParsableCommand, CommandError> {
// swift-format-ignore: NeverForceUnwrap
self.parseAll(arguments: arguments).map { $0.last! }
}

/// Returns the fully-parsed matching commands for `arguments`, or an
/// appropriate error.
///
/// - Parameter arguments: The array of arguments to parse. This should not
/// include the command name as the first argument.
///
/// - Returns: The parsed commands or error.
mutating func parseAll(
arguments: [String]
) -> Result<[ParsableCommand], CommandError> {
do {
try handleCustomCompletion(arguments)
} catch let error as ParserError {
Expand All @@ -308,13 +323,13 @@ extension CommandParser {
do {
try checkForCompletionScriptRequest(&split)
try descendingParse(&split)
let result = try extractLastParsedValue(split)
let result = try extractParsedValues(split)

// HelpCommand is a valid result, but needs extra information about
// the tree from the parser to build its stack of commands.
if var helpResult = result as? HelpCommand {
if var helpResult = result.last as? HelpCommand {
try helpResult.buildCommandStack(with: self)
return .success(helpResult)
return .success([helpResult])
}
return .success(result)
} catch let error as CommandError {
Expand All @@ -325,9 +340,11 @@ extension CommandParser {
CommandError(commandStack: commandStack, parserError: error))
} catch let helpRequest as HelpRequested {
return .success(
HelpCommand(
commandStack: commandStack,
visibility: helpRequest.visibility))
[
HelpCommand(
commandStack: commandStack,
visibility: helpRequest.visibility)
])
} catch {
return .failure(
CommandError(commandStack: commandStack, parserError: .invalidState))
Expand Down
13 changes: 13 additions & 0 deletions Tests/ArgumentParserEndToEndTests/SubcommandEndToEndTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ extension SubcommandEndToEndTests {
XCTAssertEqual(a.foo.name, "Foo")
}

let allCmds = try Foo.parseAllAsRoot(["--name", "Foo", "a", "--bar", "42"])
XCTAssertEqual(allCmds.count, 2)
guard let fooCmd = (allCmds[0] as? Foo) else {
XCTFail("")
return
}
XCTAssertEqual(fooCmd.name, "Foo")
guard let aCmd = (allCmds[1] as? CommandA) else {
XCTFail("")
return
}
XCTAssertEqual(aCmd.bar, 42)

AssertParseCommand(Foo.self, Foo.self, ["--name", "Foo"]) { foo in
XCTAssertEqual(foo.name, "Foo")
}
Expand Down