Convert Xcode project to swift package
This commit is contained in:
@ -0,0 +1,13 @@
|
||||
import Foundation
|
||||
|
||||
struct BackNavigationTemplate: Template {
|
||||
|
||||
enum Key: String, CaseIterable {
|
||||
case url = "URL"
|
||||
case text = "TEXT"
|
||||
}
|
||||
|
||||
static let templateName = "back.html"
|
||||
|
||||
let raw: String
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import Foundation
|
||||
|
||||
struct OverviewSectionCleanTemplate: Template {
|
||||
|
||||
enum Key: String, CaseIterable {
|
||||
case items = "ITEMS"
|
||||
}
|
||||
|
||||
static let templateName = "overview-section-clean.html"
|
||||
|
||||
let raw: String
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import Foundation
|
||||
|
||||
struct OverviewSectionTemplate: Template {
|
||||
|
||||
enum Key: String, CaseIterable {
|
||||
case url = "URL"
|
||||
case title = "TITLE"
|
||||
case items = "ITEMS"
|
||||
case more = "MORE"
|
||||
}
|
||||
|
||||
static let templateName = "overview-section.html"
|
||||
|
||||
let raw: String
|
||||
}
|
16
Sources/Generator/Templates/Elements/PageHeadTemplate.swift
Normal file
16
Sources/Generator/Templates/Elements/PageHeadTemplate.swift
Normal file
@ -0,0 +1,16 @@
|
||||
import Foundation
|
||||
|
||||
struct PageHeadTemplate: Template {
|
||||
|
||||
enum Key: String, CaseIterable {
|
||||
case author = "AUTHOR"
|
||||
case title = "TITLE"
|
||||
case description = "DESCRIPTION"
|
||||
case image = "IMAGE"
|
||||
case customPageContent = "CUSTOM"
|
||||
}
|
||||
|
||||
let raw: String
|
||||
|
||||
static let templateName = "head.html"
|
||||
}
|
18
Sources/Generator/Templates/Elements/PageImageTemplate.swift
Normal file
18
Sources/Generator/Templates/Elements/PageImageTemplate.swift
Normal file
@ -0,0 +1,18 @@
|
||||
import Foundation
|
||||
|
||||
struct PageImageTemplate: Template {
|
||||
|
||||
enum Key: String, CaseIterable {
|
||||
case image = "IMAGE"
|
||||
case image2x = "IMAGE_2X"
|
||||
case width = "WIDTH"
|
||||
case height = "HEIGHT"
|
||||
case leftText = "LEFT_TEXT"
|
||||
case rightText = "RIGHT_TEXT"
|
||||
}
|
||||
|
||||
static let templateName = "image.html"
|
||||
|
||||
let raw: String
|
||||
|
||||
}
|
37
Sources/Generator/Templates/Elements/PageVideoTemplate.swift
Normal file
37
Sources/Generator/Templates/Elements/PageVideoTemplate.swift
Normal file
@ -0,0 +1,37 @@
|
||||
import Foundation
|
||||
|
||||
struct PageVideoTemplate: Template {
|
||||
|
||||
typealias VideoSource = (url: String, type: VideoType)
|
||||
|
||||
enum Key: String, CaseIterable {
|
||||
case options = "OPTIONS"
|
||||
case sources = "SOURCES"
|
||||
}
|
||||
|
||||
enum VideoOption: String {
|
||||
case controls
|
||||
case autoplay
|
||||
case muted
|
||||
case loop
|
||||
case playsinline
|
||||
case poster
|
||||
case preload
|
||||
}
|
||||
|
||||
static let templateName = "video.html"
|
||||
|
||||
let raw: String
|
||||
|
||||
func generate<T>(sources: [VideoSource], options: T) -> String where T: Sequence, T.Element == VideoOption {
|
||||
let sourcesCode = sources.map(makeSource).joined(separator: "\n")
|
||||
let optionCode = options.map { $0.rawValue }.joined(separator: " ")
|
||||
return generate([.sources: sourcesCode, .options: optionCode])
|
||||
}
|
||||
|
||||
private func makeSource(_ source: VideoSource) -> String {
|
||||
"""
|
||||
<source src="\(source.url)" type="\(source.type.htmlType)">
|
||||
"""
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import Foundation
|
||||
|
||||
struct PlaceholderTemplate: Template {
|
||||
|
||||
enum Key: String, CaseIterable {
|
||||
case title = "TITLE"
|
||||
case text = "TEXT"
|
||||
}
|
||||
|
||||
static let templateName = "empty.html"
|
||||
|
||||
var raw: String
|
||||
}
|
50
Sources/Generator/Templates/Elements/ThumbnailTemplate.swift
Normal file
50
Sources/Generator/Templates/Elements/ThumbnailTemplate.swift
Normal file
@ -0,0 +1,50 @@
|
||||
import Foundation
|
||||
|
||||
protocol ThumbnailTemplate {
|
||||
|
||||
func generate(_ content: [ThumbnailKey : String], shouldIndent: Bool) -> String
|
||||
}
|
||||
|
||||
enum ThumbnailKey: String, CaseIterable {
|
||||
case url = "URL"
|
||||
case image = "IMAGE"
|
||||
case image2x = "IMAGE_2X"
|
||||
case title = "TITLE"
|
||||
case corner = "CORNER"
|
||||
}
|
||||
|
||||
struct LargeThumbnailTemplate: Template, ThumbnailTemplate {
|
||||
|
||||
typealias Key = ThumbnailKey
|
||||
|
||||
static let templateName = "thumbnail-large.html"
|
||||
|
||||
let raw: String
|
||||
|
||||
func makeCorner(text: String) -> String {
|
||||
"<span class=\"corner\"><span>\(text)</span></span>"
|
||||
}
|
||||
|
||||
func makeTitleSuffix(_ suffix: String) -> String {
|
||||
"<span class=\"suffix\">\(suffix)</span>"
|
||||
}
|
||||
}
|
||||
|
||||
struct SquareThumbnailTemplate: Template, ThumbnailTemplate {
|
||||
|
||||
typealias Key = ThumbnailKey
|
||||
|
||||
static let templateName = "thumbnail-square.html"
|
||||
|
||||
let raw: String
|
||||
}
|
||||
|
||||
struct SmallThumbnailTemplate: Template, ThumbnailTemplate {
|
||||
|
||||
typealias Key = ThumbnailKey
|
||||
|
||||
static let templateName = "thumbnail-small.html"
|
||||
|
||||
let raw: String
|
||||
}
|
||||
|
15
Sources/Generator/Templates/Elements/TopBarTemplate.swift
Normal file
15
Sources/Generator/Templates/Elements/TopBarTemplate.swift
Normal file
@ -0,0 +1,15 @@
|
||||
import Foundation
|
||||
|
||||
struct TopBarTemplate: Template {
|
||||
|
||||
enum Key: String, CaseIterable {
|
||||
case title = "TITLE"
|
||||
case titleLink = "TITLE_URL"
|
||||
case elements = "ELEMENTS"
|
||||
case languageButton = "LANG_BUTTON"
|
||||
}
|
||||
|
||||
static let templateName = "bar.html"
|
||||
|
||||
var raw: String
|
||||
}
|
158
Sources/Generator/Templates/Filled/LocalizedSiteTemplate.swift
Normal file
158
Sources/Generator/Templates/Filled/LocalizedSiteTemplate.swift
Normal file
@ -0,0 +1,158 @@
|
||||
import Foundation
|
||||
import Ink
|
||||
|
||||
struct LocalizedSiteTemplate {
|
||||
|
||||
let author: String
|
||||
|
||||
let factory: TemplateFactory
|
||||
|
||||
let topBar: PrefilledTopBarTemplate
|
||||
|
||||
// MARK: Site Elements
|
||||
|
||||
var backNavigation: BackNavigationTemplate {
|
||||
factory.backNavigation
|
||||
}
|
||||
|
||||
let pageHead: PageHeadGenerator
|
||||
|
||||
let overviewSection: OverviewSectionGenerator
|
||||
|
||||
private let fullDateFormatter: DateFormatter
|
||||
|
||||
private let month: DateFormatter
|
||||
|
||||
private let day: DateFormatter
|
||||
|
||||
var language: String {
|
||||
topBar.language
|
||||
}
|
||||
|
||||
// MARK: Pages
|
||||
|
||||
var page: PageTemplate {
|
||||
factory.page
|
||||
}
|
||||
|
||||
init(factory: TemplateFactory, language: String, site: Element) {
|
||||
self.author = site.author
|
||||
self.factory = factory
|
||||
|
||||
let df = DateFormatter()
|
||||
df.dateStyle = .long
|
||||
df.timeStyle = .none
|
||||
df.locale = Locale(identifier: language)
|
||||
self.fullDateFormatter = df
|
||||
|
||||
let df2 = DateFormatter()
|
||||
df2.dateFormat = "MMMM"
|
||||
df2.locale = Locale(identifier: language)
|
||||
self.month = df2
|
||||
|
||||
let df3 = DateFormatter()
|
||||
df3.dateFormat = "dd"
|
||||
df3.locale = Locale(identifier: language)
|
||||
self.day = df3
|
||||
|
||||
let sections = site.sortedItems.map {
|
||||
PrefilledTopBarTemplate.SectionInfo(
|
||||
name: $0.title(for: language),
|
||||
url: $0.path + Element.htmlPagePathAddition(for: language))
|
||||
}
|
||||
|
||||
self.topBar = .init(
|
||||
factory: factory,
|
||||
language: language,
|
||||
sections: sections,
|
||||
topBarWebsiteTitle: site.topBarTitle)
|
||||
self.pageHead = PageHeadGenerator(
|
||||
factory: factory)
|
||||
self.overviewSection = OverviewSectionGenerator(
|
||||
factory: factory)
|
||||
}
|
||||
|
||||
// MARK: Content
|
||||
|
||||
func makePlaceholder(metadata: Element.LocalizedMetadata) -> String {
|
||||
makePlaceholder(title: metadata.placeholderTitle, text: metadata.placeholderText)
|
||||
}
|
||||
|
||||
func makePlaceholder(title: String, text: String) -> String {
|
||||
factory.placeholder.generate([
|
||||
.title: title,
|
||||
.text: text])
|
||||
}
|
||||
|
||||
func makeBackLink(text: String, language: String) -> String {
|
||||
let content: [BackNavigationTemplate.Key : String] = [
|
||||
.text: text,
|
||||
.url: ".." + Element.htmlPagePathAddition(for: language)
|
||||
]
|
||||
return backNavigation.generate(content)
|
||||
}
|
||||
|
||||
func makeDateString(start: Date?, end: Date?) -> String {
|
||||
guard let start = start else {
|
||||
return ""
|
||||
}
|
||||
guard let end = end else {
|
||||
return fullDateFormatter.string(from: start)
|
||||
}
|
||||
|
||||
switch language {
|
||||
case "de":
|
||||
return makeGermanDateString(start: start, end: end)
|
||||
case "en":
|
||||
fallthrough
|
||||
default:
|
||||
return makeEnglishDateString(start: start, end: end)
|
||||
}
|
||||
}
|
||||
|
||||
private func makeGermanDateString(start: Date, end: Date) -> String {
|
||||
guard Calendar.current.isDate(start, equalTo: end, toGranularity: .year) else {
|
||||
return "\(fullDateFormatter.string(from: start)) - \(fullDateFormatter.string(from: end))"
|
||||
}
|
||||
|
||||
let startDay = day.string(from: start)
|
||||
guard Calendar.current.isDate(start, equalTo: end, toGranularity: .month) else {
|
||||
let startMonth = month.string(from: start)
|
||||
return "\(startDay). \(startMonth) - \(fullDateFormatter.string(from: end))"
|
||||
}
|
||||
return "\(startDay). - \(fullDateFormatter.string(from: end))"
|
||||
}
|
||||
|
||||
private func makeEnglishDateString(start: Date, end: Date) -> String {
|
||||
guard Calendar.current.isDate(start, equalTo: end, toGranularity: .year) else {
|
||||
return "\(fullDateFormatter.string(from: start)) - \(fullDateFormatter.string(from: end))"
|
||||
}
|
||||
|
||||
guard Calendar.current.isDate(start, equalTo: end, toGranularity: .month) else {
|
||||
let startDay = day.string(from: start)
|
||||
let startMonth = month.string(from: start)
|
||||
return "\(startMonth) \(startDay) - \(fullDateFormatter.string(from: end))"
|
||||
}
|
||||
return fullDateFormatter.string(from: start)
|
||||
.insert(" - \(day.string(from: end))", beforeLast: ",")
|
||||
}
|
||||
|
||||
func makeHeaderContent(page: Element, metadata: Element.LocalizedMetadata, language: String) -> [HeaderKey : String] {
|
||||
let backText = page.backLinkText(for: language)
|
||||
|
||||
var content = [HeaderKey : String]()
|
||||
if let backText = backText.nonEmpty {
|
||||
content[.backLink] = makeBackLink(text: backText, language: language)
|
||||
}
|
||||
if let suffix = metadata.titleSuffix {
|
||||
content[.title] = factory.html.make(title: metadata.title, suffix: suffix)
|
||||
} else {
|
||||
content[.title] = metadata.title
|
||||
}
|
||||
content[.subtitle] = metadata.subtitle
|
||||
content[.titleText] = metadata.description
|
||||
content[.date] = makeDateString(start: page.date, end: page.endDate)
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
import Foundation
|
||||
|
||||
struct PrefilledTopBarTemplate {
|
||||
|
||||
let language: String
|
||||
|
||||
let sections: [SectionInfo]
|
||||
|
||||
let topBarWebsiteTitle: String
|
||||
|
||||
private let factory: TemplateFactory
|
||||
|
||||
init(factory: TemplateFactory, language: String, sections: [SectionInfo], topBarWebsiteTitle: String) {
|
||||
self.factory = factory
|
||||
self.language = language
|
||||
self.sections = sections
|
||||
self.topBarWebsiteTitle = topBarWebsiteTitle
|
||||
}
|
||||
|
||||
func generate(sectionUrl: String?, languageButton: String?, page: Element) -> String {
|
||||
var content = [TopBarTemplate.Key : String]()
|
||||
content[.title] = topBarWebsiteTitle
|
||||
content[.titleLink] = factory.html.topBarWebsiteTitle(language: language, from: page)
|
||||
content[.elements] = elements(activeSectionUrl: sectionUrl)
|
||||
content[.languageButton] = languageButton.unwrapped(factory.html.topBarLanguageButton) ?? ""
|
||||
return factory.topBar.generate(content)
|
||||
}
|
||||
|
||||
private func elements(activeSectionUrl: String?) -> String {
|
||||
sections
|
||||
.map { factory.html.topBarNavigationLink(url: $0.url, text: $0.name, isActive: activeSectionUrl == $0.url) }
|
||||
.joined(separator: "\n")
|
||||
}
|
||||
|
||||
struct SectionInfo {
|
||||
|
||||
let name: String
|
||||
|
||||
let url: String
|
||||
}
|
||||
}
|
32
Sources/Generator/Templates/Pages/HeaderTemplate.swift
Normal file
32
Sources/Generator/Templates/Pages/HeaderTemplate.swift
Normal file
@ -0,0 +1,32 @@
|
||||
import Foundation
|
||||
|
||||
protocol HeaderTemplate {
|
||||
|
||||
func generate(_ content: [ThumbnailKey : String], shouldIndent: Bool) throws -> String
|
||||
}
|
||||
|
||||
enum HeaderKey: String, CaseIterable {
|
||||
case backLink = "BACK_LINK"
|
||||
case title = "TITLE"
|
||||
case subtitle = "SUBTITLE"
|
||||
case titleText = "TITLE_TEXT"
|
||||
case date = "DATE"
|
||||
}
|
||||
|
||||
struct CenteredHeaderTemplate: Template {
|
||||
|
||||
typealias Key = HeaderKey
|
||||
|
||||
let raw: String
|
||||
|
||||
static let templateName = "header-center.html"
|
||||
}
|
||||
|
||||
struct LeftHeaderTemplate: Template {
|
||||
|
||||
typealias Key = HeaderKey
|
||||
|
||||
let raw: String
|
||||
|
||||
static let templateName = "header-left.html"
|
||||
}
|
21
Sources/Generator/Templates/Pages/PageTemplate.swift
Normal file
21
Sources/Generator/Templates/Pages/PageTemplate.swift
Normal file
@ -0,0 +1,21 @@
|
||||
import Foundation
|
||||
|
||||
struct PageTemplate: Template {
|
||||
|
||||
enum Key: String, CaseIterable {
|
||||
case head = "HEAD"
|
||||
case topBar = "TOP_BAR"
|
||||
case contentClass = "CONTENT_CLASS"
|
||||
case header = "HEADER"
|
||||
case content = "CONTENT"
|
||||
case previousPageLinkText = "PREV_TEXT"
|
||||
case previousPageUrl = "PREV_LINK"
|
||||
case nextPageLinkText = "NEXT_TEXT"
|
||||
case nextPageUrl = "NEXT_LINK"
|
||||
case footer = "FOOTER"
|
||||
}
|
||||
|
||||
static let templateName = "page.html"
|
||||
|
||||
let raw: String
|
||||
}
|
58
Sources/Generator/Templates/Template.swift
Normal file
58
Sources/Generator/Templates/Template.swift
Normal file
@ -0,0 +1,58 @@
|
||||
import Foundation
|
||||
|
||||
protocol Template {
|
||||
|
||||
associatedtype Key where Key: RawRepresentable, Key.RawValue == String, Key: CaseIterable, Key: Hashable
|
||||
|
||||
static var templateName: String { get }
|
||||
|
||||
var raw: String { get }
|
||||
|
||||
init(raw: String)
|
||||
|
||||
}
|
||||
|
||||
extension Template {
|
||||
|
||||
init(in folder: URL) throws {
|
||||
let url = folder.appendingPathComponent(Self.templateName)
|
||||
try self.init(from: url)
|
||||
}
|
||||
|
||||
init(from url: URL) throws {
|
||||
let raw = try String(contentsOf: url)
|
||||
self.init(raw: raw)
|
||||
}
|
||||
|
||||
func generate(_ content: [Key : String], to url: URL) -> Bool {
|
||||
let content = generate(content)
|
||||
return files.write(content, to: url)
|
||||
}
|
||||
|
||||
func generate(_ content: [Key : String], shouldIndent: Bool = false) -> String {
|
||||
var result = raw.components(separatedBy: "\n")
|
||||
|
||||
Key.allCases.forEach { key in
|
||||
let newContent = content[key]?.withoutEmptyLines ?? ""
|
||||
let stringMarker = "<!--\(key.rawValue)-->"
|
||||
let indices = result.enumerated().filter { $0.element.contains(stringMarker) }
|
||||
.map { $0.offset }
|
||||
guard !indices.isEmpty else {
|
||||
return
|
||||
}
|
||||
for index in indices {
|
||||
let old = result[index].components(separatedBy: stringMarker)
|
||||
// Add indentation to all added lines
|
||||
let indentation = old.first!
|
||||
guard shouldIndent, indentation.trimmingCharacters(in: .whitespaces).isEmpty else {
|
||||
// Prefix is not indentation, so just insert new content
|
||||
result[index] = old.joined(separator: newContent)
|
||||
continue
|
||||
}
|
||||
let indentedReplacements = newContent.indented(by: indentation)
|
||||
result[index] = old.joined(separator: indentedReplacements)
|
||||
}
|
||||
}
|
||||
return result.joined(separator: "\n").withoutEmptyLines
|
||||
}
|
||||
}
|
78
Sources/Generator/Templates/TemplateFactory.swift
Normal file
78
Sources/Generator/Templates/TemplateFactory.swift
Normal file
@ -0,0 +1,78 @@
|
||||
import Foundation
|
||||
|
||||
final class TemplateFactory {
|
||||
|
||||
let templateFolder: URL
|
||||
|
||||
// MARK: Site Elements
|
||||
|
||||
let backNavigation: BackNavigationTemplate
|
||||
|
||||
let pageHead: PageHeadTemplate
|
||||
|
||||
let topBar: TopBarTemplate
|
||||
|
||||
let overviewSection: OverviewSectionTemplate
|
||||
|
||||
let overviewSectionClean: OverviewSectionCleanTemplate
|
||||
|
||||
let placeholder: PlaceholderTemplate
|
||||
|
||||
// MARK: Thumbnails
|
||||
|
||||
let largeThumbnail: LargeThumbnailTemplate
|
||||
|
||||
let squareThumbnail: SquareThumbnailTemplate
|
||||
|
||||
let smallThumbnail: SmallThumbnailTemplate
|
||||
|
||||
func thumbnail(style: ThumbnailStyle) -> ThumbnailTemplate {
|
||||
switch style {
|
||||
case .large:
|
||||
return largeThumbnail
|
||||
case .square:
|
||||
return squareThumbnail
|
||||
case .small:
|
||||
return smallThumbnail
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Headers
|
||||
|
||||
let leftHeader: LeftHeaderTemplate
|
||||
|
||||
let centeredHeader: CenteredHeaderTemplate
|
||||
|
||||
// MARK: Pages
|
||||
|
||||
let page: PageTemplate
|
||||
|
||||
let image: PageImageTemplate
|
||||
|
||||
let video: PageVideoTemplate
|
||||
|
||||
// MARK: HTML
|
||||
|
||||
let html: HTMLElementsGenerator
|
||||
|
||||
// MARK: Init
|
||||
|
||||
init(templateFolder: URL) throws {
|
||||
self.templateFolder = templateFolder
|
||||
self.backNavigation = try .init(in: templateFolder)
|
||||
self.pageHead = try .init(in: templateFolder)
|
||||
self.topBar = try .init(in: templateFolder)
|
||||
self.overviewSection = try .init(in: templateFolder)
|
||||
self.overviewSectionClean = try .init(in: templateFolder)
|
||||
self.placeholder = try .init(in: templateFolder)
|
||||
self.largeThumbnail = try .init(in: templateFolder)
|
||||
self.squareThumbnail = try .init(in: templateFolder)
|
||||
self.smallThumbnail = try .init(in: templateFolder)
|
||||
self.leftHeader = try .init(in: templateFolder)
|
||||
self.centeredHeader = try .init(in: templateFolder)
|
||||
self.page = try .init(in: templateFolder)
|
||||
self.image = try .init(in: templateFolder)
|
||||
self.video = try .init(in: templateFolder)
|
||||
self.html = .init()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user