Convert Xcode project to swift package
This commit is contained in:
113
Sources/Generator/Generators/HTMLElementsGenerator.swift
Normal file
113
Sources/Generator/Generators/HTMLElementsGenerator.swift
Normal file
@@ -0,0 +1,113 @@
|
||||
import Foundation
|
||||
|
||||
struct HTMLElementsGenerator {
|
||||
|
||||
init() {
|
||||
|
||||
}
|
||||
|
||||
func make(title: String, suffix: String) -> String {
|
||||
"\(title)<span class=\"suffix\">\(suffix)</span>"
|
||||
}
|
||||
|
||||
func topBarWebsiteTitle(language: String, from page: Element) -> String {
|
||||
guard let pathToRoot = page.pathToRoot else {
|
||||
return Element.htmlPageName(for: language)
|
||||
}
|
||||
return pathToRoot + Element.htmlPagePathAddition(for: language)
|
||||
}
|
||||
|
||||
func topBarLanguageButton(_ language: String) -> String {
|
||||
"<a href=\"\(Element.htmlPageName(for: language))\">\(language)</a>"
|
||||
}
|
||||
|
||||
func topBarNavigationLink(url: String, text: String, isActive: Bool) -> String {
|
||||
"<a\(isActive ? " class=\"active\"" : "") href=\"/\(url)\">\(text)</a>"
|
||||
}
|
||||
|
||||
func linkPreviewImage(file: String) -> String {
|
||||
"<meta property=\"og:image\" content=\"\(file)\" />"
|
||||
}
|
||||
|
||||
func makePrevText(_ text: String) -> String {
|
||||
"<span class=\"icon-back\"></span>\(text)"
|
||||
}
|
||||
|
||||
func makeNextText(_ text: String) -> String {
|
||||
"\(text)<span class=\"icon-next\"></span>"
|
||||
}
|
||||
|
||||
func svgImage(file: String) -> String {
|
||||
"""
|
||||
<span class="image">
|
||||
<img src="\(file)"/>
|
||||
</span>
|
||||
"""
|
||||
}
|
||||
|
||||
func svgImage(file: String, x: Int, y: Int, width: Int, height: Int) -> String {
|
||||
"""
|
||||
<span class="image">
|
||||
<img src="\(file)#svgView(viewBox(\(x), \(y), \(width), \(height)))" style="aspect-ratio:\(Float(width)/Float(height))"/>
|
||||
</span>
|
||||
"""
|
||||
}
|
||||
|
||||
func downloadButtons(_ buttons: [(file: String, text: String, downloadName: String?)]) -> String {
|
||||
let content = buttons.map {
|
||||
if let download = $0.downloadName {
|
||||
return button(file: $0.file, text: $0.text, downloadName: download)
|
||||
} else {
|
||||
return button(file: $0.file, text: $0.text)
|
||||
}
|
||||
}.joined(separator: "\n")
|
||||
return flexParagraph(content)
|
||||
}
|
||||
|
||||
func externalButtons(_ buttons: [(url: String, text: String)]) -> String {
|
||||
let content = buttons
|
||||
.map { externalLink(url: $0.url, text: $0.text) }
|
||||
.joined(separator: "\n")
|
||||
return flexParagraph(content)
|
||||
}
|
||||
|
||||
private func flexParagraph(_ content: String) -> String {
|
||||
"""
|
||||
<p style="display: flex">
|
||||
\(content)
|
||||
</p>
|
||||
"""
|
||||
}
|
||||
|
||||
private func button(file: String, text: String) -> String {
|
||||
"""
|
||||
<a class="download-button" href="\(file)">
|
||||
\(text)<span class="icon icon-download"></span>
|
||||
</a>
|
||||
"""
|
||||
}
|
||||
|
||||
private func button(file: String, text: String, downloadName: String) -> String {
|
||||
"""
|
||||
<a class="download-button" href="\(file)" download="\(downloadName)">
|
||||
\(text)<span class="icon icon-download"></span>
|
||||
</a>
|
||||
"""
|
||||
}
|
||||
|
||||
private func externalLink(url: String, text: String) -> String {
|
||||
"""
|
||||
<a class="download-button" href="\(url)">
|
||||
\(text)<span class="icon icon-download icon-rotate"></span>
|
||||
</a>
|
||||
"""
|
||||
}
|
||||
|
||||
func scriptInclude(path: String) -> String {
|
||||
"<script src=\"\(path)\"></script>"
|
||||
}
|
||||
|
||||
func codeHighlightFooter() -> String {
|
||||
"<script>hljs.highlightAll();</script>"
|
||||
}
|
||||
}
|
226
Sources/Generator/Generators/MarkdownProcessor.swift
Normal file
226
Sources/Generator/Generators/MarkdownProcessor.swift
Normal file
@@ -0,0 +1,226 @@
|
||||
import Foundation
|
||||
import Ink
|
||||
import Splash
|
||||
|
||||
struct PageContentGenerator {
|
||||
|
||||
private let factory: TemplateFactory
|
||||
|
||||
private let swift = SyntaxHighlighter(format: HTMLOutputFormat())
|
||||
|
||||
init(factory: TemplateFactory) {
|
||||
self.factory = factory
|
||||
}
|
||||
|
||||
func generate(page: Element, language: String, content: String) -> (content: String, includesCode: Bool) {
|
||||
var hasCodeContent = false
|
||||
|
||||
let imageModifier = Modifier(target: .images) { html, markdown in
|
||||
processMarkdownImage(markdown: markdown, html: html, page: page)
|
||||
}
|
||||
let codeModifier = Modifier(target: .codeBlocks) { html, markdown in
|
||||
if markdown.starts(with: "```swift") {
|
||||
let code = markdown.between("```swift", and: "```").trimmed
|
||||
return "<pre><code>" + swift.highlight(code) + "</pre></code>"
|
||||
}
|
||||
hasCodeContent = true
|
||||
return html
|
||||
}
|
||||
let linkModifier = Modifier(target: .links) { html, markdown in
|
||||
handleLink(page: page, language: language, html: html, markdown: markdown)
|
||||
}
|
||||
let htmlModifier = Modifier(target: .html) { html, markdown in
|
||||
handleHTML(page: page, language: language, html: html, markdown: markdown)
|
||||
}
|
||||
|
||||
let parser = MarkdownParser(modifiers: [imageModifier, codeModifier, linkModifier, htmlModifier])
|
||||
return (parser.html(from: content), hasCodeContent)
|
||||
}
|
||||
|
||||
private func handleLink(page: Element, language: String, html: String, markdown: Substring) -> String {
|
||||
let file = markdown.between("(", and: ")")
|
||||
if file.hasPrefix("page:") {
|
||||
let pageId = file.replacingOccurrences(of: "page:", with: "")
|
||||
guard let pagePath = files.getPage(for: pageId) else {
|
||||
log.add(warning: "Page id '\(pageId)' not found", source: page.path)
|
||||
// Remove link since the page can't be found
|
||||
return markdown.between("[", and: "]")
|
||||
}
|
||||
let fullPath = pagePath + Element.htmlPagePathAddition(for: language)
|
||||
// Adjust file path to get the page url
|
||||
let url = page.relativePathToOtherSiteElement(file: fullPath)
|
||||
return html.replacingOccurrences(of: file, with: url)
|
||||
}
|
||||
|
||||
if let filePath = page.nonAbsolutePathRelativeToRootForContainedInputFile(file) {
|
||||
// The target of the page link must be present after generation is complete
|
||||
files.expect(file: filePath, source: page.path)
|
||||
}
|
||||
return html
|
||||
}
|
||||
|
||||
private func handleHTML(page: Element, language: String, html: String, markdown: Substring) -> String {
|
||||
#warning("Check HTML code in markdown for required resources")
|
||||
//print("[HTML] Found in page \(page.path):")
|
||||
//print(markdown)
|
||||
// Things to check:
|
||||
// <img src=
|
||||
// <a href=
|
||||
//
|
||||
return html
|
||||
}
|
||||
|
||||
private func processMarkdownImage(markdown: Substring, html: String, page: Element) -> String {
|
||||
// Split the markdown 
|
||||
// For images: 
|
||||
// For videos: 
|
||||
// For svg with custom area: 
|
||||
// For downloads: 
|
||||
// External pages: 
|
||||
let fileAndTitle = markdown.between("(", and: ")")
|
||||
let alt = markdown.between("[", and: "]").nonEmpty
|
||||
switch alt {
|
||||
case "download":
|
||||
return handleDownloadButtons(page: page, content: fileAndTitle)
|
||||
case "external":
|
||||
return handleExternalButtons(page: page, content: fileAndTitle)
|
||||
case "html":
|
||||
return handleExternalHTML(page: page, file: fileAndTitle)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
let file = fileAndTitle.dropAfterFirst(" ")
|
||||
let title = fileAndTitle.contains(" ") ? fileAndTitle.dropBeforeFirst(" ").nonEmpty : nil
|
||||
|
||||
let fileExtension = file.lastComponentAfter(".").lowercased()
|
||||
if let _ = ImageType(fileExtension: fileExtension) {
|
||||
return handleImage(page: page, file: file, rightTitle: title, leftTitle: alt)
|
||||
}
|
||||
if let _ = VideoType(rawValue: fileExtension) {
|
||||
return handleVideo(page: page, file: file, optionString: alt)
|
||||
}
|
||||
if fileExtension == "svg" {
|
||||
return handleSvg(page: page, file: file, area: alt)
|
||||
}
|
||||
return handleFile(page: page, file: file, fileExtension: fileExtension)
|
||||
}
|
||||
|
||||
private func handleImage(page: Element, file: String, rightTitle: String?, leftTitle: String?) -> String {
|
||||
let imagePath = page.pathRelativeToRootForContainedInputFile(file)
|
||||
|
||||
let size = files.requireImage(source: imagePath, destination: imagePath, width: configuration.pageImageWidth)
|
||||
|
||||
let imagePath2x = imagePath.insert("@2x", beforeLast: ".")
|
||||
let file2x = file.insert("@2x", beforeLast: ".")
|
||||
files.requireImage(source: imagePath, destination: imagePath2x, width: 2 * configuration.pageImageWidth)
|
||||
|
||||
let content: [PageImageTemplate.Key : String] = [
|
||||
.image: file,
|
||||
.image2x: file2x,
|
||||
.width: "\(Int(size.width))",
|
||||
.height: "\(Int(size.height))",
|
||||
.leftText: leftTitle ?? "",
|
||||
.rightText: rightTitle ?? ""]
|
||||
return factory.image.generate(content)
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
guard let option = PageVideoTemplate.VideoOption(rawValue: optionText) else {
|
||||
log.add(warning: "Unknown video option \(optionText)", source: page.path)
|
||||
return nil
|
||||
}
|
||||
return option
|
||||
}
|
||||
} ?? []
|
||||
#warning("Check page folder for alternative video versions")
|
||||
let sources: [PageVideoTemplate.VideoSource] = [(url: file, type: .mp4)]
|
||||
|
||||
let filePath = page.pathRelativeToRootForContainedInputFile(file)
|
||||
files.require(file: filePath)
|
||||
return factory.video.generate(sources: sources, options: options)
|
||||
}
|
||||
|
||||
private func handleSvg(page: Element, file: String, area: String?) -> String {
|
||||
let imagePath = page.pathRelativeToRootForContainedInputFile(file)
|
||||
files.require(file: imagePath)
|
||||
|
||||
guard let area = area else {
|
||||
return factory.html.svgImage(file: file)
|
||||
}
|
||||
let parts = area.components(separatedBy: ",").map { $0.trimmed }
|
||||
guard parts.count == 4,
|
||||
let x = Int(parts[0].trimmed),
|
||||
let y = Int(parts[1].trimmed),
|
||||
let width = Int(parts[2].trimmed),
|
||||
let height = Int(parts[3].trimmed) else {
|
||||
log.add(warning: "Invalid area string for svg image", source: page.path)
|
||||
return factory.html.svgImage(file: file)
|
||||
}
|
||||
|
||||
return factory.html.svgImage(file: file, x: x, y: y, width: width, height: height)
|
||||
}
|
||||
|
||||
private func handleFile(page: Element, file: String, fileExtension: String) -> String {
|
||||
log.add(warning: "Unhandled file \(file) with extension \(fileExtension)", source: page.path)
|
||||
return ""
|
||||
}
|
||||
|
||||
private func handleDownloadButtons(page: Element, content: String) -> String {
|
||||
let buttons = content
|
||||
.components(separatedBy: ";")
|
||||
.compactMap { button -> (file: String, text: String, downloadName: String?)? in
|
||||
let parts = button.components(separatedBy: ",")
|
||||
guard parts.count == 2 || parts.count == 3 else {
|
||||
log.add(warning: "Invalid button definition", source: page.path)
|
||||
return nil
|
||||
}
|
||||
let file = parts[0].trimmed
|
||||
let title = parts[1].trimmed
|
||||
let downloadName = parts.count == 3 ? parts[2].trimmed : nil
|
||||
|
||||
// Ensure that file is available
|
||||
let filePath = page.pathRelativeToRootForContainedInputFile(file)
|
||||
files.require(file: filePath)
|
||||
|
||||
return (file, title, downloadName)
|
||||
}
|
||||
return factory.html.downloadButtons(buttons)
|
||||
}
|
||||
|
||||
private func handleExternalButtons(page: Element, content: String) -> String {
|
||||
let buttons = content
|
||||
.components(separatedBy: ";")
|
||||
.compactMap { button -> (url: String, text: String)? in
|
||||
let parts = button.components(separatedBy: ",")
|
||||
guard parts.count == 2 else {
|
||||
log.add(warning: "Invalid external link definition", source: page.path)
|
||||
return nil
|
||||
}
|
||||
let url = parts[0].trimmed
|
||||
let title = parts[1].trimmed
|
||||
|
||||
return (url, title)
|
||||
}
|
||||
return factory.html.externalButtons(buttons)
|
||||
}
|
||||
|
||||
private func handleExternalHTML(page: Element, file: String) -> String {
|
||||
let url = page.inputFolder.appendingPathComponent(file)
|
||||
guard url.exists else {
|
||||
log.add(error: "File \(file) not found", source: page.path)
|
||||
return ""
|
||||
}
|
||||
do {
|
||||
return try String(contentsOf: url)
|
||||
} catch {
|
||||
log.add(error: "File \(file) could not be read", source: page.path, error: error)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
52
Sources/Generator/Generators/OverviewPageGenerator.swift
Normal file
52
Sources/Generator/Generators/OverviewPageGenerator.swift
Normal file
@@ -0,0 +1,52 @@
|
||||
import Foundation
|
||||
|
||||
struct OverviewPageGenerator {
|
||||
|
||||
private let factory: LocalizedSiteTemplate
|
||||
|
||||
init(factory: LocalizedSiteTemplate) {
|
||||
self.factory = factory
|
||||
}
|
||||
|
||||
func generate(
|
||||
section: Element,
|
||||
language: 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] = factory.pageHead.generate(page: section, language: language)
|
||||
let languageButton = section.nextLanguage(for: language)
|
||||
content[.topBar] = factory.topBar.generate(
|
||||
sectionUrl: section.sectionUrl(for: language),
|
||||
languageButton: languageButton,
|
||||
page: section)
|
||||
content[.contentClass] = "overview"
|
||||
content[.header] = makeHeader(page: section, metadata: metadata, language: language)
|
||||
content[.content] = makeContent(section: section, language: language)
|
||||
content[.footer] = section.customFooterContent()
|
||||
guard factory.page.generate(content, to: url) else {
|
||||
return
|
||||
}
|
||||
files.generated(page: path)
|
||||
}
|
||||
|
||||
private func makeContent(section: Element, language: String) -> String {
|
||||
if section.hasNestingElements {
|
||||
return factory.overviewSection.generate(
|
||||
sections: section.sortedItems,
|
||||
in: section,
|
||||
language: language,
|
||||
sectionItemCount: section.overviewItemCount)
|
||||
} else {
|
||||
return factory.overviewSection.generate(section: section, language: language)
|
||||
}
|
||||
}
|
||||
|
||||
private func makeHeader(page: Element, metadata: Element.LocalizedMetadata, language: String) -> String {
|
||||
let content = factory.makeHeaderContent(page: page, metadata: metadata, language: language)
|
||||
return factory.factory.centeredHeader.generate(content)
|
||||
}
|
||||
}
|
47
Sources/Generator/Generators/OverviewSectionGenerator.swift
Normal file
47
Sources/Generator/Generators/OverviewSectionGenerator.swift
Normal file
@@ -0,0 +1,47 @@
|
||||
import Foundation
|
||||
|
||||
struct OverviewSectionGenerator {
|
||||
|
||||
private let multipleSectionsTemplate: OverviewSectionTemplate
|
||||
|
||||
private let singleSectionsTemplate: OverviewSectionCleanTemplate
|
||||
|
||||
private let generator: ThumbnailListGenerator
|
||||
|
||||
init(factory: TemplateFactory) {
|
||||
self.multipleSectionsTemplate = factory.overviewSection
|
||||
self.singleSectionsTemplate = factory.overviewSectionClean
|
||||
self.generator = ThumbnailListGenerator(factory: factory)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
var content = [OverviewSectionTemplate.Key : String]()
|
||||
content[.url] = relativeUrl
|
||||
content[.title] = metadata.title
|
||||
content[.items] = generator.generateContent(
|
||||
items: section.itemsForOverview(sectionItemCount),
|
||||
parent: parent,
|
||||
language: language,
|
||||
style: section.thumbnailStyle)
|
||||
content[.more] = metadata.moreLinkText
|
||||
|
||||
return multipleSectionsTemplate.generate(content)
|
||||
}
|
||||
.joined(separator: "\n")
|
||||
}
|
||||
|
||||
func generate(section: Element, language: String) -> String {
|
||||
var content = [OverviewSectionCleanTemplate.Key : String]()
|
||||
content[.items] = generator.generateContent(
|
||||
items: section.itemsForOverview(),
|
||||
parent: section,
|
||||
language: language,
|
||||
style: section.thumbnailStyle)
|
||||
return singleSectionsTemplate.generate(content)
|
||||
}
|
||||
}
|
88
Sources/Generator/Generators/PageGenerator.swift
Normal file
88
Sources/Generator/Generators/PageGenerator.swift
Normal file
@@ -0,0 +1,88 @@
|
||||
import Foundation
|
||||
import Ink
|
||||
|
||||
struct PageGenerator {
|
||||
|
||||
struct NavigationLink {
|
||||
|
||||
let link: String
|
||||
|
||||
let text: String
|
||||
}
|
||||
|
||||
private let factory: LocalizedSiteTemplate
|
||||
|
||||
init(factory: LocalizedSiteTemplate) {
|
||||
self.factory = factory
|
||||
}
|
||||
|
||||
func generate(page: Element, language: String, nextPage: NavigationLink?, previousPage: NavigationLink?) {
|
||||
guard !page.isExternalPage else {
|
||||
return
|
||||
}
|
||||
let path = page.fullPageUrl(for: language)
|
||||
let inputContentPath = page.path + "/\(language).md"
|
||||
let metadata = page.localized(for: language)
|
||||
let nextLanguage = page.nextLanguage(for: language)
|
||||
let (pageContent, pageIncludesCode, pageIsEmpty) = makeContent(
|
||||
page: page, metadata: metadata, language: language, path: inputContentPath)
|
||||
|
||||
var content = [PageTemplate.Key : String]()
|
||||
content[.head] = factory.pageHead.generate(page: page, language: language, includesCode: pageIncludesCode)
|
||||
let sectionUrl = page.sectionUrl(for: language)
|
||||
content[.topBar] = factory.topBar.generate(sectionUrl: sectionUrl, languageButton: nextLanguage, page: page)
|
||||
content[.contentClass] = "content"
|
||||
|
||||
content[.header] = makeHeader(page: page, metadata: metadata, language: language)
|
||||
content[.content] = pageContent
|
||||
content[.previousPageLinkText] = previousPage.unwrapped { factory.factory.html.makePrevText($0.text) }
|
||||
content[.previousPageUrl] = previousPage?.link
|
||||
content[.nextPageLinkText] = nextPage.unwrapped { factory.factory.html.makeNextText($0.text) }
|
||||
content[.nextPageUrl] = nextPage?.link
|
||||
content[.footer] = page.customFooterContent()
|
||||
|
||||
if pageIncludesCode {
|
||||
let highlightCode = factory.factory.html.codeHighlightFooter()
|
||||
content[.footer] = (content[.footer].unwrapped { $0 + "\n" } ?? "") + highlightCode
|
||||
}
|
||||
|
||||
let url = files.urlInOutputFolder(path)
|
||||
if page.state == .draft {
|
||||
files.isDraft(path: page.path)
|
||||
} else if pageIsEmpty, page.state != .hidden {
|
||||
files.isEmpty(page: path)
|
||||
}
|
||||
guard factory.page.generate(content, to: url) else {
|
||||
return
|
||||
}
|
||||
files.generated(page: path)
|
||||
}
|
||||
|
||||
private func makeContent(page: Element, metadata: Element.LocalizedMetadata, language: String, path: String) -> (content: String, includesCode: Bool, isEmpty: Bool) {
|
||||
let create = configuration.createMdFilesIfMissing
|
||||
if let raw = files.contentOfOptionalFile(atPath: path, source: page.path, createEmptyFileIfMissing: create)?
|
||||
.trimmed.nonEmpty {
|
||||
let (content, includesCode) = PageContentGenerator(factory: factory.factory)
|
||||
.generate(page: page, language: language, content: raw)
|
||||
return (content, includesCode, false)
|
||||
} else {
|
||||
let (content, includesCode) = PageContentGenerator(factory: factory.factory)
|
||||
.generate(page: page, language: language, content: metadata.placeholderText)
|
||||
let placeholder = factory.makePlaceholder(title: metadata.placeholderTitle, text: content)
|
||||
return (placeholder, includesCode, true)
|
||||
}
|
||||
}
|
||||
|
||||
private func makeHeader(page: Element, metadata: Element.LocalizedMetadata, language: String) -> String? {
|
||||
let content = factory.makeHeaderContent(page: page, metadata: metadata, language: language)
|
||||
switch page.headerType {
|
||||
case .none:
|
||||
return nil
|
||||
case .left:
|
||||
return factory.factory.leftHeader.generate(content)
|
||||
case .center:
|
||||
return factory.factory.centeredHeader.generate(content)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
47
Sources/Generator/Generators/PageHeadGenerator.swift
Normal file
47
Sources/Generator/Generators/PageHeadGenerator.swift
Normal file
@@ -0,0 +1,47 @@
|
||||
import Foundation
|
||||
|
||||
struct PageHeadGenerator {
|
||||
|
||||
static let linkPreviewDesiredImageWidth = 1600
|
||||
|
||||
let factory: TemplateFactory
|
||||
|
||||
init(factory: TemplateFactory) {
|
||||
self.factory = factory
|
||||
}
|
||||
|
||||
func generate(page: Element, language: String, includesCode: Bool = false) -> String {
|
||||
let metadata = page.localized(for: language)
|
||||
|
||||
var content = [PageHeadTemplate.Key : String]()
|
||||
content[.author] = page.author
|
||||
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 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] = factory.html.linkPreviewImage(file: linkPreviewImageName)
|
||||
}
|
||||
content[.customPageContent] = page.customHeadContent()
|
||||
if includesCode {
|
||||
let scriptPath = "assets/js/highlight.js"
|
||||
let relative = page.relativePathToOtherSiteElement(file: scriptPath)
|
||||
let includeText = factory.html.scriptInclude(path: relative)
|
||||
if let head = content[.customPageContent] {
|
||||
content[.customPageContent] = head + "\n" + includeText
|
||||
} else {
|
||||
content[.customPageContent] = includeText
|
||||
}
|
||||
}
|
||||
|
||||
return factory.pageHead.generate(content)
|
||||
}
|
||||
}
|
60
Sources/Generator/Generators/SiteGenerator.swift
Normal file
60
Sources/Generator/Generators/SiteGenerator.swift
Normal file
@@ -0,0 +1,60 @@
|
||||
import Foundation
|
||||
|
||||
struct SiteGenerator {
|
||||
|
||||
let templates: TemplateFactory
|
||||
|
||||
init() throws {
|
||||
let templatesFolder = files.urlInContentFolder("templates")
|
||||
self.templates = try TemplateFactory(templateFolder: templatesFolder)
|
||||
}
|
||||
|
||||
func generate(site: Element) {
|
||||
site.languages.forEach {
|
||||
generate(site: site, metadata: $0)
|
||||
}
|
||||
}
|
||||
|
||||
private func generate(site: Element, metadata: Element.LocalizedMetadata) {
|
||||
let language = metadata.language
|
||||
let template = LocalizedSiteTemplate(
|
||||
factory: templates,
|
||||
language: language,
|
||||
site: site)
|
||||
|
||||
// Generate sections
|
||||
let overviewGenerator = OverviewPageGenerator(factory: template)
|
||||
let pageGenerator = PageGenerator(factory: template)
|
||||
|
||||
var elementsToProcess: [Element] = [site]
|
||||
while let element = elementsToProcess.popLast() {
|
||||
// Move recursively down to all pages
|
||||
elementsToProcess.append(contentsOf: element.elements)
|
||||
|
||||
processAllFiles(for: element)
|
||||
|
||||
if !element.elements.isEmpty {
|
||||
overviewGenerator.generate(section: element, language: language)
|
||||
} else {
|
||||
#warning("Determine previous and next pages (with relative links)")
|
||||
pageGenerator.generate(
|
||||
page: element,
|
||||
language: language,
|
||||
nextPage: nil,
|
||||
previousPage: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func processAllFiles(for element: Element) {
|
||||
element.requiredFiles.forEach(files.require)
|
||||
element.externalFiles.forEach(files.exclude)
|
||||
element.images.forEach {
|
||||
files.requireImage(
|
||||
source: $0.sourcePath,
|
||||
destination: $0.destinationPath,
|
||||
width: $0.desiredWidth,
|
||||
desiredHeight: $0.desiredHeight)
|
||||
}
|
||||
}
|
||||
}
|
54
Sources/Generator/Generators/ThumbnailListGenerator.swift
Normal file
54
Sources/Generator/Generators/ThumbnailListGenerator.swift
Normal file
@@ -0,0 +1,54 @@
|
||||
import Foundation
|
||||
|
||||
struct ThumbnailListGenerator {
|
||||
|
||||
private let factory: TemplateFactory
|
||||
|
||||
init(factory: TemplateFactory) {
|
||||
self.factory = factory
|
||||
}
|
||||
|
||||
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(_ item: Element, parent: Element, language: String, style: ThumbnailStyle) -> String {
|
||||
let fullThumbnailPath = item.thumbnailFilePath(for: language)
|
||||
let relativeImageUrl = parent.relativePathToFileWithPath(fullThumbnailPath)
|
||||
let metadata = item.localized(for: language)
|
||||
var content = [ThumbnailKey : String]()
|
||||
|
||||
if item.state.hasThumbnailLink {
|
||||
let fullPageUrl = item.fullPageUrl(for: language)
|
||||
let relativePageUrl = parent.relativePathToFileWithPath(fullPageUrl)
|
||||
content[.url] = "href=\"\(relativePageUrl)\""
|
||||
}
|
||||
|
||||
content[.image] = relativeImageUrl
|
||||
if style == .large, let suffix = metadata.thumbnailSuffix {
|
||||
content[.title] = factory.html.make(title: metadata.title, suffix: suffix)
|
||||
} else {
|
||||
content[.title] = metadata.title
|
||||
}
|
||||
content[.image2x] = relativeImageUrl.insert("@2x", beforeLast: ".")
|
||||
content[.corner] = item.cornerText(for: language).unwrapped {
|
||||
factory.largeThumbnail.makeCorner(text: $0)
|
||||
}
|
||||
|
||||
files.requireImage(
|
||||
source: fullThumbnailPath,
|
||||
destination: fullThumbnailPath,
|
||||
width: style.width,
|
||||
desiredHeight: style.height)
|
||||
|
||||
// Create image version for high-resolution screens
|
||||
files.requireImage(
|
||||
source: fullThumbnailPath,
|
||||
destination: fullThumbnailPath.insert("@2x", beforeLast: "."),
|
||||
width: style.width * 2,
|
||||
desiredHeight: style.height * 2)
|
||||
|
||||
return factory.thumbnail(style: style).generate(content, shouldIndent: false)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user