mirror of
https://github.com/samsonjs/samhuri.net.git
synced 2026-04-27 14:57:40 +00:00
Rearrange Swift code
This commit is contained in:
parent
ba8951e6a7
commit
3bd2ff0a12
53 changed files with 427 additions and 384 deletions
|
|
@ -7,6 +7,10 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
protocol JSONFeedRendering {
|
||||||
|
func renderJSONFeedPost(_ post: Post, site: Site) throws -> String
|
||||||
|
}
|
||||||
|
|
||||||
final class JSONFeedWriter {
|
final class JSONFeedWriter {
|
||||||
let fileWriter: FileWriting
|
let fileWriter: FileWriting
|
||||||
let jsonFeed: JSONFeed
|
let jsonFeed: JSONFeed
|
||||||
|
|
@ -16,7 +20,7 @@ final class JSONFeedWriter {
|
||||||
self.fileWriter = fileWriter
|
self.fileWriter = fileWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeFeed(_ posts: [Post], for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws {
|
func writeFeed(_ posts: [Post], for site: Site, to targetURL: URL, with renderer: JSONFeedRendering) throws {
|
||||||
let feed = Feed(
|
let feed = Feed(
|
||||||
title: site.title,
|
title: site.title,
|
||||||
home_page_url: site.url.absoluteString,
|
home_page_url: site.url.absoluteString,
|
||||||
|
|
@ -37,7 +41,7 @@ final class JSONFeedWriter {
|
||||||
url: url.absoluteString,
|
url: url.absoluteString,
|
||||||
external_url: post.link?.absoluteString,
|
external_url: post.link?.absoluteString,
|
||||||
author: FeedAuthor(name: post.author, avatar: nil, url: nil),
|
author: FeedAuthor(name: post.author, avatar: nil, url: nil),
|
||||||
content_html: try templateRenderer.renderFeedPost(post, site: site),
|
content_html: try renderer.renderJSONFeedPost(post, site: site),
|
||||||
tags: post.tags
|
tags: post.tags
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
26
samhuri.net/Sources/samhuri.net/Posts/Model/MonthPosts.swift
Normal file
26
samhuri.net/Sources/samhuri.net/Posts/Model/MonthPosts.swift
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
// MonthPosts.swift
|
||||||
|
// samhuri.net
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2020-01-01.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct MonthPosts {
|
||||||
|
let month: Month
|
||||||
|
var posts: [Post]
|
||||||
|
let path: String
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
month.padded
|
||||||
|
}
|
||||||
|
|
||||||
|
var isEmpty: Bool {
|
||||||
|
posts.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
var year: Int {
|
||||||
|
posts[0].date.year
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,55 +7,6 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct MonthPosts {
|
|
||||||
let month: Month
|
|
||||||
var posts: [Post]
|
|
||||||
let path: String
|
|
||||||
|
|
||||||
var title: String {
|
|
||||||
month.padded
|
|
||||||
}
|
|
||||||
|
|
||||||
var isEmpty: Bool {
|
|
||||||
posts.isEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
var year: Int {
|
|
||||||
posts[0].date.year
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: -
|
|
||||||
|
|
||||||
struct YearPosts {
|
|
||||||
let year: Int
|
|
||||||
var byMonth: [Month: MonthPosts]
|
|
||||||
let path: String
|
|
||||||
|
|
||||||
var title: String {
|
|
||||||
"\(year)"
|
|
||||||
}
|
|
||||||
|
|
||||||
var isEmpty: Bool {
|
|
||||||
byMonth.isEmpty || byMonth.values.allSatisfy { $0.isEmpty }
|
|
||||||
}
|
|
||||||
|
|
||||||
var months: [Month] {
|
|
||||||
Array(byMonth.keys)
|
|
||||||
}
|
|
||||||
|
|
||||||
subscript(month: Month) -> MonthPosts {
|
|
||||||
get {
|
|
||||||
byMonth[month, default: MonthPosts(month: month, posts: [], path: "\(path)/\(month.padded)")]
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
byMonth[month] = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: -
|
|
||||||
|
|
||||||
struct PostsByYear {
|
struct PostsByYear {
|
||||||
private(set) var byYear: [Int: YearPosts]
|
private(set) var byYear: [Int: YearPosts]
|
||||||
let path: String
|
let path: String
|
||||||
35
samhuri.net/Sources/samhuri.net/Posts/Model/YearPosts.swift
Normal file
35
samhuri.net/Sources/samhuri.net/Posts/Model/YearPosts.swift
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
//
|
||||||
|
// YearPosts.swift
|
||||||
|
// samhuri.net
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2020-01-01.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct YearPosts {
|
||||||
|
let year: Int
|
||||||
|
var byMonth: [Month: MonthPosts]
|
||||||
|
let path: String
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
"\(year)"
|
||||||
|
}
|
||||||
|
|
||||||
|
var isEmpty: Bool {
|
||||||
|
byMonth.isEmpty || byMonth.values.allSatisfy { $0.isEmpty }
|
||||||
|
}
|
||||||
|
|
||||||
|
var months: [Month] {
|
||||||
|
Array(byMonth.keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
subscript(month: Month) -> MonthPosts {
|
||||||
|
get {
|
||||||
|
byMonth[month, default: MonthPosts(month: month, posts: [], path: "\(path)/\(month.padded)")]
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
byMonth[month] = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
//
|
|
||||||
// PageRenderer+Posts.swift
|
|
||||||
// samhuri.net
|
|
||||||
//
|
|
||||||
// Created by Sami Samhuri on 2019-12-22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Plot
|
|
||||||
|
|
||||||
extension PageRenderer: PostsTemplateRenderer {
|
|
||||||
func renderArchive(postsByYear: PostsByYear, site: Site) throws -> String {
|
|
||||||
let context = SiteContext(site: site, subtitle: "Archive")
|
|
||||||
return render(.archive(postsByYear), context: context)
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderYearPosts(_ yearPosts: YearPosts, site: Site) throws -> String {
|
|
||||||
let context = SiteContext(site: site, subtitle: yearPosts.title)
|
|
||||||
return render(.yearPosts(yearPosts), context: context)
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderMonthPosts(_ posts: MonthPosts, site: Site) throws -> String {
|
|
||||||
let assets = posts.posts.templateAssets
|
|
||||||
let context = SiteContext(site: site, subtitle: "\(posts.month.name) \(posts.year)", templateAssets: assets)
|
|
||||||
return render(.monthPosts(posts), context: context)
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderPost(_ post: Post, site: Site) throws -> String {
|
|
||||||
let context = SiteContext(site: site, subtitle: post.title, templateAssets: post.templateAssets)
|
|
||||||
return render(.post(post, articleClass: "container"), context: context)
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderRecentPosts(_ posts: [Post], site: Site) throws -> String {
|
|
||||||
let context = SiteContext(site: site, subtitle: nil, templateAssets: posts.templateAssets)
|
|
||||||
return render(.recentPosts(posts), context: context)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Feeds
|
|
||||||
|
|
||||||
func renderFeedPost(_ post: Post, site: Site) throws -> String {
|
|
||||||
let context = SiteContext(site: site, subtitle: post.title, templateAssets: post.templateAssets)
|
|
||||||
let url = site.url.appendingPathComponent(post.path)
|
|
||||||
// Turn relative URLs into absolute ones.
|
|
||||||
return Node.feedPost(post, url: url, styles: context.styles)
|
|
||||||
.render(indentedBy: .spaces(2))
|
|
||||||
.replacingOccurrences(of: "href=\"/", with: "href=\"\(site.url)/")
|
|
||||||
.replacingOccurrences(of: "src=\"/", with: "src=\"\(site.url)/")
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderRSSFeed(posts: [Post], feedURL: URL, site: Site) throws -> String {
|
|
||||||
try RSS(
|
|
||||||
.title(site.title),
|
|
||||||
.if(site.description != nil, .description(site.description!)),
|
|
||||||
.link(site.url),
|
|
||||||
.pubDate(posts[0].date),
|
|
||||||
.atomLink(feedURL),
|
|
||||||
.group(posts.map { post in
|
|
||||||
let url = site.url.appendingPathComponent(post.path)
|
|
||||||
return .item(
|
|
||||||
.title(post.isLink ? "→ \(post.title)" : post.title),
|
|
||||||
.pubDate(post.date),
|
|
||||||
.element(named: "author", text: post.author),
|
|
||||||
.link(url),
|
|
||||||
.guid(.text(url.absoluteString), .isPermaLink(true)),
|
|
||||||
.content(try renderFeedPost(post, site: site))
|
|
||||||
)
|
|
||||||
})
|
|
||||||
).render(indentedBy: .spaces(2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -20,9 +20,9 @@ final class PostWriter {
|
||||||
// MARK: - Post pages
|
// MARK: - Post pages
|
||||||
|
|
||||||
extension PostWriter {
|
extension PostWriter {
|
||||||
func writePosts(_ posts: [Post], for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws {
|
func writePosts(_ posts: [Post], for site: Site, to targetURL: URL, with renderer: PostsRendering) throws {
|
||||||
for post in posts {
|
for post in posts {
|
||||||
let postHTML = try templateRenderer.renderPost(post, site: site)
|
let postHTML = try renderer.renderPost(post, site: site)
|
||||||
let postURL = targetURL
|
let postURL = targetURL
|
||||||
.appendingPathComponent(outputPath)
|
.appendingPathComponent(outputPath)
|
||||||
.appendingPathComponent(filePath(date: post.date, slug: post.slug))
|
.appendingPathComponent(filePath(date: post.date, slug: post.slug))
|
||||||
|
|
@ -38,8 +38,8 @@ extension PostWriter {
|
||||||
// MARK: - Recent posts page
|
// MARK: - Recent posts page
|
||||||
|
|
||||||
extension PostWriter {
|
extension PostWriter {
|
||||||
func writeRecentPosts(_ recentPosts: [Post], for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws {
|
func writeRecentPosts(_ recentPosts: [Post], for site: Site, to targetURL: URL, with renderer: PostsRendering) throws {
|
||||||
let recentPostsHTML = try templateRenderer.renderRecentPosts(recentPosts, site: site)
|
let recentPostsHTML = try renderer.renderRecentPosts(recentPosts, site: site)
|
||||||
let fileURL = targetURL.appendingPathComponent("index.html")
|
let fileURL = targetURL.appendingPathComponent("index.html")
|
||||||
try fileWriter.write(string: recentPostsHTML, to: fileURL)
|
try fileWriter.write(string: recentPostsHTML, to: fileURL)
|
||||||
}
|
}
|
||||||
|
|
@ -48,8 +48,8 @@ extension PostWriter {
|
||||||
// MARK: - Post archive page
|
// MARK: - Post archive page
|
||||||
|
|
||||||
extension PostWriter {
|
extension PostWriter {
|
||||||
func writeArchive(posts: PostsByYear, for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws {
|
func writeArchive(posts: PostsByYear, for site: Site, to targetURL: URL, with renderer: PostsRendering) throws {
|
||||||
let archiveHTML = try templateRenderer.renderArchive(postsByYear: posts, site: site)
|
let archiveHTML = try renderer.renderArchive(postsByYear: posts, site: site)
|
||||||
let archiveURL = targetURL.appendingPathComponent(outputPath).appendingPathComponent("index.html")
|
let archiveURL = targetURL.appendingPathComponent(outputPath).appendingPathComponent("index.html")
|
||||||
try fileWriter.write(string: archiveHTML, to: archiveURL)
|
try fileWriter.write(string: archiveHTML, to: archiveURL)
|
||||||
}
|
}
|
||||||
|
|
@ -58,10 +58,10 @@ extension PostWriter {
|
||||||
// MARK: - Yearly post index pages
|
// MARK: - Yearly post index pages
|
||||||
|
|
||||||
extension PostWriter {
|
extension PostWriter {
|
||||||
func writeYearIndexes(posts: PostsByYear, for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws {
|
func writeYearIndexes(posts: PostsByYear, for site: Site, to targetURL: URL, with renderer: PostsRendering) throws {
|
||||||
for yearPosts in posts.byYear.values {
|
for yearPosts in posts.byYear.values {
|
||||||
let yearDir = targetURL.appendingPathComponent(yearPosts.path)
|
let yearDir = targetURL.appendingPathComponent(yearPosts.path)
|
||||||
let yearHTML = try templateRenderer.renderYearPosts(yearPosts, site: site)
|
let yearHTML = try renderer.renderYearPosts(yearPosts, site: site)
|
||||||
let yearURL = yearDir.appendingPathComponent("index.html")
|
let yearURL = yearDir.appendingPathComponent("index.html")
|
||||||
try fileWriter.write(string: yearHTML, to: yearURL)
|
try fileWriter.write(string: yearHTML, to: yearURL)
|
||||||
}
|
}
|
||||||
|
|
@ -71,11 +71,11 @@ extension PostWriter {
|
||||||
// MARK: - Monthly post roll-up pages
|
// MARK: - Monthly post roll-up pages
|
||||||
|
|
||||||
extension PostWriter {
|
extension PostWriter {
|
||||||
func writeMonthRollups(posts: PostsByYear, for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws {
|
func writeMonthRollups(posts: PostsByYear, for site: Site, to targetURL: URL, with renderer: PostsRendering) throws {
|
||||||
for yearPosts in posts.byYear.values {
|
for yearPosts in posts.byYear.values {
|
||||||
for monthPosts in yearPosts.byMonth.values {
|
for monthPosts in yearPosts.byMonth.values {
|
||||||
let monthDir = targetURL.appendingPathComponent(monthPosts.path)
|
let monthDir = targetURL.appendingPathComponent(monthPosts.path)
|
||||||
let monthHTML = try templateRenderer.renderMonthPosts(monthPosts, site: site)
|
let monthHTML = try renderer.renderMonthPosts(monthPosts, site: site)
|
||||||
let monthURL = monthDir.appendingPathComponent("index.html")
|
let monthURL = monthDir.appendingPathComponent("index.html")
|
||||||
try fileWriter.write(string: monthHTML, to: monthURL)
|
try fileWriter.write(string: monthHTML, to: monthURL)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
//
|
||||||
|
// PostsPlugin+Builder.swift
|
||||||
|
// samhuri.net
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-15.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension PostsPlugin {
|
||||||
|
final class Builder {
|
||||||
|
private let renderer: Renderer
|
||||||
|
private var path: String?
|
||||||
|
private var jsonFeed: JSONFeed?
|
||||||
|
private var rssFeed: RSSFeed?
|
||||||
|
|
||||||
|
init(renderer: Renderer) {
|
||||||
|
self.renderer = renderer
|
||||||
|
}
|
||||||
|
|
||||||
|
func path(_ path: String) -> Self {
|
||||||
|
precondition(self.path == nil, "path is already defined")
|
||||||
|
self.path = path
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsonFeed(
|
||||||
|
path: String? = nil,
|
||||||
|
avatarPath: String? = nil,
|
||||||
|
iconPath: String? = nil,
|
||||||
|
faviconPath: String? = nil
|
||||||
|
) -> Self {
|
||||||
|
precondition(jsonFeed == nil, "JSON feed is already defined")
|
||||||
|
jsonFeed = JSONFeed(
|
||||||
|
path: path ?? "feed.json",
|
||||||
|
avatarPath: avatarPath,
|
||||||
|
iconPath: iconPath,
|
||||||
|
faviconPath: faviconPath
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func rssFeed(path: String? = nil) -> Self {
|
||||||
|
precondition(rssFeed == nil, "RSS feed is already defined")
|
||||||
|
rssFeed = RSSFeed(path: path ?? "feed.xml")
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func build() -> PostsPlugin {
|
||||||
|
let postRepo: PostRepo
|
||||||
|
let postWriter: PostWriter
|
||||||
|
if let outputPath = path {
|
||||||
|
postRepo = PostRepo(outputPath: outputPath)
|
||||||
|
postWriter = PostWriter(outputPath: outputPath)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
postRepo = PostRepo()
|
||||||
|
postWriter = PostWriter()
|
||||||
|
}
|
||||||
|
|
||||||
|
let jsonFeedWriter: JSONFeedWriter?
|
||||||
|
if let jsonFeed = jsonFeed {
|
||||||
|
jsonFeedWriter = JSONFeedWriter(jsonFeed: jsonFeed)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
jsonFeedWriter = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let rssFeedWriter: RSSFeedWriter?
|
||||||
|
if let rssFeed = rssFeed {
|
||||||
|
rssFeedWriter = RSSFeedWriter(rssFeed: rssFeed)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rssFeedWriter = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return PostsPlugin(
|
||||||
|
renderer: renderer,
|
||||||
|
postRepo: postRepo,
|
||||||
|
postWriter: postWriter,
|
||||||
|
jsonFeedWriter: jsonFeedWriter,
|
||||||
|
rssFeedWriter: rssFeedWriter
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,20 +8,22 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
final class PostsPlugin: Plugin {
|
final class PostsPlugin: Plugin {
|
||||||
let templateRenderer: PostsTemplateRenderer
|
typealias Renderer = PostsRendering & JSONFeedRendering & RSSFeedRendering
|
||||||
|
|
||||||
|
let renderer: Renderer
|
||||||
let postRepo: PostRepo
|
let postRepo: PostRepo
|
||||||
let postWriter: PostWriter
|
let postWriter: PostWriter
|
||||||
let jsonFeedWriter: JSONFeedWriter?
|
let jsonFeedWriter: JSONFeedWriter?
|
||||||
let rssFeedWriter: RSSFeedWriter?
|
let rssFeedWriter: RSSFeedWriter?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
templateRenderer: PostsTemplateRenderer,
|
renderer: Renderer,
|
||||||
postRepo: PostRepo = PostRepo(),
|
postRepo: PostRepo = PostRepo(),
|
||||||
postWriter: PostWriter = PostWriter(),
|
postWriter: PostWriter = PostWriter(),
|
||||||
jsonFeedWriter: JSONFeedWriter?,
|
jsonFeedWriter: JSONFeedWriter?,
|
||||||
rssFeedWriter: RSSFeedWriter?
|
rssFeedWriter: RSSFeedWriter?
|
||||||
) {
|
) {
|
||||||
self.templateRenderer = templateRenderer
|
self.renderer = renderer
|
||||||
self.postRepo = postRepo
|
self.postRepo = postRepo
|
||||||
self.postWriter = postWriter
|
self.postWriter = postWriter
|
||||||
self.jsonFeedWriter = jsonFeedWriter
|
self.jsonFeedWriter = jsonFeedWriter
|
||||||
|
|
@ -43,12 +45,12 @@ final class PostsPlugin: Plugin {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try postWriter.writeRecentPosts(postRepo.recentPosts, for: site, to: targetURL, with: templateRenderer)
|
try postWriter.writeRecentPosts(postRepo.recentPosts, for: site, to: targetURL, with: renderer)
|
||||||
try postWriter.writePosts(postRepo.sortedPosts, for: site, to: targetURL, with: templateRenderer)
|
try postWriter.writePosts(postRepo.sortedPosts, for: site, to: targetURL, with: renderer)
|
||||||
try postWriter.writeArchive(posts: postRepo.posts, for: site, to: targetURL, with: templateRenderer)
|
try postWriter.writeArchive(posts: postRepo.posts, for: site, to: targetURL, with: renderer)
|
||||||
try postWriter.writeYearIndexes(posts: postRepo.posts, for: site, to: targetURL, with: templateRenderer)
|
try postWriter.writeYearIndexes(posts: postRepo.posts, for: site, to: targetURL, with: renderer)
|
||||||
try postWriter.writeMonthRollups(posts: postRepo.posts, for: site, to: targetURL, with: templateRenderer)
|
try postWriter.writeMonthRollups(posts: postRepo.posts, for: site, to: targetURL, with: renderer)
|
||||||
try jsonFeedWriter?.writeFeed(postRepo.postsForFeed, for: site, to: targetURL, with: templateRenderer)
|
try jsonFeedWriter?.writeFeed(postRepo.postsForFeed, for: site, to: targetURL, with: renderer)
|
||||||
try rssFeedWriter?.writeFeed(postRepo.postsForFeed, for: site, to: targetURL, with: templateRenderer)
|
try rssFeedWriter?.writeFeed(postRepo.postsForFeed, for: site, to: targetURL, with: renderer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// PostsTemplateRenderer.swift
|
// PostsRendering.swift
|
||||||
// samhuri.net
|
// samhuri.net
|
||||||
//
|
//
|
||||||
// Created by Sami Samhuri on 2019-12-17.
|
// Created by Sami Samhuri on 2019-12-17.
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
protocol PostsTemplateRenderer {
|
protocol PostsRendering {
|
||||||
func renderArchive(postsByYear: PostsByYear, site: Site) throws -> String
|
func renderArchive(postsByYear: PostsByYear, site: Site) throws -> String
|
||||||
|
|
||||||
func renderYearPosts(_ yearPosts: YearPosts, site: Site) throws -> String
|
func renderYearPosts(_ yearPosts: YearPosts, site: Site) throws -> String
|
||||||
|
|
@ -17,10 +17,4 @@ protocol PostsTemplateRenderer {
|
||||||
func renderPost(_ post: Post, site: Site) throws -> String
|
func renderPost(_ post: Post, site: Site) throws -> String
|
||||||
|
|
||||||
func renderRecentPosts(_ posts: [Post], site: Site) throws -> String
|
func renderRecentPosts(_ posts: [Post], site: Site) throws -> String
|
||||||
|
|
||||||
// MARK: - Feeds
|
|
||||||
|
|
||||||
func renderFeedPost(_ post: Post, site: Site) throws -> String
|
|
||||||
|
|
||||||
func renderRSSFeed(posts: [Post], feedURL: URL, site: Site) throws -> String
|
|
||||||
}
|
}
|
||||||
|
|
@ -7,6 +7,10 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
protocol RSSFeedRendering {
|
||||||
|
func renderRSSFeed(posts: [Post], feedURL: URL, site: Site) throws -> String
|
||||||
|
}
|
||||||
|
|
||||||
final class RSSFeedWriter {
|
final class RSSFeedWriter {
|
||||||
let fileWriter: FileWriting
|
let fileWriter: FileWriting
|
||||||
let rssFeed: RSSFeed
|
let rssFeed: RSSFeed
|
||||||
|
|
@ -16,9 +20,9 @@ final class RSSFeedWriter {
|
||||||
self.fileWriter = fileWriter
|
self.fileWriter = fileWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeFeed(_ posts: [Post], for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws {
|
func writeFeed(_ posts: [Post], for site: Site, to targetURL: URL, with renderer: RSSFeedRendering) throws {
|
||||||
let feedURL = site.url.appendingPathComponent(rssFeed.path)
|
let feedURL = site.url.appendingPathComponent(rssFeed.path)
|
||||||
let feedXML = try templateRenderer.renderRSSFeed(posts: posts, feedURL: feedURL, site: site)
|
let feedXML = try renderer.renderRSSFeed(posts: posts, feedURL: feedURL, site: site)
|
||||||
let feedFileURL = targetURL.appendingPathComponent(rssFeed.path)
|
let feedFileURL = targetURL.appendingPathComponent(rssFeed.path)
|
||||||
try fileWriter.write(string: feedXML, to: feedFileURL)
|
try fileWriter.write(string: feedXML, to: feedFileURL)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// PageRenderer+JSONFeed.swift
|
||||||
|
// samhuri.net
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2020-01-01.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Plot
|
||||||
|
|
||||||
|
extension PageRenderer: JSONFeedRendering {
|
||||||
|
func renderJSONFeedPost(_ post: Post, site: Site) throws -> String {
|
||||||
|
let context = SiteContext(site: site, subtitle: post.title, templateAssets: post.templateAssets)
|
||||||
|
let url = site.url.appendingPathComponent(post.path)
|
||||||
|
// Turn relative URLs into absolute ones.
|
||||||
|
return Node.feedPost(post, url: url, styles: context.styles)
|
||||||
|
.render(indentedBy: .spaces(2))
|
||||||
|
.replacingOccurrences(of: "href=\"/", with: "href=\"\(site.url)/")
|
||||||
|
.replacingOccurrences(of: "src=\"/", with: "src=\"\(site.url)/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
//
|
||||||
|
// PageRenderer+Posts.swift
|
||||||
|
// samhuri.net
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Plot
|
||||||
|
|
||||||
|
extension PageRenderer: PostsRendering {
|
||||||
|
func renderArchive(postsByYear: PostsByYear, site: Site) throws -> String {
|
||||||
|
let context = SiteContext(site: site, subtitle: "Archive")
|
||||||
|
return render(.archive(postsByYear), context: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderYearPosts(_ yearPosts: YearPosts, site: Site) throws -> String {
|
||||||
|
let context = SiteContext(site: site, subtitle: yearPosts.title)
|
||||||
|
return render(.yearPosts(yearPosts), context: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderMonthPosts(_ posts: MonthPosts, site: Site) throws -> String {
|
||||||
|
let assets = posts.posts.templateAssets
|
||||||
|
let context = SiteContext(site: site, subtitle: "\(posts.month.name) \(posts.year)", templateAssets: assets)
|
||||||
|
return render(.monthPosts(posts), context: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderPost(_ post: Post, site: Site) throws -> String {
|
||||||
|
let context = SiteContext(site: site, subtitle: post.title, templateAssets: post.templateAssets)
|
||||||
|
return render(.post(post, articleClass: "container"), context: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderRecentPosts(_ posts: [Post], site: Site) throws -> String {
|
||||||
|
let context = SiteContext(site: site, subtitle: nil, templateAssets: posts.templateAssets)
|
||||||
|
return render(.recentPosts(posts), context: context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
//
|
||||||
|
// PageRenderer+RSSFeed.swift
|
||||||
|
// samhuri.net
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2020-01-01.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Plot
|
||||||
|
|
||||||
|
extension PageRenderer: RSSFeedRendering {
|
||||||
|
func renderRSSFeed(posts: [Post], feedURL: URL, site: Site) throws -> String {
|
||||||
|
try RSS(
|
||||||
|
.title(site.title),
|
||||||
|
.if(site.description != nil, .description(site.description!)),
|
||||||
|
.link(site.url),
|
||||||
|
.pubDate(posts[0].date),
|
||||||
|
.atomLink(feedURL),
|
||||||
|
.group(posts.map { post in
|
||||||
|
let url = site.url.appendingPathComponent(post.path)
|
||||||
|
return .item(
|
||||||
|
.title(post.isLink ? "→ \(post.title)" : post.title),
|
||||||
|
.pubDate(post.date),
|
||||||
|
.element(named: "author", text: post.author),
|
||||||
|
.link(url),
|
||||||
|
.guid(.text(url.absoluteString), .isPermaLink(true)),
|
||||||
|
.content(try renderJSONFeedPost(post, site: site))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
).render(indentedBy: .spaces(2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
//
|
||||||
|
// ProjectsPlugin+Builder.swift
|
||||||
|
// samhuri.net
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension ProjectsPlugin {
|
||||||
|
final class Builder {
|
||||||
|
let renderer: ProjectsRenderer
|
||||||
|
private var path: String?
|
||||||
|
private var projects: [PartialProject] = []
|
||||||
|
private var assets: TemplateAssets?
|
||||||
|
|
||||||
|
init(renderer: ProjectsRenderer) {
|
||||||
|
self.renderer = renderer
|
||||||
|
}
|
||||||
|
|
||||||
|
func path(_ path: String) -> Self {
|
||||||
|
precondition(self.path == nil, "path is already defined")
|
||||||
|
self.path = path
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func assets(_ assets: TemplateAssets) -> Self {
|
||||||
|
precondition(self.assets == nil, "assets are already defined")
|
||||||
|
self.assets = assets
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func add(_ title: String, description: String) -> Self {
|
||||||
|
let project = PartialProject(title: title, description: description)
|
||||||
|
projects.append(project)
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func build() -> ProjectsPlugin {
|
||||||
|
if projects.isEmpty {
|
||||||
|
print("WARNING: No projects have been added")
|
||||||
|
}
|
||||||
|
return ProjectsPlugin(
|
||||||
|
projects: projects,
|
||||||
|
renderer: renderer,
|
||||||
|
projectAssets: assets ?? .empty(),
|
||||||
|
outputPath: path
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,7 +16,7 @@ final class ProjectsPlugin: Plugin {
|
||||||
let fileWriter: FileWriting
|
let fileWriter: FileWriting
|
||||||
let outputPath: String
|
let outputPath: String
|
||||||
let partialProjects: [PartialProject]
|
let partialProjects: [PartialProject]
|
||||||
let templateRenderer: ProjectsTemplateRenderer
|
let renderer: ProjectsRenderer
|
||||||
let projectAssets: TemplateAssets
|
let projectAssets: TemplateAssets
|
||||||
|
|
||||||
var projects: [Project] = []
|
var projects: [Project] = []
|
||||||
|
|
@ -24,13 +24,13 @@ final class ProjectsPlugin: Plugin {
|
||||||
|
|
||||||
init(
|
init(
|
||||||
projects: [PartialProject],
|
projects: [PartialProject],
|
||||||
templateRenderer: ProjectsTemplateRenderer,
|
renderer: ProjectsRenderer,
|
||||||
projectAssets: TemplateAssets,
|
projectAssets: TemplateAssets,
|
||||||
outputPath: String? = nil,
|
outputPath: String? = nil,
|
||||||
fileWriter: FileWriting = FileWriter()
|
fileWriter: FileWriting = FileWriter()
|
||||||
) {
|
) {
|
||||||
self.partialProjects = projects
|
self.partialProjects = projects
|
||||||
self.templateRenderer = templateRenderer
|
self.renderer = renderer
|
||||||
self.projectAssets = projectAssets
|
self.projectAssets = projectAssets
|
||||||
self.outputPath = outputPath ?? "projects"
|
self.outputPath = outputPath ?? "projects"
|
||||||
self.fileWriter = fileWriter
|
self.fileWriter = fileWriter
|
||||||
|
|
@ -56,12 +56,12 @@ final class ProjectsPlugin: Plugin {
|
||||||
|
|
||||||
let projectsDir = targetURL.appendingPathComponent(outputPath)
|
let projectsDir = targetURL.appendingPathComponent(outputPath)
|
||||||
let projectsURL = projectsDir.appendingPathComponent("index.html")
|
let projectsURL = projectsDir.appendingPathComponent("index.html")
|
||||||
let projectsHTML = try templateRenderer.renderProjects(projects, site: site)
|
let projectsHTML = try renderer.renderProjects(projects, site: site)
|
||||||
try fileWriter.write(string: projectsHTML, to: projectsURL)
|
try fileWriter.write(string: projectsHTML, to: projectsURL)
|
||||||
|
|
||||||
for project in projects {
|
for project in projects {
|
||||||
let projectURL = projectsDir.appendingPathComponent("\(project.title)/index.html")
|
let projectURL = projectsDir.appendingPathComponent("\(project.title)/index.html")
|
||||||
let projectHTML = try templateRenderer.renderProject(project, site: site, assets: projectAssets)
|
let projectHTML = try renderer.renderProject(project, site: site, assets: projectAssets)
|
||||||
try fileWriter.write(string: projectHTML, to: projectURL)
|
try fileWriter.write(string: projectHTML, to: projectURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,7 +7,8 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
protocol ProjectsTemplateRenderer {
|
protocol ProjectsRenderer {
|
||||||
func renderProjects(_ projects: [Project], site: Site) throws -> String
|
func renderProjects(_ projects: [Project], site: Site) throws -> String
|
||||||
|
|
||||||
func renderProject(_ project: Project, site: Site, assets: TemplateAssets) throws -> String
|
func renderProject(_ project: Project, site: Site, assets: TemplateAssets) throws -> String
|
||||||
}
|
}
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Plot
|
import Plot
|
||||||
|
|
||||||
extension PageRenderer: ProjectsTemplateRenderer {
|
extension PageRenderer: ProjectsRenderer {
|
||||||
func renderProjects(_ projects: [Project], site: Site) throws -> String {
|
func renderProjects(_ projects: [Project], site: Site) throws -> String {
|
||||||
let context = SiteContext(site: site, subtitle: "Projects", templateAssets: .empty())
|
let context = SiteContext(site: site, subtitle: "Projects", templateAssets: .empty())
|
||||||
return render(.projects(projects), context: context)
|
return render(.projects(projects), context: context)
|
||||||
|
|
@ -12,9 +12,9 @@ final class MarkdownRenderer: Renderer {
|
||||||
let fileManager: FileManager = .default
|
let fileManager: FileManager = .default
|
||||||
let fileWriter: FileWriting
|
let fileWriter: FileWriting
|
||||||
let markdownParser = MarkdownParser()
|
let markdownParser = MarkdownParser()
|
||||||
let pageRenderer: MarkdownPageRenderer
|
let pageRenderer: PageRendering
|
||||||
|
|
||||||
init(pageRenderer: MarkdownPageRenderer, fileWriter: FileWriting = FileWriter()) {
|
init(pageRenderer: PageRendering, fileWriter: FileWriting = FileWriter()) {
|
||||||
self.pageRenderer = pageRenderer
|
self.pageRenderer = pageRenderer
|
||||||
self.fileWriter = fileWriter
|
self.fileWriter = fileWriter
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// MarkdownPageRenderer.swift
|
// PageRendering.swift
|
||||||
// samhuri.net
|
// samhuri.net
|
||||||
//
|
//
|
||||||
// Created by Sami Samhuri on 2019-12-03.
|
// Created by Sami Samhuri on 2019-12-03.
|
||||||
|
|
@ -7,6 +7,6 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
protocol MarkdownPageRenderer {
|
protocol PageRendering {
|
||||||
func renderPage(site: Site, bodyHTML: String, metadata: [String: String]) throws -> String
|
func renderPage(site: Site, bodyHTML: String, metadata: [String: String]) throws -> String
|
||||||
}
|
}
|
||||||
80
samhuri.net/Sources/samhuri.net/Site/Site+Builder.swift
Normal file
80
samhuri.net/Sources/samhuri.net/Site/Site+Builder.swift
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
//
|
||||||
|
// Site+Builder.swift
|
||||||
|
// samhuri.net
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-15.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Site {
|
||||||
|
final class Builder {
|
||||||
|
private let title: String
|
||||||
|
private let description: String?
|
||||||
|
private let author: String
|
||||||
|
private let email: String
|
||||||
|
private let url: URL
|
||||||
|
|
||||||
|
private var styles: [String] = []
|
||||||
|
private var scripts: [String] = []
|
||||||
|
|
||||||
|
private var plugins: [Plugin] = []
|
||||||
|
private var renderers: [Renderer] = []
|
||||||
|
|
||||||
|
init(
|
||||||
|
title: String,
|
||||||
|
description: String? = nil,
|
||||||
|
author: String,
|
||||||
|
email: String,
|
||||||
|
url: URL
|
||||||
|
) {
|
||||||
|
self.title = title
|
||||||
|
self.description = description
|
||||||
|
self.author = author
|
||||||
|
self.email = email
|
||||||
|
self.url = url
|
||||||
|
}
|
||||||
|
|
||||||
|
func styles(_ styles: String...) -> Self {
|
||||||
|
self.styles.append(contentsOf: styles)
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func scripts(_ scripts: String...) -> Self {
|
||||||
|
self.scripts.append(contentsOf: scripts)
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func plugin(_ plugin: Plugin) -> Self {
|
||||||
|
plugins.append(plugin)
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderer(_ renderer: Renderer) -> Self {
|
||||||
|
renderers.append(renderer)
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func build() -> Site {
|
||||||
|
Site(
|
||||||
|
author: author,
|
||||||
|
email: email,
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
|
url: url,
|
||||||
|
styles: styles,
|
||||||
|
scripts: scripts,
|
||||||
|
renderers: renderers,
|
||||||
|
plugins: plugins
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Markdown
|
||||||
|
|
||||||
|
extension Site.Builder {
|
||||||
|
func renderMarkdown(pageRenderer: PageRendering) -> Self {
|
||||||
|
renderer(MarkdownRenderer(pageRenderer: pageRenderer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ final class PageRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PageRenderer: MarkdownPageRenderer {
|
extension PageRenderer: PageRendering {
|
||||||
func renderPage(site: Site, bodyHTML: String, metadata: [String: String]) throws -> String {
|
func renderPage(site: Site, bodyHTML: String, metadata: [String: String]) throws -> String {
|
||||||
let pageTitle = metadata["Title"]
|
let pageTitle = metadata["Title"]
|
||||||
let scripts = metadata.commaSeparatedList(key: "Scripts")
|
let scripts = metadata.commaSeparatedList(key: "Scripts")
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
//
|
|
||||||
// PostsPluginBuilder.swift
|
|
||||||
// samhuri.net
|
|
||||||
//
|
|
||||||
// Created by Sami Samhuri on 2019-12-15.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
final class PostsPluginBuilder {
|
|
||||||
private let templateRenderer: PostsTemplateRenderer
|
|
||||||
private var path: String?
|
|
||||||
private var jsonFeed: JSONFeed?
|
|
||||||
private var rssFeed: RSSFeed?
|
|
||||||
|
|
||||||
init(templateRenderer: PostsTemplateRenderer) {
|
|
||||||
self.templateRenderer = templateRenderer
|
|
||||||
}
|
|
||||||
|
|
||||||
func path(_ path: String) -> PostsPluginBuilder {
|
|
||||||
precondition(self.path == nil, "path is already defined")
|
|
||||||
self.path = path
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
|
|
||||||
func jsonFeed(
|
|
||||||
path: String? = nil,
|
|
||||||
avatarPath: String? = nil,
|
|
||||||
iconPath: String? = nil,
|
|
||||||
faviconPath: String? = nil
|
|
||||||
) -> PostsPluginBuilder {
|
|
||||||
precondition(jsonFeed == nil, "JSON feed is already defined")
|
|
||||||
jsonFeed = JSONFeed(
|
|
||||||
path: path ?? "feed.json",
|
|
||||||
avatarPath: avatarPath,
|
|
||||||
iconPath: iconPath,
|
|
||||||
faviconPath: faviconPath
|
|
||||||
)
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
|
|
||||||
func rssFeed(path: String? = nil) -> PostsPluginBuilder {
|
|
||||||
precondition(rssFeed == nil, "RSS feed is already defined")
|
|
||||||
rssFeed = RSSFeed(path: path ?? "feed.xml")
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
|
|
||||||
func build() -> PostsPlugin {
|
|
||||||
let postRepo: PostRepo
|
|
||||||
let postWriter: PostWriter
|
|
||||||
if let outputPath = path {
|
|
||||||
postRepo = PostRepo(outputPath: outputPath)
|
|
||||||
postWriter = PostWriter(outputPath: outputPath)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
postRepo = PostRepo()
|
|
||||||
postWriter = PostWriter()
|
|
||||||
}
|
|
||||||
|
|
||||||
let jsonFeedWriter: JSONFeedWriter?
|
|
||||||
if let jsonFeed = jsonFeed {
|
|
||||||
jsonFeedWriter = JSONFeedWriter(jsonFeed: jsonFeed)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
jsonFeedWriter = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let rssFeedWriter: RSSFeedWriter?
|
|
||||||
if let rssFeed = rssFeed {
|
|
||||||
rssFeedWriter = RSSFeedWriter(rssFeed: rssFeed)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rssFeedWriter = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return PostsPlugin(
|
|
||||||
templateRenderer: templateRenderer,
|
|
||||||
postRepo: postRepo,
|
|
||||||
postWriter: postWriter,
|
|
||||||
jsonFeedWriter: jsonFeedWriter,
|
|
||||||
rssFeedWriter: rssFeedWriter
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
//
|
|
||||||
// ProjectsPluginBuilder.swift
|
|
||||||
// samhuri.net
|
|
||||||
//
|
|
||||||
// Created by Sami Samhuri on 2019-12-19.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
final class ProjectsPluginBuilder {
|
|
||||||
let templateRenderer: ProjectsTemplateRenderer
|
|
||||||
private var path: String?
|
|
||||||
private var projects: [PartialProject] = []
|
|
||||||
private var assets: TemplateAssets?
|
|
||||||
|
|
||||||
init(templateRenderer: ProjectsTemplateRenderer) {
|
|
||||||
self.templateRenderer = templateRenderer
|
|
||||||
}
|
|
||||||
|
|
||||||
func path(_ path: String) -> ProjectsPluginBuilder {
|
|
||||||
precondition(self.path == nil, "path is already defined")
|
|
||||||
self.path = path
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
|
|
||||||
func assets(_ assets: TemplateAssets) -> ProjectsPluginBuilder {
|
|
||||||
precondition(self.assets == nil, "assets are already defined")
|
|
||||||
self.assets = assets
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
|
|
||||||
func add(_ title: String, description: String) -> ProjectsPluginBuilder {
|
|
||||||
let project = PartialProject(title: title, description: description)
|
|
||||||
projects.append(project)
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
|
|
||||||
func build() -> ProjectsPlugin {
|
|
||||||
if projects.isEmpty {
|
|
||||||
print("WARNING: No projects have been added")
|
|
||||||
}
|
|
||||||
return ProjectsPlugin(
|
|
||||||
projects: projects,
|
|
||||||
templateRenderer: templateRenderer,
|
|
||||||
projectAssets: assets ?? .empty(),
|
|
||||||
outputPath: path
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
//
|
|
||||||
// SiteBuilder.swift
|
|
||||||
// samhuri.net
|
|
||||||
//
|
|
||||||
// Created by Sami Samhuri on 2019-12-15.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
final class SiteBuilder {
|
|
||||||
private let title: String
|
|
||||||
private let description: String?
|
|
||||||
private let author: String
|
|
||||||
private let email: String
|
|
||||||
private let url: URL
|
|
||||||
|
|
||||||
private var styles: [String] = []
|
|
||||||
private var scripts: [String] = []
|
|
||||||
|
|
||||||
private var plugins: [Plugin] = []
|
|
||||||
private var renderers: [Renderer] = []
|
|
||||||
|
|
||||||
init(
|
|
||||||
title: String,
|
|
||||||
description: String? = nil,
|
|
||||||
author: String,
|
|
||||||
email: String,
|
|
||||||
url: URL
|
|
||||||
) {
|
|
||||||
self.title = title
|
|
||||||
self.description = description
|
|
||||||
self.author = author
|
|
||||||
self.email = email
|
|
||||||
self.url = url
|
|
||||||
}
|
|
||||||
|
|
||||||
func styles(_ styles: String...) -> SiteBuilder {
|
|
||||||
self.styles.append(contentsOf: styles)
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
|
|
||||||
func scripts(_ scripts: String...) -> SiteBuilder {
|
|
||||||
self.scripts.append(contentsOf: scripts)
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
|
|
||||||
func plugin(_ plugin: Plugin) -> SiteBuilder {
|
|
||||||
plugins.append(plugin)
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderer(_ renderer: Renderer) -> SiteBuilder {
|
|
||||||
renderers.append(renderer)
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
|
|
||||||
func build() -> Site {
|
|
||||||
Site(
|
|
||||||
author: author,
|
|
||||||
email: email,
|
|
||||||
title: title,
|
|
||||||
description: description,
|
|
||||||
url: url,
|
|
||||||
styles: styles,
|
|
||||||
scripts: scripts,
|
|
||||||
renderers: renderers,
|
|
||||||
plugins: plugins
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Markdown
|
|
||||||
|
|
||||||
extension SiteBuilder {
|
|
||||||
func renderMarkdown(pageRenderer: MarkdownPageRenderer) -> SiteBuilder {
|
|
||||||
renderer(MarkdownRenderer(pageRenderer: pageRenderer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -10,8 +10,15 @@ public extension samhuri {
|
||||||
self.siteURLOverride = siteURLOverride
|
self.siteURLOverride = siteURLOverride
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func generate(sourceURL: URL, targetURL: URL) throws {
|
||||||
|
let renderer = PageRenderer()
|
||||||
|
let site = buildSite(renderer: renderer)
|
||||||
|
let generator = try SiteGenerator(sourceURL: sourceURL, site: site)
|
||||||
|
try generator.generate(targetURL: targetURL)
|
||||||
|
}
|
||||||
|
|
||||||
func buildSite(renderer: PageRenderer) -> Site {
|
func buildSite(renderer: PageRenderer) -> Site {
|
||||||
let projectsPlugin = ProjectsPluginBuilder(templateRenderer: renderer)
|
let projectsPlugin = ProjectsPlugin.Builder(renderer: renderer)
|
||||||
.path("projects")
|
.path("projects")
|
||||||
.assets(TemplateAssets(scripts: [
|
.assets(TemplateAssets(scripts: [
|
||||||
"https://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js",
|
"https://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js",
|
||||||
|
|
@ -35,7 +42,7 @@ public extension samhuri {
|
||||||
.add("samhuri.net", description: "this site")
|
.add("samhuri.net", description: "this site")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
let postsPlugin = PostsPluginBuilder(templateRenderer: renderer)
|
let postsPlugin = PostsPlugin.Builder(renderer: renderer)
|
||||||
.path("posts")
|
.path("posts")
|
||||||
.jsonFeed(
|
.jsonFeed(
|
||||||
avatarPath: "images/me.jpg",
|
avatarPath: "images/me.jpg",
|
||||||
|
|
@ -45,7 +52,7 @@ public extension samhuri {
|
||||||
.rssFeed()
|
.rssFeed()
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
return SiteBuilder(
|
return Site.Builder(
|
||||||
title: "samhuri.net",
|
title: "samhuri.net",
|
||||||
description: "just some blog",
|
description: "just some blog",
|
||||||
author: "Sami Samhuri",
|
author: "Sami Samhuri",
|
||||||
|
|
@ -58,12 +65,5 @@ public extension samhuri {
|
||||||
.plugin(postsPlugin)
|
.plugin(postsPlugin)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func generate(sourceURL: URL, targetURL: URL) throws {
|
|
||||||
let renderer = PageRenderer()
|
|
||||||
let site = buildSite(renderer: renderer)
|
|
||||||
let generator = try SiteGenerator(sourceURL: sourceURL, site: site)
|
|
||||||
try generator.generate(targetURL: targetURL)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue