Commit dependencies

Don't depend on bootstrapping for most cases
This commit is contained in:
Andrew Naylor 2016-09-24 17:08:40 +01:00
parent f4a984768d
commit 661259574e
16 changed files with 1491 additions and 4 deletions

1
.gitignore vendored
View file

@ -70,7 +70,6 @@ Network Trash Folder
Temporary Items Temporary Items
.apdisk .apdisk
Seeds/
mas-cli.zip mas-cli.zip
mas-cli.dSYM.zip mas-cli.dSYM.zip
mas.xcarchive.zip mas.xcarchive.zip

View file

@ -8,9 +8,6 @@ env:
- LC_ALL=en_US.UTF-8 - LC_ALL=en_US.UTF-8
- LANGUAGE=en_US.UTF-8 - LANGUAGE=en_US.UTF-8
install:
- script/bootstrap
script: script:
- script/build - script/build

View file

@ -0,0 +1,95 @@
//
// Argument.swift
// Commandant
//
// Created by Syo Ikeda on 12/14/15.
// Copyright (c) 2015 Carthage. All rights reserved.
//
/// Describes an argument that can be provided on the command line.
public struct Argument<T> {
/// The default value for this argument. This is the value that will be used
/// if the argument is never explicitly specified on the command line.
///
/// If this is nil, this argument is always required.
public let defaultValue: T?
/// A human-readable string describing the purpose of this argument. This will
/// be shown in help messages.
public let usage: String
public init(defaultValue: T? = nil, usage: String) {
self.defaultValue = defaultValue
self.usage = usage
}
fileprivate func invalidUsageError<ClientError>(_ value: String) -> CommandantError<ClientError> {
let description = "Invalid value for '\(self)': \(value)"
return .usageError(description: description)
}
}
/// Evaluates the given argument in the given mode.
///
/// If parsing command line arguments, and no value was specified on the command
/// line, the argument's `defaultValue` is used.
public func <| <T: ArgumentProtocol, ClientError>(mode: CommandMode, argument: Argument<T>) -> Result<T, CommandantError<ClientError>> {
switch mode {
case let .arguments(arguments):
guard let stringValue = arguments.consumePositionalArgument() else {
if let defaultValue = argument.defaultValue {
return .success(defaultValue)
} else {
return .failure(missingArgumentError(argument.usage))
}
}
if let value = T.from(string: stringValue) {
return .success(value)
} else {
return .failure(argument.invalidUsageError(stringValue))
}
case .usage:
return .failure(informativeUsageError(argument))
}
}
/// Evaluates the given argument list in the given mode.
///
/// If parsing command line arguments, and no value was specified on the command
/// line, the argument's `defaultValue` is used.
public func <| <T: ArgumentProtocol, ClientError>(mode: CommandMode, argument: Argument<[T]>) -> Result<[T], CommandantError<ClientError>> {
switch mode {
case let .arguments(arguments):
guard let firstValue = arguments.consumePositionalArgument() else {
if let defaultValue = argument.defaultValue {
return .success(defaultValue)
} else {
return .failure(missingArgumentError(argument.usage))
}
}
var values = [T]()
guard let value = T.from(string: firstValue) else {
return .failure(argument.invalidUsageError(firstValue))
}
values.append(value)
while let nextValue = arguments.consumePositionalArgument() {
guard let value = T.from(string: nextValue) else {
return .failure(argument.invalidUsageError(nextValue))
}
values.append(value)
}
return .success(values)
case .usage:
return .failure(informativeUsageError(argument))
}
}

View file

