mirror of
https://github.com/twostraws/Brisk.git
synced 2024-10-05 14:47:14 +03:00
Adding initial files.
This commit is contained in:
commit
4b69686f75
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
7
BriskScript/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
BriskScript/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
File diff suppressed because it is too large
Load Diff
15
BriskScript/Package.swift
Normal file
15
BriskScript/Package.swift
Normal file
@ -0,0 +1,15 @@
|
||||
// swift-tools-version:5.1
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "BriskScript",
|
||||
platforms: [
|
||||
.macOS(.v10_15)
|
||||
],
|
||||
dependencies: [
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "BriskScript", dependencies: [], path: "." )
|
||||
]
|
||||
)
|
15
BriskScript/Sources/Brisk/Array.swift
Normal file
15
BriskScript/Sources/Brisk/Array.swift
Normal file
@ -0,0 +1,15 @@
|
||||
//
|
||||
// Array.swift
|
||||
// Terminal
|
||||
//
|
||||
// Created by Paul Hudson on 20/02/2020.
|
||||
// Copyright © 2020 Hacking with Swift. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Array where Element: Equatable {
|
||||
mutating func remove(_ obj: Element) {
|
||||
self = self.filter { $0 != obj }
|
||||
}
|
||||
}
|
38
BriskScript/Sources/Brisk/BinaryFloatingPoint.swift
Normal file
38
BriskScript/Sources/Brisk/BinaryFloatingPoint.swift
Normal file
@ -0,0 +1,38 @@
|
||||
//
|
||||
// BinaryFloatingPoint.swift
|
||||
// Terminal
|
||||
//
|
||||
// Created by Paul Hudson on 20/02/2020.
|
||||
// Copyright © 2020 Hacking with Swift. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension BinaryFloatingPoint {
|
||||
/// e
|
||||
static var e: Self { 2.71828182845904523536028747135266250 }
|
||||
|
||||
/// log2(e)
|
||||
static var log2e: Self { 1.44269504088896340735992468100189214 }
|
||||
|
||||
/// log10(e)
|
||||
static var log10e: Self { 0.434294481903251827651128918916605082 }
|
||||
|
||||
/// loge(2)
|
||||
static var ln2: Self { 0.693147180559945309417232121458176568 }
|
||||
|
||||
/// loge(10)
|
||||
static var ln10: Self { 2.30258509299404568401799145468436421 }
|
||||
|
||||
/// pi/2
|
||||
static var pi2: Self { 1.57079632679489661923132169163975144 }
|
||||
|
||||
/// pi/4
|
||||
static var pi4: Self { 0.785398163397448309615660845819875721 }
|
||||
|
||||
/// sqrt(2)
|
||||
static var sqrt2: Self { 1.41421356237309504880168872420969808 }
|
||||
|
||||
/// 1/sqrt(2)
|
||||
static var sqrt1_2: Self { 0.707106781186547524400844362104849039 }
|
||||
}
|
21
BriskScript/Sources/Brisk/Comparable.swift
Normal file
21
BriskScript/Sources/Brisk/Comparable.swift
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// Comparable.swift
|
||||
// Terminal
|
||||
//
|
||||
// Created by Paul Hudson on 20/02/2020.
|
||||
// Copyright © 2020 Hacking with Swift. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Comparable {
|
||||
func clamp(low: Self, high: Self) -> Self {
|
||||
if self > high {
|
||||
return high
|
||||
} else if self < low {
|
||||
return low
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
}
|
81
BriskScript/Sources/Brisk/Data.swift
Normal file
81
BriskScript/Sources/Brisk/Data.swift
Normal file
@ -0,0 +1,81 @@
|
||||
//
|
||||
// Data.swift
|
||||
// Terminal
|
||||
//
|
||||
// Created by Paul Hudson on 20/02/2020.
|
||||
// Copyright © 2020 Hacking with Swift. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
extension Data {
|
||||
func md5() -> String {
|
||||
let hashed = Insecure.MD5.hash(data: self)
|
||||
return hashed.compactMap { String(format: "%02x", $0) }.joined()
|
||||
}
|
||||
|
||||
func sha1() -> String {
|
||||
let hashed = Insecure.SHA1.hash(data: self)
|
||||
return hashed.compactMap { String(format: "%02x", $0) }.joined()
|
||||
}
|
||||
|
||||
func sha256() -> String {
|
||||
let hashed = SHA256.hash(data: self)
|
||||
return hashed.compactMap { String(format: "%02x", $0) }.joined()
|
||||
}
|
||||
|
||||
func base64() -> Data {
|
||||
base64EncodedData()
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func write(to file: String) -> Bool {
|
||||
do {
|
||||
let url = URL(fileURLWithPath: file.expandingPath())
|
||||
try self.write(to: url, options: .atomic)
|
||||
return true
|
||||
} catch {
|
||||
printOrDie("Failed to write \(file.expandingPath()): \(error.localizedDescription)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
init(url: String) {
|
||||
guard let parsedURL = URL(string: url) else {
|
||||
printOrDie("Bad URL: \(url)")
|
||||
self = Data()
|
||||
return
|
||||
}
|
||||
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
var result = Data()
|
||||
|
||||
let task = URLSession.shared.dataTask(with: parsedURL) { data, _, error in
|
||||
if let data = data {
|
||||
result = data
|
||||
} else {
|
||||
printOrDie("Fetched failed for \(url) – \(error?.localizedDescription ?? "Unknown error")")
|
||||
}
|
||||
|
||||
semaphore.signal()
|
||||
}
|
||||
|
||||
task.resume()
|
||||
semaphore.wait()
|
||||
self = result
|
||||
}
|
||||
|
||||
init?(file: String) {
|
||||
do {
|
||||
let contents = try String(contentsOfFile: file.expandingPath())
|
||||
self = Data(contents.utf8)
|
||||
} catch {
|
||||
if Brisk.haltOnError {
|
||||
exit("Unable to read contents of \(file).")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
21
BriskScript/Sources/Brisk/Date.swift
Normal file
21
BriskScript/Sources/Brisk/Date.swift
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// Date.swift
|
||||
// Terminal
|
||||
//
|
||||
// Created by Paul Hudson on 20/02/2020.
|
||||
// Copyright © 2020 Hacking with Swift. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Date {
|
||||
static func unixTime() -> Int {
|
||||
Int(Date().timeIntervalSince1970)
|
||||
}
|
||||
|
||||
func string(using format: String) -> String {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = format
|
||||
return dateFormatter.string(from: Date())
|
||||
}
|
||||
}
|
55
BriskScript/Sources/Brisk/Decodable.swift
Normal file
55
BriskScript/Sources/Brisk/Decodable.swift
Normal file
@ -0,0 +1,55 @@
|
||||
//
|
||||
// Encodable.swift
|
||||
// Terminal
|
||||
//
|
||||
// Created by Paul Hudson on 20/02/2020.
|
||||
// Copyright © 2020 Hacking with Swift. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
func decode<T: Decodable>(string input: String, as type: T.Type, keys: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys, dates: JSONDecoder.DateDecodingStrategy = .deferredToDate) -> T {
|
||||
let decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = keys
|
||||
decoder.dateDecodingStrategy = dates
|
||||
|
||||
let fileData = Data(input.utf8)
|
||||
|
||||
do {
|
||||
return try decoder.decode(type, from: fileData)
|
||||
} catch {
|
||||
exit("Failed to decode \(input) as \(type)", code: 1)
|
||||
}
|
||||
}
|
||||
|
||||
func decode<T: Decodable>(file: String, as type: T.Type, keys: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys, dates: JSONDecoder.DateDecodingStrategy = .deferredToDate) -> T {
|
||||
let decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = keys
|
||||
decoder.dateDecodingStrategy = dates
|
||||
|
||||
guard let fileData = Data(file: file) else {
|
||||
exit("Failed to load \(file) for decoding.")
|
||||
}
|
||||
|
||||
do {
|
||||
return try decoder.decode(type, from: fileData)
|
||||
} catch {
|
||||
exit("Failed to decode \(file) as \(type)", code: 1)
|
||||
}
|
||||
}
|
||||
|
||||
extension Decodable {
|
||||
init(url: String, keys: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys, dates: JSONDecoder.DateDecodingStrategy = .deferredToDate) {
|
||||
let data = Data(url: url)
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = keys
|
||||
decoder.dateDecodingStrategy = dates
|
||||
|
||||
if let decoded = try? decoder.decode(Self.self, from: data) {
|
||||
self = decoded
|
||||
} else {
|
||||
exit("Unable to decode \(Self.self) from \(url)")
|
||||
}
|
||||
}
|
||||
}
|
93
BriskScript/Sources/Brisk/Directory.swift
Normal file
93
BriskScript/Sources/Brisk/Directory.swift
Normal file
@ -0,0 +1,93 @@
|
||||
//
|
||||
// Directory.swift
|
||||
// Terminal
|
||||
//
|
||||
// Created by Paul Hudson on 20/02/2020.
|
||||
// Copyright © 2020 Hacking with Swift. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
var homeDir: String {
|
||||
NSHomeDirectory()
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func mkdir(_ directory: String, withIntermediates: Bool = true) -> Bool {
|
||||
do {
|
||||
try FileManager.default.createDirectory(atPath: directory.expandingPath(), withIntermediateDirectories: withIntermediates)
|
||||
return true
|
||||
} catch {
|
||||
printOrDie("Failed to create directory: \(directory)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func scandir(_ directory: String, recursively: Bool = false) -> [String] {
|
||||
do {
|
||||
if recursively {
|
||||
let enumerator = FileManager.default.enumerator(atPath: directory.expandingPath())
|
||||
return enumerator?.allObjects as? [String] ?? []
|
||||
} else {
|
||||
return try FileManager.default.contentsOfDirectory(atPath: directory.expandingPath())
|
||||
}
|
||||
} catch {
|
||||
printOrDie("Failed to read directory: \(directory)")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
func recurse(_ directory: String, predicate: (String) -> Bool, action: (String) throws -> Void) rethrows {
|
||||
let enumerator = FileManager.default.enumerator(atPath: directory.expandingPath())
|
||||
|
||||
while let file = enumerator?.nextObject() as? String {
|
||||
if predicate(file) {
|
||||
try action(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func recurse(_ directory: String, extensions: String..., action: (String) throws -> Void) rethrows {
|
||||
let enumerator = FileManager.default.enumerator(atPath: directory.expandingPath())
|
||||
|
||||
while let file = enumerator?.nextObject() as? String {
|
||||
if extensions.isEmpty == false {
|
||||
if extensions.contains(where: file.hasSuffix) {
|
||||
try action(file)
|
||||
}
|
||||
} else {
|
||||
try action(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isdir(_ name: String) -> Bool {
|
||||
var isDir: ObjCBool = false
|
||||
|
||||
if FileManager.default.fileExists(atPath: name.expandingPath(), isDirectory: &isDir) {
|
||||
if isDir.boolValue {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func rmdir(_ file: String) -> Bool {
|
||||
fileDelete(file)
|
||||
}
|
||||
|
||||
func getcwd() -> String {
|
||||
FileManager.default.currentDirectoryPath
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func chdir(_ newDirectory: String) -> Bool {
|
||||
if FileManager.default.changeCurrentDirectoryPath(newDirectory.expandingPath()) {
|
||||
return true
|
||||
} else {
|
||||
printOrDie("Failed to change to directory \(newDirectory)")
|
||||
return false
|
||||
}
|
||||
}
|
38
BriskScript/Sources/Brisk/Encodable.swift
Normal file
38
BriskScript/Sources/Brisk/Encodable.swift
Normal file
@ -0,0 +1,38 @@
|
||||
//
|
||||
// Encodable.swift
|
||||
// Terminal
|
||||
//
|
||||
// Created by Paul Hudson on 20/02/2020.
|
||||
// Copyright © 2020 Hacking with Swift. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Encodable {
|
||||
func jsonData(keys: JSONEncoder.KeyEncodingStrategy = .useDefaultKeys, dates: JSONEncoder.DateEncodingStrategy = .deferredToDate) -> Data {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.keyEncodingStrategy = keys
|
||||
encoder.dateEncodingStrategy = dates
|
||||
|
||||
do {
|
||||
return try encoder.encode(self)
|
||||
} catch {
|
||||
printOrDie("Failed to encode to JSON.")
|
||||
return Data()
|
||||
}
|
||||
}
|
||||
|
||||
func jsonString(keys: JSONEncoder.KeyEncodingStrategy = .useDefaultKeys, dates: JSONEncoder.DateEncodingStrategy = .deferredToDate) -> String {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.keyEncodingStrategy = keys
|
||||
encoder.dateEncodingStrategy = dates
|
||||
|
||||
do {
|
||||
let data = try encoder.encode(self)
|
||||
return String(decoding: data, as: UTF8.self)
|
||||
} catch {
|
||||
printOrDie("Failed to encode to JSON.")
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
119
BriskScript/Sources/Brisk/File.swift
Normal file
119
BriskScript/Sources/Brisk/File.swift
Normal file
@ -0,0 +1,119 @@
|
||||
//
|
||||
// File.swift
|
||||
// Terminal
|
||||
//
|
||||
// Created by Paul Hudson on 20/02/2020.
|
||||
// Copyright © 2020 Hacking with Swift. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@discardableResult
|
||||
func fileCreate(_ file: String, contents: Data? = nil) -> Bool {
|
||||
if FileManager.default.createFile(atPath: file.expandingPath(), contents: contents) {
|
||||
return true
|
||||
} else {
|
||||
printOrDie("Failed to create file: \(file)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func fileDelete(_ file: String) -> Bool {
|
||||
do {
|
||||
try FileManager.default.removeItem(atPath: file.expandingPath())
|
||||
return true
|
||||
} catch {
|
||||
printOrDie("Failed to delete \(file)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func fileExists(_ name: String) -> Bool {
|
||||
FileManager.default.fileExists(atPath: name.expandingPath())
|
||||
}
|
||||
|
||||
func fileProperties(_ file: String) -> [FileAttributeKey: Any] {
|
||||
do {
|
||||
return try FileManager.default.attributesOfItem(atPath: file.expandingPath())
|
||||
} catch {
|
||||
printOrDie("Failed to open \(file) for reading.")
|
||||
return [:]
|
||||
}
|
||||
}
|
||||
|
||||
func fileSize(_ file: String) -> UInt64 {
|
||||
do {
|
||||
let attr = try FileManager.default.attributesOfItem(atPath: file.expandingPath())
|
||||
if let fileSize = attr[.size] as? UInt64 {
|
||||
return fileSize
|
||||
} else {
|
||||
printOrDie("Failed to read size of \(file).")
|
||||
return 0
|
||||
}
|
||||
} catch {
|
||||
printOrDie("Failed to read size of \(file).")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func fileCreation(_ file: String) -> Date {
|
||||
do {
|
||||
let attr = try FileManager.default.attributesOfItem(atPath: file.expandingPath())
|
||||
if let date = attr[.creationDate] as? Date {
|
||||
return date
|
||||
} else {
|
||||
printOrDie("Failed to read creation date of \(file).")
|
||||
return Date()
|
||||
}
|
||||
} catch {
|
||||
printOrDie("Failed to read creation date of \(file).")
|
||||
return Date()
|
||||
}
|
||||
}
|
||||
|
||||
func fileModified(_ file: String) -> Date {
|
||||
do {
|
||||
let attr = try FileManager.default.attributesOfItem(atPath: file.expandingPath())
|
||||
if let date = attr[.modificationDate] as? Date {
|
||||
return date
|
||||
} else {
|
||||
printOrDie("Failed to read modification date of \(file).")
|
||||
return Date()
|
||||
}
|
||||
} catch {
|
||||
printOrDie("Failed to read modification date of \(file).")
|
||||
return Date()
|
||||
}
|
||||
}
|
||||
|
||||
func tempFile() -> String {
|
||||
NSTemporaryDirectory() + UUID().uuidString
|
||||
}
|
||||
|
||||
func basename(of file: String) -> String {
|
||||
URL(fileURLWithPath: file).lastPathComponent
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func fileCopy(_ from: String, to: String) -> Bool {
|
||||
let fromPath = from.expandingPath()
|
||||
var toPath = to.expandingPath()
|
||||
|
||||
var isDir: ObjCBool = false
|
||||
|
||||
if FileManager.default.fileExists(atPath: toPath, isDirectory: &isDir) {
|
||||
if isDir.boolValue {
|
||||
toPath += ""/basename(of: fromPath)
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
try FileManager.default.copyItem(atPath: fromPath, toPath: toPath)
|
||||
return true
|
||||
} catch {
|
||||
printOrDie("Failed to copy \(fromPath) to \(toPath). Error: \(error.localizedDescription)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
41
BriskScript/Sources/Brisk/Numeric.swift
Normal file
41
BriskScript/Sources/Brisk/Numeric.swift
Normal file
@ -0,0 +1,41 @@
|
||||
//
|
||||
// Comparable.swift
|
||||
// Terminal
|
||||
//
|
||||
// Created by Paul Hudson on 20/02/2020.
|
||||
// Copyright © 2020 Hacking with Swift. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
func +<I: BinaryInteger, F: BinaryFloatingPoint>(lhs: I, rhs: F) -> F {
|
||||
return F(lhs) + rhs
|
||||
}
|
||||
|
||||
func +<I: BinaryInteger, F: BinaryFloatingPoint>(lhs: F, rhs: I) -> F {
|
||||
return lhs + F(rhs)
|
||||
}
|
||||
|
||||
func -<I: BinaryInteger, F: BinaryFloatingPoint>(lhs: I, rhs: F) -> F {
|
||||
return F(lhs) - rhs
|
||||
}
|
||||
|
||||
func -<I: BinaryInteger, F: BinaryFloatingPoint>(lhs: F, rhs: I) -> F {
|
||||
return lhs - F(rhs)
|
||||
}
|
||||
|
||||
func *<I: BinaryInteger, F: BinaryFloatingPoint>(lhs: I, rhs: F) -> F {
|
||||
return F(lhs) * rhs
|
||||
}
|
||||
|
||||
func *<I: BinaryInteger, F: BinaryFloatingPoint>(lhs: F, rhs: I) -> F {
|
||||
return lhs * F(rhs)
|
||||
}
|
||||
|
||||
func /<I: BinaryInteger, F: BinaryFloatingPoint>(lhs: I, rhs: F) -> F {
|
||||
return F(lhs) / rhs
|
||||
}
|
||||
|
||||
func /<I: BinaryInteger, F: BinaryFloatingPoint>(lhs: F, rhs: I) -> F {
|
||||
return lhs / F(rhs)
|
||||
}
|
29
BriskScript/Sources/Brisk/Process.swift
Normal file
29
BriskScript/Sources/Brisk/Process.swift
Normal file
@ -0,0 +1,29 @@
|
||||
//
|
||||
// Process.swift
|
||||
// Terminal
|
||||
//
|
||||
// Created by Paul Hudson on 20/02/2020.
|
||||
// Copyright © 2020 Hacking with Swift. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
func getpid() -> Int {
|
||||
Int(ProcessInfo.processInfo.processIdentifier)
|
||||
}
|
||||
|
||||
func getHostName() -> String {
|
||||
ProcessInfo.processInfo.hostName
|
||||
}
|
||||
|
||||
func getUserName() -> String {
|
||||
ProcessInfo.processInfo.userName
|
||||
}
|
||||
|
||||
func getenv(_ key: String) -> String {
|
||||
ProcessInfo.processInfo.environment[key, default: ""]
|
||||
}
|
||||
|
||||
func setenv(_ key: String, _ value: String) {
|
||||
setenv(key, value, 1)
|
||||
}
|
53
BriskScript/Sources/Brisk/Sequence.swift
Normal file
53
BriskScript/Sources/Brisk/Sequence.swift
Normal file
@ -0,0 +1,53 @@
|
||||
//
|
||||
// Sequence.swift
|
||||
// Terminal
|
||||
//
|
||||
// Created by Paul Hudson on 20/02/2020.
|
||||
// Copyright © 2020 Hacking with Swift. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Sequence where Element: Hashable {
|
||||
func unique() -> [Element] {
|
||||
var checked = Set<Element>()
|
||||
var result = [Element]()
|
||||
|
||||
for item in self {
|
||||
if checked.contains(item) == false {
|
||||
checked.insert(item)
|
||||
result.append(item)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
extension Sequence where Element: Equatable {
|
||||
func indexes(of searchItem: Element) -> [Int] {
|
||||
var returnValue = [Int]()
|
||||
|
||||
for (index, item) in self.enumerated() {
|
||||
if item == searchItem {
|
||||
returnValue.append(index)
|
||||
}
|
||||
}
|
||||
|
||||
return returnValue
|
||||
}
|
||||
}
|
||||
|
||||
extension Sequence {
|
||||
func any(match predicate: (Element) throws -> Bool) rethrows -> Bool {
|
||||
try contains(where: predicate)
|
||||
}
|
||||
|
||||
func none(match predicate: (Element) throws -> Bool) rethrows -> Bool {
|
||||
try !contains(where: predicate)
|
||||
}
|
||||
|
||||
func random(_ num: Int) -> [Element] {
|
||||
Array(shuffled().prefix(num))
|
||||
}
|
||||
}
|
236
BriskScript/Sources/Brisk/String.swift
Normal file
236
BriskScript/Sources/Brisk/String.swift
Normal file
@ -0,0 +1,236 @@
|
||||
//
|
||||
// String.swift
|
||||
// Terminal
|
||||
//
|
||||
// Created by Paul Hudson on 20/02/2020.
|
||||
// Copyright © 2020 Hacking with Swift. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
extension String {
|
||||
var lines: [String] {
|
||||
components(separatedBy: .newlines)
|
||||
}
|
||||
|
||||
static func / (lhs: String, rhs: String) -> String {
|
||||
"\(lhs)/\(rhs)"
|
||||
}
|
||||
|
||||
func md5() -> String {
|
||||
let data = Data(self.utf8)
|
||||
let hashed = Insecure.MD5.hash(data: data)
|
||||
return hashed.compactMap { String(format: "%02x", $0) }.joined()
|
||||
}
|
||||
|
||||
func sha1() -> String {
|
||||
let data = Data(self.utf8)
|
||||
let hashed = Insecure.SHA1.hash(data: data)
|
||||
return hashed.compactMap { String(format: "%02x", $0) }.joined()
|
||||
}
|
||||
|
||||
func sha256() -> String {
|
||||
let data = Data(self.utf8)
|
||||
let hashed = SHA256.hash(data: data)
|
||||
return hashed.compactMap { String(format: "%02x", $0) }.joined()
|
||||
}
|
||||
|
||||
func base64() -> String {
|
||||
Data(utf8).base64EncodedString()
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func write(to file: String) -> Bool {
|
||||
do {
|
||||
try self.write(toFile: file.expandingPath(), atomically: true, encoding: .utf8)
|
||||
return true
|
||||
} catch {
|
||||
printOrDie("Failed to write \(file.expandingPath()): \(error.localizedDescription)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func replacing(_ search: String, with replacement: String) -> String {
|
||||
self.replacingOccurrences(of: search, with: replacement)
|
||||
}
|
||||
|
||||
mutating func replace(_ search: String, with replacement: String) {
|
||||
self = self.replacingOccurrences(of: search, with: replacement)
|
||||
}
|
||||
|
||||
/**
|
||||
Replaces occurences of one string with another, up to `count` times.
|
||||
- Parameter of: The string to look for.
|
||||
- Parameter with: The string to replace.
|
||||
- Parameter count: The maximum number of replacements
|
||||
- Returns: The string with replacements made.
|
||||
*/
|
||||
func replacing(_ search: String, with replacement: String, count maxReplacements: Int) -> String {
|
||||
var count = 0
|
||||
var returnValue = self
|
||||
|
||||
while let range = returnValue.range(of: search) {
|
||||
returnValue = returnValue.replacingCharacters(in: range, with: replacement)
|
||||
count += 1
|
||||
|
||||
// exit as soon as we've made all replacements
|
||||
if count == maxReplacements {
|
||||
return returnValue
|
||||
}
|
||||
}
|
||||
|
||||
return returnValue
|
||||
}
|
||||
|
||||
mutating func replace(_ search: String, with replacement: String, count maxReplacements: Int) {
|
||||
self = replacing(search, with: replacement, count: maxReplacements)
|
||||
}
|
||||
|
||||
mutating func trim(_ characters: String = " \t\n\r\0") {
|
||||
self = self.trimmingCharacters(in: CharacterSet(charactersIn: characters))
|
||||
}
|
||||
|
||||
func trimmed(_ characters: String = " \t\n\r\0") -> String {
|
||||
self.trimmingCharacters(in: CharacterSet(charactersIn: characters))
|
||||
}
|
||||
|
||||
func matches(regex: String, options: NSRegularExpression.Options = []) -> Bool {
|
||||
do {
|
||||
let regex = try NSRegularExpression(pattern: regex, options: options)
|
||||
let matches = regex.matches(in: self, range: NSRange(location: 0, length: self.utf16.count))
|
||||
return matches.count > 0
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func replacing(regex: String, with replacement: String, options: NSString.CompareOptions) -> String {
|
||||
self.replacingOccurrences(of: regex, with: replacement, options: options.union([.regularExpression]))
|
||||
}
|
||||
|
||||
mutating func replace(regex: String, with replacement: String, options: NSString.CompareOptions) {
|
||||
self = self.replacingOccurrences(of: regex, with: replacement, options: options.union([.regularExpression]))
|
||||
}
|
||||
|
||||
/**
|
||||
Lets you read one character from this string using its integer index
|
||||
*/
|
||||
subscript(idx: Int) -> String {
|
||||
String(self[index(startIndex, offsetBy: idx)])
|
||||
}
|
||||
|
||||
/**
|
||||
Lets you read a slice of a string using a regular int range.
|
||||
*/
|
||||
subscript(range: Range<Int>) -> String {
|
||||
guard range.lowerBound < count else { return "" }
|
||||
guard range.upperBound < count else { return self[range.lowerBound...] }
|
||||
|
||||
let start = index(startIndex, offsetBy: range.lowerBound)
|
||||
let end = index(startIndex, offsetBy: range.upperBound)
|
||||
return String(self[start ..< end])
|
||||
}
|
||||
|
||||
/**
|
||||
Lets you read a slice of a string using a regular int range.
|
||||
*/
|
||||
subscript(range: ClosedRange<Int>) -> String {
|
||||
guard range.lowerBound < count else { return "" }
|
||||
guard range.upperBound < count else { return self[range.lowerBound...] }
|
||||
|
||||
let start = index(startIndex, offsetBy: range.lowerBound)
|
||||
let end = index(startIndex, offsetBy: range.upperBound)
|
||||
return String(self[start ... end])
|
||||
}
|
||||
|
||||
/**
|
||||
Lets you read a slice of a string using a partial range from, e.g. 3...
|
||||
*/
|
||||
subscript(range: CountablePartialRangeFrom<Int>) -> String {
|
||||
guard range.lowerBound < count else { return "" }
|
||||
let start = index(startIndex, offsetBy: range.lowerBound)
|
||||
return String(self[start ..< endIndex])
|
||||
}
|
||||
|
||||
/**
|
||||
Lets you read a slice of a string using a partial range through, e.g. ...3
|
||||
*/
|
||||
subscript(range: PartialRangeThrough<Int>) -> String {
|
||||
guard range.upperBound < count else { return self }
|
||||
let end = index(startIndex, offsetBy: range.upperBound)
|
||||
return String(self[startIndex ... end])
|
||||
}
|
||||
|
||||
/**
|
||||
Lets you read a slice of a string using a partial range up to, e.g. ..<3
|
||||
*/
|
||||
subscript(range: PartialRangeUpTo<Int>) -> String {
|
||||
guard range.upperBound < count else { return self }
|
||||
let end = index(startIndex, offsetBy: range.upperBound)
|
||||
return String(self[startIndex ..< end])
|
||||
}
|
||||
|
||||
func expandingPath() -> String {
|
||||
var result = self
|
||||
|
||||
if result.first == "~" {
|
||||
result = homeDir + result.dropFirst()
|
||||
} else if result.first == "." {
|
||||
result = getcwd() + result.dropFirst()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
init(url: String) {
|
||||
let data = Data(url: url)
|
||||
self = String(decoding: data, as: UTF8.self)
|
||||
}
|
||||
|
||||
init?(file: String) {
|
||||
do {
|
||||
self = try String(contentsOfFile: file.expandingPath())
|
||||
} catch {
|
||||
if Brisk.haltOnError {
|
||||
exit("Unable to read contents of \(file).")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes a prefix from a string; does nothing if the prefix doesn't exist.
|
||||
*/
|
||||
func deletingPrefix(_ prefix: String) -> String {
|
||||
guard self.hasPrefix(prefix) else { return self }
|
||||
return String(self.dropFirst(prefix.count))
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes a suffix from a string; does nothing if the suffix doesn't exist.
|
||||
*/
|
||||
func deletingSuffix(_ suffix: String) -> String {
|
||||
guard self.hasSuffix(suffix) else { return self }
|
||||
return String(self.dropLast(suffix.count))
|
||||
}
|
||||
|
||||
/**
|
||||
Ensures a string starts with a given prefix.
|
||||
Parameter prefix: The prefix to ensure.
|
||||
*/
|
||||
func withPrefix(_ prefix: String) -> String {
|
||||
if self.hasPrefix(prefix) { return self }
|
||||
return "\(prefix)\(self)"
|
||||
}
|
||||
|
||||
/**
|
||||
Ensures a string ends with a given suffix.
|
||||
Parameter suffix: The suffix to ensure.
|
||||
*/
|
||||
func withSuffix(_ suffix: String) -> String {
|
||||
if self.hasSuffix(suffix) { return self }
|
||||
return "\(self)\(suffix)"
|
||||
}
|
||||
}
|
39
BriskScript/Sources/Brisk/System.swift
Normal file
39
BriskScript/Sources/Brisk/System.swift
Normal file
@ -0,0 +1,39 @@
|
||||
//
|
||||
// Settings.swift
|
||||
// Terminal
|
||||
//
|
||||
// Created by Paul Hudson on 20/02/2020.
|
||||
// Copyright © 2020 Hacking with Swift. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
#if canImport(Cocoa)
|
||||
import Cocoa
|
||||
#endif
|
||||
|
||||
enum Brisk {
|
||||
static var haltOnError = false
|
||||
}
|
||||
|
||||
func printOrDie(_ message: String) {
|
||||
if Brisk.haltOnError {
|
||||
fatalError(message)
|
||||
} else {
|
||||
print(message)
|
||||
}
|
||||
}
|
||||
|
||||
func exit(_ message: String = "", code: Int = 0) -> Never {
|
||||
if message.isEmpty == false {
|
||||
print(message)
|
||||
}
|
||||
|
||||
exit(Int32(code))
|
||||
}
|
||||
|
||||
#if canImport(Cocoa)
|
||||
func open(_ thing: String) {
|
||||
NSWorkspace.shared.openFile(thing)
|
||||
}
|
||||
#endif
|
25
BriskScript/Sources/Brisk/URL.swift
Normal file
25
BriskScript/Sources/Brisk/URL.swift
Normal file
@ -0,0 +1,25 @@
|
||||
//
|
||||
// Comparable.swift
|
||||
// Terminal
|
||||
//
|
||||
// Created by Paul Hudson on 20/02/2020.
|
||||
// Copyright © 2020 Hacking with Swift. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension URL {
|
||||
/**
|
||||
Create new URLs by appending strings
|
||||
- Parameter lhs: The base URL to use
|
||||
- Parameter rhs: The string to append
|
||||
- Returns: A new URL combining the two
|
||||
*/
|
||||
static func +(lhs: URL, rhs: String) -> URL {
|
||||
return lhs.appendingPathComponent(rhs)
|
||||
}
|
||||
|
||||
static func +=(lhs: inout URL, rhs: String) {
|
||||
lhs.appendPathComponent(rhs)
|
||||
}
|
||||
}
|
106
BriskScript/Sources/Brisk/XML.swift
Normal file
106
BriskScript/Sources/Brisk/XML.swift
Normal file
@ -0,0 +1,106 @@
|
||||
//
|
||||
// XML.swift
|
||||
// Terminal
|
||||
//
|
||||
// Created by Paul Hudson on 20/02/2020.
|
||||
// Copyright © 2020 Hacking with Swift. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum XML {
|
||||
class XMLNode {
|
||||
let tag: String
|
||||
var data: String
|
||||
let attributes: [String: String]
|
||||
var childNodes: [XMLNode]
|
||||
|
||||
init(tag: String, data: String, attributes: [String: String], childNodes: [XMLNode] = []) {
|
||||
self.tag = tag
|
||||
self.data = data
|
||||
self.attributes = attributes
|
||||
self.childNodes = childNodes
|
||||
}
|
||||
|
||||
func getElementsByTagName(_ name: String) -> [XMLNode] {
|
||||
var results = [XMLNode]()
|
||||
|
||||
for node in childNodes {
|
||||
if node.tag == name {
|
||||
results.append(node)
|
||||
}
|
||||
|
||||
results += node.getElementsByTagName(name)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func hasAttribute(_ name: String) -> Bool {
|
||||
attributes[name] != nil
|
||||
}
|
||||
|
||||
func getAttribute(_ name: String) -> String {
|
||||
attributes[name, default: ""]
|
||||
}
|
||||
}
|
||||
|
||||
class MicroDOM: NSObject, XMLParserDelegate {
|
||||
private let parser: XMLParser
|
||||
private var stack = [XMLNode]()
|
||||
private var tree: XMLNode?
|
||||
|
||||
init(data: Data) {
|
||||
parser = XMLParser(data: data)
|
||||
super.init()
|
||||
parser.delegate = self
|
||||
}
|
||||
|
||||
func parse() -> XMLNode? {
|
||||
parser.parse()
|
||||
|
||||
guard parser.parserError == nil else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return tree
|
||||
}
|
||||
|
||||
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String: String] = [:]) {
|
||||
let node = XMLNode(tag: elementName, data: "", attributes: attributeDict, childNodes: [])
|
||||
stack.append(node)
|
||||
}
|
||||
|
||||
func parser(_ parser: XMLParser, foundCharacters string: String) {
|
||||
stack.last?.data = string
|
||||
}
|
||||
|
||||
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
|
||||
let lastElement = stack.removeLast()
|
||||
|
||||
if let last = stack.last {
|
||||
last.childNodes += [lastElement]
|
||||
} else {
|
||||
tree = lastElement
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseXML(_ data: Data) -> XML.XMLNode? {
|
||||
let dom = XML.MicroDOM(data: data)
|
||||
return dom.parse()
|
||||
}
|
||||
|
||||
func parseXML(_ string: String) -> XML.XMLNode? {
|
||||
let data = Data(string.utf8)
|
||||
let dom = XML.MicroDOM(data: data)
|
||||
return dom.parse()
|
||||
}
|
||||
|
||||
func parseXML(from file: String) -> XML.XMLNode? {
|
||||
guard let string = String(file: file) else { return nil }
|
||||
let data = Data(string.utf8)
|
||||
let dom = XML.MicroDOM(data: data)
|
||||
return dom.parse()
|
||||
}
|
3
BriskScript/Sources/main.swift
Normal file
3
BriskScript/Sources/main.swift
Normal file
@ -0,0 +1,3 @@
|
||||
import Foundation
|
||||
|
||||
print("Hello, Brisk!")
|
45
CODE_OF_CONDUCT.md
Normal file
45
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,45 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at paul@hackingwithswift.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Paul Hudson
|
||||
|
||||
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.
|
714
README.md
Normal file
714
README.md
Normal file
@ -0,0 +1,714 @@
|
||||
<p align="center">
|
||||
<img src="https://www.hackingwithswift.com/img/brisk/logo.png" alt="Brisk logo" width="413" maxHeight="83" />
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/Swift-5.1-brightgreen.svg" />
|
||||
<img src="https://img.shields.io/badge/macOS-10.15-blue.svg" />
|
||||
<a href="https://twitter.com/twostraws">
|
||||
<img src="https://img.shields.io/badge/Contact-@twostraws-lightgrey.svg?style=flat" alt="Twitter: @twostraws" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Brisk is a *proof of concept* scripting library for Swift developers. It keeps all the features we like about Swift, but provides wrappers around common functionality to make them more convenient for local scripting.
|
||||
|
||||
Do you see that 💥 right there next to the logo? That’s there for a reason: Brisk bypasses some of Swift’s built-in safety features to make it behave more like Python or Ruby, which means it’s awesome for quick scripts but a really, really bad idea to use in shipping apps.
|
||||
|
||||
This means you get:
|
||||
|
||||
1. All of Swift’s type safety
|
||||
2. All of Swift’s functionality (protocols, extensions, etc)
|
||||
3. All of Swift’s performance
|
||||
|
||||
But:
|
||||
|
||||
1. Many calls that use `try` are assumed to work – if they don’t, your code will print a message and either continue or halt depending on your setting.
|
||||
2. You get many helper functions that make common scripting functionality easier: reading and writing files, parsing JSON and XML, string manipulation, regular expressions, and more.
|
||||
3. Network fetches are synchronous.
|
||||
4. Strings can be indexed using integers, and you can add, subtract, multiply, and divide `Int`, `Double`, and `CGFloat` freely. So `someStr[3]` and `someInt + someDouble` works as in scripting languages. (Again, please don’t use this in production code.)
|
||||
5. We assume many sensible defaults: you want to write strings with UTF-8, you want to create directories with intermediates, `trim()` should remove whitespace unless asked otherwise, and so on.
|
||||
|
||||
We don’t replace any of Swift’s default functionality, which means if you want to mix Brisk’s scripting wrappers with the full Foundation APIs (or Apple’s other frameworks), you can.
|
||||
|
||||
So, it’s called Brisk: it’s fast like Swift, but with that little element of risk 🙂
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
Run these two commands:
|
||||
|
||||
```
|
||||
git clone https://github.com/twostraws/Brisk
|
||||
cd Brisk
|
||||
make install
|
||||
```
|
||||
|
||||
Brisk installs a template full of its helper functions in `~/.brisk`, plus a simple helper script in `/usr/local/bin`.
|
||||
|
||||
Usage:
|
||||
|
||||
```
|
||||
brisk myscriptname
|
||||
```
|
||||
|
||||
That will create a new directory called `myscriptname`, copy in all the helper functions, then open it in Xcode ready for you to edit. Using Xcode means you get full code completion, and can run your script by pressing Cmd+R like usual.
|
||||
|
||||
**Warning:** The `brisk` command is easily the most experimental part of this whole package, so please let me know how you get on with it. Ideally it should create open Xcode straight to an editing window saying `print("Hello, Brisk!")`, but let me know if you get something else.
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
This creates a directory, changes into it, copies in an example JSON file, parses it into a string array, then saves the number of items in a new file called output.txt:
|
||||
|
||||
```swift
|
||||
mkdir("example")
|
||||
chdir("example")
|
||||
fileCopy("~/example.json", to: ".")
|
||||
|
||||
let names = decode(file: "example.json", as: [String].self)
|
||||
let output = "Result \(names.count)"
|
||||
output.write(to: "output.txt")
|
||||
```
|
||||
|
||||
If you were writing this using the regular Foundation APIs, your code might look something like this:
|
||||
|
||||
```swift
|
||||
let fm = FileManager.default
|
||||
try fm.createDirectory(atPath: "example", withIntermediateDirectories: true)
|
||||
fm.changeCurrentDirectoryPath("example")
|
||||
try fm.copyItem(atPath: NSHomeDirectory() + "/example.json", toPath: "example")
|
||||
|
||||
let input = try String(contentsOfFile: "example")
|
||||
let data = Data(input.utf8)
|
||||
let names = try JSONDecoder().decode([String].self, from: data)
|
||||
let output = "Result \(names.count)"
|
||||
try output.write(toFile: "output.txt", atomically: true, encoding: .utf8)
|
||||
```
|
||||
|
||||
The Foundation code has lots of throwing functions, which is why we need to repeat the use of `try`. This is really important when shipping production software because it forces us to handle errors gracefully, but in simple scripts where you know the structure of your code, it gets in the way.
|
||||
|
||||
This example finds all .txt files in a directory and its subdirectories, counting how many lines there are in total:
|
||||
|
||||
```swift
|
||||
var totalLines = 0
|
||||
|
||||
for file in scandir("~/Input", recursively: true) {
|
||||
guard file.hasSuffix(".txt") else { continue }
|
||||
let contents = String(file: "~/Input"/file) ?? ""
|
||||
totalLines += contents.lines.count
|
||||
}
|
||||
|
||||
print("Counted \(totalLines) lines")
|
||||
```
|
||||
|
||||
Or using `recurse()`:
|
||||
|
||||
```swift
|
||||
var totalLines = 0
|
||||
|
||||
recurse("~/Input", extensions: ".txt") { file in
|
||||
let contents = String(file: "~/Input"/file) ?? ""
|
||||
totalLines += contents.lines.count
|
||||
}
|
||||
|
||||
print("Counted \(totalLines) lines")
|
||||
```
|
||||
|
||||
And here’s the same thing using the Foundation APIs:
|
||||
|
||||
```swift
|
||||
let enumerator = FileManager.default.enumerator(atPath: NSHomeDirectory() + "/Input")
|
||||
let files = enumerator?.allObjects as! [String]
|
||||
var totalLines = 0
|
||||
|
||||
for file in files {
|
||||
guard file.hasSuffix(".txt") else { continue }
|
||||
let contents = try! String(contentsOfFile: NSHomeDirectory() + "/Input/\(file)")
|
||||
totalLines += contents.components(separatedBy: .newlines).count
|
||||
}
|
||||
|
||||
print("Counted \(totalLines) lines")
|
||||
```
|
||||
|
||||
Here are some more examples – I’m not going to keep on showing you the Foundation equivalent, because you can imagine it for yourself.
|
||||
|
||||
This fetches the contents of Swift.org and checks whether it was changed since the script was last run:
|
||||
|
||||
```swift
|
||||
let html = String(url: "https://www.swift.org")
|
||||
let newHash = html.sha256()
|
||||
let oldHash = String(file: "oldHash")
|
||||
newHash.write(to: "oldHash")
|
||||
|
||||
if newHash != oldHash {
|
||||
print("Site changed!")
|
||||
}
|
||||
```
|
||||
|
||||
This creates an array of names, removes any duplicates, then writes the result out to a file as JSON:
|
||||
|
||||
```swift
|
||||
let names = ["Ron", "Harry", "Ron", "Hermione", "Ron"]
|
||||
let json = names.unique().jsonData()
|
||||
json.write(to: "names.txt")
|
||||
```
|
||||
|
||||
This checks whether a string matches a regular expression:
|
||||
|
||||
```swift
|
||||
let example = "Hacking with Swift is a great site."
|
||||
|
||||
if example.matches(regex: "(great|awesome) site") {
|
||||
print("Trufax")
|
||||
}
|
||||
```
|
||||
|
||||
Loop through all files in a directory recursively, printing the name of each file and its string contents:
|
||||
|
||||
```swift
|
||||
recurse("~/Input") { file in
|
||||
let text = String(file: "~/Input"/file) ?? ""
|
||||
print("\(file): \(text)")
|
||||
}
|
||||
```
|
||||
|
||||
Print whether a directory contains any zip files:
|
||||
|
||||
```swift
|
||||
let contents = scandir("~/Input")
|
||||
let hasZips = contents.any { $0.hasSuffix(".zip") }
|
||||
print(hasZips)
|
||||
```
|
||||
|
||||
This loads Apple’s latest newsroom RSS and prints out the titles of all the stories:
|
||||
|
||||
```swift
|
||||
let data = Data(url: "https://apple.com/newsroom/rss-feed.rss")
|
||||
if let node = parseXML(data) {
|
||||
let titles = node.getElementsByTagName("title")
|
||||
|
||||
for title in titles {
|
||||
print(title.data)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Wait, but… why?
|
||||
|
||||
I was working on a general purpose scripting library for Swift, following fairly standard Swift conventions – you created a struct to represent the file you wanted to work with, for example.
|
||||
|
||||
And it worked – you could write scripts in Swift that were a little less cumbersome than Foundation. But it still wasn’t *nice*: you could achieve results, but it still felt like Python, Ruby, or any number of alternatives were better choices, and I was choosing Swift just because it was Swift.
|
||||
|
||||
So, Brisk is a pragmatic selection of wrappers around Foundation APIs, letting us get quick results for common operations, but still draw on the full power of the language and Apple’s frameworks. The result is a set of function calls, initializers, and extensions that make common things trivial, while allowing you to benefit from Swift’s power features and “gracefully upgrade” to the full fat Foundation APIs whenever you need.
|
||||
|
||||
|
||||
## Naming conventions
|
||||
|
||||
This code has gone through so many iterations over time, because it’s fundamentally built on functions I’ve been using locally. However, as I worked towards an actual proof of concept I had to try to bring things together a cohesive way, which meant figuring out How to Name Things.
|
||||
|
||||
* When using long-time standard things from POSIX or C, those function names were preserved. So, `mkdir()`, `chdir()`, `getcwd()`, all exist.
|
||||
* Where equivalent functions existed in other popular languages, they were imported: `isdir()`, `scandir()`, `recurse()`, `getpid()`, `basename()`, etc.
|
||||
* Where functionality made for natural extensions of common Swift types – `String`, `Comparable`, `Date`, etc – extensions were always preferred.
|
||||
|
||||
The only really problematic names were things for common file operations, such as checking whether a file exists or reading the contents of a file. Originally I used short names such as `exists("someFile.txt")` and `copy("someFile", to: "dir")`, which made for concise and expressive code. However, as soon as you made a variable called `copy` – which is easily done! – you lose visibility to the function
|
||||
|
||||
I then moved to using `File.copy()`, `File.exists()` and more, giving the functions a clear namespace. That works great for avoiding name collisions, and also helps with discoverability, but became more cumbersome to read and write. So, after trying them both for a while I found that the current versions worked best: `fileDelete()`, and so on.
|
||||
|
||||
I’d be more than happy to continue exploring alternatives!
|
||||
|
||||
|
||||
|
||||
## Reference
|
||||
|
||||
This needs way more documentation, but hopefully this is enough to get you started.
|
||||
|
||||
|
||||
### Extensions on Array
|
||||
|
||||
Removes all instances of an element from an array:
|
||||
|
||||
```swift
|
||||
func remove(_: Element)
|
||||
```
|
||||
|
||||
### Extensions on Comparable
|
||||
|
||||
Clamps any comparable value between a low and a high value, inclusive:
|
||||
|
||||
```swift
|
||||
func Comparable.clamp(low: Self, high: Self) -> Self
|
||||
```
|
||||
|
||||
### Extensions on Data
|
||||
|
||||
Calculates the hash value of this `Data` instance:
|
||||
|
||||
```swift
|
||||
func Data.md5() -> String
|
||||
func Data.sha1() -> String
|
||||
func Data.sha256() -> String
|
||||
```
|
||||
|
||||
Converts the `Data` instance to base 64 representation:
|
||||
|
||||
```swift
|
||||
func Data.base64() -> String
|
||||
```
|
||||
|
||||
Writes the `Data` instance to a file path; returns true on success or false otherwise:
|
||||
|
||||
```swift
|
||||
func write(to file: String) -> Bool
|
||||
```
|
||||
|
||||
Creates a `Data` instance by downloading from a URL or by reading a local file:
|
||||
|
||||
```swift
|
||||
Data(url: String)
|
||||
Data?(file: String)
|
||||
```
|
||||
|
||||
|
||||
### Extensions on Date
|
||||
|
||||
Reads a `Date` instance as an Unix epoch time integer:
|
||||
|
||||
```swift
|
||||
func unixTime() -> Int
|
||||
```
|
||||
|
||||
Formats a `Date` as a string:
|
||||
|
||||
```swift
|
||||
func string(using format: String) -> String
|
||||
```
|
||||
|
||||
### Decoding
|
||||
|
||||
Decodes a string to a specific `Decodable` type, optionally providing strategies for decoding keys and dates:
|
||||
|
||||
```swift
|
||||
func decode<T: Decodable>(string input: String, as type: T.Type, keys: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys, dates: JSONDecoder.DateDecodingStrategy = .deferredToDate) -> T
|
||||
```
|
||||
|
||||
The same as above, except now loading from a local file:
|
||||
|
||||
```swift
|
||||
func decode<T: Decodable>(file: String, as type: T.Type, keys: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys, dates: JSONDecoder.DateDecodingStrategy = .deferredToDate) -> T
|
||||
```
|
||||
|
||||
Creates a `Decodable` instance by fetching data a URL:
|
||||
|
||||
```swift
|
||||
Decodable.init(url: String, keys: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys, dates: JSONDecoder.DateDecodingStrategy = .deferredToDate)
|
||||
```
|
||||
|
||||
|
||||
### Directories
|
||||
|
||||
The user’s home directory:
|
||||
|
||||
```swift
|
||||
Directory.homeDir: String
|
||||
```
|
||||
|
||||
Makes a directory:
|
||||
|
||||
```swift
|
||||
@discardableResult func mkdir(_ directory: String, withIntermediates: Bool = true) -> Bool
|
||||
```
|
||||
|
||||
Removes a directory:
|
||||
|
||||
```swift
|
||||
@discardableResult func rmdir(_ file: String) -> Bool
|
||||
func getcwd() -> String
|
||||
```
|
||||
|
||||
Returns true if a file path represents a directory, or false otherwise:
|
||||
|
||||
```swift
|
||||
func isdir(_ name: String) -> Bool
|
||||
```
|
||||
|
||||
Changes the current working directory:
|
||||
|
||||
```swift
|
||||
func chdir(_ newDirectory: String) -> Bool
|
||||
```
|
||||
|
||||
Retrieves all files in a directory, either including all subdirectories or not:
|
||||
|
||||
```swift
|
||||
func scandir(_ directory: String, recursively: Bool = false) -> [String]
|
||||
```
|
||||
|
||||
Runs through all files in a directory, including subdirectories, and runs a closure for each file that matches an extension list:
|
||||
|
||||
```swift
|
||||
func recurse(_ directory: String, extensions: String..., action: (String) throws -> Void) rethrows
|
||||
```
|
||||
|
||||
Same as above, except now you can pass in a custom predicate:
|
||||
|
||||
```swift
|
||||
func recurse(_ directory: String, predicate: (String) -> Bool, action: (String) throws -> Void) rethrows
|
||||
```
|
||||
|
||||
|
||||
### Extensions on Encodable
|
||||
|
||||
Converts any `Encodable` type to some JSON `Data`, optionally providing strategies for encoding keys and dates:
|
||||
|
||||
```swift
|
||||
func Encodable.jsonData(keys: JSONEncoder.KeyEncodingStrategy = .useDefaultKeys, dates: JSONEncoder.DateEncodingStrategy = .deferredToDate) -> Data
|
||||
```
|
||||
|
||||
Same as above, except converts it a JSON `String`:
|
||||
|
||||
```swift
|
||||
func Decodable.jsonString(keys: JSONEncoder.KeyEncodingStrategy = .useDefaultKeys, dates: JSONEncoder.DateEncodingStrategy = .deferredToDate) -> String
|
||||
```
|
||||
|
||||
|
||||
### Files
|
||||
|
||||
Creates a file, optionally providing initial contents. Returns true on success or false otherwise:
|
||||
|
||||
```swift
|
||||
@discardableResult func fileCreate(_ file: String, contents: Data? = nil) -> Bool
|
||||
```
|
||||
|
||||
Removes a file at a path; returns true on success or false otherwise:
|
||||
|
||||
```swift
|
||||
@discardableResult func fileDelete(_ file: String) -> Bool
|
||||
```
|
||||
|
||||
Returns true if a file exists:
|
||||
|
||||
```swift
|
||||
func fileExists(_ name: String) -> Bool
|
||||
```
|
||||
|
||||
Returns all properties for a file:
|
||||
|
||||
```swift
|
||||
func fileProperties(_ file: String) -> [FileAttributeKey: Any]
|
||||
```
|
||||
|
||||
Returns the size of a file:
|
||||
|
||||
```swift
|
||||
func fileSize(_ file: String) -> UInt64
|
||||
```
|
||||
|
||||
Returns the date a file was created or modified:
|
||||
|
||||
```swift
|
||||
func fileCreation(_ file: String) -> Date
|
||||
func fileModified(_ file: String) -> Date
|
||||
```
|
||||
|
||||
Returns a temporary filename:
|
||||
|
||||
```swift
|
||||
func tempFile() -> String
|
||||
```
|
||||
|
||||
Returns the base name of a file – the filename itself, excluding any directories:
|
||||
|
||||
```swift
|
||||
func basename(of file: String) -> String
|
||||
```
|
||||
|
||||
Copies a file from one place to another:
|
||||
|
||||
```swift
|
||||
@discardableResult func fileCopy(_ from: String, to: String) -> Bool
|
||||
```
|
||||
|
||||
|
||||
### Numeric operators
|
||||
|
||||
A series of operator overloads that let you add, subtract, multiply, and divide across integers, floats, and doubles:
|
||||
|
||||
```swift
|
||||
func +<I: BinaryInteger, F: BinaryFloatingPoint>(lhs: I, rhs: F) -> F
|
||||
func +<I: BinaryInteger, F: BinaryFloatingPoint>(lhs: F, rhs: I) -> F
|
||||
func -<I: BinaryInteger, F: BinaryFloatingPoint>(lhs: I, rhs: F) -> F
|
||||
func -<I: BinaryInteger, F: BinaryFloatingPoint>(lhs: F, rhs: I) -> F
|
||||
func *<I: BinaryInteger, F: BinaryFloatingPoint>(lhs: I, rhs: F) -> F
|
||||
func *<I: BinaryInteger, F: BinaryFloatingPoint>(lhs: F, rhs: I) -> F
|
||||
func /<I: BinaryInteger, F: BinaryFloatingPoint>(lhs: I, rhs: F) -> F
|
||||
func /<I: BinaryInteger, F: BinaryFloatingPoint>(lhs: F, rhs: I) -> F
|
||||
```
|
||||
|
||||
|
||||
### Processes
|
||||
|
||||
Returns the current process ID:
|
||||
|
||||
```swift
|
||||
func getpid() -> Int
|
||||
```
|
||||
|
||||
Returns the host name:
|
||||
|
||||
```swift
|
||||
func getHostName() -> String
|
||||
```
|
||||
|
||||
Returns the username of the logged in user:
|
||||
|
||||
```swift
|
||||
func getUserName() -> String
|
||||
```
|
||||
|
||||
Gets or sets environment variables:
|
||||
|
||||
```swift
|
||||
func getenv(_ key: String) -> String
|
||||
func setenv(_ key: String, _ value: String)
|
||||
```
|
||||
|
||||
|
||||
### Extensions on Sequence
|
||||
|
||||
Returns any sequence, with duplicates removed. Element must conform to `Hashable`:
|
||||
|
||||
```swift
|
||||
func Sequence.unique() -> [Element]
|
||||
```
|
||||
|
||||
Returns all the indexes where an element exists in a sequence. Element must conform to `Equatable`:
|
||||
|
||||
```swift
|
||||
func Sequence.indexes(of searchItem: Element) -> [Int]
|
||||
```
|
||||
|
||||
Returns true if any or none of the items in a sequence match a predicate:
|
||||
|
||||
```swift
|
||||
func any(match predicate: (Element) throws -> Bool) rethrows -> Bool
|
||||
func none(match predicate: (Element) throws -> Bool) rethrows -> Bool
|
||||
```
|
||||
|
||||
Returns several random numbers from a sequence, up to the number requested:
|
||||
|
||||
```swift
|
||||
Sequence.random(_ num: Int) -> [Element]
|
||||
```
|
||||
|
||||
|
||||
### Extensions on String
|
||||
|
||||
The string as an array of lines:
|
||||
|
||||
```swift
|
||||
var lines: [String]
|
||||
```
|
||||
|
||||
An operator that lets us join strings together into a path:
|
||||
|
||||
```swift
|
||||
static func / (lhs: String, rhs: String) -> String
|
||||
```
|
||||
|
||||
Calculates the hash value of this `String` instance:
|
||||
|
||||
```swift
|
||||
func md5() -> String
|
||||
func sha1() -> String
|
||||
func sha256() -> String
|
||||
```
|
||||
|
||||
Converts the `String` instance to base 64 representation:
|
||||
|
||||
```swift
|
||||
func base64() -> String
|
||||
```
|
||||
|
||||
Writes a string to a file:
|
||||
|
||||
```swift
|
||||
@discardableResult func write(to file: String) -> Bool
|
||||
```
|
||||
|
||||
Replaces all instances of one string with another in the source `String`:
|
||||
|
||||
```swift
|
||||
func replacing(_ search: String, with replacement: String) -> String
|
||||
mutating func String.replace(_ search: String, with replacement: String)
|
||||
```
|
||||
|
||||
Replaces `count` instances of one string with another in the source `String`:
|
||||
|
||||
```swift
|
||||
func replacing(_ search: String, with replacement: String, count maxReplacements: Int) -> String
|
||||
mutating func String.replace(_ search: String, with replacement: String, count maxReplacements: Int)
|
||||
```
|
||||
|
||||
Trims characters from a string, whitespace by default:
|
||||
|
||||
```swift
|
||||
mutating func trim(_ characters: String = " \t\n\r\0")
|
||||
func String.trimmed(_ characters: String = " \t\n\r\0") -> String
|
||||
```
|
||||
|
||||
Returns true if a string matches a regular expression, with optional extra options:
|
||||
|
||||
```swift
|
||||
func matches(regex: String, options: NSRegularExpression.Options = []) -> Bool
|
||||
```
|
||||
|
||||
Replaces matches for a regular expression with a replacement string:
|
||||
|
||||
```swift
|
||||
replacing(regex: String, with replacement: String, options: NSString.CompareOptions) -> String
|
||||
mutating func String.replace(regex: String, with replacement: String, options: NSString.CompareOptions)
|
||||
```
|
||||
|
||||
Subscripts to let us read strings using integers and ranges:
|
||||
|
||||
```swift
|
||||
subscript(idx: Int) -> String
|
||||
subscript(range: Range<Int>) -> String
|
||||
subscript(range: ClosedRange<Int>) -> String
|
||||
subscript(range: CountablePartialRangeFrom<Int>) -> String
|
||||
subscript(range: PartialRangeThrough<Int>) -> String
|
||||
subscript(range: PartialRangeUpTo<Int>) -> String
|
||||
```
|
||||
|
||||
Expands path components such as `.` and `~`:
|
||||
|
||||
```swift
|
||||
expandingPath() -> String
|
||||
```
|
||||
|
||||
Creates a `String` instance by downloading from a URL or by reading a local file:
|
||||
|
||||
```swift
|
||||
String.init(url: String)
|
||||
String.init?(file: String)
|
||||
```
|
||||
|
||||
Removes a prefix or suffix from a string, if it exists:
|
||||
|
||||
```swift
|
||||
deletingPrefix(_ prefix: String) -> String
|
||||
deletingSuffix(_ suffix: String) -> String
|
||||
```
|
||||
|
||||
Adds a prefix or suffix to a string, if it doesn’t already have it:
|
||||
|
||||
```swift
|
||||
func String.withPrefix(_ prefix: String) -> String
|
||||
func String.withSuffix(_ suffix: String) -> String
|
||||
```
|
||||
|
||||
|
||||
### System functionality
|
||||
|
||||
Many functions will print a message and return a default value if their functionality failed. Set this to true if you want your script to terminate on these problems:
|
||||
|
||||
```swift
|
||||
static var Brisk.haltOnError: Bool
|
||||
```
|
||||
|
||||
Prints a message, or terminates the script if `Brisk.haltOnError` is true:
|
||||
|
||||
```swift
|
||||
func printOrDie(_ message: String)
|
||||
```
|
||||
|
||||
Terminates the program, printing a message and returning an error code to the system:
|
||||
|
||||
```swift
|
||||
func exit(_ message: String = "", code: Int = 0) -> Never
|
||||
```
|
||||
|
||||
If Cocoa is available, this opens a file or folder using the correct app. This is helpful for showing the results of a script, because you can use `open(getcwd())`:
|
||||
|
||||
```swift
|
||||
func open(_ thing: String)
|
||||
```
|
||||
|
||||
### Extensions on URL
|
||||
|
||||
Add a string to a URL:
|
||||
|
||||
```swift
|
||||
static func +(lhs: URL, rhs: String) -> URL
|
||||
static func +=(lhs: inout URL, rhs: String)
|
||||
```
|
||||
|
||||
### XML parsing
|
||||
|
||||
Parses an instance of `Data` or `String` into an XML, or loads a file and does the same:
|
||||
|
||||
```swift
|
||||
func parseXML(_ data: Data) -> XML.XMLNode?
|
||||
func parseXML(_ string: String) -> XML.XMLNode?
|
||||
func parseXML(from file: String) -> XML.XMLNode?
|
||||
```
|
||||
|
||||
The resulting `XMLNode` has the following properties:
|
||||
|
||||
- `tag`: The tag name used, e.g. `<h1>`.
|
||||
- `data`: The text inside the tag, e.g. `<h1>This bit is the data</h1>`
|
||||
- `attributes`: A dictionary containing the keys and values for all attributes.
|
||||
- `childNodes`: an array of `XMLNode` that belong to this node.
|
||||
|
||||
It also has a tiny subset of minidom functionality to make querying possible.
|
||||
|
||||
This finds all elements by a tag name, looking through all children, grandchildren, and so on:
|
||||
|
||||
```swift
|
||||
func getElementsByTagName(_ name: String) -> [XMLNode]
|
||||
```
|
||||
|
||||
This returns true if the current node has a specific attribute, or false otherwise:
|
||||
|
||||
```swift
|
||||
func hasAttribute(_ name: String) -> Bool
|
||||
```
|
||||
|
||||
This reads a single attribute, or sends back an empty string otherwise:
|
||||
|
||||
```swift
|
||||
func getAttribute(_ name: String) -> String
|
||||
```
|
||||
|
||||
|
||||
## Contribution guide
|
||||
|
||||
Any help you can offer with this project is most welcome – there are opportunities big and small so that someone with only a small amount of Swift experience can help.
|
||||
|
||||
Some suggestions you might want to explore, ordered by usefulness:
|
||||
|
||||
- Write some tests.
|
||||
- Contribute example scripts.
|
||||
- Add more helper functions.
|
||||
|
||||
|
||||
## What now?
|
||||
|
||||
This is a proof of concept scripting library for Swift developers. I don’t think it’s perfect, but I do at least hope it gives you some things to think about.
|
||||
|
||||
Some tips:
|
||||
|
||||
1. If you already write scripts in Bash, Ruby, Python, PHP, JavaScript, etc, your muscle memory will always feel like it’s drawing you back there. That’s OK – learning anything new takes time.
|
||||
2. Stay away from macOS protected directories, such as your Desktop, Documents, and Photos.
|
||||
3. If you intend to keep scripts around for a long period of time, you can easily “upgrade” your code from Brisk’s helpers up to Foundation calls; nothing is overridden.
|
||||
4. The code is open source. Even if you end up not using Brisk at all, you’re welcome to read the code, learn from it, take it for your own projects, and so on.
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
Brisk was designed and built by Paul Hudson, and is copyright © Paul Hudson 2020. Brisk is licensed under the MIT license; for the full license please see the LICENSE file.
|
||||
|
||||
Swift, the Swift logo, and Xcode are trademarks of Apple Inc., registered in the U.S. and other countries.
|
||||
|
||||
If you find Brisk useful, you might find my website full of Swift tutorials equally useful: [Hacking with Swift](https://www.hackingwithswift.com).
|
BIN
Working/logo.png
Normal file
BIN
Working/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 61 KiB |
BIN
Working/logo.psd
Normal file
BIN
Working/logo.psd
Normal file
Binary file not shown.
28
brisk.sh
Normal file
28
brisk.sh
Normal file
@ -0,0 +1,28 @@
|
||||
# Ensure they provide a script name
|
||||
if [ $# -ne 1 ]
|
||||
then
|
||||
echo "Usage: brisk <script name>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Refuse to overwrite existing directories
|
||||
if [ -e "$1" ]
|
||||
then
|
||||
echo "Error: the script \"$1\" already exists."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy in our template project
|
||||
cp -R ~/.brisk/BriskScript "$1"
|
||||
cd "$1"
|
||||
|
||||
# Update Xcode's user data so that it opens the new main.swift file
|
||||
PWD=`pwd`
|
||||
sed -i '' "s,PATHTOMAINDOTSWIFT,$PWD\/Sources\/main.swift," .swiftpm/xcode/package.xcworkspace/xcuserdata/username.xcuserdatad/UserInterfaceState.xcuserstate
|
||||
|
||||
# Rename our user data to their username, so we open straight to main.swift
|
||||
USERDATA=".swiftpm/xcode/package.xcworkspace/xcuserdata/"
|
||||
mv $USERDATA/username.xcuserdatad $USERDATA/`whoami`.xcuserdatad
|
||||
|
||||
# And we're all go for Xcode!
|
||||
xed .
|
27
makefile
Normal file
27
makefile
Normal file
@ -0,0 +1,27 @@
|
||||
install:
|
||||
@# Flush out any existing Brisk template
|
||||
@rm -rf ~/.brisk
|
||||
|
||||
@# Prepare to park our template in the hidden folder
|
||||
@mkdir ~/.brisk
|
||||
|
||||
@# Copy our entire template into the hidden folder
|
||||
@cp -R BriskScript ~/.brisk/BriskScript
|
||||
|
||||
@# Place our script creator in a sensible place
|
||||
@install brisk.sh /usr/local/bin/brisk
|
||||
|
||||
@# Make it executable, so they can run "brisk fizz"
|
||||
@chmod u+x /usr/local/bin/brisk
|
||||
|
||||
@echo "Installation complete!"
|
||||
@echo "You can now run \"brisk example\" to make a new script."
|
||||
|
||||
uninstall:
|
||||
@# Remove any existing Brisk template
|
||||
@rm -rf ~/.brisk
|
||||
|
||||
@# Remove our script creator
|
||||
@rm -rf /usr/local/bin/brisk
|
||||
|
||||
@echo "Uninstall complete."
|
Loading…
Reference in New Issue
Block a user