/
ParsableCommand.swift
198 lines (173 loc) · 7.27 KB
/
ParsableCommand.swift
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
//===----------------------------------------------------------*- 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, *) {
checkAsyncHierarchy(self, root: "\(self)")
}
#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 includesUnconditionalArguments: Bool {
ArgumentSet(self, visibility: .private, parent: .root).contains(where: {
$0.isRepeatingPositional && $0.parsingStrategy == .allRemainingInput
})
}
/// `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 defaultIncludesUnconditionalArguments: Bool {
configuration.defaultSubcommand?.includesUnconditionalArguments == 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))
}
}
#endif
}