@ -0,0 +1,191 @@
//
// ArgumentParser.swift
// Commandant
//
// Created by Justin Spahr-Summers on 2014-11-21.
// Copyright (c) 2014 Carthage. All rights reserved.
//
import Foundation
/// Represents an argument passed on the command line.
private enum RawArgument: Equatable {
/// A key corresponding to an option (e.g., `verbose` for `--verbose`).
case key(String)
/// A value, either associated with an option or passed as a positional
/// argument.
case value(String)
/// One or more flag arguments (e.g 'r' and 'f' for `-rf`)
case flag(Set<Character>)
}
private func ==(lhs: RawArgument, rhs: RawArgument) -> Bool {
switch (lhs, rhs) {
case let (.key(left), .key(right)):
return left == right
case let (.value(left), .value(right)):
return left == right
case let (.flag(left), .flag(right)):
return left == right
default:
return false
}
}
extension RawArgument: CustomStringConvertible {
fileprivate var description: String {
switch self {
case let .key(key):
return "--\(key)"
case let .value(value):
return "\"\(value)\""
case let .flag(flags):
return "-\(String(flags))"
}
}
}
/// Destructively parses a list of command-line arguments.
public final class ArgumentParser {
/// The remaining arguments to be extracted, in their raw form.
private var rawArguments: [RawArgument] = []
/// Initializes the generator from a simple list of command-line arguments.
public init(_ arguments: [String]) {
// The first instance of `--` terminates the option list.
let params = arguments.split(maxSplits: 1, omittingEmptySubsequences: false) { $0 == "--" }
// Parse out the keyed and flag options.
let options = params.first!
rawArguments.append(contentsOf: options.map { arg in
if arg.hasPrefix("-") {
// Do we have `--{key}` or `-{flags}`.
let opt = arg.characters.dropFirst()
if opt.first == "-" {
return .key(String(opt.dropFirst()))
} else {
return .flag(Set(opt))
}
} else {
return .value(arg)
}
})
// Remaining arguments are all positional parameters.
if params.count == 2 {
let positional = params.last!
rawArguments.append(contentsOf: positional.map(RawArgument.value))
}
}
/// Returns the remaining arguments.
internal var remainingArguments: [String]? {
return rawArguments.isEmpty ? nil : rawArguments.map { $0.description }
}
/// Returns whether the given key was enabled or disabled, or nil if it
/// was not given at all.
///
/// If the key is found, it is then removed from the list of arguments
/// remaining to be parsed.
internal func consumeBoolean(forKey key: String) -> Bool? {
let oldArguments = rawArguments
rawArguments.removeAll()
var result: Bool?
for arg in oldArguments {
if arg == .key(key) {
result = true
} else if arg == .key("no-\(key)") {
result = false
} else {
rawArguments.append(arg)
}
}
return result
}
/// Returns the value associated with the given flag, or nil if the flag was
/// not specified. If the key is presented, but no value was given, an error
/// is returned.
///
/// If a value is found, the key and the value are both removed from the
/// list of arguments remaining to be parsed.
internal func consumeValue(forKey key: String) -> Result<String?, CommandantError<NoError>> {
let oldArguments = rawArguments
rawArguments.removeAll()
var foundValue: String?
var index = 0
while index < oldArguments.count {
defer { index += 1 }
let arg = oldArguments[index]
guard arg == .key(key) else {
rawArguments.append(arg)
continue
}
index += 1
guard index < oldArguments.count, case let .value(value) = oldArguments[index] else {
return .failure(missingArgumentError("--\(key)"))
}
foundValue = value
}
return .success(foundValue)
}
/// Returns the next positional argument that hasn't yet been returned, or
/// nil if there are no more positional arguments.
internal func consumePositionalArgument() -> String? {
for (index, arg) in rawArguments.enumerated() {
if case let .value(value) = arg {
rawArguments.remove(at: index)
return value
}
}
return nil
}
/// Returns whether the given key was specified and removes it from the
/// list of arguments remaining.
internal func consume(key: String) -> Bool {
let oldArguments = rawArguments
rawArguments = oldArguments.filter { $0 != .key(key) }
return rawArguments.count < oldArguments.count
}
/// Returns whether the given flag was specified and removes it from the
/// list of arguments remaining.
internal func consumeBoolean(flag: Character) -> Bool {
for (index, arg) in rawArguments.enumerated() {
if case let .flag(flags) = arg, flags.contains(flag) {
var flags = flags
flags.remove(flag)
if flags.isEmpty {
rawArguments.remove(at: index)
} else {
rawArguments[index] = .flag(flags)
}
return true
}
}
return false
}
}

View file

@ -0,0 +1,41 @@
//
// ArgumentProtocol.swift
// Commandant
//
// Created by Syo Ikeda on 12/14/15.
// Copyright (c) 2015 Carthage. All rights reserved.
//
/// Represents a value that can be converted from a command-line argument.
public protocol ArgumentProtocol {
/// A human-readable name for this type.
static var name: String { get }
/// Attempts to parse a value from the given command-line argument.
static func from(string: String) -> Self?
}
extension Int: ArgumentProtocol {
public static let name = "integer"
public static func from(string: String) -> Int? {
return Int(string)
}
}
extension String: ArgumentProtocol {
public static let name = "string"
public static func from(string: String) -> String? {
return string
}
}
// MARK: - migration support
@available(*, unavailable, renamed: "ArgumentProtocol")
public typealias ArgumentType = ArgumentProtocol
extension ArgumentProtocol {
@available(*, unavailable, renamed: "from(string:)")
static func fromString(_ string: String) -> Self? { return nil }
}

View file

