Rearrange Swift code

This commit is contained in:
Sami Samhuri 2020-01-01 20:21:06 -08:00
parent ba8951e6a7
commit 3bd2ff0a12
53 changed files with 427 additions and 384 deletions

View file

@ -7,6 +7,10 @@
import Foundation
protocol JSONFeedRendering {
func renderJSONFeedPost(_ post: Post, site: Site) throws -> String
}
final class JSONFeedWriter {
let fileWriter: FileWriting
let jsonFeed: JSONFeed
@ -16,7 +20,7 @@ final class JSONFeedWriter {
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(
title: site.title,
home_page_url: site.url.absoluteString,
@ -37,7 +41,7 @@ final class JSONFeedWriter {
url: url.absoluteString,
external_url: post.link?.absoluteString,
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
)
}

View 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
}
}

View file

@ -7,55 +7,6 @@
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 {
private(set) var byYear: [Int: YearPosts]
let path: String

View 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
}
}
}

View file

@ -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))
}
}

View file

@ -20,9 +20,9 @@ final class PostWriter {
// MARK: - Post pages
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 {
let postHTML = try templateRenderer.renderPost(post, site: site)
let postHTML = try renderer.renderPost(post, site: site)
let postURL = targetURL
.appendingPathComponent(outputPath)
.appendingPathComponent(filePath(date: post.date, slug: post.slug))
@ -38,8 +38,8 @@ extension PostWriter {
// MARK: - Recent posts page
extension PostWriter {
func writeRecentPosts(_ recentPosts: [Post], for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws {
let recentPostsHTML = try templateRenderer.renderRecentPosts(recentPosts, site: site)
func writeRecentPosts(_ recentPosts: [Post], for site: Site, to targetURL: URL, with renderer: PostsRendering) throws {
let recentPostsHTML = try renderer.renderRecentPosts(recentPosts, site: site)
let fileURL = targetURL.appendingPathComponent("index.html")
try fileWriter.write(string: recentPostsHTML, to: fileURL)
}
@ -48,8 +48,8 @@ extension PostWriter {
// MARK: - Post archive page
extension PostWriter {
func writeArchive(posts: PostsByYear, for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws {
let archiveHTML = try templateRenderer.renderArchive(postsByYear: posts, site: site)
func writeArchive(posts: PostsByYear, for site: Site, to targetURL: URL, with renderer: PostsRendering) throws {
let archiveHTML = try renderer.renderArchive(postsByYear: posts, site: site)
let archiveURL = targetURL.appendingPathComponent(outputPath).appendingPathComponent("index.html")
try fileWriter.write(string: archiveHTML, to: archiveURL)
}
@ -58,10 +58,10 @@ extension PostWriter {
// MARK: - Yearly post index pages
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 {
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")
try fileWriter.write(string: yearHTML, to: yearURL)
}
@ -71,11 +71,11 @@ extension PostWriter {
// MARK: - Monthly post roll-up pages
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 monthPosts in yearPosts.byMonth.values {
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")
try fileWriter.write(string: monthHTML, to: monthURL)
}

View file

@ -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
)
}
}
}

View file

@ -8,20 +8,22 @@
import Foundation
final class PostsPlugin: Plugin {
let templateRenderer: PostsTemplateRenderer
typealias Renderer = PostsRendering & JSONFeedRendering & RSSFeedRendering
let renderer: Renderer
let postRepo: PostRepo
let postWriter: PostWriter
let jsonFeedWriter: JSONFeedWriter?
let rssFeedWriter: RSSFeedWriter?
init(
templateRenderer: PostsTemplateRenderer,
renderer: Renderer,
postRepo: PostRepo = PostRepo(),
postWriter: PostWriter = PostWriter(),
jsonFeedWriter: JSONFeedWriter?,
rssFeedWriter: RSSFeedWriter?
) {
self.templateRenderer = templateRenderer
self.renderer = renderer
self.postRepo = postRepo
self.postWriter = postWriter
self.jsonFeedWriter = jsonFeedWriter
@ -43,12 +45,12 @@ final class PostsPlugin: Plugin {
return
}
try postWriter.writeRecentPosts(postRepo.recentPosts, for: site, to: targetURL, with: templateRenderer)
try postWriter.writePosts(postRepo.sortedPosts, for: site, to: targetURL, with: templateRenderer)
try postWriter.writeArchive(posts: postRepo.posts, for: site, to: targetURL, with: templateRenderer)
try postWriter.writeYearIndexes(posts: postRepo.posts, for: site, to: targetURL, with: templateRenderer)
try postWriter.writeMonthRollups(posts: postRepo.posts, for: site, to: targetURL, with: templateRenderer)
try jsonFeedWriter?.writeFeed(postRepo.postsForFeed, for: site, to: targetURL, with: templateRenderer)
try rssFeedWriter?.writeFeed(postRepo.postsForFeed, 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: renderer)
try postWriter.writeArchive(posts: postRepo.posts, for: site, to: targetURL, with: renderer)
try postWriter.writeYearIndexes(posts: postRepo.posts, for: site, to: targetURL, with: renderer)
try postWriter.writeMonthRollups(posts: postRepo.posts, for: site, to: targetURL, with: renderer)
try jsonFeedWriter?.writeFeed(postRepo.postsForFeed, for: site, to: targetURL, with: renderer)
try rssFeedWriter?.writeFeed(postRepo.postsForFeed, for: site, to: targetURL, with: renderer)
}
}

View file

@ -1,5 +1,5 @@
//
// PostsTemplateRenderer.swift
// PostsRendering.swift
// samhuri.net
//
// Created by Sami Samhuri on 2019-12-17.
@ -7,7 +7,7 @@
import Foundation
protocol PostsTemplateRenderer {
protocol PostsRendering {
func renderArchive(postsByYear: PostsByYear, 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 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
}

View file

@ -7,6 +7,10 @@
import Foundation
protocol RSSFeedRendering {
func renderRSSFeed(posts: [Post], feedURL: URL, site: Site) throws -> String
}
final class RSSFeedWriter {
let fileWriter: FileWriting
let rssFeed: RSSFeed
@ -16,9 +20,9 @@ final class RSSFeedWriter {
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 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)
try fileWriter.write(string: feedXML, to: feedFileURL)
}

View file

@ -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)/")
}
}

View file

@ -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)
}
}

View file

@ -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))
}
}

