Last active
September 2, 2021 14:03
-
-
Save griffin-stewie/a7e1bdef29eb249d30f7569aabd54ec1 to your computer and use it in GitHub Desktop.
Swift Command Line Tool Helpers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
// O(n^2) | |
public extension Array where Element: Equatable { | |
/// Remove duplicated elements | |
/// Complexity: O(n^2), where n is the length of the sequence. | |
/// - Returns: A new Array containing those elements from self that are not duplicates | |
func unique() -> Array<Element> { | |
return reduce(into: []) { (array, r) in | |
if !array.contains(r) { | |
array.append(r) | |
} | |
} | |
} | |
/// Remove duplicated elements in place | |
/// Complexity: O(n^2), where n is the length of the sequence. | |
mutating func uniqueInPlace() { | |
self = reduce(into: []) { (array, r) in | |
if !array.contains(r) { | |
array.append(r) | |
} | |
} | |
} | |
} | |
// https://stackoverflow.com/a/46354989 by mxcl | |
// O(n) | |
public extension Array where Element: Hashable { | |
/// Remove duplicated elements | |
/// Complexity: O(n), where n is the length of the sequence. | |
/// - Returns: A new Array containing those elements from self that are not duplicates | |
func uniqued() -> [Element] { | |
var seen = Set<Element>() | |
return filter{ seen.insert($0).inserted } | |
} | |
} | |
// https://stackoverflow.com/a/34712330 | |
// You can use this for String as well | |
extension RangeReplaceableCollection where Element: Hashable { | |
public func removingDuplicates() -> Self { | |
var set = Set<Element>() | |
return filter { set.insert($0).inserted } | |
} | |
public mutating func removeDuplicates() { | |
var set = Set<Element>() | |
removeAll { !set.insert($0).inserted } | |
} | |
} | |
// https://stackoverflow.com/a/55684308 | |
extension RangeReplaceableCollection { | |
/// Returns a collection containing, in order, the first instances of | |
/// elements of the sequence that compare equally for the keyPath. | |
public func unique<T: Hashable>(for keyPath: KeyPath<Element, T>) -> Self { | |
var unique = Set<T>() | |
return filter { unique.insert($0[keyPath: keyPath]).inserted } | |
} | |
/// Keeps only, in order, the first instances of | |
/// elements of the collection that compare equally for the keyPath. | |
public mutating func uniqueInPlace<T: Hashable>(for keyPath: KeyPath<Element, T>) { | |
var unique = Set<T>() | |
removeAll { !unique.insert($0[keyPath: keyPath]).inserted } | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
extension CaseIterable { | |
/// Shorthanded of `allCasesDescription(surrounded: String = "'", separator: String = ", ") -> String` | |
/// It returns `, ` separated string. | |
public static var allCasesDescription: String { | |
return self.allCasesDescription(surrounded: "", separator: ", ") | |
} | |
/// Representation of string that contains elements. | |
/// - Parameters: | |
/// - surrounded: each element surrounded by. | |
/// - separator: separator of each element | |
/// - Returns: string | |
public static func allCasesDescription(surrounded: String = "'", separator: String = ", ") -> String { | |
return self.allCases | |
.map { String(describing: $0) } | |
.map { "\(surrounded)\($0)\(surrounded)" } | |
.joined(separator: separator) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
extension Collection where Self.Iterator.Element: RandomAccessCollection { | |
/// Swapping Rows and Columns in a 2D Array | |
/// - Returns: transposed 2D Array | |
func transposed() -> [[Self.Iterator.Element.Iterator.Element]] { | |
guard let firstRow = self.first else { return [] } | |
return firstRow.indices.map { index in | |
self.map{ $0[index] } | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
extension FileHandle: TextOutputStream { | |
/// Appends the given string to the stream. | |
/// - Parameter string: will be appended. Argument will be encoded as UTF-8. | |
public func write(_ string: String) { | |
guard let data = string.data(using: .utf8) else { return } | |
self.write(data) | |
} | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
public extension FileManager { | |
/// Move to specified directory and execute closure | |
/// Move back to original location after closure | |
/// - Parameters: | |
/// - path: Destination | |
/// - closure: Invoked in the path you gave | |
/// - Throws: exception | |
func chdir(_ path: String, closure: () throws -> Void) rethrows { | |
let previous = self.currentDirectoryPath | |
self.changeCurrentDirectoryPath(path) | |
defer { self.changeCurrentDirectoryPath(previous) } | |
try closure() | |
} | |
/// Remove it if it exists | |
/// | |
/// - Parameter url: File URL you want to remove | |
/// - Throws: exception from `removeItem(at:)` | |
func removeItemIfExists(at url: Foundation.URL) throws { | |
if self.fileExists(atPath: url.path) { | |
try self.removeItem(at: url) | |
} | |
} | |
/// Trash it if it exists | |
/// | |
/// - Parameter url: File URL you want to throw away | |
/// - Throws: exception from `trashItem(at: resultingItemURL:)` | |
func trashItemIfExists(at url: Foundation.URL) throws { | |
if self.fileExists(atPath: url.path) { | |
try self.trashItem(at: url, resultingItemURL: nil) | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Logging | |
// It depends on "https://github.com/apple/swift-log". | |
public var logger = Logger(label: "com.example.cli", | |
factory: StreamLogHandler.standardError) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
extension OutputStream: TextOutputStream { | |
public func write(_ string: String) { | |
let encodedDataArray = [UInt8](string.utf8) | |
write(encodedDataArray, maxLength: encodedDataArray.count) | |
} | |
public func writeln(_ string: String) { | |
write(string + "\n") | |
} | |
} | |
extension OutputStream { | |
static var stdout: OutputStream { OutputStream(toFileAtPath: "/dev/stdout", append: false)! } | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import ArgumentParser | |
import Path | |
// It depends on | |
// - https://github.com/apple/swift-log | |
// - https://github.com/mxcl/Path.swift | |
extension Path: ExpressibleByArgument { | |
public init?(argument: String) { | |
self = Path(argument) ?? Path.cwd/argument | |
} | |
public var defaultValueDescription: String { | |
if self == Path.cwd/"." { | |
return "current directory" | |
} | |
return String(describing: self) | |
} | |
} | |
extension URL { | |
/// Last component with/whithout extension. | |
/// - Parameter dropExtension: drop file extension if it's true. Otherwise it keeps file extension. | |
/// - Returns: Last component with/whithout extension. | |
func basename(dropExtension: Bool) -> String { | |
guard let p = Path(url: self) else { | |
preconditionFailure("file URL expected") | |
} | |
return p.basename(dropExtension: dropExtension) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
// https://stackoverflow.com/a/34712330 | |
// You can use this for String as well | |
extension RangeReplaceableCollection where Element: Hashable { | |
public func removingDuplicates() -> Self { | |
var set = Set<Element>() | |
return filter { set.insert($0).inserted } | |
} | |
public mutating func removeDuplicates() { | |
var set = Set<Element>() | |
removeAll { !set.insert($0).inserted } | |
} | |
} | |
// https://stackoverflow.com/a/55684308 | |
extension RangeReplaceableCollection { | |
/// Returns a collection containing, in order, the first instances of | |
/// elements of the sequence that compare equally for the keyPath. | |
public func unique<T: Hashable>(for keyPath: KeyPath<Element, T>) -> Self { | |
var unique = Set<T>() | |
return filter { unique.insert($0[keyPath: keyPath]).inserted } | |
} | |
/// Keeps only, in order, the first instances of | |
/// elements of the collection that compare equally for the keyPath. | |
public mutating func uniqueInPlace<T: Hashable>(for keyPath: KeyPath<Element, T>) { | |
var unique = Set<T>() | |
removeAll { !unique.insert($0[keyPath: keyPath]).inserted } | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
/// Common Error type | |
public struct RuntimeError: Error, CustomStringConvertible { | |
/// It shows on terminal when error occurs | |
public var description: String | |
init(_ description: String) { | |
self.description = description | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import TSCBasic | |
/// Helper class that invoke other command line tool. It handles signal as well. | |
public final class Shell { | |
public static let shared: Shell = Shell() | |
public typealias TerminationHandler = () -> Void | |
private var handlers: [TerminationHandler] = [] | |
private var signalSource: DispatchSourceSignal? | |
public init() { | |
} | |
// Setup function before you use if you want to handle signals | |
public func monitoringSignals() { | |
// Make sure the signal does not terminate the application. | |
signal(SIGINT, SIG_IGN); | |
signalSource = DispatchSource.makeSignalSource(signal: SIGINT, queue: .main) | |
signalSource!.setEventHandler { [weak self] in | |
if let strongSelf = self { | |
for h in strongSelf.handlers { | |
h() | |
} | |
} | |
exit(EXIT_FAILURE) | |
} | |
signalSource!.resume() | |
} | |
/// Run external command line tools | |
/// synchronous. Use TSCBasic.Process because gets freeze when using Foundation.Process | |
/// - Parameters: | |
/// - arguments: arguments include command itself | |
/// - processSet: ProcessSet to handle signals | |
/// - terminationHandler: callback when signal arrived | |
/// - Returns: status code | |
/// - Throws: exception from TSCBasic.Process | |
public func run(arguments: [String], outputRedirection: TSCBasic.Process.OutputRedirection = .none, processSet: ProcessSet? = nil, terminationHandler: TerminationHandler? = nil, verbose: Bool = Process.verbose) throws -> (result: ProcessResult, statusCode: Int32) { | |
if let h = terminationHandler { | |
handlers.append(h) | |
} else if let processSet = processSet { | |
let handler = { processSet.terminate() } | |
handlers.append(handler) | |
} | |
let result = try _run(arguments: arguments, outputRedirection: outputRedirection, processSet: processSet) | |
switch result.exitStatus { | |
case .signalled(let v): | |
return (result, v) | |
case .terminated(let v): | |
return (result, v) | |
} | |
} | |
} | |
extension Shell { | |
fileprivate func _run(arguments: [String], outputRedirection: TSCBasic.Process.OutputRedirection = .none, processSet: ProcessSet? = nil, verbose: Bool = Process.verbose) throws -> ProcessResult { | |
let process = TSCBasic.Process.init(arguments: arguments, outputRedirection: outputRedirection, verbose: verbose) | |
try process.launch() | |
try processSet?.add(process) | |
return try process.waitUntilExit() | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
public extension String { | |
/// It returns lines | |
public var lines: [String] { | |
var ret: [String] = [] | |
self.enumerateLines { (line, _) in | |
ret.append(line) | |
} | |
return ret | |
} | |
/// decode character reference like `焱` | |
/// | |
/// - Returns: decoded string | |
public func decodeNumericEntities() -> String { | |
let str = NSMutableString(string: self) | |
CFStringTransform(str, nil, "Any-Hex/XML10" as CFString, true) | |
return str as String | |
} | |
} | |
public extension String { | |
/// Shorthand to get NSString | |
public var ns: NSString { | |
return self as NSString | |
} | |
/// It returns true if `self` is number. otherwise false. | |
public var isNumber: Bool { | |
return !isEmpty && rangeOfCharacter(from: CharacterSet.decimalDigits.inverted) == nil | |
} | |
} | |
public extension StringProtocol { | |
/// Remove given prefix if it exists. otherwise it returns `self` as is. | |
/// - Parameter prefix: it you whould like to remove. | |
/// - Returns: New String | |
public func dropPrefix(_ prefix: String) -> String { | |
guard hasPrefix(prefix) else { | |
return String(self) | |
} | |
return String(dropFirst(prefix.count)) | |
} | |
/// Remove given suffix if it exists. otherwise it returns `self` as is. | |
/// - Parameter suffix: it you whould like to remove. | |
/// - Returns: New String | |
public func dropSuffix(_ suffix: String) -> String { | |
guard hasSuffix(suffix) else { | |
return String(self) | |
} | |
return String(dropLast(suffix.count)) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
/// Convenience extension | |
extension String: Error, CustomStringConvertible { | |
public var description: String { | |
self | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
extension TextOutputStream { | |
mutating func writeln(_ string: String) { | |
write(string + "\n") | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Darwin | |
/// StandardError | |
public struct StandardError: TextOutputStream { | |
public mutating func write(_ string: String) { | |
for byte in string.utf8 { putc(numericCast(byte), stderr) } | |
} | |
} | |
/// Global variable of StandardError | |
public var standardError = StandardError() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment