Copy required files for pages

This commit is contained in:
Christoph Hagen 2022-08-16 12:26:45 +02:00
parent 14b935249f
commit 02a3dd4007
13 changed files with 85 additions and 46 deletions

View File

@ -23,7 +23,7 @@
E22E878C289E4A8900E51191 /* Ink in Frameworks */ = {isa = PBXBuildFile; productRef = E22E878B289E4A8900E51191 /* Ink */; }; E22E878C289E4A8900E51191 /* Ink in Frameworks */ = {isa = PBXBuildFile; productRef = E22E878B289E4A8900E51191 /* Ink */; };
E22E8793289E7EC700E51191 /* Page+LocalizedMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22E8792289E7EC700E51191 /* Page+LocalizedMetadata.swift */; }; E22E8793289E7EC700E51191 /* Page+LocalizedMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22E8792289E7EC700E51191 /* Page+LocalizedMetadata.swift */; };
E22E8795289E81D700E51191 /* FileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22E8794289E81D700E51191 /* FileSystem.swift */; }; E22E8795289E81D700E51191 /* FileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22E8794289E81D700E51191 /* FileSystem.swift */; };
E22E8798289EA42C00E51191 /* ImageProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22E8797289EA42C00E51191 /* ImageProcessor.swift */; }; E22E8798289EA42C00E51191 /* FileProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22E8797289EA42C00E51191 /* FileProcessor.swift */; };
E22E879B289EE02F00E51191 /* Optional+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22E879A289EE02F00E51191 /* Optional+Extensions.swift */; }; E22E879B289EE02F00E51191 /* Optional+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22E879A289EE02F00E51191 /* Optional+Extensions.swift */; };
E22E879E289EFDFC00E51191 /* OverviewPageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22E879D289EFDFC00E51191 /* OverviewPageGenerator.swift */; }; E22E879E289EFDFC00E51191 /* OverviewPageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22E879D289EFDFC00E51191 /* OverviewPageGenerator.swift */; };
E22E87A0289F008200E51191 /* ThumbnailListGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22E879F289F008200E51191 /* ThumbnailListGenerator.swift */; }; E22E87A0289F008200E51191 /* ThumbnailListGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22E879F289F008200E51191 /* ThumbnailListGenerator.swift */; };
@ -82,7 +82,7 @@
E22E8788289DDF5700E51191 /* Page+Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Page+Metadata.swift"; sourceTree = "<group>"; }; E22E8788289DDF5700E51191 /* Page+Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Page+Metadata.swift"; sourceTree = "<group>"; };
E22E8792289E7EC700E51191 /* Page+LocalizedMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Page+LocalizedMetadata.swift"; sourceTree = "<group>"; }; E22E8792289E7EC700E51191 /* Page+LocalizedMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Page+LocalizedMetadata.swift"; sourceTree = "<group>"; };
E22E8794289E81D700E51191 /* FileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystem.swift; sourceTree = "<group>"; }; E22E8794289E81D700E51191 /* FileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystem.swift; sourceTree = "<group>"; };
E22E8797289EA42C00E51191 /* ImageProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProcessor.swift; sourceTree = "<group>"; }; E22E8797289EA42C00E51191 /* FileProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProcessor.swift; sourceTree = "<group>"; };
E22E879A289EE02F00E51191 /* Optional+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+Extensions.swift"; sourceTree = "<group>"; }; E22E879A289EE02F00E51191 /* Optional+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+Extensions.swift"; sourceTree = "<group>"; };
E22E879D289EFDFC00E51191 /* OverviewPageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverviewPageGenerator.swift; sourceTree = "<group>"; }; E22E879D289EFDFC00E51191 /* OverviewPageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverviewPageGenerator.swift; sourceTree = "<group>"; };
E22E879F289F008200E51191 /* ThumbnailListGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbnailListGenerator.swift; sourceTree = "<group>"; }; E22E879F289F008200E51191 /* ThumbnailListGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbnailListGenerator.swift; sourceTree = "<group>"; };
@ -149,7 +149,7 @@
E2C5A5D328A0222B00102A25 /* Templates */, E2C5A5D328A0222B00102A25 /* Templates */,
E22E8799289EE02300E51191 /* Extensions */, E22E8799289EE02300E51191 /* Extensions */,
E22E876B289D855D00E51191 /* ThumbnailStyle.swift */, E22E876B289D855D00E51191 /* ThumbnailStyle.swift */,
E22E8797289EA42C00E51191 /* ImageProcessor.swift */, E22E8797289EA42C00E51191 /* FileProcessor.swift */,
E22E8777289DA0E100E51191 /* GenerationError.swift */, E22E8777289DA0E100E51191 /* GenerationError.swift */,
E22E8794289E81D700E51191 /* FileSystem.swift */, E22E8794289E81D700E51191 /* FileSystem.swift */,
); );
@ -315,7 +315,7 @@
E22E876E289D868100E51191 /* Site+LocalizedMetadata.swift in Sources */, E22E876E289D868100E51191 /* Site+LocalizedMetadata.swift in Sources */,
E2C5A5D528A0223C00102A25 /* OverviewPageTemplate.swift in Sources */, E2C5A5D528A0223C00102A25 /* OverviewPageTemplate.swift in Sources */,
E22E876C289D855D00E51191 /* ThumbnailStyle.swift in Sources */, E22E876C289D855D00E51191 /* ThumbnailStyle.swift in Sources */,
E22E8798289EA42C00E51191 /* ImageProcessor.swift in Sources */, E22E8798289EA42C00E51191 /* FileProcessor.swift in Sources */,
E26555E428A2C4FA00BAF496 /* LinkPreviewMetadataProvider.swift in Sources */, E26555E428A2C4FA00BAF496 /* LinkPreviewMetadataProvider.swift in Sources */,
E22E87AA289F1AEE00E51191 /* PageHeadGenerator.swift in Sources */, E22E87AA289F1AEE00E51191 /* PageHeadGenerator.swift in Sources */,
E2D55EDB28A2511D00B9453E /* OverviewSectionCleanTemplate.swift in Sources */, E2D55EDB28A2511D00B9453E /* OverviewSectionCleanTemplate.swift in Sources */,

