Update generation
- Move to global objects for files and validation - Only write changed files - Check images for changes before scaling - Simplify code
This commit is contained in:
@ -1,21 +1,5 @@
|
||||
import Foundation
|
||||
|
||||
private struct AboveRootDummy: SiteElement {
|
||||
var sortIndex: Int? { nil }
|
||||
|
||||
var sortDate: Date? { nil }
|
||||
|
||||
var path: String { "" }
|
||||
|
||||
let inputFolder: URL
|
||||
|
||||
func title(for language: String) -> String { "" }
|
||||
|
||||
func cornerText(for language: String) -> String? { nil }
|
||||
|
||||
var elements: [SiteElement] { [] }
|
||||
}
|
||||
|
||||
struct IndexPageGenerator {
|
||||
|
||||
private let factory: LocalizedSiteTemplate
|
||||
@ -25,41 +9,34 @@ struct IndexPageGenerator {
|
||||
}
|
||||
|
||||
func generate(
|
||||
site: Site,
|
||||
site: Element,
|
||||
language: String,
|
||||
languageButton: String?,
|
||||
sectionItemCount: Int,
|
||||
to url: URL) throws {
|
||||
to url: URL) {
|
||||
let localized = site.localized(for: language)
|
||||
|
||||
var content = [PageTemplate.Key : String]()
|
||||
content[.head] = try makeHead(site: site, language: language)
|
||||
content[.topBar] = factory.topBar.generate(section: nil, languageButton: languageButton)
|
||||
content[.head] = factory.pageHead.generate(page: site, language: language)
|
||||
content[.topBar] = factory.topBar.generate(sectionUrl: nil, languageButton: languageButton)
|
||||
content[.contentClass] = "overview"
|
||||
content[.header] = makeHeader(localized: localized)
|
||||
let sections = site.elements.compactMap { $0 as? Section }
|
||||
content[.content] = try factory.overviewSection.generate(
|
||||
sections: sections,
|
||||
content[.content] = factory.overviewSection.generate(
|
||||
sections: site.elements,
|
||||
in: site,
|
||||
language: language,
|
||||
sectionItemCount: sectionItemCount)
|
||||
content[.footer] = try site.customFooterContent()
|
||||
try factory.page.generate(content, to: url)
|
||||
content[.footer] = site.customFooterContent()
|
||||
guard factory.page.generate(content, to: url) else {
|
||||
return
|
||||
}
|
||||
log.add(info: "Generated \(url.lastPathComponent)", source: site.path)
|
||||
}
|
||||
|
||||
private func makeHead(site: Site, language: String) throws -> String {
|
||||
let localized = site.localized(for: language)
|
||||
return try factory.pageHead.generate(page: PageHeadInfo(
|
||||
author: site.metadata.author,
|
||||
linkPreviewTitle: localized.linkPreviewTitle,
|
||||
linkPreviewDescription: localized.linkPreviewDescription,
|
||||
linkPreviewImage: site.linkPreviewImage(for: language),
|
||||
customHeadContent: try site.customHeadContent()))
|
||||
}
|
||||
|
||||
private func makeHeader(localized: Site.LocalizedMetadata) -> String {
|
||||
private func makeHeader(localized: Element.LocalizedMetadata) -> String {
|
||||
var content = [HeaderKey : String]()
|
||||
content[.title] = localized.title
|
||||
#warning("Add title suffix")
|
||||
content[.subtitle] = localized.subtitle
|
||||
content[.titleText] = localized.description
|
||||
return factory.factory.centeredHeader.generate(content)
|
||||
|
@ -9,31 +9,18 @@ struct PageContentGenerator {
|
||||
|
||||
private let factory: TemplateFactory
|
||||
|
||||
private let files: FileProcessor
|
||||
|
||||
private let swift = SyntaxHighlighter(format: HTMLOutputFormat())
|
||||
|
||||
init(factory: TemplateFactory, files: FileProcessor) {
|
||||
init(factory: TemplateFactory) {
|
||||
self.factory = factory
|
||||
self.files = files
|
||||
}
|
||||
|
||||
func generate(page: Page, language: String, at url: URL) throws -> String {
|
||||
var errorToThrow: Error? = nil
|
||||
|
||||
let content = try wrap(.missingPage(page: url.path, language: language)) {
|
||||
try String(contentsOf: url)
|
||||
}
|
||||
func generate(page: Element, language: String, content: String) -> String {
|
||||
|
||||
var hasCodeContent = false
|
||||
|
||||
let imageModifier = Modifier(target: .images) { html, markdown in
|
||||
do {
|
||||
return try processMarkdownImage(markdown: markdown, html: html, page: page)
|
||||
} catch {
|
||||
errorToThrow = error
|
||||
return ""
|
||||
}
|
||||
processMarkdownImage(markdown: markdown, html: html, page: page)
|
||||
}
|
||||
let codeModifier = Modifier(target: .codeBlocks) { html, markdown in
|
||||
if markdown.starts(with: "```swift") {
|
||||
@ -51,17 +38,13 @@ struct PageContentGenerator {
|
||||
let parser = MarkdownParser(modifiers: [imageModifier, codeModifier, linkModifier])
|
||||
|
||||
if hasCodeContent {
|
||||
#warning("Automatically add hljs hightlighting if code samples are found")
|
||||
#warning("Automatically add hljs highlighting if code samples are found")
|
||||
}
|
||||
|
||||
let result = parser.html(from: content)
|
||||
if let error = errorToThrow {
|
||||
throw error
|
||||
}
|
||||
return result
|
||||
return parser.html(from: content)
|
||||
}
|
||||
|
||||
private func processMarkdownImage(markdown: Substring, html: String, page: Page) throws -> String {
|
||||
private func processMarkdownImage(markdown: Substring, html: String, page: Element) -> String {
|
||||
// Split the markdown 
|
||||
// For images: 
|
||||
// For videos: 
|
||||
@ -72,27 +55,27 @@ struct PageContentGenerator {
|
||||
let alt = markdown.between("[", and: "]").nonEmpty
|
||||
|
||||
let fileExtension = file.lastComponentAfter(".").lowercased()
|
||||
switch files.mediaType(forExtension: fileExtension) {
|
||||
switch MediaType(fileExtension: fileExtension) {
|
||||
case .image:
|
||||
return try handleImage(page: page, file: file, rightTitle: title, leftTitle: alt)
|
||||
return handleImage(page: page, file: file, rightTitle: title, leftTitle: alt)
|
||||
case .video:
|
||||
return try handleVideo(page: page, file: file, optionString: alt)
|
||||
return handleVideo(page: page, file: file, optionString: alt)
|
||||
case .file:
|
||||
if fileExtension == "svg" {
|
||||
return try handleSvg(page: page, file: file)
|
||||
return handleSvg(page: page, file: file)
|
||||
}
|
||||
return try handleFile(page: page, file: file, fileExtension: fileExtension)
|
||||
return handleFile(page: page, file: file, fileExtension: fileExtension)
|
||||
}
|
||||
}
|
||||
|
||||
private func handleImage(page: Page, file: String, rightTitle: String?, leftTitle: String?) throws -> String {
|
||||
private func handleImage(page: Element, file: String, rightTitle: String?, leftTitle: String?) -> String {
|
||||
let imagePath = page.pathRelativeToRootForContainedInputFile(file)
|
||||
|
||||
let size = try files.requireImage(source: imagePath, destination: imagePath, width: pageImageWidth)
|
||||
let size = files.requireImage(source: imagePath, destination: imagePath, width: pageImageWidth)
|
||||
|
||||
let imagePath2x = imagePath.insert("@2x", beforeLast: ".")
|
||||
let file2x = file.insert("@2x", beforeLast: ".")
|
||||
try files.requireImage(source: imagePath, destination: imagePath2x, width: 2 * pageImageWidth)
|
||||
files.requireImage(source: imagePath, destination: imagePath2x, width: 2 * pageImageWidth)
|
||||
|
||||
let content: [PageImageTemplate.Key : String] = [
|
||||
.image: file,
|
||||
@ -104,7 +87,7 @@ struct PageContentGenerator {
|
||||
return factory.image.generate(content)
|
||||
}
|
||||
|
||||
private func handleVideo(page: Page, file: String, optionString: String?) throws -> String {
|
||||
private func handleVideo(page: Element, file: String, optionString: String?) -> String {
|
||||
let options: [PageVideoTemplate.VideoOption] = optionString.unwrapped { string in
|
||||
string.components(separatedBy: " ").compactMap { optionText in
|
||||
guard let optionText = optionText.trimmed.nonEmpty else {
|
||||
@ -125,7 +108,7 @@ struct PageContentGenerator {
|
||||
return factory.video.generate(sources: sources, options: options)
|
||||
}
|
||||
|
||||
private func handleSvg(page: Page, file: String) throws -> String {
|
||||
private func handleSvg(page: Element, file: String) -> String {
|
||||
let imagePath = page.pathRelativeToRootForContainedInputFile(file)
|
||||
files.require(file: imagePath)
|
||||
|
||||
@ -136,7 +119,7 @@ struct PageContentGenerator {
|
||||
"""
|
||||
}
|
||||
|
||||
private func handleFile(page: Page, file: String, fileExtension: String) throws -> String {
|
||||
private func handleFile(page: Element, file: String, fileExtension: String) -> String {
|
||||
#warning("Handle other files in markdown")
|
||||
print("[WARN] Unhandled file \(file) with extension \(fileExtension)")
|
||||
return ""
|
||||
|
@ -4,65 +4,53 @@ struct OverviewPageGenerator {
|
||||
|
||||
private let factory: LocalizedSiteTemplate
|
||||
|
||||
let outputFolder: URL
|
||||
|
||||
init(factory: LocalizedSiteTemplate, files: FileProcessor) {
|
||||
init(factory: LocalizedSiteTemplate) {
|
||||
self.factory = factory
|
||||
self.outputFolder = files.outputFolder
|
||||
}
|
||||
|
||||
func generate(
|
||||
section: Section,
|
||||
section: Element,
|
||||
language: String,
|
||||
backText: String?) throws {
|
||||
let url = outputFolder.appendingPathComponent(section.localizedPath(for: language))
|
||||
backText: String?) {
|
||||
let path = section.localizedPath(for: language)
|
||||
let url = files.urlInOutputFolder(path)
|
||||
|
||||
let metadata = section.localized(for: language)
|
||||
|
||||
var content = [PageTemplate.Key : String]()
|
||||
content[.head] = try makeHead(section: section, language: language)
|
||||
content[.head] = factory.pageHead.generate(page: section, language: language)
|
||||
let languageButton = section.nextLanguage(for: language)
|
||||
content[.topBar] = factory.topBar.generate(
|
||||
section: section.sectionId,
|
||||
sectionUrl: section.sectionUrl(for: language),
|
||||
languageButton: languageButton)
|
||||
content[.contentClass] = "overview"
|
||||
content[.header] = makeHeader(metadata: metadata, language: language, backText: backText)
|
||||
content[.content] = try makeContent(section: section, language: language)
|
||||
content[.footer] = try section.customFooterContent()
|
||||
try factory.page.generate(content, to: url)
|
||||
content[.content] = makeContent(section: section, language: language)
|
||||
content[.footer] = section.customFooterContent()
|
||||
guard factory.page.generate(content, to: url) else {
|
||||
return
|
||||
}
|
||||
log.add(info: "Generated \(path)", source: section.path)
|
||||
}
|
||||
|
||||
private func makeContent(section: Section, language: String) throws -> String {
|
||||
private func makeContent(section: Element, language: String) -> String {
|
||||
if section.hasNestingElements {
|
||||
let sections = section.elements.compactMap { $0 as? Section }
|
||||
return try factory.overviewSection.generate(
|
||||
sections: sections,
|
||||
return factory.overviewSection.generate(
|
||||
sections: section.elements,
|
||||
in: section,
|
||||
language: language,
|
||||
sectionItemCount: section.metadata.sectionOverviewItemCount)
|
||||
sectionItemCount: section.overviewItemCount)
|
||||
} else {
|
||||
return try factory.overviewSection.generate(section: section, language: language)
|
||||
return factory.overviewSection.generate(section: section, language: language)
|
||||
}
|
||||
}
|
||||
|
||||
private func makeHead(section: Section, language: String) throws -> String {
|
||||
let localized = section.localized(for: language)
|
||||
|
||||
let image = section.linkPreviewImage(for: language)
|
||||
let info = PageHeadInfo(
|
||||
author: factory.author,
|
||||
linkPreviewTitle: localized.title,
|
||||
linkPreviewDescription: localized.linkPreviewDescription,
|
||||
linkPreviewImage: image,
|
||||
customHeadContent: try section.customHeadContent())
|
||||
return try factory.pageHead.generate(page: info)
|
||||
}
|
||||
|
||||
private func makeHeader(metadata: Section.LocalizedMetadata,
|
||||
private func makeHeader(metadata: Element.LocalizedMetadata,
|
||||
language: String,
|
||||
backText: String?) -> String {
|
||||
var content = [HeaderKey : String]()
|
||||
content[.title] = metadata.title
|
||||
#warning("Add title suffix")
|
||||
content[.subtitle] = metadata.subtitle
|
||||
content[.titleText] = metadata.description
|
||||
content[.backLink] = backText.unwrapped { factory.makeBackLink(text: $0, language: language) }
|
||||
|
@ -6,19 +6,16 @@ struct OverviewSectionGenerator {
|
||||
|
||||
private let singleSectionsTemplate: OverviewSectionCleanTemplate
|
||||
|
||||
let files: FileProcessor
|
||||
|
||||
private let generator: ThumbnailListGenerator
|
||||
|
||||
init(factory: TemplateFactory, files: FileProcessor) {
|
||||
init(factory: TemplateFactory) {
|
||||
self.multipleSectionsTemplate = factory.overviewSection
|
||||
self.singleSectionsTemplate = factory.overviewSectionClean
|
||||
self.files = files
|
||||
self.generator = ThumbnailListGenerator(factory: factory, files: files)
|
||||
self.generator = ThumbnailListGenerator(factory: factory)
|
||||
}
|
||||
|
||||
func generate(sections: [Section], in parent: SiteElement, language: String, sectionItemCount: Int) throws -> String {
|
||||
try sections.map { section in
|
||||
func generate(sections: [Element], in parent: Element, language: String, sectionItemCount: Int) -> String {
|
||||
sections.map { section in
|
||||
let metadata = section.localized(for: language)
|
||||
let fullUrl = section.fullPageUrl(for: language)
|
||||
let relativeUrl = parent.relativePathToFileWithPath(fullUrl)
|
||||
@ -26,44 +23,31 @@ struct OverviewSectionGenerator {
|
||||
var content = [OverviewSectionTemplate.Key : String]()
|
||||
content[.url] = relativeUrl
|
||||
content[.title] = metadata.title
|
||||
content[.items] = try sectionContent(section: section, in: parent, language: language, shownItemCount: sectionItemCount)
|
||||
content[.more] = metadata.moreLinkTitle
|
||||
content[.items] = sectionContent(section: section, in: parent, language: language, shownItemCount: sectionItemCount)
|
||||
content[.more] = metadata.moreLinkText
|
||||
|
||||
return multipleSectionsTemplate.generate(content)
|
||||
}
|
||||
.joined(separator: "\n")
|
||||
}
|
||||
|
||||
func generate(section: Section, language: String) throws -> String {
|
||||
func generate(section: Element, language: String) -> String {
|
||||
var content = [OverviewSectionCleanTemplate.Key : String]()
|
||||
content[.items] = try sectionContent(section: section, in: section, language: language, shownItemCount: nil)
|
||||
content[.items] = sectionContent(section: section, in: section, language: language, shownItemCount: nil)
|
||||
return singleSectionsTemplate.generate(content)
|
||||
}
|
||||
|
||||
private func sectionContent(section: Section, in parent: SiteElement, language: String, shownItemCount: Int?) throws -> String {
|
||||
let sectionItems: [SiteElement]
|
||||
private func sectionContent(section: Element, in parent: Element, language: String, shownItemCount: Int?) -> String {
|
||||
let sectionItems: [Element]
|
||||
if let shownItemCount = shownItemCount {
|
||||
sectionItems = Array(section.sortedItems.prefix(shownItemCount))
|
||||
} else {
|
||||
sectionItems = section.sortedItems
|
||||
}
|
||||
|
||||
let items: [ThumbnailInfo] = sectionItems.map { item in
|
||||
#warning("Check if page exists for the language")
|
||||
let fullPageUrl = item.fullPageUrl(for: language)
|
||||
let relativePageUrl = parent.relativePathToFileWithPath(fullPageUrl)
|
||||
let fullThumbnailPath = item.thumbnailFilePath(for: language)
|
||||
let relativeImageUrl = parent.relativePathToFileWithPath(fullThumbnailPath)
|
||||
|
||||
return ThumbnailInfo(
|
||||
url: relativePageUrl,
|
||||
imageFilePath: fullThumbnailPath,
|
||||
imageHtmlUrl: relativeImageUrl,
|
||||
title: item.title(for: language),
|
||||
cornerText: item.cornerText(for: language))
|
||||
}
|
||||
return try generator.generateContent(
|
||||
items: items,
|
||||
style: section.metadata.thumbnailStyle)
|
||||
return generator.generateContent(
|
||||
items: sectionItems,
|
||||
parent: parent,
|
||||
language: language,
|
||||
style: section.thumbnailStyle)
|
||||
}
|
||||
}
|
||||
|
@ -12,70 +12,67 @@ struct PageGenerator {
|
||||
|
||||
private let factory: LocalizedSiteTemplate
|
||||
|
||||
private let files: FileProcessor
|
||||
|
||||
init(factory: LocalizedSiteTemplate, files: FileProcessor) {
|
||||
init(factory: LocalizedSiteTemplate) {
|
||||
self.factory = factory
|
||||
self.files = files
|
||||
}
|
||||
|
||||
func generate(page: Page, language: String, backText: String, nextPage: NavigationLink?, previousPage: NavigationLink?) throws {
|
||||
func generate(page: Element, language: String, backText: String, nextPage: NavigationLink?, previousPage: NavigationLink?) {
|
||||
guard !page.isExternalPage else {
|
||||
return
|
||||
}
|
||||
guard !page.metadata.isDraft else {
|
||||
guard page.state != .draft else {
|
||||
return
|
||||
}
|
||||
let path = page.fullPageUrl(for: language)
|
||||
let inputContentUrl = page.inputFolder.appendingPathComponent("\(language).md")
|
||||
let inputContentPath = page.path + "/\(language).md"
|
||||
#warning("Make prev and next navigation relative")
|
||||
let metadata = page.localized(for: language)
|
||||
let nextLanguage = page.nextLanguage(for: language)
|
||||
var content = [PageTemplate.Key : String]()
|
||||
content[.head] = try makeHead(page: page, language: language)
|
||||
content[.topBar] = factory.topBar.generate(section: page.sectionId, languageButton: nextLanguage)
|
||||
content[.head] = factory.pageHead.generate(page: page, language: language)
|
||||
let sectionUrl = page.sectionUrl(for: language)
|
||||
content[.topBar] = factory.topBar.generate(sectionUrl: sectionUrl, languageButton: nextLanguage)
|
||||
content[.contentClass] = "content"
|
||||
if !page.metadata.useCustomHeader {
|
||||
content[.header] = makeHeader(page: page.metadata, metadata: metadata, language: language, backText: backText)
|
||||
if !page.useCustomHeader {
|
||||
content[.header] = makeHeader(page: page, metadata: metadata, language: language, backText: backText)
|
||||
}
|
||||
content[.content] = try makeContent(page: page, language: language, url: inputContentUrl)
|
||||
let pageContent = makeContent(page: page, language: language, path: inputContentPath)
|
||||
content[.content] = pageContent ?? factory.placeholder
|
||||
content[.previousPageLinkText] = previousPage.unwrapped { factory.makePrevText($0.text) }
|
||||
content[.previousPageUrl] = previousPage?.link
|
||||
content[.nextPageLinkText] = nextPage.unwrapped { factory.makeNextText($0.text) }
|
||||
content[.nextPageUrl] = nextPage?.link
|
||||
content[.footer] = try page.customFooterContent()
|
||||
content[.footer] = page.customFooterContent()
|
||||
|
||||
let url = files.outputFolder.appendingPathComponent(path)
|
||||
try factory.page.generate(content, to: url)
|
||||
}
|
||||
|
||||
private func makeContent(page: Page, language: String, url: URL) throws -> String {
|
||||
guard url.exists else {
|
||||
print("Generated empty page \(page.path)")
|
||||
return factory.placeholder
|
||||
let url = files.urlInOutputFolder(path)
|
||||
guard factory.page.generate(content, to: url) else {
|
||||
return
|
||||
}
|
||||
print("Generated page \(page.path)")
|
||||
return try PageContentGenerator(factory: factory.factory, files: files)
|
||||
.generate(page: page, language: language, at: url)
|
||||
log.add(info: "Generated \(pageContent == nil ? "empty page " : "")\(path)", source: page.path)
|
||||
}
|
||||
|
||||
private func makeHead(page: Page, language: String) throws -> String {
|
||||
let metadata = page.localized(for: language)
|
||||
let info = PageHeadInfo(
|
||||
author: page.metadata.author ?? factory.author,
|
||||
linkPreviewTitle: metadata.linkPreviewTitle,
|
||||
linkPreviewDescription: metadata.linkPreviewDescription,
|
||||
linkPreviewImage: page.linkPreviewImage(for: language),
|
||||
customHeadContent: try page.customHeadContent())
|
||||
return try factory.pageHead.generate(page: info)
|
||||
private func makeContent(page: Element, language: String, path: String) -> String? {
|
||||
guard let content = files.contentOfOptionalFile(atPath: path, source: page.path) else {
|
||||
return nil
|
||||
}
|
||||
return PageContentGenerator(factory: factory.factory)
|
||||
.generate(page: page, language: language, content: content)
|
||||
}
|
||||
|
||||
private func makeHeader(page: Page.Metadata, metadata: Page.LocalizedMetadata, language: String, backText: String) -> String {
|
||||
private func makeHeader(page: Element, metadata: Element.LocalizedMetadata, language: String, backText: String) -> String {
|
||||
var content = [HeaderKey : String]()
|
||||
content[.backLink] = factory.makeBackLink(text: backText, language: language)
|
||||
content[.title] = metadata.title
|
||||
if let suffix = metadata.titleSuffix {
|
||||
content[.title] = make(title: metadata.title, suffix: suffix)
|
||||
} else {
|
||||
content[.title] = metadata.title
|
||||
}
|
||||
content[.subtitle] = metadata.subtitle
|
||||
content[.date] = factory.makeDateString(start: page.date, end: page.endDate)
|
||||
return factory.factory.leftHeader.generate(content)
|
||||
}
|
||||
|
||||
private func make(title: String, suffix: String) -> String {
|
||||
"\(title)<span class=\"suffix\">\(suffix)</span>"
|
||||
}
|
||||
}
|
||||
|
@ -1,60 +1,36 @@
|
||||
import Foundation
|
||||
|
||||
protocol PageHeadInfoProvider {
|
||||
|
||||
var author: String { get }
|
||||
|
||||
var linkPreviewTitle: String { get }
|
||||
|
||||
var linkPreviewDescription: String { get }
|
||||
|
||||
var linkPreviewImage: String? { get }
|
||||
|
||||
var customHeadContent: String? { get }
|
||||
}
|
||||
|
||||
struct PageHeadInfo: PageHeadInfoProvider {
|
||||
|
||||
let author: String
|
||||
|
||||
let linkPreviewTitle: String
|
||||
|
||||
let linkPreviewDescription: String
|
||||
|
||||
let linkPreviewImage: String?
|
||||
|
||||
let customHeadContent: String?
|
||||
}
|
||||
|
||||
struct PageHeadGenerator {
|
||||
|
||||
static let linkPreviewDesiredImageWidth = 1600
|
||||
|
||||
let template: PageHeadTemplate
|
||||
|
||||
let files: FileProcessor
|
||||
|
||||
init(factory: TemplateFactory, files: FileProcessor) {
|
||||
init(factory: TemplateFactory) {
|
||||
self.template = factory.pageHead
|
||||
self.files = files
|
||||
}
|
||||
|
||||
func generate(page: PageHeadInfoProvider) throws -> String {
|
||||
func generate(page: Element, language: String) -> String {
|
||||
let metadata = page.localized(for: language)
|
||||
|
||||
var content = [PageHeadTemplate.Key : String]()
|
||||
content[.author] = page.author
|
||||
content[.title] = page.linkPreviewTitle
|
||||
content[.description] = page.linkPreviewDescription
|
||||
if let image = page.linkPreviewImage {
|
||||
content[.title] = metadata.linkPreviewTitle
|
||||
content[.description] = metadata.linkPreviewDescription
|
||||
if let image = page.linkPreviewImage(for: language) {
|
||||
// Note: Generate separate destination link for the image,
|
||||
// since we don't want a single large image for thumbnails.
|
||||
// Warning: Link preview source path must be relative to root
|
||||
let linkPreviewImagePath = image.insert("-link", beforeLast: ".")
|
||||
try files.requireImage(
|
||||
source: image,
|
||||
destination: linkPreviewImagePath,
|
||||
width: Site.linkPreviewDesiredImageWidth)
|
||||
#warning("Make link preview image path absolute")
|
||||
content[.image] = "<meta property=\"og:image\" content=\"\(linkPreviewImagePath)\" />"
|
||||
let linkPreviewImageName = image.insert("-link", beforeLast: ".")
|
||||
let sourceImagePath = page.pathRelativeToRootForContainedInputFile(image)
|
||||
let destinationImagePath = page.pathRelativeToRootForContainedInputFile(linkPreviewImageName)
|
||||
files.requireImage(
|
||||
source: sourceImagePath,
|
||||
destination: destinationImagePath,
|
||||
width: PageHeadGenerator.linkPreviewDesiredImageWidth)
|
||||
content[.image] = "<meta property=\"og:image\" content=\"\(linkPreviewImageName)\" />"
|
||||
}
|
||||
content[.customPageContent] = page.customHeadContent
|
||||
content[.customPageContent] = page.customHeadContent()
|
||||
|
||||
return template.generate(content)
|
||||
}
|
||||
|
@ -2,60 +2,45 @@ import Foundation
|
||||
|
||||
struct SiteGenerator {
|
||||
|
||||
let site: Site
|
||||
|
||||
let templates: TemplateFactory
|
||||
|
||||
private let files: FileProcessor
|
||||
|
||||
private var outputFolder: URL {
|
||||
files.outputFolder
|
||||
}
|
||||
|
||||
init(site: Site, files: FileProcessor) throws {
|
||||
self.site = site
|
||||
let templatesFolder = site.inputFolder.appendingPathComponent("templates")
|
||||
init() throws {
|
||||
let templatesFolder = files.urlInContentFolder("templates")
|
||||
self.templates = try TemplateFactory(templateFolder: templatesFolder)
|
||||
self.files = files
|
||||
}
|
||||
|
||||
func generate() throws {
|
||||
try site.metadata.languages.forEach { metadata in
|
||||
let language = metadata.languageIdentifier
|
||||
func generate(site: Element) throws {
|
||||
try site.languages.forEach { metadata in
|
||||
let language = metadata.language
|
||||
let template = try LocalizedSiteTemplate(
|
||||
factory: templates,
|
||||
language: language,
|
||||
site: site,
|
||||
files: files)
|
||||
site: site)
|
||||
|
||||
|
||||
// Generate sections
|
||||
let overviewGenerator = OverviewPageGenerator(factory: template, files: files)
|
||||
let pageGenerator = PageGenerator(factory: template, files: files)
|
||||
let backLinkText = try site.backLinkText(for: language)
|
||||
var elementsToProcess: [(element: SiteElement, backText: String?)] = site.elements.map { ($0, backLinkText) }
|
||||
while let (element, backText) = elementsToProcess.popLast() {
|
||||
if let section = element as? Section {
|
||||
try overviewGenerator.generate(
|
||||
section: section,
|
||||
let overviewGenerator = OverviewPageGenerator(factory: template)
|
||||
let pageGenerator = PageGenerator(factory: template)
|
||||
var elementsToProcess: [Element] = site.elements
|
||||
while let element = elementsToProcess.popLast() {
|
||||
// Move recursively down to all pages
|
||||
elementsToProcess.append(contentsOf: element.elements)
|
||||
|
||||
element.requiredFiles.forEach(files.require)
|
||||
|
||||
let backLinkText = element.backLinkText(for: language)
|
||||
if !element.elements.isEmpty {
|
||||
overviewGenerator.generate(
|
||||
section: element,
|
||||
language: language,
|
||||
backText: backText)
|
||||
let elementBackText = try element.backLinkText(for: language)
|
||||
let nestedElements = section.elements.map { ($0, elementBackText) }
|
||||
elementsToProcess.append(contentsOf: nestedElements)
|
||||
}
|
||||
if let page = element as? Page {
|
||||
backText: backLinkText)
|
||||
} else {
|
||||
#warning("Determine previous and next pages")
|
||||
try pageGenerator.generate(
|
||||
page: page,
|
||||
pageGenerator.generate(
|
||||
page: element,
|
||||
language: language,
|
||||
backText: backText ?? metadata.defaultBackLinkText,
|
||||
backText: backLinkText,
|
||||
nextPage: nil,
|
||||
previousPage: nil)
|
||||
for file in page.metadata.requiredFiles {
|
||||
let relativePath = page.path + "/" + file
|
||||
files.require(file: relativePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,9 +48,9 @@ struct SiteGenerator {
|
||||
|
||||
// Generate front page
|
||||
let relativeUrl = site.localizedPath(for: language)
|
||||
let indexPageUrl = outputFolder.appendingPathComponent(relativeUrl)
|
||||
let indexPageUrl = files.urlInOutputFolder(relativeUrl)
|
||||
let button = site.nextLanguage(for: language)
|
||||
try generator.generate(
|
||||
generator.generate(
|
||||
site: site,
|
||||
language: language,
|
||||
languageButton: button,
|
||||
|
@ -1,14 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
struct ThumbnailInfo {
|
||||
|
||||
let url: String?
|
||||
|
||||
let imageFilePath: String
|
||||
|
||||
let imageHtmlUrl: String
|
||||
|
||||
let title: String
|
||||
|
||||
let cornerText: String?
|
||||
}
|
@ -4,35 +4,37 @@ struct ThumbnailListGenerator {
|
||||
|
||||
private let factory: TemplateFactory
|
||||
|
||||
let files: FileProcessor
|
||||
|
||||
init(factory: TemplateFactory, files: FileProcessor) {
|
||||
init(factory: TemplateFactory) {
|
||||
self.factory = factory
|
||||
self.files = files
|
||||
}
|
||||
|
||||
func generateContent(items: [ThumbnailInfo], style: ThumbnailStyle) throws -> String {
|
||||
try items.map { try itemContent($0, style: style) }
|
||||
func generateContent(items: [Element], parent: Element, language: String, style: ThumbnailStyle) -> String {
|
||||
items.map { itemContent($0, parent: parent, language: language, style: style) }
|
||||
.joined(separator: "\n")
|
||||
}
|
||||
|
||||
private func itemContent(_ thumbnail: ThumbnailInfo, style: ThumbnailStyle) throws -> String {
|
||||
private func itemContent(_ item: Element, parent: Element, language: String, style: ThumbnailStyle) -> String {
|
||||
let fullPageUrl = item.fullPageUrl(for: language)
|
||||
let relativePageUrl = parent.relativePathToFileWithPath(fullPageUrl)
|
||||
let fullThumbnailPath = item.thumbnailFilePath(for: language)
|
||||
let relativeImageUrl = parent.relativePathToFileWithPath(fullThumbnailPath)
|
||||
|
||||
var content = [ThumbnailKey : String]()
|
||||
content[.url] = thumbnail.url.unwrapped { "href=\"\($0)\"" }
|
||||
content[.image] = thumbnail.imageHtmlUrl
|
||||
content[.title] = thumbnail.title
|
||||
content[.image2x] = thumbnail.imageHtmlUrl.insert("@2x", beforeLast: ".")
|
||||
content[.corner] = thumbnail.cornerText.unwrapped {
|
||||
content[.url] = "href=\"\(relativePageUrl)\""
|
||||
content[.image] = relativeImageUrl
|
||||
content[.title] = item.title(for: language)
|
||||
#warning("Generate thumbnail suffix")
|
||||
content[.image2x] = relativeImageUrl.insert("@2x", beforeLast: ".")
|
||||
content[.corner] = item.cornerText(for: language).unwrapped {
|
||||
factory.largeThumbnail.makeCorner(text: $0)
|
||||
}
|
||||
|
||||
try files.requireImage(
|
||||
source: thumbnail.imageFilePath,
|
||||
destination: thumbnail.imageFilePath,
|
||||
files.requireImage(
|
||||
source: fullThumbnailPath,
|
||||
destination: fullThumbnailPath,
|
||||
width: style.width,
|
||||
desiredHeight: style.height,
|
||||
createDoubleVersion: true)
|
||||
|
||||
return try factory.thumbnail(style: style).generate(content, shouldIndent: false)
|
||||
return factory.thumbnail(style: style).generate(content, shouldIndent: false)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user