Skip to content

Commit b2975c4

Browse files
committed
More palatable async command support
This approach removes the downsides of the parallel `AsyncParsableCommand` protocol, with the downside of a bit of boilerplate in the root type of the command tree. In this formulation, `ParsableCommand` still provides only a non-async version of `static func main`, with an async version provided by an `AsyncMain` protocol. Instead of marking the root type of a command hierarchy with @main, a developer will need to instead create a type that conforms to AsyncMain and specifies the root command, like so: @main enum Main: AsyncMain { typealias Command = <#command#> } The command hierarchy can then include async and non-async commands at any level, including the root.
1 parent 52bff59 commit b2975c4

File tree

2 files changed

+20
-64
lines changed

2 files changed

+20
-64
lines changed

Sources/ArgumentParser/Parsable Types/AsyncParsableCommand.swift

Lines changed: 15 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -10,78 +10,31 @@
1010
//===----------------------------------------------------------------------===//
1111

1212
/// A type that can be executed as part of a nested tree of commands.
13-
public protocol AsyncParsableCommand: ParsableArguments {
14-
/// Runs this command.
15-
///
16-
/// After implementing this method, you can run your command-line
17-
/// application by calling the static `main()` method on the root type.
18-
/// This method has a default implementation that prints help text
19-
/// for this command.
13+
public protocol AsyncParsableCommand: ParsableCommand {
2014
mutating func run() async throws
2115
}
2216

2317
extension AsyncParsableCommand {
24-
public mutating func run() async throws {
25-
throw CleanExit.helpRequest(Self.asCommand)
18+
public mutating func run() throws {
19+
throw CleanExit.helpRequest(self)
2620
}
2721
}
2822

29-
// MARK: - API
30-
31-
extension AsyncParsableCommand {
32-
/// Parses an instance of this type, or one of its subcommands, from
33-
/// command-line arguments.
34-
///
35-
/// - Parameter arguments: An array of arguments to use for parsing. If
36-
/// `arguments` is `nil`, this uses the program's command-line arguments.
37-
/// - Returns: A new instance of this type, one of its subcommands, or a
38-
/// command type internal to the `ArgumentParser` library.
39-
public static func parseAsRoot(
40-
_ arguments: [String]? = nil
41-
) throws -> AsyncParsableCommand {
42-
var parser = CommandParser(self.asCommand)
43-
let arguments = arguments ?? Array(CommandLine.arguments.dropFirst())
44-
return try parser.parse(arguments: arguments).get() as! AsyncParsableCommand
45-
}
46-
47-
/// Returns the text of the help screen for the given subcommand of this
48-
/// command.
49-
///
50-
/// - Parameters:
51-
/// - subcommand: The subcommand to generate the help screen for.
52-
/// `subcommand` must be declared in the subcommand tree of this
53-
/// command.
54-
/// - columns: The column width to use when wrapping long line in the
55-
/// help screen. If `columns` is `nil`, uses the current terminal
56-
/// width, or a default value of `80` if the terminal width is not
57-
/// available.
58-
public static func helpMessage(
59-
for subcommand: ParsableCommand.Type,
60-
columns: Int? = nil
61-
) -> String {
62-
let stack = CommandParser(self.asCommand).commandStack(for: subcommand)
63-
return HelpGenerator(commandStack: stack).rendered(screenWidth: columns)
64-
}
23+
public protocol AsyncMain {
24+
associatedtype Command: ParsableCommand
25+
}
6526

66-
/// Parses an instance of this type, or one of its subcommands, from
67-
/// the given arguments and calls its `run()` method, exiting with a
68-
/// relevant error message if necessary.
69-
///
70-
/// - Parameter arguments: An array of arguments to use for parsing. If
71-
/// `arguments` is `nil`, this uses the program's command-line arguments.
72-
public static func main(_ arguments: [String]?) async {
27+
extension AsyncMain {
28+
public static func main() async {
7329
do {
74-
var command = try parseAsRoot(arguments)
75-
try await command.run()
30+
var command = try Command.parseAsRoot()
31+
if var command = command as? AsyncParsableCommand {
32+
try await command.run()
33+
} else {
34+
try command.run()
35+
}
7636
} catch {
77-
exit(withError: error)
37+
Command.exit(withError: error)
7838
}
7939
}
80-
81-
/// Parses an instance of this type, or one of its subcommands, from
82-
/// command-line arguments and calls its `run()` method, exiting with a
83-
/// relevant error message if necessary.
84-
public static func main() async {
85-
await self.main(nil)
86-
}
8740
}

Tools/changelog-authors/ChangelogAuthors.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ import Foundation
1414

1515
// MARK: Command
1616

17-
@main
18-
struct ChangelogAuthors: AsyncParsableCommand {
17+
struct ChangelogAuthors: AsyncParsableCommand {
1918
static var configuration: CommandConfiguration {
2019
CommandConfiguration(
2120
abstract: "A helper tool for generating author info for the changelog.",
@@ -100,3 +99,7 @@ struct ChangelogAuthors: AsyncParsableCommand {
10099
print(references(for: authors))
101100
}
102101
}
102+
103+
@main enum Main: AsyncMain {
104+
typealias Command = ChangelogAuthors
105+
}

0 commit comments

Comments
 (0)