mirror of
https://github.com/mas-cli/mas
synced 2024-11-24 20:43:10 +00:00
Commit dependencies
Don't depend on bootstrapping for most cases
This commit is contained in:
parent
f4a984768d
commit
661259574e
16 changed files with 1491 additions and 4 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -70,7 +70,6 @@ Network Trash Folder
|
|||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
Seeds/
|
||||
mas-cli.zip
|
||||
mas-cli.dSYM.zip
|
||||
mas.xcarchive.zip
|
||||
|
|
|
@ -8,9 +8,6 @@ env:
|
|||
- LC_ALL=en_US.UTF-8
|
||||
- LANGUAGE=en_US.UTF-8
|
||||
|
||||
install:
|
||||
- script/bootstrap
|
||||
|
||||
script:
|
||||
- script/build
|
||||
|
||||
|
|
95
Seeds/Commandant/Sources/Commandant/Argument.swift
Normal file
95
Seeds/Commandant/Sources/Commandant/Argument.swift
Normal 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))
|
||||
}
|
||||
}
|
191
Seeds/Commandant/Sources/Commandant/ArgumentParser.swift
Normal file
191
Seeds/Commandant/Sources/Commandant/ArgumentParser.swift
Normal 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
|
||||
}
|
||||
}
|
41
Seeds/Commandant/Sources/Commandant/ArgumentProtocol.swift
Normal file
41
Seeds/Commandant/Sources/Commandant/ArgumentProtocol.swift
Normal 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 }
|
||||
}
|
224
Seeds/Commandant/Sources/Commandant/Command.swift
Normal file
224
Seeds/Commandant/Sources/Commandant/Command.swift
Normal 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)
|
||||
}
|
||||
}
|
17
Seeds/Commandant/Sources/Commandant/Commandant.h
Normal file
17
Seeds/Commandant/Sources/Commandant/Commandant.h
Normal 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>
|
147
Seeds/Commandant/Sources/Commandant/Errors.swift
Normal file
147
Seeds/Commandant/Sources/Commandant/Errors.swift
Normal 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
|
75
Seeds/Commandant/Sources/Commandant/HelpCommand.swift
Normal file
75
Seeds/Commandant/Sources/Commandant/HelpCommand.swift
Normal 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")
|
||||
}
|
||||
}
|
28
Seeds/Commandant/Sources/Commandant/Info.plist
Normal file
28
Seeds/Commandant/Sources/Commandant/Info.plist
Normal 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>
|
14
Seeds/Commandant/Sources/Commandant/LinuxSupport.swift
Normal file
14
Seeds/Commandant/Sources/Commandant/LinuxSupport.swift
Normal 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
|
214
Seeds/Commandant/Sources/Commandant/Option.swift
Normal file
214
Seeds/Commandant/Sources/Commandant/Option.swift
Normal 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
|
63
Seeds/Commandant/Sources/Commandant/Switch.swift
Normal file
63
Seeds/Commandant/Sources/Commandant/Switch.swift
Normal 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))
|
||||
}
|
||||
}
|
8
Seeds/Result/Result/Result.h
Normal file
8
Seeds/Result/Result/Result.h
Normal 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[];
|
||||
|
192
Seeds/Result/Result/Result.swift
Normal file
192
Seeds/Result/Result/Result.swift
Normal 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
|
182
Seeds/Result/Result/ResultProtocol.swift
Normal file
182
Seeds/Result/Result/ResultProtocol.swift
Normal 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()
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue