import Foundation final class WebsiteGenerator { let language: ContentLanguage let localizedSettings: LocalizedSettings private var outputDirectory: URL { URL(filePath: content.settings.outputDirectoryPath) } private var postsPerPage: Int { content.settings.posts.postsPerPage } private var postFeedTitle: String { localizedSettings.posts.title } private var postFeedDescription: String { localizedSettings.posts.description } private var postFeedUrlPrefix: String { localizedSettings.posts.feedUrlPrefix } private var navigationIconPath: String { content.settings.navigationBar.iconPath } private var mainContentMaximumWidth: CGFloat { CGFloat(content.settings.posts.contentWidth) } private let content: Content private let imageGenerator: ImageGenerator private var navigationBarData: NavigationBarData { createNavigationBarData( settings: content.settings.navigationBar, iconDescription: localizedSettings.navigationBarIconDescription) } init(content: Content, language: ContentLanguage) { self.language = language self.content = content self.localizedSettings = content.settings.localized(in: language) self.imageGenerator = ImageGenerator( storage: content.storage, inputImageFolder: content.storage.filesFolder, relativeImageOutputPath: "images") } func generateWebsite(callback: (String) -> Void) -> Bool { guard imageGenerator.prepareForGeneration() else { return false } guard createPostFeedPages() else { return false } guard imageGenerator.runJobs(callback: callback) else { return false } return imageGenerator.save() } private func createPostFeedPages() -> Bool { let totalCount = content.posts.count guard totalCount > 0 else { return true } let numberOfPages = (totalCount + postsPerPage - 1) / postsPerPage // Round up for pageIndex in 1...numberOfPages { let startIndex = (pageIndex - 1) * postsPerPage let endIndex = min(pageIndex * postsPerPage, totalCount) let postsOnPage = content.posts[startIndex.. NavigationBarData { let navigationItems: [NavigationBarLink] = settings.tags.map { let localized = $0.localized(in: language) return .init(text: localized.name, url: localized.urlComponent) } return NavigationBarData( navigationIconPath: navigationIconPath, iconDescription: iconDescription, navigationItems: navigationItems) } private func createImageSet(for image: ImageResource) -> FeedEntryData.Image { imageGenerator.generateImageSet( for: image.id, maxWidth: mainContentMaximumWidth, maxHeight: mainContentMaximumWidth, altText: image.getDescription(for: language)) } private func createPostFeedPage(_ pageIndex: Int, pageCount: Int, posts: ArraySlice, bar: NavigationBarData) -> Bool { let posts: [FeedEntryData] = posts.map { post in let localized: LocalizedPost = post.localized(in: language) let linkUrl = post.linkedPage.map { FeedEntryData.Link( url: content.pageLink($0, language: language), text: language == .english ? "View" : "Anzeigen") // TODO: Add to settings } return FeedEntryData( entryId: "\(post.id)", title: localized.title, textAboveTitle: post.dateText(in: language), link: linkUrl, tags: post.tags.map { $0.data(in: language) }, text: [localized.content], // TODO: Convert from markdown to html images: localized.images.map(createImageSet)) } let feed = PageInFeed( language: language, title: postFeedTitle, description: postFeedDescription, navigationBarData: bar, pageNumber: pageIndex, totalPages: pageCount, posts: posts) let fileContent = feed.content if pageIndex == 1 { return save(fileContent, to: "\(postFeedUrlPrefix).html") } else { return save(fileContent, to: "\(postFeedUrlPrefix)-\(pageIndex).html") } } private func generatePagesFolderIfNeeded() -> Bool { let relativePath = content.settings.pages.pageUrlPrefix return content.storage.write(in: .outputPath) { folder in let outputFile = folder.appendingPathComponent(relativePath, isDirectory: true) do { try outputFile.ensureFolderExistence() return true } catch { return false } } } func generate(page: Page) -> Bool { guard generatePagesFolderIfNeeded() else { print("Failed to generate output folder") return false } let pageGenerator = PageGenerator(content: content, imageGenerator: imageGenerator, navigationBarData: navigationBarData) let content = pageGenerator.generate(page: page, language: language) let path = self.content.pageLink(page, language: language) + ".html" guard save(content, to: path) else { print("Failed to save page") return false } guard imageGenerator.runJobs(callback: { _ in }) else { return false } guard copy(requiredVideoFiles: pageGenerator.results.requiredVideoFiles) else { return false } return true } private func copy(requiredVideoFiles: Set) -> Bool { print("Copying \(requiredVideoFiles.count) videos...") for fileId in requiredVideoFiles { guard let outputPath = content.pathToFile(fileId) else { return false } guard content.storage.copy(file: fileId, to: outputPath) else { print("Failed to copy video file to output folder") return false } } return true } private func save(_ content: String, to relativePath: String) -> Bool { guard let data = content.data(using: .utf8) else { print("Failed to create data for \(relativePath)") return false } return save(data, to: relativePath) } private func save(_ data: Data, to relativePath: String) -> Bool { self.content.storage.write(in: .outputPath) { folder in let outputFile = folder.appendingPathComponent(relativePath, isDirectory: false) do { try data.write(to: outputFile) return true } catch { print("Failed to save \(outputFile.path()): \(error)") return false } } } }