Skip to content

Commit 1103b6f

Browse files
authored
Merge pull request #1 from minuscorp/balance-checking
Added examples and fixed bug with unbalanced declarations.
2 parents ef6e8e1 + c93298f commit 1103b6f

7 files changed

Lines changed: 4788 additions & 6 deletions

File tree

Examples/Commandant.swift

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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

Comments
 (0)