ChWebsiteApp/CHDataManagement/Generator/Results/PageGenerationResults.swift
2025-02-07 14:08:51 +01:00

292 lines
8.4 KiB
Swift

import Foundation
struct ImageToGenerate {
let size: Int
let image: FileResource
}
extension ImageToGenerate: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(size)
hasher.combine(image.id)
}
}
final class PageGenerationResults: ObservableObject {
let itemId: LocalizedItemId
private unowned let delegate: GenerationResults
/// The files that could not be accessed
@Published
private(set) var inaccessibleFiles: Set<FileResource>
/// The files that could not be parsed, with the error message produced
@Published
private(set) var unparsableFiles: [FileResource : Set<String>]
/// The missing files directly used by this page, and the source of the file
@Published
private(set) var missingFiles: [String: Set<String>]
/// The missing files linked to from other files.
@Published
private(set) var missingLinkedFiles: [String : Set<FileResource>]
/// The missing tags linked to by this page, and the source of the link
@Published
private(set) var missingLinkedTags: [String : Set<String>]
/// The missing pages linked to by this page, and the source of the link
@Published
private(set) var missingLinkedPages: [String : Set<String>]
/// The footer scripts or html to add to the end of the body
@Published
private(set) var requiredFooters: Set<String>
/// The known header elements to include in the page
@Published
private(set) var requiredHeaders: Set<KnownHeaderElement>
/// The known icons that need to be included as hidden SVGs
@Published
private(set) var requiredIcons: Set<PageIcon>
/// The pages linked to by the page
@Published
private(set) var linkedPages: Set<Page>
/// The tags linked to by this page
@Published
private(set) var linkedTags: Set<Tag>
/// The links to external content in this page
@Published
private(set) var externalLinks: Set<String>
/// The files used by this page, but not necessarily required in the output folder
@Published
private(set) var usedFiles: Set<FileResource>
/// The files that need to be copied
@Published
private(set) var requiredFiles: Set<FileResource>
/// The image versions required for this page
@Published
private(set) var imagesToGenerate: Set<ImageVersion>
@Published
private(set) var invalidCommands: [(command: CommandType?, markdown: String)]
@Published
private(set) var invalidBlocks: [(block: ContentBlock?, markdown: String)]
@Published
private(set) var warnings: Set<String>
/// The files that could not be saved to the output folder
@Published
private(set) var unsavedOutputFiles: [String: Set<ItemReference>] = [:]
private(set) var pageIsEmpty: Bool
private(set) var redirect: (originalUrl: String, newUrl: String)?
init(itemId: LocalizedItemId, delegate: GenerationResults) {
self.itemId = itemId
self.delegate = delegate
inaccessibleFiles = []
unparsableFiles = [:]
missingFiles = [:]
missingLinkedFiles = [:]
missingLinkedTags = [:]
missingLinkedPages = [:]
requiredHeaders = []
requiredFooters = []
requiredIcons = []
linkedPages = []
linkedTags = []
externalLinks = []
usedFiles = []
requiredFiles = []
imagesToGenerate = []
invalidCommands = []
invalidBlocks = []
warnings = []
unsavedOutputFiles = [:]
pageIsEmpty = false
redirect = nil
}
func onMain(_ operation: @escaping () -> Void) {
DispatchQueue.main.async {
operation()
}
}
func reset() {
onMain {
self.inaccessibleFiles = []
self.unparsableFiles = [:]
self.missingFiles = [:]
self.missingLinkedFiles = [:]
self.missingLinkedTags = [:]
self.missingLinkedPages = [:]
self.requiredHeaders = []
self.requiredFooters = []
self.requiredIcons = []
self.linkedPages = []
self.linkedTags = []
self.externalLinks = []
self.usedFiles = []
self.requiredFiles = []
self.imagesToGenerate = []
self.invalidCommands = []
self.invalidBlocks = []
self.warnings = []
self.unsavedOutputFiles = [:]
self.pageIsEmpty = false
self.redirect = nil
}
}
// MARK: Adding entries
func inaccessibleContent(file: FileResource) {
onMain { self.inaccessibleFiles.insert(file) }
delegate.inaccessibleContent(file: file)
}
func invalid(command: CommandType?, _ markdown: Substring) {
let markdown = String(markdown)
onMain { self.invalidCommands.append((command, markdown)) }
delegate.invalidCommand(markdown)
}
func invalid(block: ContentBlock?, _ markdown: Substring) {
let markdown = String(markdown)
onMain { self.invalidBlocks.append((block, markdown)) }
delegate.invalidBlock(markdown)
}
func missing(page: String, source: String) {
onMain { self.missingLinkedPages[page, default: []].insert(source) }
delegate.missing(page: page)
}
func missing(tag: String, source: String) {
onMain { self.missingLinkedTags[tag, default: []].insert(source) }
delegate.missing(tag: tag)
}
func missing(file: String, source: String) {
onMain { self.missingFiles[file, default: []].insert(source) }
delegate.missing(file: file)
}
func require(image: ImageVersion) {
onMain { self.imagesToGenerate.insert(image) }
used(file: image.image)
delegate.generate(image)
}
func require(imageSet: ImageSet) {
let jobs = imageSet.jobs
onMain { self.imagesToGenerate.formUnion(jobs) }
used(file: imageSet.image)
delegate.generate(jobs)
}
func invalidFormat(file: FileResource, error: String) {
onMain { self.unparsableFiles[file, default: []].insert(error) }
delegate.unparsable(file: file)
}
func missing(file: String, containedIn sourceFile: FileResource) {
onMain { self.missingLinkedFiles[file, default: []].insert(sourceFile) }
delegate.missing(file: file)
}
func used(file: FileResource) {
onMain { self.usedFiles.insert(file) }
// TODO: Notify delegate
}
func require(file: FileResource) {
onMain { self.requiredFiles.insert(file) }
used(file: file)
delegate.require(file: file)
}
func require(files: [FileResource]) {
onMain { self.requiredFiles.formUnion(files) }
onMain { self.usedFiles.formUnion(files) }
delegate.require(files: files)
}
func require(footer: String) {
onMain { self.requiredFooters.insert(footer) }
}
func require(header: KnownHeaderElement) {
onMain { self.requiredHeaders.insert(header) }
}
func require(headers: KnownHeaderElement...) {
onMain { self.requiredHeaders.formUnion(headers) }
}
func require(icon: PageIcon) {
onMain { self.requiredIcons.insert(icon) }
}
func require(icons: PageIcon...) {
onMain { self.requiredIcons.formUnion(icons) }
}
func require(icons: [PageIcon]) {
onMain { self.requiredIcons.formUnion(icons) }
}
func linked(to page: Page) {
onMain { self.linkedPages.insert(page) }
}
func linked(to tag: Tag) {
onMain { self.linkedTags.insert(tag) }
}
func externalLink(to url: String) {
onMain { self.externalLinks.insert(url) }
delegate.externalLink(url)
}
func warning(_ warning: String) {
onMain { self.warnings.insert(warning) }
delegate.warning(warning)
}
func unsavedOutput(_ path: String, source: ItemReference) {
onMain { self.unsavedOutputFiles[path, default: []].insert(source) }
delegate.unsaved(path)
}
func markPageAsEmpty() {
guard case .page(let page) = itemId.itemType else { return }
onMain { self.pageIsEmpty = true }
delegate.empty(.init(language: itemId.language, pageId: page.id))
}
func redirect(from originalUrl: String, to newUrl: String) {
onMain { self.redirect = (originalUrl, newUrl) }
delegate.redirect(from: originalUrl, to: newUrl)
}
}