mirror of
https://github.com/samsonjs/samhuri.net.git
synced 2026-04-27 14:57:40 +00:00
Move template rendering from SiteGenerator to samhuri.net
Also renames samhuri_net module to samhuri.net. Vanity!
This commit is contained in:
parent
527e9e6617
commit
e22c17e810
39 changed files with 276 additions and 264 deletions
|
|
@ -117,6 +117,8 @@ Execution, trying TDD for the first time:
|
||||||
|
|
||||||
- [x] Replace site.json with Swift code
|
- [x] Replace site.json with Swift code
|
||||||
|
|
||||||
|
- [x] Move template rendering from SiteGenerator to samhuri.net
|
||||||
|
|
||||||
- [ ] Replace page template with Swift code
|
- [ ] Replace page template with Swift code
|
||||||
|
|
||||||
- [ ] Replace projects.json with Swift code
|
- [ ] Replace projects.json with Swift code
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ let package = Package(
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/johnsundell/ink.git", from: "0.1.0"),
|
.package(url: "https://github.com/johnsundell/ink.git", from: "0.1.0"),
|
||||||
.package(url: "https://github.com/stencilproject/Stencil.git", from: "0.13.0"),
|
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||||
|
|
@ -26,7 +25,6 @@ let package = Package(
|
||||||
name: "SiteGenerator",
|
name: "SiteGenerator",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"Ink",
|
"Ink",
|
||||||
"Stencil",
|
|
||||||
]),
|
]),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "SiteGeneratorTests",
|
name: "SiteGeneratorTests",
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
//
|
|
||||||
// AnyPlugin.swift
|
|
||||||
// SiteGenerator
|
|
||||||
//
|
|
||||||
// Created by Sami Samhuri on 2019-12-14.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
struct AnyPlugin: Plugin {
|
|
||||||
private let _setUp: (Site, URL) throws -> Void
|
|
||||||
private let _render: (Site, URL, TemplateRenderer) throws -> Void
|
|
||||||
|
|
||||||
init<PluginType: Plugin>(_ plugin: PluginType) {
|
|
||||||
self._setUp = { site, sourceURL in
|
|
||||||
try plugin.setUp(site: site, sourceURL: sourceURL)
|
|
||||||
}
|
|
||||||
self._render = { site, targetURL, templateRenderer in
|
|
||||||
try plugin.render(site: site, targetURL: targetURL, templateRenderer: templateRenderer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setUp(site: Site, sourceURL: URL) throws {
|
|
||||||
try _setUp(site, sourceURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
func render(site: Site, targetURL: URL, templateRenderer: TemplateRenderer) throws {
|
|
||||||
try _render(site, targetURL, templateRenderer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
//
|
|
||||||
// SitePlugin.swift
|
|
||||||
// SiteGenerator
|
|
||||||
//
|
|
||||||
// Created by Sami Samhuri on 2019-12-10.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
public enum SitePlugin: String, Codable {
|
|
||||||
case posts
|
|
||||||
case projects
|
|
||||||
|
|
||||||
func construct(options: [String: Any]) -> AnyPlugin {
|
|
||||||
switch self {
|
|
||||||
case .posts:
|
|
||||||
let plugin = PostsPlugin(options: options)
|
|
||||||
return AnyPlugin(plugin)
|
|
||||||
|
|
||||||
case .projects:
|
|
||||||
let plugin = ProjectsPlugin(options: options)
|
|
||||||
return AnyPlugin(plugin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
//
|
|
||||||
// SiteTemplateRenderer.swift
|
|
||||||
// SiteGenerator
|
|
||||||
//
|
|
||||||
// Created by Sami Samhuri on 2019-12-03.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import PathKit
|
|
||||||
import Stencil
|
|
||||||
|
|
||||||
final class SiteTemplateRenderer: TemplateRenderer {
|
|
||||||
let site: Site
|
|
||||||
let stencil: Environment
|
|
||||||
|
|
||||||
init(site: Site, templatesURL: URL) {
|
|
||||||
self.site = site
|
|
||||||
let templatesPath = Path(templatesURL.path)
|
|
||||||
let loader = FileSystemLoader(paths: [templatesPath])
|
|
||||||
self.stencil = Environment(loader: loader)
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderPage(template: String, bodyHTML: String, metadata: [String: String]) throws -> String {
|
|
||||||
let page = Page(metadata: metadata)
|
|
||||||
let context = PageContext(site: site, body: bodyHTML, page: page, metadata: metadata)
|
|
||||||
let pageHTML = try stencil.renderTemplate(name: template, context: context.dictionary)
|
|
||||||
return pageHTML
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderTemplate(name: String, context: [String: Any]) throws -> String {
|
|
||||||
let siteContext = SiteContext(site: site)
|
|
||||||
let contextDict = siteContext.dictionary.merging(context, uniquingKeysWith: { _, new in new })
|
|
||||||
return try stencil.renderTemplate(name: name, context: contextDict)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
//
|
|
||||||
// templateRenderer.swift
|
|
||||||
// SiteGenerator
|
|
||||||
//
|
|
||||||
// Created by Sami Samhuri on 2019-12-03.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
public protocol TemplateRenderer: AnyObject {
|
|
||||||
func renderPage(template: String, bodyHTML: String, metadata: [String: String]) throws -> String
|
|
||||||
func renderTemplate(name: String, context: [String: Any]) throws -> String
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
//
|
||||||
|
// MarkdownPageRenderer.swift
|
||||||
|
// SiteGenerator
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-03.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public protocol MarkdownPageRenderer {
|
||||||
|
func renderPage(site: Site, bodyHTML: String, metadata: [String: String]) throws -> String
|
||||||
|
}
|
||||||
|
|
@ -11,10 +11,10 @@ import Ink
|
||||||
public final class MarkdownRenderer: Renderer {
|
public final class MarkdownRenderer: Renderer {
|
||||||
let fileManager: FileManager = .default
|
let fileManager: FileManager = .default
|
||||||
let markdownParser = MarkdownParser()
|
let markdownParser = MarkdownParser()
|
||||||
let defaultTemplate: String
|
let pageRenderer: MarkdownPageRenderer
|
||||||
|
|
||||||
public init(defaultTemplate: String) {
|
public init(pageRenderer: MarkdownPageRenderer) {
|
||||||
self.defaultTemplate = defaultTemplate
|
self.pageRenderer = pageRenderer
|
||||||
}
|
}
|
||||||
|
|
||||||
public func canRenderFile(named filename: String, withExtension ext: String) -> Bool {
|
public func canRenderFile(named filename: String, withExtension ext: String) -> Bool {
|
||||||
|
|
@ -22,11 +22,11 @@ public final class MarkdownRenderer: Renderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse Markdown and render it as HTML, running it through a Stencil template.
|
/// Parse Markdown and render it as HTML, running it through a Stencil template.
|
||||||
public func render(fileURL: URL, targetDir: URL, templateRenderer: TemplateRenderer) throws {
|
public func render(site: Site, fileURL: URL, targetDir: URL) throws {
|
||||||
let bodyMarkdown = try String(contentsOf: fileURL, encoding: .utf8)
|
let bodyMarkdown = try String(contentsOf: fileURL, encoding: .utf8)
|
||||||
let bodyHTML = markdownParser.html(from: bodyMarkdown).trimmingCharacters(in: .whitespacesAndNewlines)
|
let bodyHTML = markdownParser.html(from: bodyMarkdown).trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
let metadata = try markdownMetadata(from: fileURL)
|
let metadata = try markdownMetadata(from: fileURL)
|
||||||
let pageHTML = try templateRenderer.renderPage(template: defaultTemplate, bodyHTML: bodyHTML, metadata: metadata)
|
let pageHTML = try pageRenderer.renderPage(site: site, bodyHTML: bodyHTML, metadata: metadata)
|
||||||
|
|
||||||
let mdFilename = fileURL.lastPathComponent
|
let mdFilename = fileURL.lastPathComponent
|
||||||
let htmlPath: String
|
let htmlPath: String
|
||||||
|
|
@ -10,5 +10,5 @@ import Foundation
|
||||||
public protocol Plugin {
|
public protocol Plugin {
|
||||||
func setUp(site: Site, sourceURL: URL) throws
|
func setUp(site: Site, sourceURL: URL) throws
|
||||||
|
|
||||||
func render(site: Site, targetURL: URL, templateRenderer: TemplateRenderer) throws
|
func render(site: Site, targetURL: URL) throws
|
||||||
}
|
}
|
||||||
|
|
@ -44,7 +44,7 @@ final class JSONFeedWriter {
|
||||||
self.jsonFeed = feed
|
self.jsonFeed = feed
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeFeed(_ posts: [Post], site: Site, to targetURL: URL, with templateRenderer: TemplateRenderer) throws {
|
func writeFeed(_ posts: [Post], for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws {
|
||||||
let items: [FeedItem] = try posts.map { post in
|
let items: [FeedItem] = try posts.map { post in
|
||||||
let url = site.url.appendingPathComponent(post.path)
|
let url = site.url.appendingPathComponent(post.path)
|
||||||
return FeedItem(
|
return FeedItem(
|
||||||
|
|
@ -54,7 +54,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.renderTemplate(name: "feed-post.html", context: [
|
content_html: try templateRenderer.renderTemplate(.feedPost, site: site, context: [
|
||||||
"post": post,
|
"post": post,
|
||||||
]),
|
]),
|
||||||
tags: post.tags
|
tags: post.tags
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ final class RSSFeedWriter {
|
||||||
self.feed = feed
|
self.feed = feed
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeFeed(_ posts: [Post], site: Site, to targetURL: URL, with templateRenderer: TemplateRenderer) throws {
|
func writeFeed(_ posts: [Post], for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws {
|
||||||
let feedSite = FeedSite(
|
let feedSite = FeedSite(
|
||||||
title: site.title.escapedForXML(),
|
title: site.title.escapedForXML(),
|
||||||
description: site.description?.escapedForXML(),
|
description: site.description?.escapedForXML(),
|
||||||
|
|
@ -67,12 +67,12 @@ final class RSSFeedWriter {
|
||||||
author: author.escapedForXML(),
|
author: author.escapedForXML(),
|
||||||
link: (post.link ?? url).absoluteString.escapedForXML(),
|
link: (post.link ?? url).absoluteString.escapedForXML(),
|
||||||
guid: url.absoluteString.escapedForXML(),
|
guid: url.absoluteString.escapedForXML(),
|
||||||
body: try templateRenderer.renderTemplate(name: "feed-post.html", context: [
|
body: try templateRenderer.renderTemplate(.feedPost, site: site, context: [
|
||||||
"post": post,
|
"post": post,
|
||||||
]).escapedForXML()
|
]).escapedForXML()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
let feedXML = try templateRenderer.renderTemplate(name: "feed.xml", context: [
|
let feedXML = try templateRenderer.renderTemplate(.rssFeed, site: site, context: [
|
||||||
"site": feedSite,
|
"site": feedSite,
|
||||||
"feedURL": site.url.appendingPathComponent(feed.path).absoluteString.escapedForXML(),
|
"feedURL": site.url.appendingPathComponent(feed.path).absoluteString.escapedForXML(),
|
||||||
"posts": renderedPosts,
|
"posts": renderedPosts,
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ final class PostWriter {
|
||||||
// MARK: - Post pages
|
// MARK: - Post pages
|
||||||
|
|
||||||
extension PostWriter {
|
extension PostWriter {
|
||||||
func writePosts(_ posts: [Post], to targetURL: URL, with templateRenderer: TemplateRenderer) throws {
|
func writePosts(_ posts: [Post], for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws {
|
||||||
for post in posts {
|
for post in posts {
|
||||||
let postHTML = try templateRenderer.renderTemplate(name: "post.html", context: [
|
let postHTML = try templateRenderer.renderTemplate(.post, site: site, context: [
|
||||||
"title": post.title,
|
"title": post.title,
|
||||||
"post": post,
|
"post": post,
|
||||||
])
|
])
|
||||||
|
|
@ -42,8 +42,8 @@ extension PostWriter {
|
||||||
// MARK: - Recent posts page
|
// MARK: - Recent posts page
|
||||||
|
|
||||||
extension PostWriter {
|
extension PostWriter {
|
||||||
func writeRecentPosts(_ recentPosts: [Post], to targetURL: URL, with templateRenderer: TemplateRenderer) throws {
|
func writeRecentPosts(_ recentPosts: [Post], for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws {
|
||||||
let recentPostsHTML = try templateRenderer.renderTemplate(name: "recent-posts.html", context: [
|
let recentPostsHTML = try templateRenderer.renderTemplate(.recentPosts, site: site, context: [
|
||||||
"recentPosts": recentPosts,
|
"recentPosts": recentPosts,
|
||||||
])
|
])
|
||||||
let fileURL = targetURL.appendingPathComponent("index.html")
|
let fileURL = targetURL.appendingPathComponent("index.html")
|
||||||
|
|
@ -55,9 +55,9 @@ extension PostWriter {
|
||||||
// MARK: - Post archive page
|
// MARK: - Post archive page
|
||||||
|
|
||||||
extension PostWriter {
|
extension PostWriter {
|
||||||
func writeArchive(posts: PostsByYear, to targetURL: URL, with templateRenderer: TemplateRenderer) throws {
|
func writeArchive(posts: PostsByYear, for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws {
|
||||||
let allYears = posts.byYear.keys.sorted(by: >)
|
let allYears = posts.byYear.keys.sorted(by: >)
|
||||||
let archiveHTML = try templateRenderer.renderTemplate(name: "posts-archive.html", context: [
|
let archiveHTML = try templateRenderer.renderTemplate(.archive, site: site, context: [
|
||||||
"title": "Archive",
|
"title": "Archive",
|
||||||
"years": allYears.map { contextDictionaryForYearPosts(posts[$0]) },
|
"years": allYears.map { contextDictionaryForYearPosts(posts[$0]) },
|
||||||
])
|
])
|
||||||
|
|
@ -89,7 +89,7 @@ extension PostWriter {
|
||||||
// MARK: - Yearly post index pages
|
// MARK: - Yearly post index pages
|
||||||
|
|
||||||
extension PostWriter {
|
extension PostWriter {
|
||||||
func writeYearIndexes(posts: PostsByYear, to targetURL: URL, with templateRenderer: TemplateRenderer) throws {
|
func writeYearIndexes(posts: PostsByYear, for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws {
|
||||||
for (year, yearPosts) in posts.byYear {
|
for (year, yearPosts) in posts.byYear {
|
||||||
let months = yearPosts.months.sorted(by: >)
|
let months = yearPosts.months.sorted(by: >)
|
||||||
let yearDir = targetURL.appendingPathComponent(yearPosts.path)
|
let yearDir = targetURL.appendingPathComponent(yearPosts.path)
|
||||||
|
|
@ -99,7 +99,7 @@ extension PostWriter {
|
||||||
"year": year,
|
"year": year,
|
||||||
"months": months.map { contextDictionaryForMonthPosts(posts[year][$0], year: year) },
|
"months": months.map { contextDictionaryForMonthPosts(posts[year][$0], year: year) },
|
||||||
]
|
]
|
||||||
let yearHTML = try templateRenderer.renderTemplate(name: "posts-year.html", context: context)
|
let yearHTML = try templateRenderer.renderTemplate(.yearPosts, site: site, context: context)
|
||||||
let yearURL = yearDir.appendingPathComponent("index.html")
|
let yearURL = yearDir.appendingPathComponent("index.html")
|
||||||
try fileManager.createDirectory(at: yearDir, withIntermediateDirectories: true, attributes: nil)
|
try fileManager.createDirectory(at: yearDir, withIntermediateDirectories: true, attributes: nil)
|
||||||
try yearHTML.write(to: yearURL, atomically: true, encoding: .utf8)
|
try yearHTML.write(to: yearURL, atomically: true, encoding: .utf8)
|
||||||
|
|
@ -110,12 +110,12 @@ extension PostWriter {
|
||||||
// MARK: - Monthly post roll-up pages
|
// MARK: - Monthly post roll-up pages
|
||||||
|
|
||||||
extension PostWriter {
|
extension PostWriter {
|
||||||
func writeMonthRollups(posts: PostsByYear, to targetURL: URL, with templateRenderer: TemplateRenderer) throws {
|
func writeMonthRollups(posts: PostsByYear, for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws {
|
||||||
for (year, yearPosts) in posts.byYear {
|
for (year, yearPosts) in posts.byYear {
|
||||||
for month in yearPosts.months {
|
for month in yearPosts.months {
|
||||||
let monthPosts = yearPosts[month]
|
let monthPosts = yearPosts[month]
|
||||||
let monthDir = targetURL.appendingPathComponent(monthPosts.path)
|
let monthDir = targetURL.appendingPathComponent(monthPosts.path)
|
||||||
let monthHTML = try templateRenderer.renderTemplate(name: "posts-month.html", context: [
|
let monthHTML = try templateRenderer.renderTemplate(.monthPosts, site: site, context: [
|
||||||
"title": "\(month.name) \(year)",
|
"title": "\(month.name) \(year)",
|
||||||
"posts": monthPosts.posts.sorted(by: >),
|
"posts": monthPosts.posts.sorted(by: >),
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -8,17 +8,20 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public final class PostsPlugin: Plugin {
|
public final class PostsPlugin: Plugin {
|
||||||
|
let templateRenderer: PostsTemplateRenderer
|
||||||
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,
|
||||||
postRepo: PostRepo = PostRepo(),
|
postRepo: PostRepo = PostRepo(),
|
||||||
postWriter: PostWriter = PostWriter(),
|
postWriter: PostWriter = PostWriter(),
|
||||||
jsonFeedWriter: JSONFeedWriter?,
|
jsonFeedWriter: JSONFeedWriter?,
|
||||||
rssFeedWriter: RSSFeedWriter?
|
rssFeedWriter: RSSFeedWriter?
|
||||||
) {
|
) {
|
||||||
|
self.templateRenderer = templateRenderer
|
||||||
self.postRepo = postRepo
|
self.postRepo = postRepo
|
||||||
self.postWriter = postWriter
|
self.postWriter = postWriter
|
||||||
self.jsonFeedWriter = jsonFeedWriter
|
self.jsonFeedWriter = jsonFeedWriter
|
||||||
|
|
@ -27,39 +30,6 @@ public final class PostsPlugin: Plugin {
|
||||||
|
|
||||||
// MARK: - Plugin methods
|
// MARK: - Plugin methods
|
||||||
|
|
||||||
convenience init(options: [String: Any]) {
|
|
||||||
let postRepo: PostRepo
|
|
||||||
let postWriter: PostWriter
|
|
||||||
if let outputPath = options["path"] as? String {
|
|
||||||
postRepo = PostRepo(outputPath: outputPath)
|
|
||||||
postWriter = PostWriter(outputPath: outputPath)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
postRepo = PostRepo()
|
|
||||||
postWriter = PostWriter()
|
|
||||||
}
|
|
||||||
|
|
||||||
let jsonFeedWriter: JSONFeedWriter?
|
|
||||||
if let jsonFeedPath = options["json_feed"] as? String {
|
|
||||||
let jsonFeed = JSONFeed(path: jsonFeedPath, avatarPath: nil, iconPath: nil, faviconPath: nil)
|
|
||||||
jsonFeedWriter = JSONFeedWriter(feed: jsonFeed)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
jsonFeedWriter = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let rssFeedWriter: RSSFeedWriter?
|
|
||||||
if let rssFeedPath = options["rss_feed"] as? String {
|
|
||||||
let rssFeed = RSSFeed(path: rssFeedPath)
|
|
||||||
rssFeedWriter = RSSFeedWriter(feed: rssFeed)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rssFeedWriter = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
self.init(postRepo: postRepo, postWriter: postWriter, jsonFeedWriter: jsonFeedWriter, rssFeedWriter: rssFeedWriter)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func setUp(site: Site, sourceURL: URL) throws {
|
public func setUp(site: Site, sourceURL: URL) throws {
|
||||||
guard postRepo.postDataExists(at: sourceURL) else {
|
guard postRepo.postDataExists(at: sourceURL) else {
|
||||||
return
|
return
|
||||||
|
|
@ -68,17 +38,17 @@ public final class PostsPlugin: Plugin {
|
||||||
try postRepo.readPosts(sourceURL: sourceURL)
|
try postRepo.readPosts(sourceURL: sourceURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func render(site: Site, targetURL: URL, templateRenderer: TemplateRenderer) throws {
|
public func render(site: Site, targetURL: URL) throws {
|
||||||
guard !postRepo.isEmpty else {
|
guard !postRepo.isEmpty else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try postWriter.writeRecentPosts(postRepo.recentPosts, to: targetURL, with: templateRenderer)
|
try postWriter.writeRecentPosts(postRepo.recentPosts, for: site, to: targetURL, with: templateRenderer)
|
||||||
try postWriter.writePosts(postRepo.sortedPosts, to: targetURL, with: templateRenderer)
|
try postWriter.writePosts(postRepo.sortedPosts, for: site, to: targetURL, with: templateRenderer)
|
||||||
try postWriter.writeArchive(posts: postRepo.posts, to: targetURL, with: templateRenderer)
|
try postWriter.writeArchive(posts: postRepo.posts, for: site, to: targetURL, with: templateRenderer)
|
||||||
try postWriter.writeYearIndexes(posts: postRepo.posts, to: targetURL, with: templateRenderer)
|
try postWriter.writeYearIndexes(posts: postRepo.posts, for: site, to: targetURL, with: templateRenderer)
|
||||||
try postWriter.writeMonthRollups(posts: postRepo.posts, to: targetURL, with: templateRenderer)
|
try postWriter.writeMonthRollups(posts: postRepo.posts, for: site, to: targetURL, with: templateRenderer)
|
||||||
try jsonFeedWriter?.writeFeed(postRepo.postsForFeed, site: site, to: targetURL, with: templateRenderer)
|
try jsonFeedWriter?.writeFeed(postRepo.postsForFeed, for: site, to: targetURL, with: templateRenderer)
|
||||||
try rssFeedWriter?.writeFeed(postRepo.postsForFeed, site: site, to: targetURL, with: templateRenderer)
|
try rssFeedWriter?.writeFeed(postRepo.postsForFeed, for: site, to: targetURL, with: templateRenderer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,14 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public final class PostsPluginBuilder {
|
public final class PostsPluginBuilder {
|
||||||
|
private let templateRenderer: PostsTemplateRenderer
|
||||||
private var path: String?
|
private var path: String?
|
||||||
private var jsonFeed: JSONFeed?
|
private var jsonFeed: JSONFeed?
|
||||||
private var rssFeed: RSSFeed?
|
private var rssFeed: RSSFeed?
|
||||||
|
|
||||||
public init() {}
|
public init(templateRenderer: PostsTemplateRenderer) {
|
||||||
|
self.templateRenderer = templateRenderer
|
||||||
|
}
|
||||||
|
|
||||||
public func path(_ path: String) -> PostsPluginBuilder {
|
public func path(_ path: String) -> PostsPluginBuilder {
|
||||||
precondition(self.path == nil, "path is already defined")
|
precondition(self.path == nil, "path is already defined")
|
||||||
|
|
@ -71,6 +74,7 @@ public final class PostsPluginBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
return PostsPlugin(
|
return PostsPlugin(
|
||||||
|
templateRenderer: templateRenderer,
|
||||||
postRepo: postRepo,
|
postRepo: postRepo,
|
||||||
postWriter: postWriter,
|
postWriter: postWriter,
|
||||||
jsonFeedWriter: jsonFeedWriter,
|
jsonFeedWriter: jsonFeedWriter,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
//
|
||||||
|
// PostsTemplateRenderer.swift
|
||||||
|
// SiteGenerator
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum PostTemplate {
|
||||||
|
case archive
|
||||||
|
case feedPost
|
||||||
|
case monthPosts
|
||||||
|
case post
|
||||||
|
case recentPosts
|
||||||
|
case rssFeed
|
||||||
|
case yearPosts
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol PostsTemplateRenderer {
|
||||||
|
func renderTemplate(_ template: PostTemplate, site: Site, context: [String: Any]) throws -> String
|
||||||
|
}
|
||||||
|
|
@ -20,22 +20,17 @@ private struct Projects: Codable {
|
||||||
final class ProjectsPlugin: Plugin {
|
final class ProjectsPlugin: Plugin {
|
||||||
let fileManager: FileManager = .default
|
let fileManager: FileManager = .default
|
||||||
let outputPath: String
|
let outputPath: String
|
||||||
|
let templateRenderer: ProjectsTemplateRenderer
|
||||||
|
|
||||||
var projects: [Project] = []
|
var projects: [Project] = []
|
||||||
var sourceURL: URL!
|
var sourceURL: URL!
|
||||||
|
|
||||||
init(outputPath: String? = nil) {
|
init(templateRenderer: ProjectsTemplateRenderer, outputPath: String? = nil) {
|
||||||
|
self.templateRenderer = templateRenderer
|
||||||
self.outputPath = outputPath ?? "projects"
|
self.outputPath = outputPath ?? "projects"
|
||||||
}
|
}
|
||||||
|
|
||||||
convenience init(options: [String: Any]) {
|
// MARK: - Plugin methods
|
||||||
if let outputPath = options["path"] as? String {
|
|
||||||
self.init(outputPath: outputPath)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
self.init()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setUp(site: Site, sourceURL: URL) throws {
|
func setUp(site: Site, sourceURL: URL) throws {
|
||||||
self.sourceURL = sourceURL
|
self.sourceURL = sourceURL
|
||||||
|
|
@ -47,7 +42,7 @@ final class ProjectsPlugin: Plugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func render(site: Site, targetURL: URL, templateRenderer: TemplateRenderer) throws {
|
func render(site: Site, targetURL: URL) throws {
|
||||||
guard !projects.isEmpty else {
|
guard !projects.isEmpty else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -55,7 +50,7 @@ final class ProjectsPlugin: Plugin {
|
||||||
let projectsDir = targetURL.appendingPathComponent(outputPath)
|
let projectsDir = targetURL.appendingPathComponent(outputPath)
|
||||||
try fileManager.createDirectory(at: projectsDir, withIntermediateDirectories: true, attributes: nil)
|
try fileManager.createDirectory(at: projectsDir, withIntermediateDirectories: true, attributes: nil)
|
||||||
let projectsURL = projectsDir.appendingPathComponent("index.html")
|
let projectsURL = projectsDir.appendingPathComponent("index.html")
|
||||||
let projectsHTML = try templateRenderer.renderTemplate(name: "projects.html", context: [
|
let projectsHTML = try templateRenderer.renderTemplate(.projects, site: site, context: [
|
||||||
"title": "Projects",
|
"title": "Projects",
|
||||||
"projects": projects,
|
"projects": projects,
|
||||||
])
|
])
|
||||||
|
|
@ -63,7 +58,7 @@ final class ProjectsPlugin: Plugin {
|
||||||
|
|
||||||
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.renderTemplate(name: "project.html", context: [
|
let projectHTML = try templateRenderer.renderTemplate(.project, site: site, context: [
|
||||||
"title": "\(project.title)",
|
"title": "\(project.title)",
|
||||||
"project": project,
|
"project": project,
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
//
|
||||||
|
// ProjectsTemplateRenderer.swift
|
||||||
|
// SiteGenerator
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum ProjectTemplate {
|
||||||
|
case project
|
||||||
|
case projects
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol ProjectsTemplateRenderer {
|
||||||
|
func renderTemplate(_ template: ProjectTemplate, site: Site, context: [String: Any]) throws -> String
|
||||||
|
}
|
||||||
|
|
@ -10,5 +10,5 @@ import Foundation
|
||||||
public protocol Renderer {
|
public protocol Renderer {
|
||||||
func canRenderFile(named filename: String, withExtension ext: String) -> Bool
|
func canRenderFile(named filename: String, withExtension ext: String) -> Bool
|
||||||
|
|
||||||
func render(fileURL: URL, targetDir: URL, templateRenderer: TemplateRenderer) throws
|
func render(site: Site, fileURL: URL, targetDir: URL) throws
|
||||||
}
|
}
|
||||||
|
|
@ -85,16 +85,16 @@ public final class SiteBuilder {
|
||||||
// MARK: - Markdown
|
// MARK: - Markdown
|
||||||
|
|
||||||
public extension SiteBuilder {
|
public extension SiteBuilder {
|
||||||
func renderMarkdown(defaultTemplate: String) -> SiteBuilder {
|
func renderMarkdown(pageRenderer: MarkdownPageRenderer) -> SiteBuilder {
|
||||||
renderer(MarkdownRenderer(defaultTemplate: defaultTemplate))
|
renderer(MarkdownRenderer(pageRenderer: pageRenderer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Projects
|
// MARK: - Projects
|
||||||
|
|
||||||
public extension SiteBuilder {
|
public extension SiteBuilder {
|
||||||
func projects(path: String? = nil) -> SiteBuilder {
|
func projects(templateRenderer: ProjectsTemplateRenderer, path: String? = nil) -> SiteBuilder {
|
||||||
plugin(ProjectsPlugin(outputPath: path))
|
plugin(ProjectsPlugin(templateRenderer: templateRenderer, outputPath: path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import Foundation
|
||||||
public final class SiteGenerator {
|
public final class SiteGenerator {
|
||||||
// Dependencies
|
// Dependencies
|
||||||
let fileManager: FileManager = .default
|
let fileManager: FileManager = .default
|
||||||
let templateRenderer: TemplateRenderer
|
|
||||||
|
|
||||||
// Site properties
|
// Site properties
|
||||||
public let site: Site
|
public let site: Site
|
||||||
|
|
@ -22,9 +21,6 @@ public final class SiteGenerator {
|
||||||
self.site = site
|
self.site = site
|
||||||
self.sourceURL = sourceURL
|
self.sourceURL = sourceURL
|
||||||
|
|
||||||
let templatesURL = sourceURL.appendingPathComponent("templates")
|
|
||||||
self.templateRenderer = SiteTemplateRenderer(site: site, templatesURL: templatesURL)
|
|
||||||
|
|
||||||
try initializePlugins()
|
try initializePlugins()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +32,7 @@ public final class SiteGenerator {
|
||||||
|
|
||||||
public func generate(targetURL: URL) throws {
|
public func generate(targetURL: URL) throws {
|
||||||
for plugin in site.plugins {
|
for plugin in site.plugins {
|
||||||
try plugin.render(site: site, targetURL: targetURL, templateRenderer: templateRenderer)
|
try plugin.render(site: site, targetURL: targetURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
let publicURL = sourceURL.appendingPathComponent("public")
|
let publicURL = sourceURL.appendingPathComponent("public")
|
||||||
|
|
@ -72,7 +68,7 @@ public final class SiteGenerator {
|
||||||
let ext = String(filename.split(separator: ".").last!)
|
let ext = String(filename.split(separator: ".").last!)
|
||||||
for renderer in site.renderers {
|
for renderer in site.renderers {
|
||||||
if renderer.canRenderFile(named: filename, withExtension: ext) {
|
if renderer.canRenderFile(named: filename, withExtension: ext) {
|
||||||
try renderer.render(fileURL: fileURL, targetDir: targetDir, templateRenderer: templateRenderer)
|
try renderer.render(site: site, fileURL: fileURL, targetDir: targetDir)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,11 @@ let package = Package(
|
||||||
.iOS(.v13),
|
.iOS(.v13),
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(path: "../samhuri_net"),
|
.package(path: "../samhuri.net"),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target( name: "gensite", dependencies: [
|
.target( name: "gensite", dependencies: [
|
||||||
"samhuri_net",
|
"samhuri.net",
|
||||||
]),
|
]),
|
||||||
.testTarget(name: "gensiteTests", dependencies: ["gensite"]),
|
.testTarget(name: "gensiteTests", dependencies: ["gensite"]),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,6 @@ import Darwin
|
||||||
import Foundation
|
import Foundation
|
||||||
import samhuri_net
|
import samhuri_net
|
||||||
|
|
||||||
func main(sourcePath: String, targetPath: String, siteURLOverride: URL?) throws {
|
|
||||||
let sourceURL = URL(fileURLWithPath: sourcePath)
|
|
||||||
let targetURL = URL(fileURLWithPath: targetPath)
|
|
||||||
let site = samhuri_net()
|
|
||||||
try site.generate(sourceURL: sourceURL, targetURL: targetURL, siteURLOverride: siteURLOverride)
|
|
||||||
}
|
|
||||||
|
|
||||||
guard CommandLine.arguments.count >= 3 else {
|
guard CommandLine.arguments.count >= 3 else {
|
||||||
let name = CommandLine.arguments[0]
|
let name = CommandLine.arguments[0]
|
||||||
fputs("Usage: \(name) <site dir> <target dir>", stderr)
|
fputs("Usage: \(name) <site dir> <target dir>", stderr)
|
||||||
|
|
@ -51,7 +44,10 @@ else {
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try main(sourcePath: sourcePath, targetPath: targetPath, siteURLOverride: siteURLOverride)
|
let sourceURL = URL(fileURLWithPath: sourcePath)
|
||||||
|
let targetURL = URL(fileURLWithPath: targetPath)
|
||||||
|
let site = samhuri.net(siteURLOverride: siteURLOverride)
|
||||||
|
try site.generate(sourceURL: sourceURL, targetURL: targetURL)
|
||||||
exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
import PackageDescription
|
import PackageDescription
|
||||||
|
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "samhuri_net",
|
name: "samhuri.net",
|
||||||
platforms: [
|
platforms: [
|
||||||
.macOS(.v10_15),
|
.macOS(.v10_15),
|
||||||
.iOS(.v13),
|
.iOS(.v13),
|
||||||
|
|
@ -12,22 +12,24 @@ let package = Package(
|
||||||
products: [
|
products: [
|
||||||
// Products define the executables and libraries produced by a package, and make them visible to other packages.
|
// Products define the executables and libraries produced by a package, and make them visible to other packages.
|
||||||
.library(
|
.library(
|
||||||
name: "samhuri_net",
|
name: "samhuri.net",
|
||||||
targets: ["samhuri_net"]),
|
targets: ["samhuri.net"]),
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(path: "../SiteGenerator"),
|
.package(path: "../SiteGenerator"),
|
||||||
|
.package(url: "https://github.com/stencilproject/Stencil.git", from: "0.13.0"),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||||
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
|
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
|
||||||
.target(
|
.target(
|
||||||
name: "samhuri_net",
|
name: "samhuri.net",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"SiteGenerator",
|
"SiteGenerator",
|
||||||
|
"Stencil",
|
||||||
]),
|
]),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "samhuri_netTests",
|
name: "samhuri.netTests",
|
||||||
dependencies: ["samhuri_net"]),
|
dependencies: ["samhuri.net"]),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//
|
//
|
||||||
// Page.swift
|
// Page.swift
|
||||||
// SiteGenerator
|
// samhuri.net
|
||||||
//
|
//
|
||||||
// Created by Sami Samhuri on 2019-12-01.
|
// Created by Sami Samhuri on 2019-12-01.
|
||||||
//
|
//
|
||||||
|
|
@ -9,14 +9,12 @@ import Foundation
|
||||||
|
|
||||||
struct Page {
|
struct Page {
|
||||||
let title: String
|
let title: String
|
||||||
let template: String?
|
|
||||||
let styles: [String]
|
let styles: [String]
|
||||||
let scripts: [String]
|
let scripts: [String]
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Page {
|
extension Page {
|
||||||
init(metadata: [String: String]) {
|
init(metadata: [String: String]) {
|
||||||
let template = metadata["Template"]
|
|
||||||
let styles = metadata["Styles", default: ""]
|
let styles = metadata["Styles", default: ""]
|
||||||
.split(separator: ",")
|
.split(separator: ",")
|
||||||
.map { $0.trimmingCharacters(in: .whitespaces) }
|
.map { $0.trimmingCharacters(in: .whitespaces) }
|
||||||
|
|
@ -24,6 +22,6 @@ extension Page {
|
||||||
.split(separator: ",")
|
.split(separator: ",")
|
||||||
.map { $0.trimmingCharacters(in: .whitespaces) }
|
.map { $0.trimmingCharacters(in: .whitespaces) }
|
||||||
let title = metadata["Title", default: ""]
|
let title = metadata["Title", default: ""]
|
||||||
self.init(title: title, template: template, styles: styles, scripts: scripts)
|
self.init(title: title, styles: styles, scripts: scripts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
//
|
//
|
||||||
// File.swift
|
// PageContext.swift
|
||||||
//
|
// samhuri.net
|
||||||
//
|
//
|
||||||
// Created by Sami Samhuri on 2019-12-02.
|
// Created by Sami Samhuri on 2019-12-02.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import SiteGenerator
|
||||||
|
|
||||||
struct PageContext {
|
struct PageContext {
|
||||||
let site: Site
|
let site: Site
|
||||||
87
samhuri.net/Sources/samhuri.net/PageRenderer.swift
Normal file
87
samhuri.net/Sources/samhuri.net/PageRenderer.swift
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
//
|
||||||
|
// PageRenderer.swift
|
||||||
|
// samhuri.net
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import PathKit
|
||||||
|
import SiteGenerator
|
||||||
|
import Stencil
|
||||||
|
|
||||||
|
final class PageRenderer {
|
||||||
|
@available(*, deprecated)
|
||||||
|
let stencil: Environment
|
||||||
|
|
||||||
|
init(templatesURL: URL) {
|
||||||
|
let templatesPath = Path(templatesURL.path)
|
||||||
|
let loader = FileSystemLoader(paths: [templatesPath])
|
||||||
|
self.stencil = Environment(loader: loader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PageRenderer: MarkdownPageRenderer {
|
||||||
|
func renderPage(site: Site, bodyHTML: String, metadata: [String: String]) throws -> String {
|
||||||
|
let page = Page(metadata: metadata)
|
||||||
|
let context = PageContext(site: site, body: bodyHTML, page: page, metadata: metadata)
|
||||||
|
let pageHTML = try stencil.renderTemplate(name: "page.html", context: context.dictionary)
|
||||||
|
return pageHTML
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PostTemplate {
|
||||||
|
@available(*, deprecated)
|
||||||
|
var htmlFilename: String {
|
||||||
|
switch self {
|
||||||
|
case .archive:
|
||||||
|
return "posts-archive.html"
|
||||||
|
case .feedPost:
|
||||||
|
return "feed-post.html"
|
||||||
|
case .monthPosts:
|
||||||
|
return "posts-month.html"
|
||||||
|
case .post:
|
||||||
|
return "post.html"
|
||||||
|
case .recentPosts:
|
||||||
|
return "recent-posts.html"
|
||||||
|
case .rssFeed:
|
||||||
|
return "feed.xml"
|
||||||
|
case .yearPosts:
|
||||||
|
return "posts-year.html"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PageRenderer: PostsTemplateRenderer {
|
||||||
|
func renderTemplate(_ template: PostTemplate, site: Site, context: [String : Any]) throws -> String {
|
||||||
|
let siteContext = SiteContext(site: site)
|
||||||
|
let contextDict = siteContext.dictionary.merging(context, uniquingKeysWith: { _, new in new })
|
||||||
|
return try stencil.renderTemplate(name: template.htmlFilename, context: contextDict)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ProjectTemplate {
|
||||||
|
@available(*, deprecated)
|
||||||
|
var htmlFilename: String {
|
||||||
|
switch self {
|
||||||
|
case .project:
|
||||||
|
return "project.html"
|
||||||
|
case .projects:
|
||||||
|
return "projects.html"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PageRenderer: ProjectsTemplateRenderer {
|
||||||
|
func renderTemplate(_ template: ProjectTemplate, site: Site, context: [String : Any]) throws -> String {
|
||||||
|
let siteContext = SiteContext(site: site)
|
||||||
|
let contextDict = siteContext.dictionary.merging(context, uniquingKeysWith: { _, new in new })
|
||||||
|
return try stencil.renderTemplate(name: template.htmlFilename, context: contextDict)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Date {
|
||||||
|
var year: Int {
|
||||||
|
Calendar.current.dateComponents([.year], from: self).year!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
//
|
//
|
||||||
// SiteContext.swift
|
// SiteContext.swift
|
||||||
// SiteGenerator
|
// samhuri.net
|
||||||
//
|
//
|
||||||
// Created by Sami Samhuri on 2019-12-01.
|
// Created by Sami Samhuri on 2019-12-01.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import SiteGenerator
|
||||||
|
|
||||||
struct SiteContext {
|
struct SiteContext {
|
||||||
let site: Site
|
let site: Site
|
||||||
47
samhuri.net/Sources/samhuri.net/samhuri.net.swift
Normal file
47
samhuri.net/Sources/samhuri.net/samhuri.net.swift
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import Foundation
|
||||||
|
import SiteGenerator
|
||||||
|
|
||||||
|
public enum samhuri {}
|
||||||
|
|
||||||
|
public extension samhuri {
|
||||||
|
struct net {
|
||||||
|
let siteURLOverride: URL?
|
||||||
|
|
||||||
|
public init(siteURLOverride: URL? = nil) {
|
||||||
|
self.siteURLOverride = siteURLOverride
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildSite(renderer: PageRenderer) -> Site {
|
||||||
|
let postsPlugin = PostsPluginBuilder(templateRenderer: renderer)
|
||||||
|
.path("posts")
|
||||||
|
.jsonFeed(
|
||||||
|
avatarPath: "images/me.jpg",
|
||||||
|
iconPath: "images/apple-touch-icon-300.png",
|
||||||
|
faviconPath: "images/apple-touch-icon-80.png"
|
||||||
|
)
|
||||||
|
.rssFeed()
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return SiteBuilder(
|
||||||
|
author: "Sami Samhuri",
|
||||||
|
email: "sami@samhuri.net",
|
||||||
|
title: "samhuri.net",
|
||||||
|
description: "just some blog",
|
||||||
|
url: siteURLOverride ?? URL(string: "https://samhuri.net")!
|
||||||
|
)
|
||||||
|
.styles("/css/normalize.css", "/css/style.css")
|
||||||
|
.renderMarkdown(pageRenderer: renderer)
|
||||||
|
.projects(templateRenderer: renderer)
|
||||||
|
.plugin(postsPlugin)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func generate(sourceURL: URL, targetURL: URL) throws {
|
||||||
|
let templatesURL = sourceURL.appendingPathComponent("templates")
|
||||||
|
let renderer = PageRenderer(templatesURL: templatesURL)
|
||||||
|
let site = buildSite(renderer: renderer)
|
||||||
|
let generator = try SiteGenerator(sourceURL: sourceURL, site: site)
|
||||||
|
try generator.generate(targetURL: targetURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
import samhuri_netTests
|
import samhuri.net.Tests
|
||||||
|
|
||||||
var tests = [XCTestCaseEntry]()
|
var tests = [XCTestCaseEntry]()
|
||||||
tests += samhuri_netTests.allTests()
|
tests += samhuri.net.Tests.allTests()
|
||||||
XCTMain(tests)
|
XCTMain(tests)
|
||||||
|
|
@ -3,7 +3,7 @@ import XCTest
|
||||||
#if !canImport(ObjectiveC)
|
#if !canImport(ObjectiveC)
|
||||||
public func allTests() -> [XCTestCaseEntry] {
|
public func allTests() -> [XCTestCaseEntry] {
|
||||||
return [
|
return [
|
||||||
testCase(samhuri_netTests.allTests),
|
testCase(samhuri.net.Tests.allTests),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
14
samhuri.net/Tests/samhuri.netTests/samhuri.netTests.swift
Normal file
14
samhuri.net/Tests/samhuri.netTests/samhuri.netTests.swift
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import XCTest
|
||||||
|
@testable import samhuri_net
|
||||||
|
|
||||||
|
extension samhuri.net {
|
||||||
|
final class Tests: XCTestCase {
|
||||||
|
func testExample() {
|
||||||
|
XCTAssert(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
static var allTests = [
|
||||||
|
("testExample", testExample),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
import Foundation
|
|
||||||
import SiteGenerator
|
|
||||||
|
|
||||||
public struct samhuri_net {
|
|
||||||
public init() {}
|
|
||||||
|
|
||||||
public func generate(sourceURL: URL, targetURL: URL, siteURLOverride: URL? = nil) throws {
|
|
||||||
let postsPlugin = PostsPluginBuilder()
|
|
||||||
.path("posts")
|
|
||||||
.jsonFeed(
|
|
||||||
avatarPath: "images/me.jpg",
|
|
||||||
iconPath: "images/apple-touch-icon-300.png",
|
|
||||||
faviconPath: "images/apple-touch-icon-80.png"
|
|
||||||
)
|
|
||||||
.rssFeed()
|
|
||||||
.build()
|
|
||||||
let site = SiteBuilder(
|
|
||||||
author: "Sami Samhuri",
|
|
||||||
email: "sami@samhuri.net",
|
|
||||||
title: "samhuri.net",
|
|
||||||
description: "just some blog",
|
|
||||||
url: siteURLOverride ?? URL(string: "https://samhuri.net")!
|
|
||||||
)
|
|
||||||
.styles("css/normalize.css", "css/style.css")
|
|
||||||
.renderMarkdown(defaultTemplate: "page.html")
|
|
||||||
.projects()
|
|
||||||
.plugin(postsPlugin)
|
|
||||||
.build()
|
|
||||||
let generator = try SiteGenerator(sourceURL: sourceURL, site: site)
|
|
||||||
try generator.generate(targetURL: targetURL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import XCTest
|
|
||||||
@testable import samhuri_net
|
|
||||||
|
|
||||||
final class samhuri_netTests: XCTestCase {
|
|
||||||
func testExample() {
|
|
||||||
// This is an example of a functional test case.
|
|
||||||
// Use XCTAssert and related functions to verify your tests produce the correct
|
|
||||||
// results.
|
|
||||||
XCTAssertEqual(samhuri_net().text, "Hello, World!")
|
|
||||||
}
|
|
||||||
|
|
||||||
static var allTests = [
|
|
||||||
("testExample", testExample),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue