import Foundation extension Content { func generateFeed() -> Bool { #warning("Implement feed generation") return false } func generateAllPages() -> Bool { guard startGenerating() else { return false } defer { endGenerating() } for page in pages { for language in ContentLanguage.allCases { guard generateInternal(page, in: language) else { return false } } } let failedAssetCopies = results.values .reduce(Set()) { $0.union($1.assets) } .filter { !$0.isExternallyStored } .filter { !storage.copy(file: $0.id, to: $0.assetUrl) } let failedFileCopies = results.values .reduce(Set()) { $0.union($1.files) } .filter { !$0.isExternallyStored } .filter { !storage.copy(file: $0.id, to: $0.absoluteUrl) } guard imageGenerator.runJobs(callback: { _ in }) else { return false } return true } func generatePage(_ page: Page) -> Bool { guard startGenerating() else { return false } defer { endGenerating() } for language in ContentLanguage.allCases { guard generateInternal(page, in: language) else { return false } } guard imageGenerator.runJobs(callback: { _ in }) else { return false } let failedAssetCopies = results.values .reduce(Set()) { $0.union($1.assets) } .filter { !$0.isExternallyStored } .filter { !storage.copy(file: $0.id, to: $0.assetUrl) } let failedFileCopies = results.values .reduce(Set()) { $0.union($1.files) } .filter { !$0.isExternallyStored } .filter { !storage.copy(file: $0.id, to: $0.absoluteUrl) } return true } func generatePage(_ page: Page, in language: ContentLanguage) -> Bool { guard startGenerating() else { return false } defer { endGenerating() } return generateInternal(page, in: language) } // MARK: Paths to items private func makeCleanAbsolutePath(_ path: String) -> String { ("/" + path).replacingOccurrences(of: "//", with: "/") } func absoluteUrlPrefixForTag(_ tag: Tag, language: ContentLanguage) -> String { makeCleanAbsolutePath(settings.paths.tagsOutputFolderPath + "/" + tag.localized(in: language).urlComponent) } func absoluteUrlToTag(_ tag: Tag, language: ContentLanguage) -> String { absoluteUrlPrefixForTag(tag, language: language) + ".html" } // MARK: Find items by id func page(_ pageId: String) -> Page? { pages.first { $0.id == pageId } } func image(_ imageId: String) -> FileResource? { files.first { $0.id == imageId && $0.type.isImage } } func video(_ videoId: String) -> FileResource? { files.first { $0.id == videoId && $0.type.isVideo } } func file(_ fileId: String) -> FileResource? { files.first { $0.id == fileId } } func tag(_ tagId: String) -> Tag? { tags.first { $0.id == tagId } } // MARK: Generation input func navigationBar(in language: ContentLanguage) -> [NavigationBar.Link] { settings.navigationItems.map { .init(text: $0.title(in: language), url: $0.absoluteUrl(in: language)) } } var defaultPageHeaders: Set { var result: Set = [.charset, .viewport] if let defaultCss = settings.posts.defaultCssFile { result.insert(.css(file: defaultCss, order: HeaderElement.defaultCssFileOrder)) } return result } // MARK: Images func recalculateGeneratedImages() { let images = Set(self.images.map { $0.id }) imageGenerator.recalculateGeneratedImages(by: images) } // MARK: Generation private func startGenerating() -> Bool { guard !isGeneratingWebsite else { return false } // TODO: Fix bug where multiple generating operations can be started // due to dispatch of locking property on main queue self.set(isGenerating: true) return true } private func endGenerating() { set(isGenerating: false) } private func generateInternal(_ page: Page, in language: ContentLanguage) -> Bool { let pageGenerator = PageGenerator( content: self, imageGenerator: imageGenerator) guard let (content, results) = pageGenerator.generate(page: page, language: language) else { print("Failed to generate page \(page.id) in language \(language)") return false } DispatchQueue.main.async { let id = ItemId(itemId: page.id, language: language, itemType: .page) self.results[id] = results } let path = page.absoluteUrl(in: language) + ".html" guard storage.write(content, to: path) else { print("Failed to save page \(page.id)") return false } return true } } prefix operator ~> prefix func ~> (operation: () throws -> Void) -> Bool { do { try operation() return true } catch { return false } }