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[.. 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 } } } }