@ -0,0 +1,224 @@
//
// Command.swift
// Commandant
//
// Created by Justin Spahr-Summers on 2014-10-10.
// Copyright (c) 2014 Carthage. All rights reserved.
//
import Foundation
/// Represents a subcommand that can be executed with its own set of arguments.
public protocol CommandProtocol {
/// The command's options type.
associatedtype Options: OptionsProtocol
associatedtype ClientError: Error = Options.ClientError
/// The action that users should specify to use this subcommand (e.g.,
/// `help`).
var verb: String { get }
/// A human-readable, high-level description of what this command is used
/// for.
var function: String { get }
/// Runs this subcommand with the given options.
func run(_ options: Options) -> Result<(), ClientError>
}
/// A type-erased command.
public struct CommandWrapper<ClientError: Error> {
public let verb: String
public let function: String
public let run: (ArgumentParser) -> Result<(), CommandantError<ClientError>>
public let usage: () -> CommandantError<ClientError>?
/// Creates a command that wraps another.
fileprivate init<C: CommandProtocol>(_ command: C) where C.ClientError == ClientError, C.Options.ClientError == ClientError {
verb = command.verb
function = command.function
run = { (arguments: ArgumentParser) -> Result<(), CommandantError<ClientError>> in
let options = C.Options.evaluate(.arguments(arguments))
if let remainingArguments = arguments.remainingArguments {
return .failure(unrecognizedArgumentsError(remainingArguments))
}
switch options {
case let .success(options):
return command
.run(options)
.mapError(CommandantError.commandError)
case let .failure(error):
return .failure(error)
}
}
usage = { () -> CommandantError<ClientError>? in
return C.Options.evaluate(.usage).error
}
}
}
/// Describes the "mode" in which a command should run.
public enum CommandMode {
/// Options should be parsed from the given command-line arguments.
case arguments(ArgumentParser)
/// Each option should record its usage information in an error, for
/// presentation to the user.
case usage
}
/// Maintains the list of commands available to run.
public final class CommandRegistry<ClientError: Error> {
private var commandsByVerb: [String: CommandWrapper<ClientError>] = [:]
/// All available commands.
public var commands: [CommandWrapper<ClientError>] {
return commandsByVerb.values.sorted { return $0.verb < $1.verb }
}
public init() {}
/// Registers the given command, making it available to run.
///
/// If another command was already registered with the same `verb`, it will
/// be overwritten.
public func register<C: CommandProtocol>(_ command: C) where C.ClientError == ClientError, C.Options.ClientError == ClientError {
commandsByVerb[command.verb] = CommandWrapper(command)
}
/// Runs the command corresponding to the given verb, passing it the given
/// arguments.
///
/// Returns the results of the execution, or nil if no such command exists.
public func run(command verb: String, arguments: [String]) -> Result<(), CommandantError<ClientError>>? {
return self[verb]?.run(ArgumentParser(arguments))
}
/// Returns the command matching the given verb, or nil if no such command
/// is registered.
public subscript(verb: String) -> CommandWrapper<ClientError>? {
return commandsByVerb[verb]
}
}
extension CommandRegistry {
/// Hands off execution to the CommandRegistry, by parsing CommandLine.arguments
/// and then running whichever command has been identified in the argument
/// list.
///
/// If the chosen command executes successfully, the process will exit with
/// a successful exit code.
///
/// If the chosen command fails, the provided error handler will be invoked,
/// then the process will exit with a failure exit code.
///
/// If a matching command could not be found but there is any `executable-verb`
/// style subcommand executable in the caller's `$PATH`, the subcommand will
/// be executed.
///
/// If a matching command could not be found or a usage error occurred,
/// a helpful error message will be written to `stderr`, then the process
/// will exit with a failure error code.
public func main(defaultVerb: String, errorHandler: (ClientError) -> ()) -> Never {
main(arguments: CommandLine.arguments, defaultVerb: defaultVerb, errorHandler: errorHandler)
}
/// Hands off execution to the CommandRegistry, by parsing `arguments`
/// and then running whichever command has been identified in the argument
/// list.
///
/// If the chosen command executes successfully, the process will exit with
/// a successful exit code.
///
/// If the chosen command fails, the provided error handler will be invoked,
/// then the process will exit with a failure exit code.
///
/// If a matching command could not be found but there is any `executable-verb`
/// style subcommand executable in the caller's `$PATH`, the subcommand will
/// be executed.
///
/// If a matching command could not be found or a usage error occurred,
/// a helpful error message will be written to `stderr`, then the process
/// will exit with a failure error code.
public func main(arguments: [String], defaultVerb: String, errorHandler: (ClientError) -> ()) -> Never {
assert(arguments.count >= 1)
var arguments = arguments
// Extract the executable name.
let executableName = arguments.remove(at: 0)
let verb = arguments.first ?? defaultVerb
if arguments.count > 0 {
// Remove the command name.
arguments.remove(at: 0)
}
switch run(command: verb, arguments: arguments) {
case .success?:
exit(EXIT_SUCCESS)
case let .failure(error)?:
switch error {
case let .usageError(description):
fputs(description + "\n", stderr)
case let .commandError(error):
errorHandler(error)
}
exit(EXIT_FAILURE)
case nil:
if let subcommandExecuted = executeSubcommandIfExists(executableName, verb: verb, arguments: arguments) {
exit(subcommandExecuted)
}
fputs("Unrecognized command: '\(verb)'. See `\(executableName) help`.\n", stderr)
exit(EXIT_FAILURE)
}
}
/// Finds and executes a subcommand which exists in your $PATH. The executable
/// name must be in the form of `executable-verb`.
///
/// - Returns: The exit status of found subcommand or nil.
private func executeSubcommandIfExists(_ executableName: String, verb: String, arguments: [String]) -> Int32? {
let subcommand = "\(NSString(string: executableName).lastPathComponent)-\(verb)"
func launchTask(_ path: String, arguments: [String]) -> Int32 {
let task = Process()
task.launchPath = path
task.arguments = arguments
task.launch()
task.waitUntilExit()
return task.terminationStatus
}
guard launchTask("/usr/bin/which", arguments: [ "-s", subcommand ]) == 0 else {
return nil
}
return launchTask("/usr/bin/env", arguments: [ subcommand ] + arguments)
}
}
// MARK: - migration support
@available(*, unavailable, renamed: "CommandProtocol")
public typealias CommandType = CommandProtocol
extension CommandRegistry {
@available(*, unavailable, renamed: "run(command:arguments:)")
public func runCommand(_ verb: String, arguments: [String]) -> Result<(), CommandantError<ClientError>>? {
return run(command: verb, arguments: arguments)
}
}

View file

@ -0,0 +1,17 @@
//
// Commandant.h
// Commandant
//
// Created by Justin Spahr-Summers on 2014-11-21.
// Copyright (c) 2014 Carthage. All rights reserved.
//
#import <Foundation/Foundation.h>
//! Project version number for Commandant.
FOUNDATION_EXPORT double CommandantVersionNumber;
//! Project version string for Commandant.
FOUNDATION_EXPORT const unsigned char CommandantVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <Commandant/PublicHeader.h>

View file