View File

@ -3,7 +3,7 @@ import Foundation
import AppKit import AppKit
#endif #endif
final class ImageProcessor { final class FileProcessor {
struct ImageOutput: Hashable { struct ImageOutput: Hashable {
@ -32,12 +32,44 @@ final class ImageProcessor {
let outputFolder: URL let outputFolder: URL
/**
The files required by the site.
The content are the links to the files relative to the source root folder.
The files will be placed at the same path relative to the output folder
*/
private var requiredFiles: Set<String> = []
private var tasks: [String : ImageOutput] = [:]
init(inputFolder: URL, outputFolder: URL) { init(inputFolder: URL, outputFolder: URL) {
self.inputFolder = inputFolder self.inputFolder = inputFolder
self.outputFolder = outputFolder self.outputFolder = outputFolder
} }
private var tasks: [String : ImageOutput] = [:] // MARK: Files
/**
Add a file as required, so that it will be copied to the output directory.
*/
func require(file: String) {
requiredFiles.insert(file)
}
func copyRequiredFiles() throws {
var missingFiles = [String]()
for file in requiredFiles {
let sourceUrl = inputFolder.appendingPathComponent(file)
guard sourceUrl.exists else {
missingFiles.append(file)
continue
}
let destinationUrl = outputFolder.appendingPathComponent(file)
try FileSystem.copy(sourceUrl, to: destinationUrl)
}
}
// MARK: Images
@discardableResult @discardableResult
func requireImage(source: String, destination: String, width: Int, desiredHeight: Int? = nil, createDoubleVersion: Bool = false) throws -> NSSize { func requireImage(source: String, destination: String, width: Int, desiredHeight: Int? = nil, createDoubleVersion: Bool = false) throws -> NSSize {

View File

@ -9,6 +9,9 @@ enum FileSystem {
.filter { $0.isDirectory } .filter { $0.isDirectory }
} }
/**
Copy a file to the destination, creating the containing folder if needed
*/
static func copy(_ source: URL, to destination: URL) throws { static func copy(_ source: URL, to destination: URL) throws {
try destination.ensureParentFolderExistence() try destination.ensureParentFolderExistence()
try source.copy(to: destination) try source.copy(to: destination)

View File

@ -20,7 +20,7 @@ struct IndexPageGenerator {
private let factory: LocalizedSiteTemplate private let factory: LocalizedSiteTemplate
init(factory: LocalizedSiteTemplate, imageProcessor: ImageProcessor) { init(factory: LocalizedSiteTemplate) {
self.factory = factory self.factory = factory
} }

View File

@ -3,10 +3,10 @@ import Ink
struct PageContentGenerator { struct PageContentGenerator {
private let imageProcessor: ImageProcessor private let files: FileProcessor
init(imageProcessor: ImageProcessor) { init(files: FileProcessor) {
self.imageProcessor = imageProcessor self.files = files
} }
func generate(page: Page, language: String, at url: URL) throws -> String { func generate(page: Page, language: String, at url: URL) throws -> String {
@ -71,7 +71,7 @@ struct PageContentGenerator {
let size: NSSize let size: NSSize
let imagePath = page.pathRelativeToRootForContainedInputFile(file) let imagePath = page.pathRelativeToRootForContainedInputFile(file)
do { do {
size = try imageProcessor.requireImage( size = try files.requireImage(
source: imagePath, source: imagePath,
destination: imagePath, destination: imagePath,
width: pageImageWidth, width: pageImageWidth,

View File

@ -6,9 +6,9 @@ struct OverviewPageGenerator {
let outputFolder: URL let outputFolder: URL
init(factory: LocalizedSiteTemplate, imageProcessor: ImageProcessor) { init(factory: LocalizedSiteTemplate, files: FileProcessor) {
self.factory = factory self.factory = factory
self.outputFolder = imageProcessor.outputFolder self.outputFolder = files.outputFolder
} }
func generate( func generate(

View File

@ -6,15 +6,15 @@ struct OverviewSectionGenerator {
private let singleSectionsTemplate: OverviewSectionCleanTemplate private let singleSectionsTemplate: OverviewSectionCleanTemplate
let imageProcessor: ImageProcessor let files: FileProcessor
private let generator: ThumbnailListGenerator private let generator: ThumbnailListGenerator
init(factory: TemplateFactory, imageProcessor: ImageProcessor) { init(factory: TemplateFactory, files: FileProcessor) {
self.multipleSectionsTemplate = factory.overviewSection self.multipleSectionsTemplate = factory.overviewSection
self.singleSectionsTemplate = factory.overviewSectionClean self.singleSectionsTemplate = factory.overviewSectionClean
self.imageProcessor = imageProcessor self.files = files
self.generator = ThumbnailListGenerator(factory: factory, imageProcessor: imageProcessor) self.generator = ThumbnailListGenerator(factory: factory, files: files)
} }
func generate(sections: [Section], in parent: SiteElement, language: String, sectionItemCount: Int) throws -> String { func generate(sections: [Section], in parent: SiteElement, language: String, sectionItemCount: Int) throws -> String {

View File

@ -12,11 +12,11 @@ struct PageGenerator {
private let factory: LocalizedSiteTemplate private let factory: LocalizedSiteTemplate
private let imageProcessor: ImageProcessor private let files: FileProcessor
init(factory: LocalizedSiteTemplate, imageProcessor: ImageProcessor) { init(factory: LocalizedSiteTemplate, files: FileProcessor) {
self.factory = factory self.factory = factory
self.imageProcessor = imageProcessor self.files = files
} }
func generate(page: Page, language: String, backText: String, nextPage: NavigationLink?, previousPage: NavigationLink?) throws { func generate(page: Page, language: String, backText: String, nextPage: NavigationLink?, previousPage: NavigationLink?) throws {
@ -45,8 +45,8 @@ struct PageGenerator {
content[.nextPageUrl] = nextPage?.link content[.nextPageUrl] = nextPage?.link
content[.footer] = try page.customFooterContent() content[.footer] = try page.customFooterContent()
let url = imageProcessor.outputFolder.appendingPathComponent(path)
try factory.contentPage.generate(content, to: url) try factory.contentPage.generate(content, to: url)
let url = files.outputFolder.appendingPathComponent(path)
} }
private func makeContent(page: Page, language: String, url: URL) throws -> String { private func makeContent(page: Page, language: String, url: URL) throws -> String {
@ -55,7 +55,7 @@ struct PageGenerator {
return factory.placeholder return factory.placeholder
} }
print("Generated page \(page.path)") print("Generated page \(page.path)")
return try PageContentGenerator(imageProcessor: imageProcessor).generate(page: page, language: language, at: url) return try PageContentGenerator(files: files).generate(page: page, language: language, at: url)
} }
private func makeHead(page: Page, language: String) throws -> String { private func makeHead(page: Page, language: String) throws -> String {

View File

@ -30,11 +30,11 @@ struct PageHeadGenerator {
let template: PageHeadTemplate let template: PageHeadTemplate
let imageProcessor: ImageProcessor let files: FileProcessor
init(factory: TemplateFactory, imageProcessor: ImageProcessor) { init(factory: TemplateFactory, files: FileProcessor) {
self.template = factory.pageHead self.template = factory.pageHead
self.imageProcessor = imageProcessor self.files = files
} }
func generate(page: PageHeadInfoProvider) throws -> String { func generate(page: PageHeadInfoProvider) throws -> String {
@ -47,7 +47,7 @@ struct PageHeadGenerator {
// since we don't want a single large image for thumbnails. // since we don't want a single large image for thumbnails.
// Warning: Link preview source path must be relative to root // Warning: Link preview source path must be relative to root
let linkPreviewImagePath = image.insert("-link", beforeLast: ".") let linkPreviewImagePath = image.insert("-link", beforeLast: ".")
try imageProcessor.requireImage( try files.requireImage(
source: image, source: image,
destination: linkPreviewImagePath, destination: linkPreviewImagePath,
width: Site.linkPreviewDesiredImageWidth) width: Site.linkPreviewDesiredImageWidth)

View File

@ -6,17 +6,17 @@ struct SiteGenerator {
let templates: TemplateFactory let templates: TemplateFactory
private let imageProcessor: ImageProcessor private let files: FileProcessor
private var outputFolder: URL { private var outputFolder: URL {
imageProcessor.outputFolder files.outputFolder
} }
init(site: Site, imageProcessor: ImageProcessor) throws { init(site: Site, files: FileProcessor) throws {
self.site = site self.site = site
let templatesFolder = site.inputFolder.appendingPathComponent("templates") let templatesFolder = site.inputFolder.appendingPathComponent("templates")
self.templates = try TemplateFactory(templateFolder: templatesFolder) self.templates = try TemplateFactory(templateFolder: templatesFolder)
self.imageProcessor = imageProcessor self.files = files
} }
func generate() throws { func generate() throws {
@ -26,12 +26,12 @@ struct SiteGenerator {
factory: templates, factory: templates,
language: language, language: language,
site: site, site: site,
imageProcessor: imageProcessor) files: files)
// Generate sections // Generate sections
let overviewGenerator = OverviewPageGenerator(factory: template, imageProcessor: imageProcessor) let overviewGenerator = OverviewPageGenerator(factory: template, files: files)
let pageGenerator = PageGenerator(factory: template, imageProcessor: imageProcessor) let pageGenerator = PageGenerator(factory: template, files: files)
let backLinkText = try site.backLinkText(for: language) let backLinkText = try site.backLinkText(for: language)
var elementsToProcess: [(element: SiteElement, backText: String?)] = site.elements.map { ($0, backLinkText) } var elementsToProcess: [(element: SiteElement, backText: String?)] = site.elements.map { ($0, backLinkText) }
while let (element, backText) = elementsToProcess.popLast() { while let (element, backText) = elementsToProcess.popLast() {
@ -52,12 +52,14 @@ struct SiteGenerator {
backText: backText ?? metadata.defaultBackLinkText, backText: backText ?? metadata.defaultBackLinkText,
nextPage: nil, nextPage: nil,
previousPage: nil) previousPage: nil)
for file in page.metadata.requiredFiles {
let relativePath = page.path + "/" + file
files.require(file: relativePath)
}
} }
} }
let generator = IndexPageGenerator( let generator = IndexPageGenerator(factory: template)
factory: template,
imageProcessor: imageProcessor)
// Generate front page // Generate front page
let relativeUrl = site.localizedPath(for: language) let relativeUrl = site.localizedPath(for: language)

View File

@ -4,11 +4,11 @@ struct ThumbnailListGenerator {
private let factory: TemplateFactory private let factory: TemplateFactory
let imageProcessor: ImageProcessor let files: FileProcessor
init(factory: TemplateFactory, imageProcessor: ImageProcessor) { init(factory: TemplateFactory, files: FileProcessor) {
self.factory = factory self.factory = factory
self.imageProcessor = imageProcessor self.files = files
} }
func generateContent(items: [ThumbnailInfo], style: ThumbnailStyle) throws -> String { func generateContent(items: [ThumbnailInfo], style: ThumbnailStyle) throws -> String {
@ -26,7 +26,7 @@ struct ThumbnailListGenerator {
factory.largeThumbnail.makeCorner(text: $0) factory.largeThumbnail.makeCorner(text: $0)
} }
try imageProcessor.requireImage( try files.requireImage(
source: thumbnail.imageFilePath, source: thumbnail.imageFilePath,
destination: thumbnail.imageFilePath, destination: thumbnail.imageFilePath,
width: style.width, width: style.width,

View File

@ -54,7 +54,7 @@ struct LocalizedSiteTemplate {
factory.contentPage factory.contentPage
} }
init(factory: TemplateFactory, language: String, site: Site, imageProcessor: ImageProcessor) throws { init(factory: TemplateFactory, language: String, site: Site, files: FileProcessor) throws {
self.author = site.metadata.author self.author = site.metadata.author
self.factory = factory self.factory = factory
@ -92,10 +92,10 @@ struct LocalizedSiteTemplate {
topBarWebsiteTitle: title) topBarWebsiteTitle: title)
self.pageHead = PageHeadGenerator( self.pageHead = PageHeadGenerator(
factory: factory, factory: factory,
imageProcessor: imageProcessor) files: files)
self.overviewSection = OverviewSectionGenerator( self.overviewSection = OverviewSectionGenerator(
factory: factory, factory: factory,
imageProcessor: imageProcessor) files: files)
self.placeholder = factory.placeholder.generate([ self.placeholder = factory.placeholder.generate([
.title: metadata.placeholderTitle, .title: metadata.placeholderTitle,

View File

@ -2,7 +2,7 @@ import Foundation
let contentDirectory = URL(fileURLWithPath: "/Users/ch/Projects/MakerSpace") let contentDirectory = URL(fileURLWithPath: "/Users/ch/Projects/MakerSpace")
let outputDirectory = URL(fileURLWithPath: "/Users/ch/Projects/MakerSpace/Site") let outputDirectory = URL(fileURLWithPath: "/Users/ch/Projects/MakerSpace/Site")
let imageProcessor = ImageProcessor( let files = FileProcessor(
inputFolder: contentDirectory, outputFolder: outputDirectory) inputFolder: contentDirectory, outputFolder: outputDirectory)
// 1: Load all site content // 1: Load all site content
@ -10,11 +10,13 @@ guard let site = try Site(folder: contentDirectory) else {
exit(0) exit(0)
} }
// site.printContents() // site.printContents()
let siteGenerator = try SiteGenerator(site: site, imageProcessor: imageProcessor) let siteGenerator = try SiteGenerator(site: site, files: files)
try siteGenerator.generate() try siteGenerator.generate()
print("Pages generated") print("Pages generated")
try imageProcessor.createImages() try files.createImages()
print("Images generated") print("Images generated")
try files.copyRequiredFiles()
print("Required files copied")
#warning("Check that all metadata for each language is present") #warning("Check that all metadata for each language is present")