import Foundation final class Importer { var posts: [String : PostFile] = [:] var pages: [String : PageOnDisk] = [:] var tags: [String : TagFile] = [:] var files: [String : FileOnDisk] = [:] var ignoredFiles: [URL] = [] var foldersToSearch: [(path: String, tag: String)] = [ ("/Users/ch/Downloads/Website/projects/electronics", "electronics"), ("/Users/ch/Downloads/Website/projects/endeavor", "endeavor"), ("/Users/ch/Downloads/Website/projects/furniture", "furniture"), ("/Users/ch/Downloads/Website/projects/lighting", "lighting"), ("/Users/ch/Downloads/Website/projects/other", "other"), ("/Users/ch/Downloads/Website/projects/sewing", "sewing"), ("/Users/ch/Downloads/Website/projects/software", "software"), ("/Users/ch/Downloads/Website/articles", "articles"), ("/Users/ch/Downloads/Website/photography", "photography"), ("/Users/ch/Downloads/Website/travel", "travel") ] func importContent() throws { for (path, name) in foldersToSearch { let folder = URL(filePath: path) let pageFolders = try findPageFolders(in: folder) let tag = try importTag(name: name, folder: folder) for pageFolder in pageFolders { try importEntry(at: pageFolder, tag: tag) } } } private func importTag(name: String, folder: URL) throws -> String { let metadataUrl = folder.appending(path: "metadata.json", directoryHint: .notDirectory) let data = try Data(contentsOf: metadataUrl) let meta = try JSONDecoder().decode(ImportableTag.self, from: data) let thumbnailUrl = folder.appending(path: "thumbnail.jpg", directoryHint: .notDirectory) var thumbnail: FileOnDisk? = nil if FileManager.default.fileExists(atPath: thumbnailUrl.path()) { thumbnail = FileOnDisk(type: .image, url: thumbnailUrl, name: "\(name)-thumbnail.jpg") add(resource: thumbnail!) } func makeTag(metadata: TagLanguage) throws -> LocalizedTagFile { let language = ContentLanguage(rawValue: metadata.language)! let originalUrl = folder .appendingPathComponent("\(language.rawValue).html", isDirectory: false) .path() .replacingOccurrences(of: "/Users/ch/Downloads/Website", with: "") return LocalizedTagFile( urlComponent: metadata.title.lowercased().replacingOccurrences(of: " ", with: "-"), name: metadata.title, subtitle: metadata.subtitle, description: metadata.description, thumbnail: thumbnail?.name, originalURL: originalUrl) } let en = meta.info(for: .english)! let de = meta.info(for: .german)! let tagId = en.title.lowercased().replacingOccurrences(of: " ", with: "-") let enTag = try makeTag(metadata: en) let deTag = try makeTag(metadata: de) let tag = TagFile( id: enTag.urlComponent, isVisible: true, german: deTag, english: enTag) tags[tagId] = tag return tagId } private func findPageFolders(in folder: URL) throws -> [URL] { try FileManager.default .contentsOfDirectory(at: folder, includingPropertiesForKeys: [.isDirectoryKey]) .filter { $0.hasDirectoryPath } } private func findResources(in folder: URL, pageId: String) throws -> [FileOnDisk] { try FileManager.default .contentsOfDirectory(at: folder, includingPropertiesForKeys: [.isDirectoryKey]) .filter { !$0.hasDirectoryPath } .compactMap { url in let fileName = url.lastPathComponent let fileExtension = url.pathExtension guard fileName != "metadata.json", fileName != "de.md", fileName != "en.md" else { return nil } let type = FileType(fileExtension: fileExtension) guard type != .resource else { self.ignoredFiles.append(url) return nil } let name = pageId + "-" + fileName return FileOnDisk(type: type, url: url, name: name) } } private func importEntry(at url: URL, tag: String) throws { let metadataUrl = url.appending(path: "metadata.json", directoryHint: .notDirectory) guard FileManager.default.fileExists(atPath: metadataUrl.path()) else { print("No entry at \(url.path())") return } let data = try Data(contentsOf: metadataUrl) let meta = try JSONDecoder().decode(GenericMetadata.self, from: data) let pageId = meta.customId ?? url.lastPathComponent let resources = try findResources(in: url, pageId: pageId) guard let languages = meta.languages else { print("No languages for \(url.path())") return } let externalFiles = meta.externalFiles ?? [] let requiredFiles = meta.requiredFiles ?? [] let date = meta.date!.toDate() let endDate = meta.endDate?.toDate() let de = languages.first { $0.language == "de" }! let en = languages.first { $0.language == "en" }! @discardableResult func makePage(_ content: GenericMetadata.LocalizedMetadata) throws -> (LocalizedPageFile, URL, LocalizedPostFile) { let language = ContentLanguage(rawValue: content.language!)! let id: String if language == .english { id = pageId } else { id = pageId + "-" + language.rawValue } let originalUrl = url .appendingPathComponent("\(language.rawValue).html", isDirectory: false) .path() .replacingOccurrences(of: "/Users/ch/Downloads/Website", with: "") var pageFiles = Set(resources.map { $0.name }) let thumbnail = try determineThumbnail(in: resources, folder: url, customPath: meta.thumbnailPath, pageId: id, language: language.rawValue) if let thumbnail { pageFiles.insert(thumbnail.name) } let page = LocalizedPageFile( url: id, files: pageFiles.sorted(), externalFiles: externalFiles.sorted(), requiredFiles: requiredFiles.sorted(), title: content.title!, linkPreviewImage: thumbnail?.name, linkPreviewTitle: content.linkPreviewTitle, linkPreviewDescription: content.linkPreviewDescription, lastModifiedDate: nil, originalURL: originalUrl) let contentUrl = url.appendingPathComponent("\(content.language!).md", isDirectory: false) let postContent = content.linkPreviewDescription ?? content.description ?? "" let post = createPost(page: page, content: postContent) return (page, contentUrl, post) } let (dePage, deUrl, dePost) = try makePage(de) let (enPage, enUrl, enPost) = try makePage(en) let page = PageFile( isDraft: meta.state == "draft", tags: [tag], createdDate: date, startDate: date, endDate: endDate, german: dePage, english: enPage) if pages[pageId] != nil { print("Conflicting page id \(pageId)") } pages[pageId] = .init(page: page, deContentUrl: deUrl, enContentUrl: enUrl) for resource in resources { add(resource: resource) } let post = PostFile( isDraft: page.isDraft || meta.state == "hidden", createdDate: page.createdDate, startDate: page.startDate, endDate: page.endDate, tags: page.tags, german: dePost, english: enPost, linkedPageId: pageId) posts[pageId] = post } private func add(resource: FileOnDisk) { guard let existingFile = files[resource.name] else { files[resource.name] = resource return } guard existingFile.url != resource.url else { return } print("Conflicting name for file \(resource.name)") } private func determineThumbnail(in resources: [FileOnDisk], folder: URL, customPath: String?, pageId: String, language: String) throws -> FileOnDisk? { guard let thumbnailUrl = findThumbnailUrl(in: folder, customPath: customPath, language: language) else { return nil } return resources.first { $0.url == thumbnailUrl } } private func determineThumbnail(in folder: URL, customPath: String?, pageId: String, language: String) throws -> FileOnDisk? { guard let thumbnailUrl = findThumbnailUrl(in: folder, customPath: customPath, language: language) else { return nil } let id = pageId + "-" + thumbnailUrl.lastPathComponent return FileOnDisk(image: id, url: thumbnailUrl) } private func findThumbnailUrl(in folder: URL, customPath: String?, language: String) -> URL? { if let customPath { return folder.appending(path: customPath, directoryHint: .notDirectory) } let thumbnailImageUrl = folder.appending(path: "thumbnail.jpg", directoryHint: .notDirectory) if FileManager.default.fileExists(atPath: thumbnailImageUrl.path()) { return thumbnailImageUrl } let localizedThumbnail = folder.appending(path: "thumbnail-\(language).jpg", directoryHint: .notDirectory) if FileManager.default.fileExists(atPath: localizedThumbnail.path()) { return localizedThumbnail } print("No thumbnail found in \(folder.path())") return nil } private func createPost(page: LocalizedPageFile, content: String) -> LocalizedPostFile { let images = page.linkPreviewImage.map { [$0] } ?? [] return LocalizedPostFile( images: images.sorted(), title: page.linkPreviewTitle ?? page.title, content: content, lastModifiedDate: nil, linkPreviewImage: nil, linkPreviewTitle: nil, linkPreviewDescription: nil) } } private extension String { private static let metadataDate: DateFormatter = { let df = DateFormatter() df.dateFormat = "dd.MM.yy" return df }() func toDate() -> Date { String.metadataDate.date(from: self)! } }