View file

@ -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
)
}
}
}

View file

@ -16,7 +16,7 @@ final class ProjectsPlugin: Plugin {
let fileWriter: FileWriting
let outputPath: String
let partialProjects: [PartialProject]
let templateRenderer: ProjectsTemplateRenderer
let renderer: ProjectsRenderer
let projectAssets: TemplateAssets
var projects: [Project] = []
@ -24,13 +24,13 @@ final class ProjectsPlugin: Plugin {
init(
projects: [PartialProject],
templateRenderer: ProjectsTemplateRenderer,
renderer: ProjectsRenderer,
projectAssets: TemplateAssets,
outputPath: String? = nil,
fileWriter: FileWriting = FileWriter()
) {
self.partialProjects = projects
self.templateRenderer = templateRenderer
self.renderer = renderer
self.projectAssets = projectAssets
self.outputPath = outputPath ?? "projects"
self.fileWriter = fileWriter
@ -56,12 +56,12 @@ final class ProjectsPlugin: Plugin {
let projectsDir = targetURL.appendingPathComponent(outputPath)
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)
for project in projects {
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)
}
}

View file

@ -7,7 +7,8 @@
import Foundation
protocol ProjectsTemplateRenderer {
protocol ProjectsRenderer {
func renderProjects(_ projects: [Project], site: Site) throws -> String
func renderProject(_ project: Project, site: Site, assets: TemplateAssets) throws -> String
}

View file

@ -8,7 +8,7 @@
import Foundation
import Plot
extension PageRenderer: ProjectsTemplateRenderer {
extension PageRenderer: ProjectsRenderer {
func renderProjects(_ projects: [Project], site: Site) throws -> String {
let context = SiteContext(site: site, subtitle: "Projects", templateAssets: .empty())
return render(.projects(projects), context: context)

View file

@ -12,9 +12,9 @@ final class MarkdownRenderer: Renderer {
let fileManager: FileManager = .default
let fileWriter: FileWriting
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.fileWriter = fileWriter
}

View file

@ -1,5 +1,5 @@
//
// MarkdownPageRenderer.swift
// PageRendering.swift
// samhuri.net
//
// Created by Sami Samhuri on 2019-12-03.
@ -7,6 +7,6 @@
import Foundation
protocol MarkdownPageRenderer {
protocol PageRendering {
func renderPage(site: Site, bodyHTML: String, metadata: [String: String]) throws -> String
}

View 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))
}
}

View file

@ -14,7 +14,7 @@ final class PageRenderer {
}
}
extension PageRenderer: MarkdownPageRenderer {
extension PageRenderer: PageRendering {
func renderPage(site: Site, bodyHTML: String, metadata: [String: String]) throws -> String {
let pageTitle = metadata["Title"]
let scripts = metadata.commaSeparatedList(key: "Scripts")

View file

@ -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
)
}
}

View file

@ -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
)
}
}

View file

@ -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))
}
}

View file

@ -10,8 +10,15 @@ public extension samhuri {
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 {
let projectsPlugin = ProjectsPluginBuilder(templateRenderer: renderer)
let projectsPlugin = ProjectsPlugin.Builder(renderer: renderer)
.path("projects")
.assets(TemplateAssets(scripts: [
"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")
.build()
let postsPlugin = PostsPluginBuilder(templateRenderer: renderer)
let postsPlugin = PostsPlugin.Builder(renderer: renderer)
.path("posts")
.jsonFeed(
avatarPath: "images/me.jpg",
@ -45,7 +52,7 @@ public extension samhuri {
.rssFeed()
.build()
return SiteBuilder(
return Site.Builder(
title: "samhuri.net",
description: "just some blog",
author: "Sami Samhuri",
@ -58,12 +65,5 @@ public extension samhuri {
.plugin(postsPlugin)
.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)
}
}
}