import Foundation final class GenerationResults: ObservableObject { /// The files that could not be accessed @Published var inaccessibleFiles: Set = [] /// The files that could not be parsed, with the error message produced @Published var unparsableFiles: Set = [] @Published var missingFiles: Set = [] @Published var missingTags: Set = [] @Published var missingPages: Set = [] @Published var externalLinks: Set = [] @Published var requiredFiles: Set = [] @Published var imagesToGenerate: Set = [] @Published var invalidCommands: Set = [] @Published var warnings: Set = [] @Published var unsavedOutputFiles: Set = [] @Published var failedImages: Set = [] @Published var emptyPages: Set = [] /// The cache of previously used GenerationResults private var cache: [ItemId : PageGenerationResults] = [:] private(set) var general: PageGenerationResults! @Published var resultCount: Int = 0 // MARK: Life cycle init() { let id = ItemId(language: .english, itemType: .general) let general = PageGenerationResults(itemId: id, delegate: self) self.general = general cache[id] = general self.resultCount = 1 } func makeResults(_ itemId: ItemId) -> PageGenerationResults { guard let result = cache[itemId] else { let result = PageGenerationResults(itemId: itemId, delegate: self) cache[itemId] = result update { self.resultCount += 1 } return result } return result } func makeResults(for type: ItemType, in language: ContentLanguage) -> PageGenerationResults { let itemId = ItemId(language: language, itemType: type) return makeResults(itemId) } func makeResults(for page: Page, in language: ContentLanguage) -> PageGenerationResults { let itemId = ItemId(language: language, itemType: .page(page)) return makeResults(itemId) } func makeResults(for tag: Tag, in language: ContentLanguage) -> PageGenerationResults { let itemId = ItemId(language: language, itemType: .tagPage(tag)) return makeResults(itemId) } func recalculate() { let inaccessibleFiles = cache.values.map { $0.inaccessibleFiles }.union() update { self.inaccessibleFiles = inaccessibleFiles } let unparsableFiles = cache.values.map { $0.unparsableFiles.keys }.union() update { self.unparsableFiles = unparsableFiles } let missingFiles = cache.values.map { $0.missingFiles.keys }.union() update { self.missingFiles = missingFiles } let missingTags = cache.values.map { $0.missingLinkedTags.keys }.union() update { self.missingTags = missingTags } let missingPages = cache.values.map { $0.missingLinkedPages.keys }.union() update { self.missingPages = missingPages } let externalLinks = cache.values.map { $0.externalLinks }.union() update { self.externalLinks = externalLinks } let requiredFiles = cache.values.map { $0.requiredFiles }.union() update { self.requiredFiles = requiredFiles } let imagesToGenerate = cache.values.map { $0.imagesToGenerate }.union() update { self.imagesToGenerate = imagesToGenerate } let invalidCommands = cache.values.map { $0.invalidCommands.map { $0.markdown }}.union() update { self.invalidCommands = invalidCommands } let warnings = cache.values.map { $0.warnings }.union() update { self.warnings = warnings } let unsavedOutputFiles = cache.values.map { $0.unsavedOutputFiles.keys }.union() update { self.unsavedOutputFiles = unsavedOutputFiles } } private func update(_ operation: @escaping () -> Void) { DispatchQueue.main.async { operation() } } // MARK: Adding entries func inaccessibleContent(file: FileResource) { update { self.inaccessibleFiles.insert(file) } } func unparsable(file: FileResource) { update { self.unparsableFiles.insert(file) } } func missing(file: String) { update { self.missingFiles.insert(file) } } func missing(tag: String) { update { self.missingTags.insert(tag) } } func missing(page: String) { update { self.missingPages.insert(page) } } func externalLink(_ url: String) { update { self.externalLinks.insert(url) } } func require(file: FileResource) { update { self.requiredFiles.insert(file) } } func require(files: S) where S: Sequence, S.Element == FileResource { update { self.requiredFiles.formUnion(files) } } func generate(_ image: ImageVersion) { update { self.imagesToGenerate.insert(image) } } func generate(_ images: S) where S: Sequence, S.Element == ImageVersion { update { self.imagesToGenerate.formUnion(images) } } func invalidCommand(_ markdown: String) { update { self.invalidCommands.insert(markdown) } } func warning(_ warning: String) { update { self.warnings.insert(warning) } } func failed(image: ImageVersion) { update { self.failedImages.insert(image) } } func unsaved(_ path: String) { update { self.unsavedOutputFiles.insert(path) } } func empty(_ page: LocalizedPageId) { update {self.emptyPages.insert(page) } } } private extension Dictionary where Value == Set { mutating func remove(keys: S, of item: ItemId) where S: Sequence, S.Element == Key { for key in keys { guard var value = self[key] else { continue } value.remove(item) if value.isEmpty { self[key] = nil } else { self[key] = value } } } }