2025-02-12 18:19:42 +01:00

248 lines
7.8 KiB
Swift

import Foundation
final class GenerationResults: ObservableObject {
/// The files that could not be accessed
@Published
var inaccessibleFiles: Set<FileResource> = []
/// The files that could not be parsed, with the error message produced
@Published
var unparsableFiles: Set<FileResource> = []
@Published
var missingFiles: Set<String> = []
@Published
var missingTags: Set<String> = []
@Published
var missingPages: Set<String> = []
@Published
var externalLinks: Set<String> = []
@Published
var requiredFiles: Set<FileResource> = []
@Published
var imagesToGenerate: Set<ImageVersion> = []
@Published
var invalidCommands: Set<String> = []
@Published
var invalidBlocks: Set<String> = []
@Published
var warnings: Set<String> = []
@Published
var unsavedOutputFiles: Set<String> = []
@Published
var failedImages: Set<ImageVersion> = []
@Published
var emptyPages: Set<LocalizedPageId> = []
/**
The url redirects to install to prevent broken links.
The key is the original url, and the value the new url of the content, relative to the output folder.
The dictionary is used to create a map of redirects for Nginx.
*/
@Published
var redirects: [String : String] = [:]
/// The cache of previously used GenerationResults
private var cache: [LocalizedItemId : PageGenerationResults] = [:]
private(set) var general: PageGenerationResults!
@Published
var resultCount: Int = 0
// MARK: Life cycle
init() {
let id = LocalizedItemId(language: .english, itemType: .general)
let general = PageGenerationResults(itemId: id, delegate: self)
self.general = general
cache[id] = general
self.resultCount = 0
}
func makeResults(_ itemId: LocalizedItemId) -> 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: ItemReference, in language: ContentLanguage) -> PageGenerationResults {
let itemId = LocalizedItemId(language: language, itemType: type)
return makeResults(itemId)
}
func makeResults(for page: Page, in language: ContentLanguage) -> PageGenerationResults {
let itemId = LocalizedItemId(language: language, itemType: .page(page))
return makeResults(itemId)
}
func makeResults(for tag: Tag, in language: ContentLanguage) -> PageGenerationResults {
let itemId = LocalizedItemId(language: language, itemType: .tagPage(tag))
return makeResults(itemId)
}
func reset() {
update {
self.inaccessibleFiles = []
self.unparsableFiles = []
self.missingFiles = []
self.missingTags = []
self.missingPages = []
self.externalLinks = []
self.requiredFiles = []
self.imagesToGenerate = []
self.invalidCommands = []
self.invalidBlocks = []
self.warnings = []
self.unsavedOutputFiles = []
self.emptyPages = []
self.redirects = [:]
}
for result in cache.values {
result.reset()
}
}
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 invalidBlocks = cache.values.map { $0.invalidBlocks.map { $0.markdown }}.union()
update { self.invalidBlocks = invalidBlocks }
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 }
let emptyPages = cache.values.filter { $0.pageIsEmpty }.map { $0.itemId }.compactMap { id -> LocalizedPageId? in
guard case .page(let page) = id.itemType else { return nil }
return LocalizedPageId(language: id.language, pageId: page.id)
}.asSet()
update { self.emptyPages = emptyPages }
let redirects = cache.values.compactMap { $0.redirect }.reduce(into: [:]) { $0[$1.originalUrl] = $1.newUrl }
update { self.redirects = redirects }
}
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<S>(files: S) where S: Sequence, S.Element == FileResource {
update { self.requiredFiles.formUnion(files) }
}
func generate(_ image: ImageVersion) {
update { self.imagesToGenerate.insert(image) }
}
func generate<S>(_ images: S) where S: Sequence, S.Element == ImageVersion {
update { self.imagesToGenerate.formUnion(images) }
}
func invalidCommand(_ markdown: String) {
update { self.invalidCommands.insert(markdown) }
}
func invalidBlock(_ markdown: String) {
update { self.invalidBlocks.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) }
}
func redirect(from originalUrl: String, to newUrl: String) {
update { self.redirects[originalUrl] = newUrl }
}
}
private extension Dictionary where Value == Set<LocalizedItemId> {
mutating func remove<S>(keys: S, of item: LocalizedItemId) 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
}
}
}
}