Adding initial files.

This commit is contained in:
Paul Hudson 2020-02-24 15:54:53 +00:00
commit 4b69686f75
28 changed files with 4722 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.DS_Store
/.build
/Packages
/*.xcodeproj

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

15
BriskScript/Package.swift Normal file
View 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: "." )
]
)

View 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 }
}
}

View 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 }
}

View 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
}
}

View 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
}
}
}
}

View 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())
}
}

View 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)")
}
}
}

View 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
}
}

View 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 ""
}
}
}

View 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
}
}

View 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)
}

View 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)
}

View 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))
}
}

View 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)"
}
}

View 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

View 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)
}
}

View 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()
}

View File

@ -0,0 +1,3 @@
import Foundation
print("Hello, Brisk!")

45
CODE_OF_CONDUCT.md Normal file
View 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
View 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
View 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? Thats there for a reason: Brisk bypasses some of Swifts built-in safety features to make it behave more like Python or Ruby, which means its awesome for quick scripts but a really, really bad idea to use in shipping apps.
This means you get:
1. All of Swifts type safety
2. All of Swifts functionality (protocols, extensions, etc)
3. All of Swifts performance
But:
1. Many calls that use `try` are assumed to work if they dont, 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 dont 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 dont replace any of Swifts default functionality, which means if you want to mix Brisks scripting wrappers with the full Foundation APIs (or Apples other frameworks), you can.
So, its called Brisk: its 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 heres 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 Im 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 Apples 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 wasnt *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 Apples frameworks. The result is a set of function calls, initializers, and extensions that make common things trivial, while allowing you to benefit from Swifts 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 its fundamentally built on functions Ive 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.
Id 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 users 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 doesnt 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 dont think its 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 its drawing you back there. Thats 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 Brisks helpers up to Foundation calls; nothing is overridden.
4. The code is open source. Even if you end up not using Brisk at all, youre 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
Working/logo.psd Normal file

Binary file not shown.

28
brisk.sh Normal file
View 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
View 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."