2022-08-16 10:39:05 +02:00
|
|
|
import Foundation
|
|
|
|
|
|
|
|
protocol Template {
|
|
|
|
|
|
|
|
associatedtype Key where Key: RawRepresentable, Key.RawValue == String, Key: CaseIterable, Key: Hashable
|
|
|
|
|
|
|
|
static var templateName: String { get }
|
|
|
|
|
|
|
|
var raw: String { get }
|
|
|
|
|
2022-12-02 10:25:54 +01:00
|
|
|
var results: GenerationResultsHandler { get }
|
|
|
|
|
|
|
|
init(raw: String, results: GenerationResultsHandler)
|
2022-08-16 10:39:05 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
extension Template {
|
|
|
|
|
2022-12-02 10:25:54 +01:00
|
|
|
init(in folder: URL, results: GenerationResultsHandler) throws {
|
2022-08-16 10:39:05 +02:00
|
|
|
let url = folder.appendingPathComponent(Self.templateName)
|
2022-12-02 10:25:54 +01:00
|
|
|
try self.init(from: url, results: results)
|
2022-08-16 10:39:05 +02:00
|
|
|
}
|
|
|
|
|
2022-12-02 10:25:54 +01:00
|
|
|
init(from url: URL, results: GenerationResultsHandler) throws {
|
2022-12-04 23:10:44 +01:00
|
|
|
do {
|
|
|
|
let raw = try String(contentsOf: url)
|
|
|
|
self.init(raw: raw, results: results)
|
|
|
|
} catch {
|
|
|
|
results.warning("Failed to load: \(error)", source: "Template \(url.lastPathComponent)")
|
|
|
|
throw error
|
|
|
|
}
|
2022-08-16 10:39:05 +02:00
|
|
|
}
|
|
|
|
|
2022-12-04 19:15:22 +01:00
|
|
|
@discardableResult
|
|
|
|
func generate(_ content: [Key : String], to file: String, source: String) -> Bool {
|
|
|
|
let content = generate(content).data(using: .utf8)!
|
|
|
|
return results.writeIfChanged(content, file: file, source: source)
|
2022-08-16 10:39:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func generate(_ content: [Key : String], shouldIndent: Bool = false) -> String {
|
|
|
|
var result = raw.components(separatedBy: "\n")
|
|
|
|
|
|
|
|
Key.allCases.forEach { key in
|
|
|
|
let newContent = content[key]?.withoutEmptyLines ?? ""
|
|
|
|
let stringMarker = "<!--\(key.rawValue)-->"
|
|
|
|
let indices = result.enumerated().filter { $0.element.contains(stringMarker) }
|
|
|
|
.map { $0.offset }
|
|
|
|
guard !indices.isEmpty else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for index in indices {
|
|
|
|
let old = result[index].components(separatedBy: stringMarker)
|
|
|
|
// Add indentation to all added lines
|
|
|
|
let indentation = old.first!
|
|
|
|
guard shouldIndent, indentation.trimmingCharacters(in: .whitespaces).isEmpty else {
|
|
|
|
// Prefix is not indentation, so just insert new content
|
|
|
|
result[index] = old.joined(separator: newContent)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
let indentedReplacements = newContent.indented(by: indentation)
|
|
|
|
result[index] = old.joined(separator: indentedReplacements)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result.joined(separator: "\n").withoutEmptyLines
|
|
|
|
}
|
|
|
|
}
|