diff --git a/Package.swift b/Package.swift index 496ee80..0069778 100644 --- a/Package.swift +++ b/Package.swift @@ -1,5 +1,26 @@ +// swift-tools-version:5.3 + import PackageDescription let package = Package( - name: "Swiftline" + name: "Swiftline", + products: [ + .library( + name: "Swiftline", + targets: ["Swiftline"]) + ], + targets: [ + .target( + name: "Swiftline", + swiftSettings: [ + .define("DEBUG", .when(configuration: .debug)), + ]), + .testTarget( + name: "SwiftlineTests", + dependencies: ["Swiftline"]), + .testTarget( + name: "GeneralTests", + dependencies: ["Swiftline"]), + ], + swiftLanguageVersions: [.v4_2, .v5] ) diff --git a/Source/Args.swift b/Source/Args.swift index c70c564..965e635 100644 --- a/Source/Args.swift +++ b/Source/Args.swift @@ -11,7 +11,7 @@ public class Args { /// Return the list of arguments passed to the script - open static var all: [String] { + public static var all: [String] { return ProcessInfo.arguments } @@ -21,7 +21,7 @@ public class Args { /// The flags are recognized as short flags `-f` or long flags `--force` /// The flag value will be the argument that follows the flag /// `--` is used to mark the terminatin of the flags - open static var parsed: ParsedArgs { + public static var parsed: ParsedArgs { if let result = cachedResults , ProcessInfo.cacheResults { return result diff --git a/Source/Argument.swift b/Source/Argument.swift index 59187c0..0f6b737 100644 --- a/Source/Argument.swift +++ b/Source/Argument.swift @@ -89,8 +89,8 @@ extension String { distanceFromEndIndex = 0 } - let actualRange = (characters.index(startIndex, offsetBy: range.lowerBound) ..< characters.index(endIndex, offsetBy: -distanceFromEndIndex)) + let actualRange = (self.index(startIndex, offsetBy: range.lowerBound) ..< self.index(endIndex, offsetBy: -distanceFromEndIndex)) - return self[actualRange] + return String(self[actualRange]) } } diff --git a/Source/ChooseSettings.swift b/Source/ChooseSettings.swift index b033e78..d614a5a 100644 --- a/Source/ChooseSettings.swift +++ b/Source/ChooseSettings.swift @@ -24,7 +24,7 @@ public enum ChoiceIndexType { public class ChooseSettings { typealias Item = T - var choices: [(choice: String, callback: (Void) -> T)] = [] + var choices: [(choice: String, callback: () -> T)] = [] /// Prompt message to use public var promptQuestion = "" @@ -41,7 +41,7 @@ public class ChooseSettings { - parameter choice: Item name - parameter callback: callback called when the item is selected, the value returned from this call back will be returned from choose */ - public func addChoice(_ choice: String..., callback: @escaping (Void) -> T) { + public func addChoice(_ choice: String..., callback: @escaping () -> T) { choice.forEach { choices.append(($0, callback)) } @@ -76,7 +76,7 @@ public class ChooseSettings { } func choiceWithStringValue(_ value: String) -> T? { - let possibleIndex = choices.index { $0.choice == value } + let possibleIndex = choices.firstIndex { $0.choice == value } if let index = possibleIndex { return choices[index].callback() } diff --git a/Source/Env.swift b/Source/Env.swift index c09189c..b6c201d 100644 --- a/Source/Env.swift +++ b/Source/Env.swift @@ -57,7 +57,7 @@ public class Env { self.keys .map { String($0) } .filter { $0 != nil } - .forEach{ self.set($0!, nil) } + .forEach{ self.set($0, nil) } } /** diff --git a/Source/RunResults.swift b/Source/RunResults.swift index a3b0e87..d8c81c5 100644 --- a/Source/RunResults.swift +++ b/Source/RunResults.swift @@ -36,5 +36,5 @@ func splitCommandToArgs(_ command: String) -> [String] { func readPipe(_ pipe: TaskPipe) -> String { let data = pipe.read() - return NSString(data: data as Data, encoding: String.Encoding.utf8.rawValue) as? String ?? "" + return NSString(data: data as Data, encoding: String.Encoding.utf8.rawValue) as String? ?? "" } diff --git a/Source/StringStyle.swift b/Source/StringStyle.swift index 4864460..188b3ae 100644 --- a/Source/StringStyle.swift +++ b/Source/StringStyle.swift @@ -54,15 +54,15 @@ extension StringStyle { } fileprivate func addCommandSeperators(_ string: String) -> String { - var rangeWithInset = (string.characters.index(after: string.startIndex) ..< string.characters.index(before: string.endIndex)) + var rangeWithInset = (string.index(after: string.startIndex) ..< string.index(before: string.endIndex)) let newString = string.replacingOccurrences(of: startOfCode, with: ";", options: .literal, range: rangeWithInset) - rangeWithInset = (newString.characters.index(after: newString.startIndex) ..< newString.characters.index(before: newString.endIndex)) + rangeWithInset = (newString.index(after: newString.startIndex) ..< newString.index(before: newString.endIndex)) return newString.replacingOccurrences(of: "m;", with: ";", options: .literal, range: rangeWithInset) } fileprivate func removeEndingCode(_ string: String) -> String { - let rangeWithInset = (string.characters.index(after: string.startIndex) ..< string.endIndex) + let rangeWithInset = (string.index(after: string.startIndex) ..< string.endIndex) return string.replacingOccurrences(of: endingColorCode(), with: "", options: .literal, range: rangeWithInset) } diff --git a/Source/StringStyleColorizer.swift b/Source/StringStyleColorizer.swift index 6873ada..81fbcde 100644 --- a/Source/StringStyleColorizer.swift +++ b/Source/StringStyleColorizer.swift @@ -9,7 +9,7 @@ public extension String { - public struct StringStyleColorizer { + struct StringStyleColorizer { let string: String diff --git a/Sources/.DS_Store b/Sources/.DS_Store new file mode 100644 index 0000000..d13d0d6 Binary files /dev/null and b/Sources/.DS_Store differ diff --git a/Sources/SwiftLine/Agree.swift b/Sources/SwiftLine/Agree.swift new file mode 100644 index 0000000..12f7ba4 --- /dev/null +++ b/Sources/SwiftLine/Agree.swift @@ -0,0 +1,23 @@ +// +// Agree.swift +// Agree +// +// Created by Omar Abdelhafith on 03/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + +/** + Displays a yes/no prompt to the user + + - parameter prompt: The prompt to display + - returns: the user decision + */ +public func agree(_ prompt: String) -> Bool { + PromptSettings.print(prompt, terminator: " ") + let value = readStringOrEmpty() + + let settings = AgreeSettings(prompt: prompt) + let validatedValue = askForValidatedItem(originalValue: value, validator: settings) + + return settings.isPositive(validatedValue) +} diff --git a/Sources/SwiftLine/AgreeSettings.swift b/Sources/SwiftLine/AgreeSettings.swift new file mode 100644 index 0000000..7c70803 --- /dev/null +++ b/Sources/SwiftLine/AgreeSettings.swift @@ -0,0 +1,41 @@ +// +// AgreeSettings.swift +// AgreeSettings +// +// Created by Omar Abdelhafith on 03/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + + +class AgreeSettings: AskerValidator { + + let positiveValues = ["Yes", "yes", "Y", "y"] + let negativeValues = ["No", "no", "N", "n"] + + let prompt: String + + init(prompt: String) { + self.prompt = prompt + } + + func validatedItem(forString string: String) -> String { + return string + } + + func invalidItemMessage(_ string: String?) -> String? { + if let message = string , positiveValues.contains(message) || negativeValues.contains(message) { + return nil + } + + return "Please enter \"yes\" or \"no\"." + } + + func newItemPromptMessage() -> String { + return "\(prompt) " + } + + func isPositive(_ item: String) -> Bool { + return positiveValues.contains(item) + } + +} diff --git a/Sources/SwiftLine/ArgConvertible.swift b/Sources/SwiftLine/ArgConvertible.swift new file mode 100644 index 0000000..67a874c --- /dev/null +++ b/Sources/SwiftLine/ArgConvertible.swift @@ -0,0 +1,63 @@ +// +// ArgConvertible.swift +// ArgConvertible +// +// Created by Omar Abdelhafith on 02/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + + +/** + * Any type that extends ArgConvertibleType can be used in ask and choose + */ +public protocol ArgConvertibleType { + + /// Create an instance out of a string + static func fromString(_ string: String) -> Self? + + /// Return the display name of a type + static func typeName() -> String +} + + +extension Int: ArgConvertibleType { + public static func fromString(_ string: String) -> Int? { + return Int(string) + } + + public static func typeName() -> String { + return "Integer" + } +} + + +extension Double: ArgConvertibleType { + public static func fromString(_ string: String) -> Double? { + return Double(string) + } + + public static func typeName() -> String { + return "Double" + } +} + +extension Float: ArgConvertibleType { + public static func fromString(_ string: String) -> Float? { + return Float(string) + } + + public static func typeName() -> String { + return "Float" + } +} + + +extension String: ArgConvertibleType { + public static func fromString(_ string: String) -> String? { + return string + } + + public static func typeName() -> String { + return "String" + } +} diff --git a/Sources/SwiftLine/Args.swift b/Sources/SwiftLine/Args.swift new file mode 100644 index 0000000..965e635 --- /dev/null +++ b/Sources/SwiftLine/Args.swift @@ -0,0 +1,61 @@ +// +// Args.swift +// Swiftline +// +// Created by Omar Abdelhafith on 25/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + + +/// Return the command line arguments passed to the script +public class Args { + + /// Return the list of arguments passed to the script + public static var all: [String] { + return ProcessInfo.arguments + } + + static var cachedResults: ParsedArgs? + + /// Return a parsed list of arguments containing the flags and the parameters passed to the scripts + /// The flags are recognized as short flags `-f` or long flags `--force` + /// The flag value will be the argument that follows the flag + /// `--` is used to mark the terminatin of the flags + public static var parsed: ParsedArgs { + + if let result = cachedResults , ProcessInfo.cacheResults { + return result + } + + var parsedFlags = [String: String]() + let parsedArgs = ArgsParser.parseFlags(all) + + parsedArgs.0.forEach { + parsedFlags[$0.argument.name] = $0.value ?? "" + } + + var arguments = parsedArgs.1 + + // the first argument is always the executable's name + var commandName = "" + if let firstArgument = arguments.first { // just in case! + commandName = firstArgument + arguments.removeFirst(1) + } + + cachedResults = ParsedArgs(command: commandName, flags: parsedFlags, parameters: arguments) + return cachedResults! + } +} + + +public struct ParsedArgs { + /// The name of the executable that was invoked from the command line + public let command: String + + /// Parsed flags will be prepred in a dictionary, the key is the flag and the value is the flag value + public let flags: [String: String] + + /// List of parameters passed to the script + public let parameters: [String] +} diff --git a/Sources/SwiftLine/ArgsParser.swift b/Sources/SwiftLine/ArgsParser.swift new file mode 100644 index 0000000..3ce0c13 --- /dev/null +++ b/Sources/SwiftLine/ArgsParser.swift @@ -0,0 +1,53 @@ +// +// ArgsParser.swift +// Swiftline +// +// Created by Omar Abdelhafith on 27/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + +class ArgsParser { + + static func parseFlags(_ args: [String]) -> ([Option], [String]) { + var options = [Option]() + var others = [String]() + var previousArgument: Argument? + var argsTerminated = false + + for argumentString in args { + let argument = Argument(argumentString) + defer { previousArgument = argument } + + if argsTerminated { + others += [argumentString] + continue + } + + if argument.isFlagTerminator { + argsTerminated = true + continue + } + + if argument.isFlag { + options += [Option(argument: argument)] + continue + } + + if let previousArgument = previousArgument , previousArgument.isFlag { + updatelastOption(forArray: &options, withValue: argumentString) + } else { + others += [argument.name] + } + } + + return (options, others) + + } + + static func updatelastOption(forArray array: inout [Option], withValue value: String) { + var previousOption = array.last! + previousOption.value = value + array.removeLast() + array += [previousOption] + } +} diff --git a/Sources/SwiftLine/Argument.swift b/Sources/SwiftLine/Argument.swift new file mode 100644 index 0000000..0f6b737 --- /dev/null +++ b/Sources/SwiftLine/Argument.swift @@ -0,0 +1,96 @@ +// +// Argument.swift +// Swiftline +// +// Created by Omar Abdelhafith on 26/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + +import Foundation + + +struct Option { + let argument: Argument + var value: String? + + init(argument: Argument, value: String? = nil) { + self.argument = argument + self.value = value + } +} + +struct Argument { + + enum ArgumentType { + case shortFlag + case longFlag + case notAFlag + case flagsTerminator + + var isFlag: Bool { + return self != .notAFlag + } + + var isFlagTerminator: Bool { + return self == .flagsTerminator + } + + init(_ argument: String) { + + if argument == "--" { + self = .flagsTerminator + } else if argument.hasPrefix("--") { + self = .longFlag + } else if argument.hasPrefix("-") { + self = .shortFlag + } else { + self = .notAFlag + } + } + } + + let type: ArgumentType + let argument: String + + init(_ argument: String) { + self.type = ArgumentType(argument) + self.argument = argument + } + + var isFlag: Bool { + return type.isFlag + } + + var isFlagTerminator: Bool { + return type.isFlagTerminator + } + + var name: String { + switch type { + case .notAFlag: + return argument + case .shortFlag: + return argument[1..) -> String { + let length = self.lengthOfBytes(using: String.Encoding.utf8) + + var distanceFromEndIndex = length - range.upperBound + if distanceFromEndIndex < 0 { + distanceFromEndIndex = 0 + } + + let actualRange = (self.index(startIndex, offsetBy: range.lowerBound) ..< self.index(endIndex, offsetBy: -distanceFromEndIndex)) + + return String(self[actualRange]) + } +} diff --git a/Sources/SwiftLine/Ask.swift b/Sources/SwiftLine/Ask.swift new file mode 100644 index 0000000..4336624 --- /dev/null +++ b/Sources/SwiftLine/Ask.swift @@ -0,0 +1,80 @@ +// +// Asker.swift +// Ask +// +// Created by Omar Abdelhafith on 31/10/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + + +/** + Display a promt to the user + + - parameter prompt: The message to display + - parameter customizationBlock: The block to costumize the prompt before displaying + + - returns: The string enters from the user + */ +public func ask(_ prompt: String, customizationBlock: ((AskSettings) -> Void)? = nil) -> String { + return ask(prompt, type: String.self, customizationBlock: customizationBlock) +} + + +/** + Display a promt to the user + + - parameter prompt:The message to display + - parameter type: The value type to be expected from the user + - parameter customizationBlock: The block to costumize the prompt before displaying + + - returns: The string casted to the type requested + - discussion: If the user enters a wrong type, ask will keep prompting until a correct value has been entered + */ +public func ask(_ prompt: String, type: T.Type, customizationBlock: ((AskSettings) -> Void)? = nil) -> T { + + PromptSettings.print(prompt) + + let settings = getSettings(customizationBlock) + + if settings.confirm { + return getValidatedStringWithConfirmation(settings) + } else { + return getValidatedString(settings) + } +} + + +// MARK:- Internal functions + + +func getValidatedString(_ validator: W) -> T where W.Item == T { + let stringOrEmpty = readStringOrEmpty() + return askForValidatedItem(originalValue: stringOrEmpty, validator: validator) +} + + +func getValidatedStringWithConfirmation(_ validator: W) -> T where W.Item == T { + + while true { + let stringOrEmpty = readStringOrEmpty() + let answer = askForValidatedItem(originalValue: stringOrEmpty, validator: validator) + + if agree("Are you sure?") { + return answer + } else { + PromptSettings.print("? ", terminator: "") + } + } +} + + +func getSettings(_ callback: ((AskSettings) -> Void)?) -> AskSettings { + let settings = AskSettings() + callback?(settings) + return settings +} + + +func readStringOrEmpty() -> String { + return PromptSettings.read() ?? "" +} diff --git a/Sources/SwiftLine/AskSettings.swift b/Sources/SwiftLine/AskSettings.swift new file mode 100644 index 0000000..f1cdcba --- /dev/null +++ b/Sources/SwiftLine/AskSettings.swift @@ -0,0 +1,82 @@ +// +// AskSettings.swift +// AskSettings +// +// Created by Omar Abdelhafith on 03/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + + +/// Settings used to costumize the behaviour of ask() +public class AskSettings { + + /// Default value to set incase the user entered a blank + public var defaultValue: T? + + /// If set to true, another message will follow successful user entry asking the user to confirm + /// his selection + public var confirm = false + + var invalidClousures: [((T) -> Bool, String)] = [] + + + /** + Add an invalid entry case + + - parameter description: The string to be printed to the stdout if the case is invalid + - parameter invalidIfTrue: If true is returned, then the user input was invalid, if false, the + user input was valid. + */ + public func addInvalidCase(_ description: String, invalidIfTrue: @escaping ((T) -> Bool)) { + invalidClousures.append((invalidIfTrue, description)) + } + + func preparedItem(originalString string: String) -> T { + if string.isEmpty && defaultValue != nil { + return defaultValue! + } + + return T.fromString(string)! + } +} + + +// MARK:- Internal extension + +extension AskSettings: AskerValidator { + + func invalidItemMessage(_ string: String?) -> String? { + guard let string = string else { + return "You provided an empty message, pelase enter anything!" + } + + guard let validatedItem = T.fromString(string) else { + return "You must enter a valid \(T.typeName())." + } + + guard let validationError = firstValidationError(validatedItem) else { + return nil + } + + return validationError + } + + func newItemPromptMessage() -> String { + return "? " + } + + func validatedItem(forString string: String) -> T { + return T.fromString(string)! + } + + func firstValidationError(_ item: T) -> String? { + + for (isInvalid, validationError) in invalidClousures { + if isInvalid(item) { + return validationError + } + } + + return nil + } +} diff --git a/Sources/SwiftLine/AskerValidator.swift b/Sources/SwiftLine/AskerValidator.swift new file mode 100644 index 0000000..ee1ce3e --- /dev/null +++ b/Sources/SwiftLine/AskerValidator.swift @@ -0,0 +1,36 @@ +// +// ConsoleEntryValidator.swift +// AskerValidator +// +// Created by Omar Abdelhafith on 02/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + + +protocol AskerValidator { + associatedtype Item + + func invalidItemMessage(_ string: String?) -> String? + func newItemPromptMessage() -> String + + func validatedItem(forString string: String) -> Item +} + +func askForValidatedItem + (originalValue value: String, validator: W) -> T where W.Item == T { + + var validatedValue: String = value + + while true { + guard let invalidMessage = validator.invalidItemMessage(validatedValue) else { + break + } + + PromptSettings.print(invalidMessage) + PromptSettings.print(validator.newItemPromptMessage(), terminator: "") + + validatedValue = readStringOrEmpty() + } + + return validator.validatedItem(forString: validatedValue) +} diff --git a/Sources/SwiftLine/Choose.swift b/Sources/SwiftLine/Choose.swift new file mode 100644 index 0000000..079b012 --- /dev/null +++ b/Sources/SwiftLine/Choose.swift @@ -0,0 +1,93 @@ +// +// Chooser.swift +// Choose +// +// Created by Omar Abdelhafith on 03/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + + + +/** + Presents a user with a menu of items to choose from + + - parameter prompt: The menu prompt message + - parameter choices: List of choices + + - returns: The user selected item + */ +public func choose(_ prompt: String, choices: String...) -> String { + return choose(prompt, type: String.self) { + for choice in choices { + $0.addChoice(choice) { return choice } + } + } +} + + +/** + Presents a user with a menu of items to choose from + + - parameter costumizationBlock: Closure to be called with a ChooseSettings, changes to the settings are reflected to the prompt + + - returns: The user selected item + */ +public func choose(_ costumizationBlock: ((ChooseSettings) -> Void)) -> T { + + let settings = getChooseSettings(costumizationBlock) + return choose(settings, type: T.self) +} + + +/** + Presents a user with a menu of items to choose from + + - parameter prompt: The menu prompt message + - parameter type: The value type to be expected from the user + - parameter costumizationBlock: Closure to be called with a ChooseSettings, changes to the settings are reflected to the prompt + + - returns: The user selected item + */ +public func choose(_ prompt: String, type: T.Type, costumizationBlock: ((ChooseSettings) -> Void)) -> T { + + let settings = getChooseSettings(costumizationBlock) + settings.promptQuestion = prompt + return choose(settings, type: type) +} + + +/** + Presents a user with a menu of items to choose from + + - parameter type: The value type to be expected from the user + - parameter costumizationBlock: Closure to be called with a ChooseSettings, changes to the settings are reflected to the prompt + + - returns: The user selected item + */ +public func choose(_ type: T.Type, costumizationBlock: ((ChooseSettings) -> Void)) -> T { + + let settings = getChooseSettings(costumizationBlock) + return choose(settings, type: type) +} + + +// MARK :- Internal functions + + +func choose(_ settings: ChooseSettings, type: T.Type) -> T { + + let items = settings.preparePromptItems() + + items.forEach { PromptSettings.print($0) } + PromptSettings.print("\(settings.promptQuestion)", terminator: "") + + let stringRead = readStringOrEmpty() + + return askForValidatedItem(originalValue: stringRead, validator: settings) +} + +func getChooseSettings(_ costumizationBlock: (ChooseSettings) -> Void) -> ChooseSettings { + let settings = ChooseSettings() + costumizationBlock(settings) + return settings +} diff --git a/Sources/SwiftLine/ChooseSettings.swift b/Sources/SwiftLine/ChooseSettings.swift new file mode 100644 index 0000000..d614a5a --- /dev/null +++ b/Sources/SwiftLine/ChooseSettings.swift @@ -0,0 +1,128 @@ +// +// ChooseSettings.swift +// ChooseSettings +// +// Created by Omar Abdelhafith on 03/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + + +/** + Choice index type + + - Letters: Use letters as choice index (a. b. c.) + - Numbers: Use numbers as choice index (1. 2. 3.) + */ +public enum ChoiceIndexType { + case letters + case numbers +} + + + +/// Settings to costumize the behavior of choose +public class ChooseSettings { + typealias Item = T + + var choices: [(choice: String, callback: () -> T)] = [] + + /// Prompt message to use + public var promptQuestion = "" + + /// Choice index used for choose items + public var index = ChoiceIndexType.numbers + + /// Index suffix used between the index and the item + public var indexSuffix = ". " + + /** + Add a new item to the list of choices + + - parameter choice: Item name + - parameter callback: callback called when the item is selected, the value returned from this call back will be returned from choose + */ + public func addChoice(_ choice: String..., callback: @escaping () -> T) { + choice.forEach { + choices.append(($0, callback)) + } + } + + // MARK:- Internal + + func validChoices() -> [String] { + let validChoices = Array(1...choices.count).map { "\($0)" } + return validChoices + stringChoices() + } + + func stringChoices() -> [String] { + return choices.map { $0.choice } + } + + func choiceForInput(_ item: String) -> T? { + if let value = Int(item) { + return choiceWithIntValue(value) + } else { + return choiceWithStringValue(item) + } + } + + func choiceWithIntValue(_ value: Int) -> T? { + let index = value - 1 + if index >= 0 && index < choices.count { + return choices[index].callback() + } + + return nil + } + + func choiceWithStringValue(_ value: String) -> T? { + let possibleIndex = choices.firstIndex { $0.choice == value } + if let index = possibleIndex { + return choices[index].callback() + } + + return nil + } + + func preparePromptItems() -> [String] { + return zip(indexChoices(), stringChoices()).map { index, string in + return "\(index)\(indexSuffix)\(string)" + } + } + + func indexChoices() -> [String] { + return stringChoices().enumerated().map { itemIndex, string in + + if index == .numbers { + return "\(itemIndex + 1)" + } else { + let character = "a".unicodeScalars.first!.value + UInt32(itemIndex) + return String(Character(UnicodeScalar(character)!)) + } + } + } + +} + +// MARK:- Internal Class + +extension ChooseSettings: AskerValidator { + func validatedItem(forString string: String) -> T { + return choiceForInput(string)! + } + + func invalidItemMessage(_ string: String?) -> String? { + if choiceForInput(string!) != nil { + return nil + } + + let baseMessage = "You must choose one of" + let choicesString = validChoices().joined(separator: ", ") + + return "\(baseMessage) [\(choicesString)]." + } + + func newItemPromptMessage() -> String { + return "? " + } +} diff --git a/Sources/SwiftLine/Colorizer.swift b/Sources/SwiftLine/Colorizer.swift new file mode 100644 index 0000000..c6e9f8c --- /dev/null +++ b/Sources/SwiftLine/Colorizer.swift @@ -0,0 +1,59 @@ +// +// Colorizer.swift +// Colorizer +// +// Created by Omar Abdelhafith on 30/10/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + + +extension String { + + /// Access the methods to change the foreground color + public var f: StringForegroundColorizer { + return foreground + } + + /// Access the methods to change the foreground color + public var foreground: StringForegroundColorizer { + return StringForegroundColorizer(string: self) + } + + /// Access the methods to change the background color + public var b: StringBackgroundColorizer { + return background + } + + /// Access the methods to change the background color + public var background: StringBackgroundColorizer { + return StringBackgroundColorizer(string: self) + } + + /// Access the methods to change the text style + public var s: StringStyleColorizer { + return style + } + + /// Access the methods to change the text style + public var style: StringStyleColorizer { + return StringStyleColorizer(string: self) + } + +} + + // MARK- Internal + +class Colorizer: CustomStringConvertible { + + let color: StringStyle + let string: String + + init(string: String, color: StringStyle) { + self.string = string + self.color = color + } + + var description: String { + return color.colorize(string: string) + } +} \ No newline at end of file diff --git a/Sources/SwiftLine/CommandExecutor.swift b/Sources/SwiftLine/CommandExecutor.swift new file mode 100644 index 0000000..b3002b9 --- /dev/null +++ b/Sources/SwiftLine/CommandExecutor.swift @@ -0,0 +1,106 @@ +// +// CommandExecutor.swift +// CommandExecutor +// +// Created by Omar Abdelhafith on 05/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + +import Foundation + + +typealias ExecutorReturnValue = (status: Int, standardOutput: TaskPipe, standardError: TaskPipe) + +class CommandExecutor { + + static var currentTaskExecutor: TaskExecutor = ActualTaskExecutor() + + class func execute(_ commandParts: [String]) -> ExecutorReturnValue { + return currentTaskExecutor.execute(commandParts) + } +} + + +protocol TaskExecutor { + func execute(_ commandParts: [String]) -> ExecutorReturnValue +} + +class DryTaskExecutor: TaskExecutor { + + func execute(_ commandParts: [String]) -> ExecutorReturnValue { + let command = commandParts.joined(separator: " ") + PromptSettings.print("Executed command '\(command)'") + return (0, + Dryipe(dataToReturn: "".data(using: String.Encoding.utf8)!), + Dryipe(dataToReturn: "".data(using: String.Encoding.utf8)!)) + } +} + +class ActualTaskExecutor: TaskExecutor { + + func execute(_ commandParts: [String]) -> ExecutorReturnValue { + let task = Process() + + task.launchPath = "/usr/bin/env" + task.arguments = commandParts + + let stdoutPipe = Pipe() + let stderrPipe = Pipe() + + task.standardOutput = stdoutPipe + task.standardError = stderrPipe + task.launch() + task.waitUntilExit() + + return (Int(task.terminationStatus), stdoutPipe, stderrPipe) + } +} + +class InteractiveTaskExecutor: TaskExecutor { + + func execute(_ commandParts: [String]) -> ExecutorReturnValue { + + let argv: [UnsafeMutablePointer?] = commandParts.map{ $0.withCString(strdup) } + defer { for case let arg? in argv { free(arg) } } + + var childFDActions: posix_spawn_file_actions_t? = nil + var outputPipe: [Int32] = [-1, -1] + + posix_spawn_file_actions_init(&childFDActions) + posix_spawn_file_actions_adddup2(&childFDActions, outputPipe[1], 1) + posix_spawn_file_actions_adddup2(&childFDActions, outputPipe[1], 2) + posix_spawn_file_actions_addclose(&childFDActions, outputPipe[0]) + posix_spawn_file_actions_addclose(&childFDActions, outputPipe[1]) + + + var pid: pid_t = 0 + let result = posix_spawn(&pid, argv[0], &childFDActions, nil, argv + [nil], nil) + + let emptyPipe = Dryipe(dataToReturn: "".data(using: String.Encoding.utf8)!) + return (Int(result), emptyPipe, emptyPipe) + } +} + +class DummyTaskExecutor: TaskExecutor { + + var commandsExecuted: [String] = [] + let statusCodeToReturn: Int + + let errorToReturn: String + let outputToReturn: String + + init(status: Int, output: String, error: String) { + statusCodeToReturn = status + outputToReturn = output + errorToReturn = error + } + + func execute(_ commandParts: [String]) -> ExecutorReturnValue { + let command = commandParts.joined(separator: " ") + commandsExecuted.append(command) + + return (statusCodeToReturn, + Dryipe(dataToReturn: outputToReturn.data(using: String.Encoding.utf8)!), + Dryipe(dataToReturn: errorToReturn.data(using: String.Encoding.utf8)!)) + } +} diff --git a/Sources/SwiftLine/Env.swift b/Sources/SwiftLine/Env.swift new file mode 100644 index 0000000..b6c201d --- /dev/null +++ b/Sources/SwiftLine/Env.swift @@ -0,0 +1,95 @@ +// +// Env.swift +// Swiftline +// +// Created by Omar Abdelhafith on 24/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + +import Darwin + + +public class Env { + + /// Return the list of all the enviromenment keys passed to the script + public static var keys: [String] { + let keyValues = run("env").stdout.components(separatedBy: "\n") + let keys = keyValues.map { $0.components(separatedBy: "=").first! }.filter { !$0.isEmpty } + return keys + } + + /// Return the list of all the enviromenment values passed to the script + public static var values: [String] { + return self.keys.map { self.get($0)! } + } + + /** + Return the enviromenment for the provided key + + - parameter key: The enviromenment variable key + + - returns: The enviromenment variable value + */ + public static func get(_ key: String) -> String? { + guard let value = getenv(key) else { return nil } + return String(cString: value) + } + + /** + Set a new value for the enviromenment variable + + - parameter key: The enviromenment variable key + - parameter value: The enviromenment variable value + */ + public static func set(_ key: String, _ value: String?) { + if let newValue = value { + setenv(key, newValue, 1) + } else { + unsetenv(key) + } + } + + + /** + Clear all the enviromenment variables + */ + public static func clear() { + self.keys + .map { String($0) } + .filter { $0 != nil } + .forEach{ self.set($0, nil) } + } + + /** + Check if the enviromenment variable key exists + + - parameter key: The enviromenment variable key + + - returns: true if exists false otherwise + */ + public static func hasKey(_ key: String) -> Bool { + return self.keys.contains(key) + } + + + /** + Check if the enviromenment variable value exists + + - parameter key: The enviromenment variable value + + - returns: true if exists false otherwise + */ + public static func hasValue(_ value: String) -> Bool { + return self.values.contains(value) + } + + /** + Iterate through the list of enviromenment variables + + - parameter callback: callback to call on each key/value pair + */ + public static func eachPair(_ callback: (_ key: String, _ value: String) -> ()) { + zip(self.keys, self.values).forEach(callback) + } + +} diff --git a/Sources/SwiftLine/Glob.swift b/Sources/SwiftLine/Glob.swift new file mode 100644 index 0000000..392e57d --- /dev/null +++ b/Sources/SwiftLine/Glob.swift @@ -0,0 +1,33 @@ +// +// Glob.swift +// Swiftline +// +// Created by Omar Abdelhafith on 30/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + +import Foundation +import Darwin + +class Glob { + + static func expand(_ pattern: String) -> [String] { + var files = [String]() + var gt: glob_t = glob_t() + + if (glob(pattern.cString(using: String.Encoding.utf8)!, 0, nil, >) == 0) { + + for i in (0..(x)! + let s = String.init(cString: c) + files.append(s) + } + + } + + globfree(>); + return files + } + +} diff --git a/Sources/SwiftLine/ProcessInfo.swift b/Sources/SwiftLine/ProcessInfo.swift new file mode 100644 index 0000000..fdd75a2 --- /dev/null +++ b/Sources/SwiftLine/ProcessInfo.swift @@ -0,0 +1,48 @@ +// +// ProcessInfo.swift +// Swiftline +// +// Created by Omar Abdelhafith on 26/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + +import Foundation + + +protocol ProcessInfoType { + var arguments: [String] { get } + var cacheResults: Bool { get } +} + +extension Foundation.ProcessInfo: ProcessInfoType { + var cacheResults: Bool { return true } +} + +class DummyProcessInfo: ProcessInfoType { + + var argsToReturn: [String] + + init(_ argsToReturn: String...) { + self.argsToReturn = argsToReturn + } + + var arguments: [String] { + return argsToReturn + } + + var cacheResults: Bool { return false } +} + +class ProcessInfo { + + static var internalProcessInfo: ProcessInfoType = Foundation.ProcessInfo() + + static var arguments: [String] { + return internalProcessInfo.arguments + } + + static var cacheResults: Bool { + return internalProcessInfo.cacheResults + } + +} diff --git a/Sources/SwiftLine/PromptPrinter.swift b/Sources/SwiftLine/PromptPrinter.swift new file mode 100644 index 0000000..32a2743 --- /dev/null +++ b/Sources/SwiftLine/PromptPrinter.swift @@ -0,0 +1,28 @@ +// +// PromptPrinter.swift +// PromptPrinter +// +// Created by Omar Abdelhafith on 02/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + + +protocol PromptPrinter { + func printString(_ string: String, terminator: String) +} + +class ConsolePromptPrinter: PromptPrinter { + + func printString(_ string: String, terminator: String = "\n") { + return print(string, separator: "", terminator: terminator) + } +} + +class DummyPromptPrinter: PromptPrinter { + + var printed = "" + + func printString(_ string: String, terminator: String = "\n") { + printed += string + terminator + } +} diff --git a/Sources/SwiftLine/PromptReader.swift b/Sources/SwiftLine/PromptReader.swift new file mode 100644 index 0000000..b884fb2 --- /dev/null +++ b/Sources/SwiftLine/PromptReader.swift @@ -0,0 +1,32 @@ +// +// PromptReader.swift +// PromptReader +// +// Created by Omar Abdelhafith on 31/10/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + + +protocol PromptReader { + func read() -> String? +} + +class ConsolePromptReader: PromptReader { + + func read() -> String? { + return readLine() + } +} + +class DummyPromptReader: PromptReader { + + var parametersToReturn: [String] + + init(toReturn: String...) { + self.parametersToReturn = toReturn + } + + func read() -> String? { + return parametersToReturn.removeFirst() + } +} \ No newline at end of file diff --git a/Sources/SwiftLine/PromptSettings.swift b/Sources/SwiftLine/PromptSettings.swift new file mode 100644 index 0000000..fa0c0a7 --- /dev/null +++ b/Sources/SwiftLine/PromptSettings.swift @@ -0,0 +1,22 @@ +// +// PromptSettings.swift +// PromptSettings +// +// Created by Omar Abdelhafith on 02/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + + +class PromptSettings { + + static var reader: PromptReader = ConsolePromptReader() + static var printer: PromptPrinter = ConsolePromptPrinter() + + class func read() -> String? { + return reader.read() + } + + class func print(_ string: String, terminator: String = "\n") { + return printer.printString(string, terminator: terminator) + } +} diff --git a/Sources/SwiftLine/RunResults.swift b/Sources/SwiftLine/RunResults.swift new file mode 100644 index 0000000..d8c81c5 --- /dev/null +++ b/Sources/SwiftLine/RunResults.swift @@ -0,0 +1,40 @@ +// +// RunResult.swift +// RunResults +// +// Created by Omar Abdelhafith on 05/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + +import Foundation + + +/** + * Structure to hold results from run + */ +public struct RunResults { + + /// Command exit status + public let exitStatus: Int + + /// Command output stdout + public let stdout: String + + /// Command output stderr + public let stderr: String +} + +// MARK:- Internal + +func splitCommandToArgs(_ command: String) -> [String] { + if command.contains(" ") { + return command.components(separatedBy: " ") + } + + return [command] +} + +func readPipe(_ pipe: TaskPipe) -> String { + let data = pipe.read() + return NSString(data: data as Data, encoding: String.Encoding.utf8.rawValue) as String? ?? "" +} diff --git a/Sources/SwiftLine/Runner.swift b/Sources/SwiftLine/Runner.swift new file mode 100644 index 0000000..9d2603f --- /dev/null +++ b/Sources/SwiftLine/Runner.swift @@ -0,0 +1,115 @@ +// +// Runner.swift +// 🏃 +// +// Created by Omar Abdelhafith on 02/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + +import Foundation + + +class 🏃{ + + class func runWithoutCapture(_ command: String) -> Int { + let initalSettings = RunSettings() + initalSettings.interactive = true + return run(command, args: [], settings: initalSettings).exitStatus + } + + class func run(_ command: String, args: String...) -> RunResults { + return run(command, args: args as [String]) + } + + class func run(_ command: String, args: [String]) -> RunResults { + let settings = RunSettings() + return run(command, args: args, settings: settings) + } + + class func run(_ command: String, settings: ((RunSettings) -> Void)) -> RunResults { + let initalSettings = RunSettings() + settings(initalSettings) + + return run(command, args: [], settings: initalSettings) + } + + class func run(_ command: String, args: [String], settings: ((RunSettings) -> Void)) -> RunResults { + let initalSettings = RunSettings() + settings(initalSettings) + + return run(command, args: args, settings: initalSettings) + } + + class func run(_ command: String, echo: EchoSettings) -> RunResults { + let initalSettings = RunSettings() + initalSettings.echo = echo + return run(command, args: [], settings: initalSettings) + } + + class func run(_ command: String, args: [String], settings: RunSettings) -> RunResults { + + let commandParts = commandToRun(command, args: args) + + let result: RunResults + + echoCommand(commandParts, settings: settings) + + if settings.dryRun { + result = executeDryCommand(commandParts) + } else if settings.interactive { + result = executeIneractiveCommand(commandParts) + } else { + result = executeActualCommand(commandParts) + } + + echoResult(result, settings: settings) + + return result + } + + fileprivate class func executeDryCommand(_ commandParts: [String]) -> RunResults { + return execute(commandParts, withExecutor: DryTaskExecutor()) + } + + fileprivate class func executeIneractiveCommand(_ commandParts: [String]) -> RunResults { + return execute(commandParts, withExecutor: InteractiveTaskExecutor()) + } + + fileprivate class func executeActualCommand(_ commandParts: [String]) -> RunResults { + return execute(commandParts, withExecutor: CommandExecutor.currentTaskExecutor) + } + + fileprivate class func execute(_ commandParts: [String], withExecutor executor: TaskExecutor) -> RunResults { + let (status, stdoutPipe, stderrPipe) = executor.execute(commandParts) + + let stdout = readPipe(stdoutPipe).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + let stderr = readPipe(stderrPipe).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + return RunResults(exitStatus: status, stdout: stdout, stderr: stderr) + } + + fileprivate class func commandToRun(_ command: String, args: [String]) -> [String] { + return splitCommandToArgs(command) + args + } + + fileprivate class func echoCommand(_ command: [String], settings: RunSettings) { + if settings.echo.contains(.Command) { + echoStringIfNotEmpty("Command", string: command.joined(separator: " ")) + } + } + + fileprivate class func echoResult(_ result: RunResults, settings: RunSettings) { + if settings.echo.contains(.Stdout) { + echoStringIfNotEmpty("Stdout", string: result.stdout) + } + + if settings.echo.contains(.Stderr) { + echoStringIfNotEmpty("Stderr", string: result.stderr) + } + } + + fileprivate class func echoStringIfNotEmpty(_ title: String, string: String) { + if !string.isEmpty { + PromptSettings.print("\(title): \n\(string)") + } + } +} diff --git a/Sources/SwiftLine/RunnerSettings.swift b/Sources/SwiftLine/RunnerSettings.swift new file mode 100644 index 0000000..ae43ca8 --- /dev/null +++ b/Sources/SwiftLine/RunnerSettings.swift @@ -0,0 +1,44 @@ +// +// RunSettings.swift +// RunSettings +// +// Created by Omar Abdelhafith on 05/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + + +/// Settings to costumize the run function +public class RunSettings { + + /// If set to true, the command wont be run on the system, the stdout will contain the command executed + public var dryRun = false + + /// Which parts of the command to be echoed during execution + public var echo = EchoSettings.None + + /// Run the command in interactive mode; output wont be captured + public var interactive = false +} + + +/// Echo settings +public struct EchoSettings: OptionSet { + + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + /// Dont echo anything, this is the default settings + public static var None = EchoSettings(rawValue: 0) + + /// Echo the stdout from the run command to the terminal + public static let Stdout = EchoSettings(rawValue: 1 << 0) + + /// Echo the stderr from the run command to the terminal + public static let Stderr = EchoSettings(rawValue: 1 << 1) + + /// Echo the command executed to the terminal + public static let Command = EchoSettings(rawValue: 1 << 2) +} diff --git a/Sources/SwiftLine/ShortHandRunner.swift b/Sources/SwiftLine/ShortHandRunner.swift new file mode 100644 index 0000000..a9fed5b --- /dev/null +++ b/Sources/SwiftLine/ShortHandRunner.swift @@ -0,0 +1,99 @@ +// +// ShortHandRunner.swift +// ShortHandRunner + +// +// Created by Omar Abdelhafith on 05/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + + +/** + Executes a command and captures its output + + - parameter command: the command to execute + - parameter args: the parameters to pass to the command + + - returns: RunResults describing the command results + */ +public func run(_ command: String, args: String...) -> RunResults { + return 🏃.run(command, args: args as [String]) +} + + +/** + Executes a command and captures its output + + - parameter command: the command to execute + - parameter argString: the arguments passed as a single string + + - returns: RunResults describing the command results + */ +public func run(_ command: String, argsString: String) -> RunResults { + let args = argsString.components(separatedBy: " ").filter { !$0.isEmpty } + return 🏃.run(command, args: args) +} + + +/** + Executes a command and captures its output + + - parameter command: the command to execute + - parameter args: the parameters to pass to the command + + - returns: RunResults describing the command results + */ +public func run(_ command: String, args: [String]) -> RunResults { + return 🏃.run(command, args: args) +} + + +/** + Executes a command and captures its output + + - parameter command: the command to execute + - parameter settingsBlock: block that receives the settings to costumize the behavior of run + + - returns: RunResults describing the command results + */ +public func run(_ command: String, settingsBlock: ((RunSettings) -> Void)) -> RunResults { + return 🏃.run(command, settings: settingsBlock) +} + + +/** + Executes a command and captures its output + + - parameter command: the command to execute + - parameter args: the parameters to pass to the command + - parameter settingsBlock: block that receives the settings to costumize the behavior of run + + - returns: RunResults describing the command results + */ +public func run(_ command: String, args: [String], settings: ((RunSettings) -> Void)) -> RunResults { + return 🏃.run(command, args: args, settings: settings) +} + + +/** + Executes a command and captures its output + + - parameter command: the command to execute + - parameter echo: echo settings that describe what parts of the command to print + +- returns: RunResults describing the command results + */ +func run(_ command: String, echo: EchoSettings) -> RunResults { + return 🏃.run(command, echo: echo) +} + +/** + Execute a command in interactive mode, output won't be captured + + - parameter command: the command to execute + +- returns: executed command exit code + */ +public func runWithoutCapture(_ command: String) -> Int { + return 🏃.runWithoutCapture(command) +} diff --git a/Sources/SwiftLine/StringBackgroundColorizer.swift b/Sources/SwiftLine/StringBackgroundColorizer.swift new file mode 100644 index 0000000..eb9ae72 --- /dev/null +++ b/Sources/SwiftLine/StringBackgroundColorizer.swift @@ -0,0 +1,48 @@ +// +// StringBackgroundColorizer.swift +// StringBackgroundColorizer +// +// Created by Omar Abdelhafith on 31/10/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + + +extension String { + + public struct StringBackgroundColorizer { + + let string: String + + public var Black: String { + return Colorizer(string: string, color: BackgroundColor.black).description + } + + public var Red: String { + return Colorizer(string: string, color: BackgroundColor.red).description + } + + public var Green: String { + return Colorizer(string: string, color: BackgroundColor.green).description + } + + public var Yellow: String { + return Colorizer(string: string, color: BackgroundColor.yellow).description + } + + public var Blue: String { + return Colorizer(string: string, color: BackgroundColor.blue).description + } + + public var Magenta: String { + return Colorizer(string: string, color: BackgroundColor.magenta).description + } + + public var Cyan: String { + return Colorizer(string: string, color: BackgroundColor.cyan).description + } + + public var White: String { + return Colorizer(string: string, color: BackgroundColor.white).description + } + } +} diff --git a/Sources/SwiftLine/StringForegroundColorizer.swift b/Sources/SwiftLine/StringForegroundColorizer.swift new file mode 100644 index 0000000..ab57bf9 --- /dev/null +++ b/Sources/SwiftLine/StringForegroundColorizer.swift @@ -0,0 +1,49 @@ +// +// StringForegroundColorizer.swift +// StringForegroundColorizer +// +// Created by Omar Abdelhafith on 31/10/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + + +extension String { + + public struct StringForegroundColorizer { + + let string: String + + public var Black: String { + return Colorizer(string: string, color: ForegroundColor.black).description + } + + public var Red: String { + return Colorizer(string: string, color: ForegroundColor.red).description + } + + public var Green: String { + return Colorizer(string: string, color: ForegroundColor.green).description + } + + public var Yellow: String { + return Colorizer(string: string, color: ForegroundColor.yellow).description + } + + public var Blue: String { + return Colorizer(string: string, color: ForegroundColor.blue).description + } + + public var Magenta: String { + return Colorizer(string: string, color: ForegroundColor.magenta).description + } + + public var Cyan: String { + return Colorizer(string: string, color: ForegroundColor.cyan).description + } + + public var White: String { + return Colorizer(string: string, color: ForegroundColor.white).description + } + } + +} diff --git a/Sources/SwiftLine/StringStyle.swift b/Sources/SwiftLine/StringStyle.swift new file mode 100644 index 0000000..188b3ae --- /dev/null +++ b/Sources/SwiftLine/StringStyle.swift @@ -0,0 +1,114 @@ +// +// StringStyle.swift +// StringStyle +// +// Created by Omar Abdelhafith on 31/10/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + +import Foundation + + +let startOfCode = "\u{001B}[" +let endOfCode = "m" +let codesSeperators = ";" + + +protocol StringStyle { + var rawValue: Int { get } + func colorize(string: String) -> String +} + + +extension StringStyle { + + func colorize(string: String) -> String { + + if hasAnyStyleCode(string) { + return colorizeStringAndAddCodeSeperators(string) + } else { + return colorizeStringWithoutPriorCode(string) + } + } + + fileprivate func colorizeStringWithoutPriorCode(_ string: String) -> String { + return "\(preparedColorCode(self.rawValue))\(string)\(endingColorCode())" + } + + fileprivate func colorizeStringAndAddCodeSeperators(_ string: String) -> String { + //To refactor and use regex matching instead of replacing strings and using tricks + let stringByRemovingEnding = removeEndingCode(string) + let sringwWithStart = "\(preparedColorCode(self.rawValue))\(stringByRemovingEnding)" + + let stringByAddingCodeSeperator = addCommandSeperators(sringwWithStart) + + return "\(stringByAddingCodeSeperator)\(endingColorCode())" + } + + fileprivate func preparedColorCode(_ color: Int) -> String { + return "\(startOfCode)\(color)\(endOfCode)" + } + + fileprivate func hasAnyStyleCode(_ string: String) -> Bool { + return string.contains(startOfCode) + } + + fileprivate func addCommandSeperators(_ string: String) -> String { + var rangeWithInset = (string.index(after: string.startIndex) ..< string.index(before: string.endIndex)) + let newString = string.replacingOccurrences(of: startOfCode, with: ";", options: .literal, range: rangeWithInset) + + rangeWithInset = (newString.index(after: newString.startIndex) ..< newString.index(before: newString.endIndex)) + return newString.replacingOccurrences(of: "m;", with: ";", options: .literal, range: rangeWithInset) + } + + fileprivate func removeEndingCode(_ string: String) -> String { + let rangeWithInset = (string.index(after: string.startIndex) ..< string.endIndex) + return string.replacingOccurrences(of: endingColorCode(), with: "", options: .literal, range: rangeWithInset) + } + + fileprivate func endingColorCode() -> String { + return preparedColorCode(0) + } +} + + +enum ForegroundColor: Int, StringStyle { + + case black = 30 + case red = 31 + case green = 32 + case yellow = 33 + case blue = 34 + case magenta = 35 + case cyan = 36 + case white = 37 +} + + +enum BackgroundColor: Int, StringStyle { + + case black = 40 + case red = 41 + case green = 42 + case yellow = 43 + case blue = 44 + case magenta = 45 + case cyan = 46 + case white = 47 +} + + +enum StringTextStyle: Int, StringStyle { + + case reset = 0 + case bold = 1 + case italic = 3 + case underline = 4 + case inverse = 7 + case strikethrough = 9 + case boldOff = 22 + case italicOff = 23 + case underlineOff = 24 + case inverseOff = 27 + case strikethroughOff = 29 +} diff --git a/Sources/SwiftLine/StringStyleColorizer.swift b/Sources/SwiftLine/StringStyleColorizer.swift new file mode 100644 index 0000000..81fbcde --- /dev/null +++ b/Sources/SwiftLine/StringStyleColorizer.swift @@ -0,0 +1,61 @@ +// +// StringStyleColorizer.swift +// StringStyleColorizer +// +// Created by Omar Abdelhafith on 31/10/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + + +public extension String { + + struct StringStyleColorizer { + + let string: String + + public var Reset: String { + return Colorizer(string: string, color: StringTextStyle.reset).description + } + + public var Bold: String { + return Colorizer(string: string, color: StringTextStyle.bold).description + } + + public var Italic: String { + return Colorizer(string: string, color: StringTextStyle.italic).description + } + + public var Underline: String { + return Colorizer(string: string, color: StringTextStyle.underline).description + } + + public var Inverse: String { + return Colorizer(string: string, color: StringTextStyle.inverse).description + } + + public var Strikethrough: String { + return Colorizer(string: string, color: StringTextStyle.strikethrough).description + } + + public var BoldOff: String { + return Colorizer(string: string, color: StringTextStyle.boldOff).description + } + + public var ItalicOff: String { + return Colorizer(string: string, color: StringTextStyle.italicOff).description + } + + public var UnderlineOff: String { + return Colorizer(string: string, color: StringTextStyle.underlineOff).description + } + + public var InverseOff: String { + return Colorizer(string: string, color: StringTextStyle.inverseOff).description + } + + public var StrikethroughOff: String { + return Colorizer(string: string, color: StringTextStyle.strikethroughOff).description + } + } + +} diff --git a/Sources/SwiftLine/TaskPipe.swift b/Sources/SwiftLine/TaskPipe.swift new file mode 100644 index 0000000..3762aab --- /dev/null +++ b/Sources/SwiftLine/TaskPipe.swift @@ -0,0 +1,29 @@ +// +// TaskPipe.swift +// TaskPipe +// +// Created by Omar Abdelhafith on 05/11/2015. +// Copyright © 2015 Omar Abdelhafith. All rights reserved. +// + +import Foundation + + +protocol TaskPipe { + func read() -> Data +} + +extension Pipe: TaskPipe { + func read() -> Data { + return fileHandleForReading.readDataToEndOfFile() + } +} + +struct Dryipe: TaskPipe { + + let dataToReturn: Data + + func read() -> Data { + return dataToReturn + } +} diff --git a/Swiftline.xcodeproj/project.pbxproj b/Swiftline.xcodeproj/project.pbxproj index 618abca..7b23b65 100644 --- a/Swiftline.xcodeproj/project.pbxproj +++ b/Swiftline.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ @@ -185,10 +185,11 @@ }; }; buildConfigurationList = "___RootConfs_" /* Build configuration list for PBXProject "Swiftline" */; - compatibilityVersion = "Xcode 3.2"; + compatibilityVersion = "Xcode 9.3"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = "___RootGroup_"; @@ -252,7 +253,7 @@ PRODUCT_BUNDLE_IDENTIFIER = Swiftline; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -269,7 +270,7 @@ PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -277,6 +278,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = __PBXFileRef_Swiftline.xcodeproj/Configs/Project.xcconfig /* Swiftline.xcodeproj/Configs/Project.xcconfig */; buildSettings = { + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -284,6 +286,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = __PBXFileRef_Swiftline.xcodeproj/Configs/Project.xcconfig /* Swiftline.xcodeproj/Configs/Project.xcconfig */; buildSettings = { + SWIFT_VERSION = 5.0; }; name = Debug; }; diff --git a/Swiftline.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Swiftline.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Swiftline.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/SwiftlineTests/Swiftline.xcodeproj/project.pbxproj b/SwiftlineTests/Swiftline.xcodeproj/project.pbxproj index 24497a8..e549fde 100644 --- a/SwiftlineTests/Swiftline.xcodeproj/project.pbxproj +++ b/SwiftlineTests/Swiftline.xcodeproj/project.pbxproj @@ -299,6 +299,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = D72765F91BF416AC00126D99; @@ -436,6 +437,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -475,6 +477,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -499,7 +502,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -521,7 +524,7 @@ PRODUCT_BUNDLE_IDENTIFIER = nsomar.Swiftline; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -540,7 +543,7 @@ PRODUCT_BUNDLE_IDENTIFIER = nsomar.SwiftlineTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -558,7 +561,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = nsomar.SwiftlineTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Release; };