@ -0,0 +1,147 @@
//
// Errors.swift
// Commandant
//
// Created by Justin Spahr-Summers on 2014-10-24.
// Copyright (c) 2014 Carthage. All rights reserved.
//
import Foundation
/// Possible errors that can originate from Commandant.
///
/// `ClientError` should be the type of error (if any) that can occur when
/// running commands.
public enum CommandantError<ClientError>: Error {
/// An option was used incorrectly.
case usageError(description: String)
/// An error occurred while running a command.
case commandError(ClientError)
}
extension CommandantError: CustomStringConvertible {
public var description: String {
switch self {
case let .usageError(description):
return description
case let .commandError(error):
return String(describing: error)
}
}
}
/// Constructs an `InvalidArgument` error that indicates a missing value for
/// the argument by the given name.
internal func missingArgumentError<ClientError>(_ argumentName: String) -> CommandantError<ClientError> {
let description = "Missing argument for \(argumentName)"
return .usageError(description: description)
}
/// Constructs an error by combining the example of key (and value, if applicable)
/// with the usage description.
internal func informativeUsageError<ClientError>(_ keyValueExample: String, usage: String) -> CommandantError<ClientError> {
let lines = usage.components(separatedBy: .newlines)
return .usageError(description: lines.reduce(keyValueExample) { previous, value in
return previous + "\n\t" + value
})
}
/// Combines the text of the two errors, if they're both `UsageError`s.
/// Otherwise, uses whichever one is not (biased toward the left).
internal func combineUsageErrors<ClientError>(_ lhs: CommandantError<ClientError>, _ rhs: CommandantError<ClientError>) -> CommandantError<ClientError> {
switch (lhs, rhs) {
case let (.usageError(left), .usageError(right)):
let combinedDescription = "\(left)\n\n\(right)"
return .usageError(description: combinedDescription)
case (.usageError, _):
return rhs
case (_, .usageError), (_, _):
return lhs
}
}
/// Constructs an error that indicates unrecognized arguments remains.
internal func unrecognizedArgumentsError<ClientError>(_ options: [String]) -> CommandantError<ClientError> {
return .usageError(description: "Unrecognized arguments: " + options.joined(separator: ", "))
}
// MARK: Argument
/// Constructs an error that describes how to use the argument, with the given
/// example of value usage if applicable.
internal func informativeUsageError<T, ClientError>(_ valueExample: String, argument: Argument<T>) -> CommandantError<ClientError> {
if argument.defaultValue != nil {
return informativeUsageError("[\(valueExample)]", usage: argument.usage)
} else {
return informativeUsageError(valueExample, usage: argument.usage)
}
}
/// Constructs an error that describes how to use the argument.
internal func informativeUsageError<T: ArgumentProtocol, ClientError>(_ argument: Argument<T>) -> CommandantError<ClientError> {
var example = ""
var valueExample = ""
if let defaultValue = argument.defaultValue {
valueExample = "\(defaultValue)"
}
if valueExample.isEmpty {
example += "(\(T.name))"
} else {
example += valueExample
}
return informativeUsageError(example, argument: argument)
}
/// Constructs an error that describes how to use the argument list.
internal func informativeUsageError<T: ArgumentProtocol, ClientError>(_ argument: Argument<[T]>) -> CommandantError<ClientError> {
var example = ""
var valueExample = ""
if let defaultValue = argument.defaultValue {
valueExample = "\(defaultValue)"
}
if valueExample.isEmpty {
example += "(\(T.name))"
} else {
example += valueExample
}
return informativeUsageError(example, argument: argument)
}
// MARK: Option
/// Constructs an error that describes how to use the option, with the given
/// example of key (and value, if applicable) usage.
internal func informativeUsageError<T, ClientError>(_ keyValueExample: String, option: Option<T>) -> CommandantError<ClientError> {
return informativeUsageError("[\(keyValueExample)]", usage: option.usage)
}
/// Constructs an error that describes how to use the option.
internal func informativeUsageError<T: ArgumentProtocol, ClientError>(_ option: Option<T>) -> CommandantError<ClientError> {
return informativeUsageError("--\(option.key) \(option.defaultValue)", option: option)
}
/// Constructs an error that describes how to use the option.
internal func informativeUsageError<T: ArgumentProtocol, ClientError>(_ option: Option<T?>) -> CommandantError<ClientError> {
return informativeUsageError("--\(option.key) (\(T.name))", option: option)
}
/// Constructs an error that describes how to use the given boolean option.
internal func informativeUsageError<ClientError>(_ option: Option<Bool>) -> CommandantError<ClientError> {
let key = option.key
return informativeUsageError((option.defaultValue ? "--no-\(key)" : "--\(key)"), option: option)
}
// MARK: - migration support
@available(*, unavailable, message: "Use ErrorProtocol instead of ClientErrorType")
public typealias ClientErrorType = Error

View file

@ -0,0 +1,75 @@
//
// HelpCommand.swift
// Commandant
//
// Created by Justin Spahr-Summers on 2014-10-10.
// Copyright (c) 2014 Carthage. All rights reserved.
//
import Foundation
/// A basic implementation of a `help` command, using information available in a
/// `CommandRegistry`.
///
/// If you want to use this command, initialize it with the registry, then add
/// it to that same registry:
///
/// let commands: CommandRegistry<MyErrorType> =
/// let helpCommand = HelpCommand(registry: commands)
/// commands.register(helpCommand)
public struct HelpCommand<ClientError: Error>: CommandProtocol {
public typealias Options = HelpOptions<ClientError>
public let verb = "help"
public let function = "Display general or command-specific help"
private let registry: CommandRegistry<ClientError>
/// Initializes the command to provide help from the given registry of
/// commands.
public init(registry: CommandRegistry<ClientError>) {
self.registry = registry
}
public func run(_ options: Options) -> Result<(), ClientError> {
if let verb = options.verb {
if let command = self.registry[verb] {
print(command.function)
if let usageError = command.usage() {
print("\n\(usageError)")
}
return .success(())
} else {
fputs("Unrecognized command: '\(verb)'\n", stderr)
}
}
print("Available commands:\n")
let maxVerbLength = self.registry.commands.map { $0.verb.characters.count }.max() ?? 0
for command in self.registry.commands {
let padding = repeatElement(Character(" "), count: maxVerbLength - command.verb.characters.count)
print(" \(command.verb)\(String(padding)) \(command.function)")
}
return .success(())
}
}
public struct HelpOptions<ClientError: Error>: OptionsProtocol {
fileprivate let verb: String?
private init(verb: String?) {
self.verb = verb
}
private static func create(_ verb: String) -> HelpOptions {
return self.init(verb: (verb == "" ? nil : verb))
}
public static func evaluate(_ m: CommandMode) -> Result<HelpOptions, CommandantError<ClientError>> {
return create
<*> m <| Argument(defaultValue: "", usage: "the command to display help for")
}
}

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.11.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2014 Carthage. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View file

@ -0,0 +1,14 @@
//
// LinuxSupport.swift
// Commandant
//
// Created by Norio Nomura on 3/26/16.
// Copyright © 2016 Carthage. All rights reserved.
//
import Foundation
// swift-corelibs-foundation is still written in Swift 2 API.
#if os(Linux)
typealias Process = Task
#endif

View file

