1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
|
//===----------------------------------------------------------*- swift -*-===//
//
// This source file is part of the Swift Argument Parser open source project
//
// Copyright (c) 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//
/// A type that can be executed as part of a nested tree of commands.
public protocol ParsableCommand: ParsableArguments {
/// Configuration for this command, including subcommands and custom help
/// text.
static var configuration: CommandConfiguration { get }
/// *For internal use only:* The name for the command on the command line.
///
/// This is generated from the configuration, if given, or from the type
/// name if not. This is a customization point so that a WrappedParsable
/// can pass through the wrapped type's name.
static var _commandName: String { get }
/// The behavior or functionality of this command.
///
/// Implement this method in your `ParsableCommand`-conforming type with the
/// functionality that this command represents.
///
/// This method has a default implementation that prints the help screen for
/// this command.
mutating func run() throws
}
// MARK: - Default implementations
extension ParsableCommand {
public static var _commandName: String {
configuration.commandName ??
String(describing: Self.self).convertedToSnakeCase(separator: "-")
}
public static var configuration: CommandConfiguration {
CommandConfiguration()
}
public mutating func run() throws {
throw CleanExit.helpRequest(self)
}
}
// MARK: - API
extension ParsableCommand {
/// Parses an instance of this type, or one of its subcommands, from
/// command-line arguments.
///
/// - 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.
public static func parseAsRoot(
_ arguments: [String]? = nil
) throws -> ParsableCommand {
var parser = CommandParser(self)
let arguments = arguments ?? Array(CommandLine.arguments.dropFirst())
return try parser.parse(arguments: arguments).get()
}
/// Returns the text of the help screen for the given subcommand of this
/// command.
///
/// - Parameters:
/// - subcommand: The subcommand to generate the help screen for.
/// `subcommand` must be declared in the subcommand tree of this
/// command.
/// - columns: The column width to use when wrapping long line in the
/// help screen. If `columns` is `nil`, uses the current terminal
/// width, or a default value of `80` if the terminal width is not
/// available.
/// - Returns: The full help screen for this type.
@_disfavoredOverload
@available(*, deprecated, renamed: "helpMessage(for:includeHidden:columns:)")
public static func helpMessage(
for _subcommand: ParsableCommand.Type,
columns: Int? = nil
) -> String {
helpMessage(for: _subcommand, includeHidden: false, columns: columns)
}
/// Returns the text of the help screen for the given subcommand of this
/// command.
///
/// - Parameters:
/// - subcommand: The subcommand to generate the help screen for.
/// `subcommand` must be declared in the subcommand tree of this
/// command.
/// - includeHidden: Include hidden help information in the generated
/// message.
/// - columns: The column width to use when wrapping long line in the
/// help screen. If `columns` is `nil`, uses the current terminal
/// width, or a default value of `80` if the terminal width is not
/// available.
/// - Returns: The full help screen for this type.
public static func helpMessage(
for subcommand: ParsableCommand.Type,
includeHidden: Bool = false,
columns: Int? = nil
) -> String {
HelpGenerator(
commandStack: CommandParser(self).commandStack(for: subcommand),
visibility: includeHidden ? .hidden : .default)
.rendered(screenWidth: columns)
}
/// Executes this command, or one of its subcommands, with the given
/// arguments.
///
/// This method parses an instance of this type, one of its subcommands, or
/// another built-in `ParsableCommand` type, from command-line arguments,
/// and then calls its `run()` method, exiting with a relevant error message
/// if necessary.
///
/// - Parameter arguments: An array of arguments to use for parsing. If
/// `arguments` is `nil`, this uses the program's command-line arguments.
public static func main(_ arguments: [String]?) {
#if DEBUG
if #available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *) {
if let asyncCommand = firstAsyncSubcommand(self) {
if Self() is AsyncParsableCommand {
failAsyncPlatform(rootCommand: self)
} else {
failAsyncHierarchy(rootCommand: self, subCommand: asyncCommand)
}
}
}
#endif
do {
var command = try parseAsRoot(arguments)
try command.run()
} catch {
exit(withError: error)
}
}
/// Executes this command, or one of its subcommands, with the program's
/// command-line arguments.
///
/// Instead of calling this method directly, you can add `@main` to the root
/// command for your command-line tool.
///
/// This method parses an instance of this type, one of its subcommands, or
/// another built-in `ParsableCommand` type, from command-line arguments,
/// and then calls its `run()` method, exiting with a relevant error message
/// if necessary.
public static func main() {
self.main(nil)
}
}
// MARK: - Internal API
extension ParsableCommand {
/// `true` if this command contains any array arguments that are declared
/// with `.unconditionalRemaining`.
internal static var includesPassthroughArguments: Bool {
ArgumentSet(self, visibility: .private, parent: nil).contains(where: {
$0.isRepeatingPositional && $0.parsingStrategy == .allRemainingInput
})
}
internal static var includesAllUnrecognizedArgument: Bool {
ArgumentSet(self, visibility: .private, parent: nil).contains(where: {
$0.isRepeatingPositional && $0.parsingStrategy == .allUnrecognized
})
}
/// `true` if this command's default subcommand contains any array arguments
/// that are declared with `.unconditionalRemaining`. This is `false` if
/// there's no default subcommand.
internal static var defaultIncludesPassthroughArguments: Bool {
configuration.defaultSubcommand?.includesPassthroughArguments == true
}
#if DEBUG
@available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *)
internal static func checkAsyncHierarchy(_ command: ParsableCommand.Type, root: String) {
for sub in command.configuration.subcommands {
checkAsyncHierarchy(sub, root: root)
guard sub.configuration.subcommands.isEmpty else { continue }
guard sub is AsyncParsableCommand.Type else { continue }
fatalError("""
--------------------------------------------------------------------
Asynchronous subcommand of a synchronous root.
The asynchronous command `\(sub)` is declared as a subcommand of the synchronous root command `\(root)`.
With this configuration, your asynchronous `run()` method will not be called. To fix this issue, change `\(root)`'s `ParsableCommand` conformance to `AsyncParsableCommand`.
--------------------------------------------------------------------
""".wrapped(to: 70))
}
}
@available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *)
internal static func firstAsyncSubcommand(_ command: ParsableCommand.Type) -> AsyncParsableCommand.Type? {
for sub in command.configuration.subcommands {
if let asyncCommand = sub as? AsyncParsableCommand.Type,
sub.configuration.subcommands.isEmpty
{
return asyncCommand
}
if let asyncCommand = firstAsyncSubcommand(sub) {
return asyncCommand
}
}
return nil
}
#endif
}
// MARK: Async Configuration Errors
func failAsyncHierarchy(
rootCommand: ParsableCommand.Type, subCommand: ParsableCommand.Type
) -> Never {
fatalError("""
--------------------------------------------------------------------
Asynchronous subcommand of a synchronous root.
The asynchronous command `\(subCommand)` is declared as a subcommand of the synchronous root command `\(rootCommand)`.
With this configuration, your asynchronous `run()` method will not be called. To fix this issue, change `\(rootCommand)`'s `ParsableCommand` conformance to `AsyncParsableCommand`.
--------------------------------------------------------------------
""".wrapped(to: 70))
}
func failAsyncPlatform(rootCommand: ParsableCommand.Type) -> Never {
fatalError("""
--------------------------------------------------------------------
Asynchronous root command needs availability annotation.
The asynchronous root command `\(rootCommand)` needs an availability annotation in order to be executed asynchronously. To fix this issue, add the following availability attribute to your `\(rootCommand)` declaration or set the minimum platform in your "Package.swift" file.
""".wrapped(to: 70)
+ """
@available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *)
--------------------------------------------------------------------
""")
}
|