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