@ -0,0 +1,214 @@
//
// Option.swift
// Commandant
//
// Created by Justin Spahr-Summers on 2014-11-21.
// Copyright (c) 2014 Carthage. All rights reserved.
//
import Foundation
/// Represents a record of options for a command, which can be parsed from
/// a list of command-line arguments.
///
/// This is most helpful when used in conjunction with the `Option` and `Switch`
/// types, and `<*>` and `<|` combinators.
///
/// Example:
///
/// struct LogOptions: OptionsProtocol {
/// let verbosity: Int
/// let outputFilename: String
/// let logName: String
///
/// static func create(verbosity: Int)(outputFilename: String)(logName: String) -> LogOptions {
/// return LogOptions(verbosity: verbosity, outputFilename: outputFilename, logName: logName)
/// }
///
/// static func evaluate(m: CommandMode) -> Result<LogOptions, CommandantError<YourErrorType>> {
/// return create
/// <*> m <| Option(key: "verbose", defaultValue: 0, usage: "the verbosity level with which to read the logs")
/// <*> m <| Option(key: "outputFilename", defaultValue: "", usage: "a file to print output to, instead of stdout")
/// <*> m <| Switch(flag: "d", key: "delete", defaultValue: false, usage: "delete the logs when finished")
/// <*> m <| Option(usage: "the log to read")
/// }
/// }
public protocol OptionsProtocol {
associatedtype ClientError: Error
/// Evaluates this set of options in the given mode.
///
/// Returns the parsed options or a `UsageError`.
static func evaluate(_ m: CommandMode) -> Result<Self, CommandantError<ClientError>>
}
/// An `OptionsProtocol` that has no options.
public struct NoOptions<ClientError: Error>: OptionsProtocol {
public init() {}
public static func evaluate(_ m: CommandMode) -> Result<NoOptions, CommandantError<ClientError>> {
return .success(NoOptions())
}
}
/// Describes an option that can be provided on the command line.
public struct Option<T> {
/// The key that controls this option. For example, a key of `verbose` would
/// be used for a `--verbose` option.
public let key: String
/// The default value for this option. This is the value that will be used
/// if the option is never explicitly specified on the command line.
public let defaultValue: T
/// A human-readable string describing the purpose of this option. This will
/// be shown in help messages.
///
/// For boolean operations, this should describe the effect of _not_ using
/// the default value (i.e., what will happen if you disable/enable the flag
/// differently from the default).
public let usage: String
public init(key: String, defaultValue: T, usage: String) {
self.key = key
self.defaultValue = defaultValue
self.usage = usage
}
}
extension Option: CustomStringConvertible {
public var description: String {
return "--\(key)"
}
}
// Inspired by the Argo library:
// https://github.com/thoughtbot/Argo
/*
Copyright (c) 2014 thoughtbot, inc.
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
infix operator <*> : LogicalDisjunctionPrecedence
infix operator <| : MultiplicationPrecedence
/// Applies `f` to the value in the given result.
///
/// In the context of command-line option parsing, this is used to chain
/// together the parsing of multiple arguments. See OptionsProtocol for an example.
public func <*> <T, U, ClientError>(f: (T) -> U, value: Result<T, CommandantError<ClientError>>) -> Result<U, CommandantError<ClientError>> {
return value.map(f)
}
/// Applies the function in `f` to the value in the given result.
///
/// In the context of command-line option parsing, this is used to chain
/// together the parsing of multiple arguments. See OptionsProtocol for an example.
public func <*> <T, U, ClientError>(f: Result<((T) -> U), CommandantError<ClientError>>, value: Result<T, CommandantError<ClientError>>) -> Result<U, CommandantError<ClientError>> {
switch (f, value) {
case let (.failure(left), .failure(right)):
return .failure(combineUsageErrors(left, right))
case let (.failure(left), .success):
return .failure(left)
case let (.success, .failure(right)):
return .failure(right)
case let (.success(f), .success(value)):
let newValue = f(value)
return .success(newValue)
}
}
/// Evaluates the given option in the given mode.
///
/// If parsing command line arguments, and no value was specified on the command
/// line, the option's `defaultValue` is used.
public func <| <T: ArgumentProtocol, ClientError>(mode: CommandMode, option: Option<T>) -> Result<T, CommandantError<ClientError>> {
let wrapped = Option<T?>(key: option.key, defaultValue: option.defaultValue, usage: option.usage)
// Since we are passing a non-nil default value, we can safely unwrap the
// result.
return (mode <| wrapped).map { $0! }
}
/// Evaluates the given option in the given mode.
///
/// If parsing command line arguments, and no value was specified on the command
/// line, `nil` is used.
public func <| <T: ArgumentProtocol, ClientError>(mode: CommandMode, option: Option<T?>) -> Result<T?, CommandantError<ClientError>> {
let key = option.key
switch mode {
case let .arguments(arguments):
var stringValue: String?
switch arguments.consumeValue(forKey: key) {
case let .success(value):
stringValue = value
case let .failure(error):
switch error {
case let .usageError(description):
return .failure(.usageError(description: description))
case .commandError:
fatalError("CommandError should be impossible when parameterized over NoError")
}
}
if let stringValue = stringValue {
if let value = T.from(string: stringValue) {
return .success(value)
}
let description = "Invalid value for '--\(key)': \(stringValue)"
return .failure(.usageError(description: description))
} else {
return .success(option.defaultValue)
}
case .usage:
return .failure(informativeUsageError(option))
}
}
/// Evaluates the given boolean option in the given mode.
///
/// If parsing command line arguments, and no value was specified on the command
/// line, the option's `defaultValue` is used.
public func <| <ClientError>(mode: CommandMode, option: Option<Bool>) -> Result<Bool, CommandantError<ClientError>> {
switch mode {
case let .arguments(arguments):
if let value = arguments.consumeBoolean(forKey: option.key) {
return .success(value)
} else {
return .success(option.defaultValue)
}
case .usage:
return .failure(informativeUsageError(option))
}
}
// MARK: - migration support
@available(*, unavailable, renamed: "OptionsProtocol")
public typealias OptionsType = OptionsProtocol

View file

@ -0,0 +1,63 @@
//
// Switch.swift
// Commandant
//
// Created by Neil Pankey on 3/31/15.
// Copyright (c) 2015 Carthage. All rights reserved.
//
/// Describes a parameterless command line flag that defaults to false and can only
/// be switched on. Canonical examples include `--force` and `--recurse`.
///
/// For a boolean toggle that can be enabled and disabled use `Option<Bool>`.
public struct Switch {
/// The key that enables this switch. For example, a key of `verbose` would be
/// used for a `--verbose` option.
public let key: String
/// Optional single letter flag that enables this switch. For example, `-v` would
/// be used as a shorthand for `--verbose`.
///
/// Multiple flags can be grouped together as a single argument and will split
/// when parsing (e.g. `rm -rf` treats 'r' and 'f' as inidividual flags).
public let flag: Character?
/// A human-readable string describing the purpose of this option. This will
/// be shown in help messages.
public let usage: String
public init(flag: Character? = nil, key: String, usage: String) {
self.flag = flag
self.key = key
self.usage = usage
}
}
extension Switch: CustomStringConvertible {
public var description: String {
var options = "--\(key)"
if let flag = self.flag {
options += "|-\(flag)"
}
return options
}
}
/// Evaluates the given boolean switch in the given mode.
///
/// If parsing command line arguments, and no value was specified on the command
/// line, the option's `defaultValue` is used.
public func <| <ClientError> (mode: CommandMode, option: Switch) -> Result<Bool, CommandantError<ClientError>> {
switch mode {
case let .arguments(arguments):
var enabled = arguments.consume(key: option.key)
if let flag = option.flag {
enabled = arguments.consumeBoolean(flag: flag)
}
return .success(enabled)
case .usage:
return .failure(informativeUsageError(option.description, usage: option.usage))
}
}

View file

@ -0,0 +1,8 @@
// Copyright (c) 2015 Rob Rix. All rights reserved.
/// Project version number for Result.
extern double ResultVersionNumber;
/// Project version string for Result.
extern const unsigned char ResultVersionString[];

View file

@ -0,0 +1,192 @@
// Copyright (c) 2015 Rob Rix. All rights reserved.
/// An enum representing either a failure with an explanatory error, or a success with a result value.
public enum Result<T, Error: Swift.Error>: ResultProtocol, CustomStringConvertible, CustomDebugStringConvertible {
case success(T)
case failure(Error)
// MARK: Constructors
/// Constructs a success wrapping a `value`.
public init(value: T) {
self = .success(value)
}
/// Constructs a failure wrapping an `error`.
public init(error: Error) {
self = .failure(error)
}
/// Constructs a result from an Optional, failing with `Error` if `nil`.
public init(_ value: T?, failWith: @autoclosure () -> Error) {
self = value.map(Result.success) ?? .failure(failWith())
}
/// Constructs a result from a function that uses `throw`, failing with `Error` if throws.
public init(_ f: @autoclosure () throws -> T) {
self.init(attempt: f)
}
/// Constructs a result from a function that uses `throw`, failing with `Error` if throws.
public init(attempt f: () throws -> T) {
do {
self = .success(try f())
} catch {
self = .failure(error as! Error)
}
}
// MARK: Deconstruction
/// Returns the value from `Success` Results or `throw`s the error.
public func dematerialize() throws -> T {
switch self {
case let .success(value):
return value
case let .failure(error):
throw error
}
}
/// Case analysis for Result.
///
/// Returns the value produced by applying `ifFailure` to `Failure` Results, or `ifSuccess` to `Success` Results.
public func analysis<Result>(ifSuccess: (T) -> Result, ifFailure: (Error) -> Result) -> Result {
switch self {
case let .success(value):
return ifSuccess(value)
case let .failure(value):
return ifFailure(value)
}
}
// MARK: Errors
/// The domain for errors constructed by Result.
public static var errorDomain: String { return "com.antitypical.Result" }
/// The userInfo key for source functions in errors constructed by Result.
public static var functionKey: String { return "\(errorDomain).function" }
/// The userInfo key for source file paths in errors constructed by Result.
public static var fileKey: String { return "\(errorDomain).file" }
/// The userInfo key for source file line numbers in errors constructed by Result.
public static var lineKey: String { return "\(errorDomain).line" }
/// Constructs an error.
public static func error(_ message: String? = nil, function: String = #function, file: String = #file, line: Int = #line) -> NSError {
var userInfo: [String: Any] = [
functionKey: function,
fileKey: file,
lineKey: line,
]
if let message = message {
userInfo[NSLocalizedDescriptionKey] = message
}
return NSError(domain: errorDomain, code: 0, userInfo: userInfo)
}
// MARK: CustomStringConvertible
public var description: String {
return analysis(
ifSuccess: { ".success(\($0))" },
ifFailure: { ".failure(\($0))" })
}
// MARK: CustomDebugStringConvertible
public var debugDescription: String {
return description
}
}
// MARK: - Derive result from failable closure
public func materialize<T>(_ f: () throws -> T) -> Result<T, NSError> {
return materialize(try f())
}
public func materialize<T>(_ f: @autoclosure () throws -> T) -> Result<T, NSError> {
do {
return .success(try f())
} catch let error as NSError {
return .failure(error)
}
}
// MARK: - Cocoa API conveniences
#if !os(Linux)
/// Constructs a Result with the result of calling `try` with an error pointer.
///
/// This is convenient for wrapping Cocoa API which returns an object or `nil` + an error, by reference. e.g.:
///
/// Result.try { NSData(contentsOfURL: URL, options: .DataReadingMapped, error: $0) }
public func `try`<T>(_ function: String = #function, file: String = #file, line: Int = #line, `try`: (NSErrorPointer) -> T?) -> Result<T, NSError> {
var error: NSError?
return `try`(&error).map(Result.success) ?? .failure(error ?? Result<T, NSError>.error(function: function, file: file, line: line))
}
/// Constructs a Result with the result of calling `try` with an error pointer.
///
/// This is convenient for wrapping Cocoa API which returns a `Bool` + an error, by reference. e.g.:
///
/// Result.try { NSFileManager.defaultManager().removeItemAtURL(URL, error: $0) }
public func `try`(_ function: String = #function, file: String = #file, line: Int = #line, `try`: (NSErrorPointer) -> Bool) -> Result<(), NSError> {
var error: NSError?
return `try`(&error) ?
.success(())
: .failure(error ?? Result<(), NSError>.error(function: function, file: file, line: line))
}
#endif
// MARK: - ErrorProtocolConvertible conformance
extension NSError: ErrorProtocolConvertible {
public static func error(from error: Swift.Error) -> Self {
func cast<T: NSError>(_ error: Swift.Error) -> T {
return error as! T
}
return cast(error)
}
}
// MARK: -
/// An error that is impossible to construct.
///
/// This can be used to describe `Result`s where failures will never
/// be generated. For example, `Result<Int, NoError>` describes a result that
/// contains an `Int`eger and is guaranteed never to be a `Failure`.
public enum NoError: Swift.Error { }
// MARK: - migration support
extension Result {
@available(*, unavailable, renamed: "success")
public static func Success(_: T) -> Result<T, Error> {
fatalError()
}
@available(*, unavailable, renamed: "failure")
public static func Failure(_: Error) -> Result<T, Error> {
fatalError()
}
}
extension NSError {
@available(*, unavailable, renamed: "error(from:)")
public static func errorFromErrorType(_ error: Swift.Error) -> Self {
fatalError()
}
}
import Foundation

View file

@ -0,0 +1,182 @@
// Copyright (c) 2015 Rob Rix. All rights reserved.
/// A type that can represent either failure with an error or success with a result value.
public protocol ResultProtocol {
associatedtype Value
associatedtype Error: Swift.Error
/// Constructs a successful result wrapping a `value`.
init(value: Value)
/// Constructs a failed result wrapping an `error`.
init(error: Error)
/// Case analysis for ResultProtocol.
///
/// Returns the value produced by appliying `ifFailure` to the error if self represents a failure, or `ifSuccess` to the result value if self represents a success.
func analysis<U>(ifSuccess: (Value) -> U, ifFailure: (Error) -> U) -> U
/// Returns the value if self represents a success, `nil` otherwise.
///
/// A default implementation is provided by a protocol extension. Conforming types may specialize it.
var value: Value? { get }
/// Returns the error if self represents a failure, `nil` otherwise.
///
/// A default implementation is provided by a protocol extension. Conforming types may specialize it.
var error: Error? { get }
}
public extension ResultProtocol {
/// Returns the value if self represents a success, `nil` otherwise.
public var value: Value? {
return analysis(ifSuccess: { $0 }, ifFailure: { _ in nil })
}
/// Returns the error if self represents a failure, `nil` otherwise.
public var error: Error? {
return analysis(ifSuccess: { _ in nil }, ifFailure: { $0 })
}
/// Returns a new Result by mapping `Success`es values using `transform`, or re-wrapping `Failure`s errors.
public func map<U>(_ transform: (Value) -> U) -> Result<U, Error> {
return flatMap { .success(transform($0)) }
}
/// Returns the result of applying `transform` to `Success`es values, or re-wrapping `Failure`s errors.
public func flatMap<U>(_ transform: (Value) -> Result<U, Error>) -> Result<U, Error> {
return analysis(
ifSuccess: transform,
ifFailure: Result<U, Error>.failure)
}
/// Returns a new Result by mapping `Failure`'s values using `transform`, or re-wrapping `Success`es values.
public func mapError<Error2>(_ transform: (Error) -> Error2) -> Result<Value, Error2> {
return flatMapError { .failure(transform($0)) }
}
/// Returns the result of applying `transform` to `Failure`s errors, or re-wrapping `Success`es values.
public func flatMapError<Error2>(_ transform: (Error) -> Result<Value, Error2>) -> Result<Value, Error2> {
return analysis(
ifSuccess: Result<Value, Error2>.success,
ifFailure: transform)
}
}
public extension ResultProtocol {
// MARK: Higher-order functions
/// Returns `self.value` if this result is a .Success, or the given value otherwise. Equivalent with `??`
public func recover(_ value: @autoclosure () -> Value) -> Value {
return self.value ?? value()
}
/// Returns this result if it is a .Success, or the given result otherwise. Equivalent with `??`
public func recover(with result: @autoclosure () -> Self) -> Self {
return analysis(
ifSuccess: { _ in self },
ifFailure: { _ in result() })
}
}
/// Protocol used to constrain `tryMap` to `Result`s with compatible `Error`s.
public protocol ErrorProtocolConvertible: Swift.Error {
static func error(from error: Swift.Error) -> Self
}
public extension ResultProtocol where Error: ErrorProtocolConvertible {
/// Returns the result of applying `transform` to `Success`es values, or wrapping thrown errors.
public func tryMap<U>(_ transform: (Value) throws -> U) -> Result<U, Error> {
return flatMap { value in
do {
return .success(try transform(value))
}
catch {
let convertedError = Error.error(from: error)
// Revisit this in a future version of Swift. https://twitter.com/jckarter/status/672931114944696321
return .failure(convertedError)
}
}
}
}
// MARK: - Operators
infix operator &&& : LogicalConjunctionPrecedence
/// Returns a Result with a tuple of `left` and `right` values if both are `Success`es, or re-wrapping the error of the earlier `Failure`.
public func &&& <L: ResultProtocol, R: ResultProtocol> (left: L, right: @autoclosure () -> R) -> Result<(L.Value, R.Value), L.Error>
where L.Error == R.Error
{
return left.flatMap { left in right().map { right in (left, right) } }
}
precedencegroup ChainingPrecedence {
associativity: left
higherThan: TernaryPrecedence
}
infix operator >>- : ChainingPrecedence
/// Returns the result of applying `transform` to `Success`es values, or re-wrapping `Failure`s errors.
///
/// This is a synonym for `flatMap`.
public func >>- <T: ResultProtocol, U> (result: T, transform: (T.Value) -> Result<U, T.Error>) -> Result<U, T.Error> {
return result.flatMap(transform)
}
/// Returns `true` if `left` and `right` are both `Success`es and their values are equal, or if `left` and `right` are both `Failure`s and their errors are equal.
public func == <T: ResultProtocol> (left: T, right: T) -> Bool
where T.Value: Equatable, T.Error: Equatable
{
if let left = left.value, let right = right.value {
return left == right
} else if let left = left.error, let right = right.error {
return left == right
}
return false
}
/// Returns `true` if `left` and `right` represent different cases, or if they represent the same case but different values.
public func != <T: ResultProtocol> (left: T, right: T) -> Bool
where T.Value: Equatable, T.Error: Equatable
{
return !(left == right)
}
/// Returns the value of `left` if it is a `Success`, or `right` otherwise. Short-circuits.
public func ?? <T: ResultProtocol> (left: T, right: @autoclosure () -> T.Value) -> T.Value {
return left.recover(right())
}
/// Returns `left` if it is a `Success`es, or `right` otherwise. Short-circuits.
public func ?? <T: ResultProtocol> (left: T, right: @autoclosure () -> T) -> T {
return left.recover(with: right())
}
// MARK: - migration support
@available(*, unavailable, renamed: "ResultProtocol")
public typealias ResultType = ResultProtocol
@available(*, unavailable, renamed: "Error")
public typealias ResultErrorType = Swift.Error
@available(*, unavailable, renamed: "ErrorProtocolConvertible")
public typealias ErrorTypeConvertible = ErrorProtocolConvertible
extension ResultProtocol {
@available(*, unavailable, renamed: "recover(with:)")
public func recoverWith(_ result: @autoclosure () -> Self) -> Self {
fatalError()
}
}
extension ErrorProtocolConvertible {
@available(*, unavailable, renamed: "error(from:)")
public static func errorFromErrorType(_ error: Swift.Error) -> Self {
fatalError()
}
}