import Foundation import ArgumentParser @main struct CHGenerator: ParsableCommand { @Argument(help: "The path to the generator configuration file") var configPath: String mutating func run() throws { try generate(configPath: configPath) } } private func loadConfiguration(at configPath: String) -> Configuration? { print("--- CONFIGURATION ----------------------------------") print(" ") print(" Configuration file: \(configPath)") let configUrl = URL(fileURLWithPath: configPath) guard configUrl.exists else { print(" Error: Configuration file not found") return nil } var config: Configuration do { let data = try Data(contentsOf: configUrl) config = try JSONDecoder().decode(from: data) config.adjustPathsRelative(to: configUrl.deletingLastPathComponent()) } catch { print(" Configuration error: \(error)") return nil } config.printOverview() print(" ") return config } private func loadSiteData(in folder: URL, runFolder: URL) throws -> (root: Element, pageMap: PageMap)? { print("--- SOURCE FILES -----------------------------------") print(" ") let log = MetadataInfoLogger(input: folder) let root = Element(atRoot: folder, log: log) let file = runFolder.appendingPathComponent("metadata.txt") defer { log.writeResults(to: file) print(" ") } guard let root else { log.printMetadataScanOverview(languages: 0) print(" Error: No site root loaded, aborting generation") return nil } let pageMap = root.languages.map { (language: $0.language, pages: root.getExternalPageMap(language: $0.language)) } log.printMetadataScanOverview(languages: root.languages.count) return (root, pageMap) } private func generatePages(from root: Element, configuration: Configuration, fileUpdates: FileUpdateChecker, pageMap: PageMap, runFolder: URL) -> (ImageData, FileData)? { print("--- GENERATION -------------------------------------") print(" ") let pageCount = pageMap.reduce(0) { $0 + $1.pages.count } let results = GenerationResultsHandler( in: configuration.contentDirectory, to: configuration.outputDirectory, configuration: configuration, fileUpdates: fileUpdates, pageMap: pageMap, pageCount: pageCount) defer { results.printOverview() } let siteGenerator: SiteGenerator do { siteGenerator = try SiteGenerator(results: results) } catch { return nil } siteGenerator.generate(site: root) let url = runFolder.appendingPathComponent("pages.txt") results.writeResults(to: url) if let error = fileUpdates.writeDetectedFileChanges(to: runFolder) { print(" Hashes not saved: \(error)") } return (results.images, results.files) } private func generateImages(_ images: ImageData, configuration: Configuration, runFolder: URL, fileUpdates: FileUpdateChecker) { print("--- IMAGES -----------------------------------------") print(" ") let reader = ImageReader(in: configuration.contentDirectory, runFolder: runFolder, fileUpdates: fileUpdates) let generator = ImageGenerator( input: configuration.contentDirectory, output: configuration.outputDirectory, reader: reader, images: images) generator.generateImages() print(" ") let file = runFolder.appendingPathComponent("images.txt") generator.writeResults(to: file) } private func copyFiles(files: FileData, configuration: Configuration, runFolder: URL) { print("--- FILES ------------------------------------------") print(" ") let generator = FileGenerator( input: configuration.contentDirectory, output: configuration.outputDirectory, runFolder: runFolder, files: files) generator.generate() let file = runFolder.appendingPathComponent("files.txt") generator.writeResults(to: file) } private func finish(start: Date, complete: Bool) { print("--- SUMMARY ----------------------------------------") print(" ") let duration = Int(-start.timeIntervalSinceNow.rounded()) if duration < 3600 { print(String(format: " Duration: %d:%02d", duration / 60, duration % 60)) } else { print(String(format: " Duration: %d:%02d:%02d", duration / 3600, (duration / 60) % 60, duration % 60)) } print(" Complete: \(complete ? "Yes" : "No")") print(" ") print("----------------------------------------------------") } private func generate(configPath: String) throws { let start = Date() var complete = false defer { // 6. Print summary finish(start: start, complete: complete) } print(" ") guard checkDependencies() else { return } // 1. Load configuration guard let configuration = loadConfiguration(at: configPath) else { return } let runFolder = configuration.contentDirectory.appendingPathComponent("run") // 2. Scan site elements guard let (siteRoot, pageMap) = try loadSiteData(in: configuration.contentDirectory, runFolder: runFolder) else { return } let fileUpdates = FileUpdateChecker(input: configuration.contentDirectory) switch fileUpdates.loadPreviousRun(from: runFolder) { case .notLoaded: print("Regarding all files as new (no hashes loaded)") case .loaded: break case .failed(let error): print("Regarding all files as new (\(error))") } // 3. Generate pages guard let (images, files) = generatePages(from: siteRoot, configuration: configuration, fileUpdates: fileUpdates, pageMap: pageMap, runFolder: runFolder) else { return } // 4. Generate images generateImages(images, configuration: configuration, runFolder: runFolder, fileUpdates: fileUpdates) // 5. Copy/minify files copyFiles(files: files, configuration: configuration, runFolder: runFolder) complete = true }