-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
627367d
commit cbd85d0
Showing
23 changed files
with
393 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
|
||
import Foundation | ||
import Runtime | ||
import PathKit | ||
|
||
class FileCache<Key: Hashable> { | ||
private let folder: Path | ||
private let index: Path | ||
private let capacity: Int | ||
private var hashStore: OrderedSet<Int> | ||
|
||
init(folder: Path, capacity: Int) throws { | ||
self.folder = folder | ||
self.index = folder + ".index" | ||
self.capacity = capacity | ||
|
||
if !folder.exists { | ||
try folder.mkpath() | ||
} | ||
|
||
if index.exists { | ||
let bytes = Array(try index.read()) | ||
hashStore = OrderedSet(bytes.rebound(to: Int.self)) | ||
} else { | ||
hashStore = [] | ||
} | ||
} | ||
|
||
deinit { | ||
try! store() | ||
} | ||
|
||
func load(key: Key) throws -> Data? { | ||
let hash = try computeHash(of: key) | ||
let file = self.file(for: hash) | ||
if file.exists { | ||
assert(hashStore.contains(hash)) | ||
use(hash: hash) | ||
return try file.read() | ||
} else { | ||
return nil | ||
} | ||
} | ||
|
||
func store(data: Data, for key: Key) throws { | ||
let hash = try computeHash(of: key) | ||
|
||
if hashStore.contains(hash) && hashStore.count >= capacity { | ||
try evictLeastRecentlyUsed() | ||
} | ||
|
||
use(hash: hash) | ||
try file(for: hash).write(data) | ||
} | ||
} | ||
|
||
extension FileCache { | ||
|
||
struct StringCannotBeStoredInCacheError : Error { | ||
let string: String | ||
let encoding: String.Encoding | ||
} | ||
|
||
func load(key: Key, encoding: String.Encoding = .utf8) throws -> String? { | ||
return try load(key: key).flatMap { String(data: $0, encoding: encoding) } | ||
} | ||
|
||
func store(string: String, for key: Key, encoding: String.Encoding = .utf8) throws { | ||
guard let data = string.data(using: encoding) else { throw StringCannotBeStoredInCacheError(string: string, encoding: encoding) } | ||
try store(data: data, for: key) | ||
} | ||
|
||
} | ||
|
||
extension FileCache { | ||
|
||
func tryCache(key: Key, encoding: String.Encoding = .utf8, builder: () throws -> String) throws -> String { | ||
if let cached = try load(key: key, encoding: encoding) { | ||
return cached | ||
} | ||
|
||
let computed = try builder() | ||
try store(string: computed, for: key, encoding: encoding) | ||
return computed | ||
} | ||
|
||
} | ||
|
||
extension Optional { | ||
func tryCache<Key : Hashable>(key: Key, | ||
encoding: String.Encoding = .utf8, | ||
builder: () throws -> String) throws -> String where Wrapped == FileCache<Key> { | ||
guard let wrapped = self else { return try builder() } | ||
return try wrapped.tryCache(key: key, encoding: encoding, builder: builder) | ||
} | ||
} | ||
|
||
extension FileCache { | ||
|
||
private func file(for hash: Int) -> Path { | ||
return folder + String(hash) | ||
} | ||
|
||
private func computeHash(of key: Key) throws -> Int { | ||
var hasher = try reliableHasher() | ||
key.hash(into: &hasher) | ||
return hasher.finalize() | ||
} | ||
|
||
private func use(hash: Int) { | ||
hashStore.remove(hash) | ||
hashStore.append(hash) | ||
} | ||
|
||
private func store() throws { | ||
let data = Data(buffer: hashStore.rebound(to: UInt8.self)) | ||
try index.write(data) | ||
} | ||
|
||
private func evictLeastRecentlyUsed() throws { | ||
let leastRecentlyUsed = hashStore.removeFirst() | ||
let file = self.file(for: leastRecentlyUsed) | ||
try file.delete() | ||
} | ||
|
||
} | ||
|
||
extension Collection { | ||
|
||
fileprivate func rebound<T>(to type: T.Type) -> UnsafeBufferPointer<T> { | ||
let pointer = UnsafeMutableBufferPointer<Element>.allocate(capacity: count) | ||
_ = pointer.initialize(from: self) | ||
let rawPointer = UnsafeRawPointer(pointer.baseAddress!) | ||
let size = count * MemoryLayout<Element>.size / MemoryLayout<T>.size | ||
return UnsafeBufferPointer(start: rawPointer.assumingMemoryBound(to: type), count: size) | ||
} | ||
|
||
fileprivate func rebound<T>(to type: T.Type) -> [T] { | ||
let pointer = self.rebound(to: type) as UnsafeBufferPointer<T> | ||
let rebound = Array(pointer) | ||
pointer.deallocate() | ||
return rebound | ||
} | ||
|
||
} | ||
|
||
// Stolen from https://github.com/apple/swift/blob/master/stdlib/public/core/SipHash.swift | ||
// in order to replicate the exact format in bytes | ||
private struct _State { | ||
private var v0: UInt64 = 0x736f6d6570736575 | ||
private var v1: UInt64 = 0x646f72616e646f6d | ||
private var v2: UInt64 = 0x6c7967656e657261 | ||
private var v3: UInt64 = 0x7465646279746573 | ||
private var v4: UInt64 = 0 | ||
private var v5: UInt64 = 0 | ||
private var v6: UInt64 = 0 | ||
private var v7: UInt64 = 0 | ||
} | ||
|
||
private func reliableHasher() throws -> Hasher { | ||
return try createInstance { coreProperty in | ||
return try createInstance(of: coreProperty.type) { property in | ||
switch property.name { | ||
case "_buffer": | ||
return try createInstance(of: property.type) | ||
case "_state": | ||
return withUnsafeBytes(of: _State()) { $0.baseAddress!.unsafeLoad(as: property.type) } | ||
default: | ||
fatalError() | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
Sources/Graphaello/Extensions/OrderedHashableDictionary.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
|
||
import Foundation | ||
|
||
@propertyWrapper | ||
class OrderedHashableDictionary<Key : Hashable & Comparable, Value: Hashable>: Hashable { | ||
var wrappedValue: [Key : Value] | ||
|
||
init(wrappedValue: [Key : Value]) { | ||
self.wrappedValue = wrappedValue | ||
} | ||
|
||
static func == (lhs: OrderedHashableDictionary<Key, Value>, rhs: OrderedHashableDictionary<Key, Value>) -> Bool { | ||
return lhs.wrappedValue == rhs.wrappedValue | ||
} | ||
|
||
func hash(into hasher: inout Hasher) { | ||
let sorted = wrappedValue.sorted { $0.key < $1.key } | ||
|
||
// Based from: https://github.com/apple/swift/blob/master/stdlib/public/core/Dictionary.swift | ||
var commutativeHash = 0 | ||
for (k, v) in sorted { | ||
var elementHasher = hasher | ||
elementHasher.combine(k) | ||
elementHasher.combine(v) | ||
commutativeHash ^= elementHasher.finalize() | ||
} | ||
hasher.combine(commutativeHash) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
...ces/Graphaello/Processing/Code Generstion/CodeTransformable/CachedCodeTransformable.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
|
||
import Foundation | ||
import Stencil | ||
|
||
extension CodeTransformable where Self: Hashable { | ||
|
||
func cached(using cache: FileCache<AnyHashable>?) -> CachedCodeTransformable<Self> { | ||
return CachedCodeTransformable(code: self, cache: cache) | ||
} | ||
|
||
} | ||
|
||
struct CachedCodeTransformable<Code : CodeTransformable & Hashable> : CodeTransformable { | ||
let code: Code | ||
let cache: FileCache<AnyHashable>? | ||
|
||
func code(using context: Stencil.Context, arguments: [Any?]) throws -> String { | ||
return try cache.tryCache(key: code) { | ||
return try code.code(using: context, arguments: arguments) | ||
} | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
Sources/Graphaello/Processing/Code Generstion/Swift/FormattedSwiftCodeTransformable.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
|
||
import Foundation | ||
import SwiftFormat | ||
import Stencil | ||
|
||
extension SwiftCodeTransformable { | ||
|
||
func withFormatting(format formatCode: Bool) -> FormattedSwiftCodeTransformable<Self> { | ||
return FormattedSwiftCodeTransformable(code: self, formatCode: formatCode) | ||
} | ||
|
||
} | ||
|
||
struct FormattedSwiftCodeTransformable<Code : SwiftCodeTransformable>: SwiftCodeTransformable { | ||
let code: Code | ||
let formatCode: Bool | ||
|
||
func code(using context: Stencil.Context, arguments: [Any?]) throws -> String { | ||
let code = try self.code.code(using: context, arguments: arguments) | ||
if formatCode { | ||
return try format(code) | ||
} | ||
return code | ||
} | ||
} | ||
|
||
|
||
extension FormattedSwiftCodeTransformable: Equatable where Code: Equatable { } | ||
|
||
extension FormattedSwiftCodeTransformable: Hashable where Code: Hashable { } |
Oops, something went wrong.