Files
sst/Sources/SstData/DictionaryFile.swift
T
2026-10-01 15:06:55 +02:00

111 lines
3.7 KiB
Swift

import Foundation
public struct DictionaryEntry: Identifiable, Equatable, Sendable {
public enum Kind: Equatable, Sendable {
case term(String)
case correction(heard: String, wrote: String)
}
public let id: UUID
public var kind: Kind
public init(id: UUID = UUID(), kind: Kind) {
self.id = id
self.kind = kind
}
/// Equality ignores `id`: two entries with the same content are the same entry.
public static func == (lhs: DictionaryEntry, rhs: DictionaryEntry) -> Bool { lhs.kind == rhs.kind }
/// The text the correction pass listens for (a term listens for itself).
public var source: String {
switch kind {
case .term(let term): term
case .correction(let heard, _): heard
}
}
/// The text that ends up in the transcript.
public var wrote: String {
switch kind {
case .term(let term): term
case .correction(_, let wrote): wrote
}
}
}
public enum DictionaryLine: Equatable, Sendable {
case entry(DictionaryEntry)
case comment(String)
case blank
case invalid(String)
}
/// The dictionary as a list of lines, so comments, blank lines and order survive an edit.
public struct DictionaryFile: Equatable, Sendable {
public var lines: [DictionaryLine]
public init(lines: [DictionaryLine] = []) {
self.lines = lines
}
public init(parsing text: String) {
var raw = text.replacingOccurrences(of: "\r\n", with: "\n")
.split(separator: "\n", omittingEmptySubsequences: false)
.map(String.init)
if raw.last == "" { raw.removeLast() }
lines = raw.map(Self.parseLine)
}
static func parseLine(_ line: String) -> DictionaryLine {
let trimmed = line.trimmingCharacters(in: .whitespaces)
if trimmed.isEmpty { return .blank }
if trimmed.hasPrefix("#") { return .comment(line) }
if let arrow = trimmed.range(of: "=>") {
let heard = trimmed[..<arrow.lowerBound].trimmingCharacters(in: .whitespaces)
let wrote = trimmed[arrow.upperBound...].trimmingCharacters(in: .whitespaces)
guard !heard.isEmpty, !wrote.isEmpty else { return .invalid(line) }
return .entry(DictionaryEntry(kind: .correction(heard: heard, wrote: wrote)))
}
return .entry(DictionaryEntry(kind: .term(trimmed)))
}
public func serialized() -> String {
let out = lines.map { line -> String in
switch line {
case .entry(let entry):
switch entry.kind {
case .term(let term): return term
case .correction(let heard, let wrote): return "\(heard) => \(wrote)"
}
case .comment(let text), .invalid(let text): return text
case .blank: return ""
}
}
return out.isEmpty ? "" : out.joined(separator: "\n") + "\n"
}
public var entries: [DictionaryEntry] {
lines.compactMap { line in
if case .entry(let entry) = line { return entry }
return nil
}
}
@discardableResult
public mutating func append(_ kind: DictionaryEntry.Kind) -> UUID {
let entry = DictionaryEntry(kind: kind)
lines.append(.entry(entry))
return entry.id
}
public mutating func update(id: UUID, kind: DictionaryEntry.Kind) {
guard let index = lines.firstIndex(where: { if case .entry(let e) = $0 { return e.id == id } else { return false } }) else { return }
lines[index] = .entry(DictionaryEntry(id: id, kind: kind))
}
public mutating func remove(id: UUID) {
lines.removeAll { if case .entry(let e) = $0 { return e.id == id } else { return false } }
}
}