Skip to content

Commit d6f4e7a

Browse files
authored
Support async custom completion functions for AsyncParsableCommand via async/await (#855)
* Support async custom completion functions for AsyncParsableCommand via async/await. * Improve ParsableArguments#_errorLabel DocC. * Fix preexisting typo. Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
1 parent aeb51bc commit d6f4e7a

10 files changed

Lines changed: 451 additions & 156 deletions

File tree

Sources/ArgumentParser/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ add_library(ArgumentParser
4949
Utilities/StringExtensions.swift
5050
Utilities/SwiftExtensions.swift
5151
Utilities/Tree.swift
52-
52+
53+
Validators/AsyncCompletionsValidator.swift
5354
Validators/CodingKeyValidator.swift
5455
Validators/NonsenseFlagsValidator.swift
5556
Validators/ParsableArgumentsValidation.swift

Sources/ArgumentParser/Parsable Properties/CompletionKind.swift

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,7 @@ public struct CompletionKind {
4141
case directory
4242
case shellCommand(String)
4343
case custom(@Sendable ([String], Int, String) -> [String])
44-
#if !canImport(Dispatch)
45-
@available(*, unavailable, message: "DispatchSemaphore is unavailable")
4644
case customAsync(@Sendable ([String], Int, String) async -> [String])
47-
#else
48-
case customAsync(@Sendable ([String], Int, String) async -> [String])
49-
#endif
5045
case customDeprecated(@Sendable ([String]) -> [String])
5146
}
5247

@@ -186,22 +181,12 @@ public struct CompletionKind {
186181
///
187182
/// The same as `custom(@Sendable @escaping ([String], Int, String) -> [String])`,
188183
/// except that the closure is asynchronous.
189-
#if !canImport(Dispatch)
190-
@available(*, unavailable, message: "DispatchSemaphore is unavailable")
191-
@available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *)
192-
public static func custom(
193-
_ completion: @Sendable @escaping ([String], Int, String) async -> [String]
194-
) -> CompletionKind {
195-
fatalError("DispatchSemaphore is unavailable")
196-
}
197-
#else
198184
@available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *)
199185
public static func custom(
200186
_ completion: @Sendable @escaping ([String], Int, String) async -> [String]
201187
) -> CompletionKind {
202188
CompletionKind(kind: .customAsync(completion))
203189
}
204-
#endif
205190

206191
/// Deprecated; only kept for backwards compatibility.
207192
///

Sources/ArgumentParser/Parsable Types/AsyncParsableCommand.swift

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,34 @@ public protocol AsyncParsableCommand: ParsableCommand {
2525

2626
@available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *)
2727
extension AsyncParsableCommand {
28+
/// Parses a new instance of this type from command-line arguments.
29+
///
30+
/// - Parameter arguments: An array of arguments to use for parsing. If
31+
/// `arguments` is `nil`, this uses the program's command-line arguments.
32+
/// - Returns: A new instance of this type.
33+
/// - Throws: If parsing failed or arguments contains a help request.
34+
public static func parse(
35+
_ arguments: [String]? = nil
36+
) async throws -> Self {
37+
try parse(try await parseAsRoot(arguments))
38+
}
39+
40+
/// Parses an instance of this type, or one of its subcommands, from
41+
/// command-line arguments.
42+
///
43+
/// - Parameter arguments: An array of arguments to use for parsing. If
44+
/// `arguments` is `nil`, this uses the program's command-line arguments.
45+
/// - Returns: A new instance of this type, one of its subcommands, or a
46+
/// command type internal to the `ArgumentParser` library.
47+
/// - Throws: If parsing fails.
48+
public static func parseAsRoot(
49+
_ arguments: [String]? = nil
50+
) async throws -> ParsableCommand {
51+
var parser = CommandParser(self)
52+
let arguments = arguments ?? Array(CommandLine._staticArguments.dropFirst())
53+
return try await parser.parse(arguments: arguments)
54+
}
55+
2856
/// Executes this command, or one of its subcommands, with the given arguments.
2957
///
3058
/// This method parses an instance of this type, one of its subcommands, or
@@ -36,7 +64,7 @@ extension AsyncParsableCommand {
3664
/// `arguments` is `nil`, this uses the program's command-line arguments.
3765
public static func main(_ arguments: [String]?) async {
3866
do {
39-
var command = try parseAsRoot(arguments)
67+
var command = try await parseAsRoot(arguments)
4068
if var asyncCommand = command as? AsyncParsableCommand {
4169
try await asyncCommand.run()
4270
} else {

Sources/ArgumentParser/Parsable Types/ParsableArguments.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public protocol ParsableArguments: Decodable, _SendableMetatype {
2626

2727
/// The label to use for "Error: ..." messages from this type (experimental).
2828
///
29-
/// Can be ignored if `_errorPrefix`'s is changed.
29+
/// `_errorLabel` will be ignored if `_errorPrefix` is changed to ignore `_errorLabel`.
3030
@available(*, deprecated, message: "Use _errorPrefix instead.")
3131
static var _errorLabel: String { get }
3232

@@ -88,9 +88,15 @@ extension ParsableArguments {
8888
/// - Throws: If parsing failed or arguments contains a help request.
8989
public static func parse(
9090
_ arguments: [String]? = nil
91+
) throws -> Self {
92+
try parse(try self.asCommand.parseAsRoot(arguments))
93+
}
94+
95+
internal static func parse(
96+
_ command: ParsableCommand
9197
) throws -> Self {
9298
// Parse the command and unwrap the result if necessary.
93-
switch try self.asCommand.parseAsRoot(arguments) {
99+
switch command {
94100
case let helpCommand as HelpCommand:
95101
throw ParserError.helpRequested(visibility: helpCommand.visibility)
96102
case let result as _WrappedParsableCommand<Self>:

0 commit comments

Comments
 (0)