|
| 1 | +/// Describes an argument that can be provided on the command line. |
| 2 | +public struct Argument<T> { |
| 3 | + /// The default value for this argument. This is the value that will be used |
| 4 | + /// if the argument is never explicitly specified on the command line. |
| 5 | + /// If this is nil, this argument is always required. |
| 6 | + public let defaultValue: T? |
| 7 | + |
| 8 | + /// A human-readable string describing the purpose of this argument. This will |
| 9 | + /// be shown in help messages. |
| 10 | + public let usage: String |
| 11 | + |
| 12 | + /// A human-readable string that describes this argument as a paramater shown |
| 13 | + /// in the list of possible parameters in help messages (e.g. for "paths", the |
| 14 | + /// user would see <paths…>). |
| 15 | + public let usageParameter: String? |
| 16 | + |
| 17 | + public init(defaultValue: T? = nil, usage: String, usageParameter: String? = nil) |
| 18 | +} |
| 19 | + |
| 20 | +/// Destructively parses a list of command-line arguments. |
| 21 | +public final class ArgumentParser { |
| 22 | + /// Initializes the generator from a simple list of command-line arguments. |
| 23 | + public init(_ arguments: [String]) |
| 24 | +} |
| 25 | + |
| 26 | +/// Represents a value that can be converted from a command-line argument. |
| 27 | +public protocol ArgumentProtocol { |
| 28 | + /// A human-readable name for this type. |
| 29 | + static var name: String |
| 30 | + |
| 31 | + /// Attempts to parse a value from the given command-line argument. |
| 32 | + static func from(string: String) -> Self? |
| 33 | +} |
| 34 | + |
| 35 | +/// Represents a subcommand that can be executed with its own set of arguments. |
| 36 | +public protocol CommandProtocol { |
| 37 | + /// The command's options type. |
| 38 | + associatedtype Options: OptionsProtocol |
| 39 | + |
| 40 | + associatedtype ClientError where ClientError == Options.ClientError |
| 41 | + |
| 42 | + /// The action that users should specify to use this subcommand (e.g., |
| 43 | + /// `help`). |
| 44 | + var verb: String |
| 45 | + |
| 46 | + /// A human-readable, high-level description of what this command is used |
| 47 | + /// for. |
| 48 | + var function: String |
| 49 | + |
| 50 | + /// Runs this subcommand with the given options. |
| 51 | + func run(_ options: Options) -> Result<Void, ClientError> |
| 52 | +} |
| 53 | + |
| 54 | +/// A type-erased command. |
| 55 | +public struct CommandWrapper<ClientError: Error> { |
| 56 | + public let verb: String |
| 57 | + |
| 58 | + public let function: String |
| 59 | + |
| 60 | + public let run: (ArgumentParser) -> Result<Void, CommandantError<ClientError>> |
| 61 | + |
| 62 | + public let usage: () -> CommandantError<ClientError>? |
| 63 | +} |
| 64 | + |
| 65 | +/// Describes the "mode" in which a command should run. |
| 66 | +public enum CommandMode { |
| 67 | + /// Options should be parsed from the given command-line arguments. |
| 68 | + case arguments(ArgumentParser) |
| 69 | + /// Each option should record its usage information in an error, for |
| 70 | + presentation to the user. |
| 71 | + case usage |
| 72 | +} |
| 73 | + |
| 74 | +/// Maintains the list of commands available to run. |
| 75 | +public final class CommandRegistry<ClientError: Error> { |
| 76 | + /// All available commands. |
| 77 | + public var commands: [CommandWrapper<ClientError>] |
| 78 | + |
| 79 | + public init() |
| 80 | + |
| 81 | + /// Registers the given commands, making those available to run. |
| 82 | + /// If another commands were already registered with the same `verb`s, those |
| 83 | + /// will be overwritten. |
| 84 | + public func register<C: CommandProtocol>(_ commands: C...) |
| 85 | + -> CommandRegistry |
| 86 | + where C.ClientError == ClientError |
| 87 | + |
| 88 | + /// Runs the command corresponding to the given verb, passing it the given |
| 89 | + /// arguments. |
| 90 | + /// Returns the results of the execution, or nil if no such command exists. |
| 91 | + public func run(command verb: String, arguments: [String]) -> Result<Void, CommandantError<ClientError>>? |
| 92 | + |
| 93 | + /// Returns the command matching the given verb, or nil if no such command |
| 94 | + /// is registered. |
| 95 | + public subscript(verb: String) -> CommandWrapper<ClientError>? |
| 96 | +} |
| 97 | + |
| 98 | +/// Possible errors that can originate from Commandant. |
| 99 | +/// `ClientError` should be the type of error (if any) that can occur when |
| 100 | +/// running commands. |
| 101 | +public enum CommandantError<ClientError>: Error { |
| 102 | + /// An option was used incorrectly. |
| 103 | + case usageError(description: String) |
| 104 | + /// An error occurred while running a command. |
| 105 | + case commandError(ClientError) |
| 106 | +} |
| 107 | + |
| 108 | +/// A basic implementation of a `help` command, using information available in a |
| 109 | +/// `CommandRegistry`. |
| 110 | +/// If you want to use this command, initialize it with the registry, then add |
| 111 | +/// it to that same registry: |
| 112 | +/// let commands: CommandRegistry<MyErrorType> = … |
| 113 | +/// let helpCommand = HelpCommand(registry: commands) |
| 114 | +/// commands.register(helpCommand) |
| 115 | +public struct HelpCommand<ClientError: Error>: CommandProtocol { |
| 116 | + public typealias Options = HelpOptions<ClientError> |
| 117 | + |
| 118 | + public let verb = "help" |
| 119 | + |
| 120 | + public let function: String |
| 121 | + |
| 122 | + /// Initializes the command to provide help from the given registry of |
| 123 | + /// commands. |
| 124 | + public init(registry: CommandRegistry<ClientError>, function: String? = nil) |
| 125 | + |
| 126 | + public func run(_ options: Options) -> Result<Void, ClientError> |
| 127 | +} |
| 128 | + |
| 129 | +public struct HelpOptions<ClientError: Error>: OptionsProtocol { |
| 130 | + public static func evaluate(_ m: CommandMode) -> Result<HelpOptions, CommandantError<ClientError>> |
| 131 | +} |
| 132 | + |
| 133 | +/// Represents a record of options for a command, which can be parsed from |
| 134 | +/// a list of command-line arguments. |
| 135 | +/// This is most helpful when used in conjunction with the `Option` and `Switch` |
| 136 | +/// types, and `<*>` and `<|` combinators. |
| 137 | +/// Example: |
| 138 | +/// struct LogOptions: OptionsProtocol { |
| 139 | +/// let verbosity: Int |
| 140 | +/// let outputFilename: String |
| 141 | +/// let shouldDelete: Bool |
| 142 | +/// let logName: String |
| 143 | +/// static func create(_ verbosity: Int) -> (String) -> (Bool) -> (String) -> LogOptions { |
| 144 | +/// return { outputFilename in { shouldDelete in { logName in LogOptions(verbosity: verbosity, outputFilename: outputFilename, shouldDelete: shouldDelete, logName: logName) } } } |
| 145 | +/// } |
| 146 | +/// static func evaluate(_ m: CommandMode) -> Result<LogOptions, CommandantError<YourErrorType>> { |
| 147 | +/// return create |
| 148 | +/// <*> m <| Option(key: "verbose", defaultValue: 0, usage: "the verbosity level with which to read the logs") |
| 149 | +/// <*> m <| Option(key: "outputFilename", defaultValue: "", usage: "a file to print output to, instead of stdout") |
| 150 | +/// <*> m <| Switch(flag: "d", key: "delete", usage: "delete the logs when finished") |
| 151 | +/// <*> m <| Argument(usage: "the log to read") |
| 152 | +/// } |
| 153 | +/// } |
| 154 | +public protocol OptionsProtocol { |
| 155 | + associatedtype ClientError: Error |
| 156 | + |
| 157 | + /// Evaluates this set of options in the given mode. |
| 158 | + /// Returns the parsed options or a `UsageError`. |
| 159 | + static func evaluate(_ m: CommandMode) -> Result<Self, CommandantError<ClientError>> |
| 160 | +} |
| 161 | + |
| 162 | +/// An `OptionsProtocol` that has no options. |
| 163 | +public struct NoOptions<ClientError: Error>: OptionsProtocol { |
| 164 | + public init() |
| 165 | + |
| 166 | + public static func evaluate(_ m: CommandMode) -> Result<NoOptions, CommandantError<ClientError>> |
| 167 | +} |
| 168 | + |
| 169 | +/// Describes an option that can be provided on the command line. |
| 170 | +public struct Option<T> { |
| 171 | + /// The key that controls this option. For example, a key of `verbose` would |
| 172 | + /// be used for a `--verbose` option. |
| 173 | + public let key: String |
| 174 | + |
| 175 | + /// The default value for this option. This is the value that will be used |
| 176 | + /// if the option is never explicitly specified on the command line. |
| 177 | + public let defaultValue: T |
| 178 | + |
| 179 | + /// A human-readable string describing the purpose of this option. This will |
| 180 | + /// be shown in help messages. |
| 181 | + /// For boolean operations, this should describe the effect of _not_ using |
| 182 | + /// the default value (i.e., what will happen if you disable/enable the flag |
| 183 | + /// differently from the default). |
| 184 | + public let usage: String |
| 185 | + |
| 186 | + public init(key: String, defaultValue: T, usage: String) |
| 187 | +} |
| 188 | + |
| 189 | +/// Applies `f` to the value in the given result. |
| 190 | +/// In the context of command-line option parsing, this is used to chain |
| 191 | +/// together the parsing of multiple arguments. See OptionsProtocol for an example. |
| 192 | +public func <*> <T, U, ClientError>(f: (T) -> U, value: Result<T, CommandantError<ClientError>>) -> Result<U, CommandantError<ClientError>> |
| 193 | + |
| 194 | +/// Applies the function in `f` to the value in the given result. |
| 195 | +/// In the context of command-line option parsing, this is used to chain |
| 196 | +/// together the parsing of multiple arguments. See OptionsProtocol for an example. |
| 197 | +public func <*> <T, U, ClientError>(f: Result<(T) -> U, CommandantError<ClientError>>, value: Result<T, CommandantError<ClientError>>) -> Result<U, CommandantError<ClientError>> |
| 198 | + |
| 199 | +/// Describes a parameterless command line flag that defaults to false and can only |
| 200 | +/// be switched on. Canonical examples include `--force` and `--recurse`. |
| 201 | +/// For a boolean toggle that can be enabled and disabled use `Option<Bool>`. |
| 202 | +public struct Switch { |
| 203 | + /// The key that enables this switch. For example, a key of `verbose` would be |
| 204 | + /// used for a `--verbose` option. |
| 205 | + public let key: String |
| 206 | + |
| 207 | + /// Optional single letter flag that enables this switch. For example, `-v` would |
| 208 | + /// be used as a shorthand for `--verbose`. |
| 209 | + /// Multiple flags can be grouped together as a single argument and will split |
| 210 | + /// when parsing (e.g. `rm -rf` treats 'r' and 'f' as inidividual flags). |
| 211 | + public let flag: Character? |
| 212 | + |
| 213 | + /// A human-readable string describing the purpose of this option. This will |
| 214 | + /// be shown in help messages. |
| 215 | + public let usage: String |
| 216 | + |
| 217 | + public init(flag: Character? = nil, key: String, usage: String) |
| 218 | +} |
0 commit comments