Skip to content

Instantly share code, notes, and snippets.

@griffin-stewie
Last active September 2, 2021 14:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save griffin-stewie/a7e1bdef29eb249d30f7569aabd54ec1 to your computer and use it in GitHub Desktop.
Save griffin-stewie/a7e1bdef29eb249d30f7569aabd54ec1 to your computer and use it in GitHub Desktop.
Swift Command Line Tool Helpers
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 }
}
}
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)
}
}
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] }
}
}
}
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)
}
}
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)
}
}
}
import Foundation
import Logging
// It depends on "https://github.com/apple/swift-log".
public var logger = Logger(label: "com.example.cli",
factory: StreamLogHandler.standardError)
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)! }
}
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)
}
}
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 }
}
}
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
}
}
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()
}
}
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 `&#28977;`
///
/// - 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))
}
}
import Foundation
/// Convenience extension
extension String: Error, CustomStringConvertible {
public var description: String {
self
}
}
import Foundation
extension TextOutputStream {
mutating func writeln(_ string: String) {
write(string + "\n")
}
}
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