diff --git a/SiteGenerator/Sources/SiteGenerator/Date+CurrentYear.swift b/SiteGenerator/Sources/SiteGenerator/Date+CurrentYear.swift deleted file mode 100644 index c64f30c..0000000 --- a/SiteGenerator/Sources/SiteGenerator/Date+CurrentYear.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Date+CurrentYear.swift -// SiteGenerator -// -// Created by Sami Samhuri on 2019-12-02. -// - -import Foundation - -extension Date { - static var currentYear: Int { - Calendar.current.dateComponents([.year], from: Date()).year! - } -} diff --git a/SiteGenerator/Sources/SiteGenerator/Date+Sugar.swift b/SiteGenerator/Sources/SiteGenerator/Date+Sugar.swift new file mode 100644 index 0000000..62dd663 --- /dev/null +++ b/SiteGenerator/Sources/SiteGenerator/Date+Sugar.swift @@ -0,0 +1,22 @@ +// +// Date+Sugar.swift +// SiteGenerator +// +// Created by Sami Samhuri on 2019-12-02. +// + +import Foundation + +extension Date { + var year: Int { + Calendar.current.dateComponents([.year], from: self).year! + } + + var month: Int { + Calendar.current.dateComponents([.month], from: self).month! + } + + var day: Int { + Calendar.current.dateComponents([.day], from: self).day! + } +} diff --git a/SiteGenerator/Sources/SiteGenerator/Generator/Contexts/PageContext.swift b/SiteGenerator/Sources/SiteGenerator/Generator/Contexts/PageContext.swift index efd1ef4..a3c23df 100644 --- a/SiteGenerator/Sources/SiteGenerator/Generator/Contexts/PageContext.swift +++ b/SiteGenerator/Sources/SiteGenerator/Generator/Contexts/PageContext.swift @@ -36,7 +36,7 @@ extension PageContext: TemplateContext { "metadata": metadata, "styles": site.styles + page.styles, "scripts": site.scripts + page.scripts, - "currentYear": Date.currentYear, + "currentYear": Date().year, ] } } diff --git a/SiteGenerator/Sources/SiteGenerator/Generator/Contexts/SiteContext.swift b/SiteGenerator/Sources/SiteGenerator/Generator/Contexts/SiteContext.swift index a16158a..7ce0b71 100644 --- a/SiteGenerator/Sources/SiteGenerator/Generator/Contexts/SiteContext.swift +++ b/SiteGenerator/Sources/SiteGenerator/Generator/Contexts/SiteContext.swift @@ -24,7 +24,7 @@ extension SiteContext: TemplateContext { "title": site.title, "styles": site.styles, "scripts": site.scripts, - "currentYear": Date.currentYear, + "currentYear": Date().year, ] } } diff --git a/SiteGenerator/Sources/SiteGenerator/Generator/Generator.swift b/SiteGenerator/Sources/SiteGenerator/Generator/Generator.swift index abb3211..ded1c45 100644 --- a/SiteGenerator/Sources/SiteGenerator/Generator/Generator.swift +++ b/SiteGenerator/Sources/SiteGenerator/Generator/Generator.swift @@ -6,13 +6,11 @@ // import Foundation -import PathKit -import Stencil -public final class Generator: PluginDelegate, RendererDelegate { +public final class Generator { // Dependencies let fileManager: FileManager = .default - let templateRenderer: Environment + let templateRenderer: TemplateRenderer // Site properties let site: Site @@ -21,13 +19,13 @@ public final class Generator: PluginDelegate, RendererDelegate { let renderers: [Renderer] public init(sourceURL: URL, plugins: [Plugin], renderers: [Renderer]) throws { - let templatesURL = sourceURL.appendingPathComponent("templates") - let templatesPath = Path(templatesURL.path) - let loader = FileSystemLoader(paths: [templatesPath]) - self.templateRenderer = Environment(loader: loader) - let siteURL = sourceURL.appendingPathComponent("site.json") - self.site = try Site.decode(from: siteURL) + let site = try Site.decode(from: siteURL) + + let templatesURL = sourceURL.appendingPathComponent("templates") + self.templateRenderer = SiteTemplateRenderer(site: site, templatesURL: templatesURL) + + self.site = site self.sourceURL = sourceURL self.plugins = plugins self.renderers = renderers @@ -39,7 +37,7 @@ public final class Generator: PluginDelegate, RendererDelegate { public func generate(targetURL: URL) throws { for plugin in plugins { - try plugin.render(targetURL: targetURL, delegate: self) + try plugin.render(targetURL: targetURL, templateRenderer: templateRenderer) } let publicURL = sourceURL.appendingPathComponent("public") @@ -77,7 +75,7 @@ public final class Generator: PluginDelegate, RendererDelegate { let ext = String(filename.split(separator: ".").last!) for renderer in renderers { if renderer.canRenderFile(named: filename, withExtension: ext) { - try renderer.render(fileURL: fileURL, targetDir: targetDir, delegate: self) + try renderer.render(fileURL: fileURL, targetDir: targetDir, templateRenderer: templateRenderer) return } } @@ -86,20 +84,4 @@ public final class Generator: PluginDelegate, RendererDelegate { let dest = targetDir.appendingPathComponent(filename) try fileManager.copyItem(at: fileURL, to: dest) } - - // MARK: - PluginDelegate and RendererDelegate - - public func renderPage(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 templateRenderer.renderTemplate(name: "\(context.template).html", context: context.dictionary) - return pageHTML - } - - public func renderTemplate(name: String?, context: [String: Any]) throws -> String { - let siteContext = SiteContext(site: site, template: name) - let contextDict = siteContext.dictionary.merging(context, uniquingKeysWith: { _, new in new }) - print("Rendering \(siteContext.template) with context \(contextDict)") - return try templateRenderer.renderTemplate(name: "\(siteContext.template).html", context: contextDict) - } } diff --git a/SiteGenerator/Sources/SiteGenerator/Generator/Plugin.swift b/SiteGenerator/Sources/SiteGenerator/Generator/Plugin.swift index e0b499b..f7abc81 100644 --- a/SiteGenerator/Sources/SiteGenerator/Generator/Plugin.swift +++ b/SiteGenerator/Sources/SiteGenerator/Generator/Plugin.swift @@ -7,13 +7,8 @@ import Foundation -public protocol PluginDelegate: AnyObject { - func renderPage(bodyHTML: String, metadata: [String: String]) throws -> String - func renderTemplate(name: String?, context: [String: Any]) throws -> String -} - public protocol Plugin { func setUp(sourceURL: URL) throws - func render(targetURL: URL, delegate: PluginDelegate) throws + func render(targetURL: URL, templateRenderer: TemplateRenderer) throws } diff --git a/SiteGenerator/Sources/SiteGenerator/Generator/Renderer.swift b/SiteGenerator/Sources/SiteGenerator/Generator/Renderer.swift index 4252857..d28018f 100644 --- a/SiteGenerator/Sources/SiteGenerator/Generator/Renderer.swift +++ b/SiteGenerator/Sources/SiteGenerator/Generator/Renderer.swift @@ -7,13 +7,8 @@ import Foundation -public protocol RendererDelegate: AnyObject { - func renderPage(bodyHTML: String, metadata: [String: String]) throws -> String - func renderTemplate(name: String?, context: [String: Any]) throws -> String -} - public protocol Renderer { func canRenderFile(named filename: String, withExtension ext: String) -> Bool - func render(fileURL: URL, targetDir: URL, delegate: RendererDelegate) throws + func render(fileURL: URL, targetDir: URL, templateRenderer: TemplateRenderer) throws } diff --git a/SiteGenerator/Sources/SiteGenerator/Generator/SiteTemplateRenderer.swift b/SiteGenerator/Sources/SiteGenerator/Generator/SiteTemplateRenderer.swift new file mode 100644 index 0000000..cf7d574 --- /dev/null +++ b/SiteGenerator/Sources/SiteGenerator/Generator/SiteTemplateRenderer.swift @@ -0,0 +1,36 @@ +// +// 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(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: "\(context.template).html", context: context.dictionary) + return pageHTML + } + + func renderTemplate(name: String?, context: [String: Any]) throws -> String { + let siteContext = SiteContext(site: site, template: name) + let contextDict = siteContext.dictionary.merging(context, uniquingKeysWith: { _, new in new }) + print("Rendering \(siteContext.template) with context \(contextDict)") + return try stencil.renderTemplate(name: "\(siteContext.template).html", context: contextDict) + } +} diff --git a/SiteGenerator/Sources/SiteGenerator/Generator/TemplateRenderer.swift b/SiteGenerator/Sources/SiteGenerator/Generator/TemplateRenderer.swift new file mode 100644 index 0000000..649b54a --- /dev/null +++ b/SiteGenerator/Sources/SiteGenerator/Generator/TemplateRenderer.swift @@ -0,0 +1,13 @@ +// +// templateRenderer.swift +// SiteGenerator +// +// Created by Sami Samhuri on 2019-12-03. +// + +import Foundation + +public protocol TemplateRenderer: AnyObject { + func renderPage(bodyHTML: String, metadata: [String: String]) throws -> String + func renderTemplate(name: String?, context: [String: Any]) throws -> String +} diff --git a/SiteGenerator/Sources/SiteGenerator/Model/Post.swift b/SiteGenerator/Sources/SiteGenerator/Model/Post.swift deleted file mode 100644 index afef3f0..0000000 --- a/SiteGenerator/Sources/SiteGenerator/Model/Post.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// Post.swift -// SiteGenerator -// -// Created by Sami Samhuri on 2019-12-01. -// - -import Foundation - -struct Post { - let date: Date - let formattedDate: String - let title: String - let slug: String - let author: String - let tags: [String] - let body: String - - var path: String { - let dateComponents = Calendar.current.dateComponents([.year], from: date) - let year = dateComponents.year! - let month = dateComponents.month! - return "/" + [ - "posts", - "\(year)", - "\(month)", - "\(slug)", - ].joined(separator: "/") - } -} diff --git a/SiteGenerator/Sources/SiteGenerator/Posts/Month.swift b/SiteGenerator/Sources/SiteGenerator/Posts/Month.swift new file mode 100644 index 0000000..9dbf1c8 --- /dev/null +++ b/SiteGenerator/Sources/SiteGenerator/Posts/Month.swift @@ -0,0 +1,30 @@ +// +// Month.swift +// SiteGenerator +// +// Created by Sami Samhuri on 2019-12-03. +// + +import Foundation + +struct Month { + static let names = [ + "January", "Februrary", "March", "April", + "May", "June", "July", "August", + "September", "October", "November", "December" + ] + + let number: Int + + var padded: String { + String(format: "%02d", number) + } + + var name: String { + Month.names[number] + } + + var abbreviatedName: String { + String(name.prefix(3)) + } +} diff --git a/SiteGenerator/Sources/SiteGenerator/Posts/Post.swift b/SiteGenerator/Sources/SiteGenerator/Posts/Post.swift new file mode 100644 index 0000000..1c98c7d --- /dev/null +++ b/SiteGenerator/Sources/SiteGenerator/Posts/Post.swift @@ -0,0 +1,74 @@ +// +// Post.swift +// SiteGenerator +// +// Created by Sami Samhuri on 2019-12-01. +// + +import Foundation + +struct Post { + let slug: String + let title: String + let author: String + let date: Date + let formattedDate: String + let link: URL? + let tags: [String] + let bodyMarkdown: String + + var isLink: Bool { + link != nil + } + + var path: String { + let dateComponents = Calendar.current.dateComponents([.year, .month], from: date) + let year = dateComponents.year! + let month = dateComponents.month! + return "/" + [ + "posts", + "\(year)", + "\(month)", + "\(slug)", + ].joined(separator: "/") + } +} + +/// Posts are sorted in reverse date order. +extension Post: Comparable { + static func < (lhs: Self, rhs: Self) -> Bool { + rhs.date < lhs.date + } +} + +extension Post { + enum Error: Swift.Error { + case deficientMetadata(missingKeys: [String]) + } + + init(bodyMarkdown: String, metadata: [String: String]) throws { + self.bodyMarkdown = bodyMarkdown + + let requiredKeys = ["Slug", "Title", "Author", "Date", "Timestamp", "Tags", "Path_deprecated"] + let missingKeys = requiredKeys.filter { metadata[$0] == nil } + guard missingKeys.isEmpty else { + throw Error.deficientMetadata(missingKeys: missingKeys) + } + + slug = metadata["Slug"]! + title = metadata["Title"]! + author = metadata["Author"]! + date = Date(timeIntervalSince1970: TimeInterval(metadata["Timestamp"]!)!) + formattedDate = metadata["Date"]! + if let urlString = metadata["Link"] { + link = URL(string: urlString)! + } + else { + link = nil + } + tags = metadata["Tags"]!.split(separator: ",").map { $0.trimmingCharacters(in: .whitespaces) } + + let handWrittenPath = metadata["Path_deprecated"]! + assert(path == handWrittenPath, "FUCK: Generated path (\(path)) doesn't match the hand-written one \(handWrittenPath)") + } +} diff --git a/SiteGenerator/Sources/SiteGenerator/Posts/PostsByYear.swift b/SiteGenerator/Sources/SiteGenerator/Posts/PostsByYear.swift new file mode 100644 index 0000000..5bb1653 --- /dev/null +++ b/SiteGenerator/Sources/SiteGenerator/Posts/PostsByYear.swift @@ -0,0 +1,67 @@ +// +// Posts.swift +// SiteGenerator +// +// Created by Sami Samhuri on 2019-12-03. +// + +import Foundation + +struct MonthPosts { + let month: Int + var posts: [Post] + + var isEmpty: Bool { + posts.isEmpty + } +} + +struct YearPosts { + let year: Int + var byMonth: [Int: MonthPosts] + + subscript(month: Int) -> MonthPosts { + get { + byMonth[month, default: MonthPosts(month: month, posts: [])] + } + set { + byMonth[month] = newValue + } + } + + var isEmpty: Bool { + byMonth.isEmpty || byMonth.values.allSatisfy { $0.isEmpty } + } +} + +struct PostsByYear { + private(set) var byYear: [Int: YearPosts] + + init(posts: [Post]) { + byYear = [:] + posts.forEach { add(post: $0) } + } + + subscript(year: Int) -> YearPosts { + get { + byYear[year, default: YearPosts(year: year, byMonth: [:])] + } + set { + byYear[year] = newValue + } + } + + var isEmpty: Bool { + byYear.isEmpty || byYear.values.allSatisfy { $0.isEmpty } + } + + mutating func add(post: Post) { + let (year, month) = (post.date.year, post.date.month) + self[year][month].posts.append(post) + } + + /// Returns posts sorted by reverse date. + func flattened() -> [Post] { + byYear.values.flatMap { $0.byMonth.values.flatMap { $0.posts } }.sorted { $1.date < $0.date } + } +} diff --git a/SiteGenerator/Sources/SiteGenerator/Posts/PostsPlugin.swift b/SiteGenerator/Sources/SiteGenerator/Posts/PostsPlugin.swift new file mode 100644 index 0000000..9cff9b8 --- /dev/null +++ b/SiteGenerator/Sources/SiteGenerator/Posts/PostsPlugin.swift @@ -0,0 +1,146 @@ +// +// PostsPlugin.swift +// SiteGenerator +// +// Created by Sami Samhuri on 2019-12-03. +// + +import Foundation +import Ink + +final class PostsPlugin: Plugin { + let fileManager: FileManager = .default + let markdownParser = MarkdownParser() + let path: String + + var posts: PostsByYear! + var sourceURL: URL! + + init(path: String = "posts") { + self.path = path + } + + func setUp(sourceURL: URL) throws { + self.sourceURL = sourceURL + let postsURL = sourceURL.appendingPathComponent("posts") + guard fileManager.fileExists(atPath: postsURL.path) else { + return + } + + let posts = try enumerateMarkdownFiles(directory: postsURL) + .compactMap { (url: URL) -> Post? in + guard let result = (try? String(contentsOf: url)).map(markdownParser.parse) else { + return nil + } + do { + return try Post(bodyMarkdown: "(TEST)", metadata: result.metadata) + } + catch { + print("Cannot create post from markdown file \(url): \(error)") + return nil + } + } + print("posts: \(posts)") + self.posts = PostsByYear(posts: posts) + } + + func render(targetURL: URL, templateRenderer: TemplateRenderer) throws { + guard posts != nil, !posts.isEmpty else { + return + } + + let postsDir = targetURL.appendingPathComponent(path) + try renderRecentPosts(postsDir: postsDir, templateRenderer: templateRenderer) + try renderYearsAndMonths(postsDir: postsDir, templateRenderer: templateRenderer) + try renderPostsByDate(postsDir: postsDir, templateRenderer: templateRenderer) + } + + func renderRecentPosts(postsDir: URL, templateRenderer: TemplateRenderer) throws { + print("renderRecentPosts(postsDir: \(postsDir), templateRenderer: \(templateRenderer)") + let recentPosts = posts.flattened().prefix(10) + try fileManager.createDirectory(at: postsDir, withIntermediateDirectories: true, attributes: nil) + let recentPostsURL = postsDir.appendingPathComponent("index.html") + let recentPostsHTML = try templateRenderer.renderTemplate(name: "recent-posts", context: ["recentPosts": recentPosts]) + try recentPostsHTML.write(to: recentPostsURL, atomically: true, encoding: .utf8) + } + + func renderPostsByDate(postsDir: URL, templateRenderer: TemplateRenderer) throws { + print("renderPostsByDate(postsDir: \(postsDir), templateRenderer: \(templateRenderer)") + for post in posts.flattened() { + let monthDir = postsDir + .appendingPathComponent(String(format: "%02d", post.date.year)) + .appendingPathComponent(String(format: "%02d", post.date.month)) + try renderPost(post, monthDir: monthDir, templateRenderer: templateRenderer) + } + } + + func renderYearsAndMonths(postsDir: URL, templateRenderer: TemplateRenderer) throws { + print("renderYearsAndMonths(postsDir: \(postsDir), templateRenderer: \(templateRenderer)") + let allMonths = (1 ... 12).reversed().map(Month.init) + for (year, monthPosts) in posts.byYear.sorted(by: { $1.key < $0.key }) { + let yearDir = postsDir.appendingPathComponent("\(year)") + var sortedPostsByMonth: [Int: [RenderedPost]] = [:] + for month in allMonths { + let sortedPosts = monthPosts[month.number].posts.sorted(by: { $1.date < $0.date }) + guard !sortedPosts.isEmpty else { + continue + } + + let renderedPosts = sortedPosts.map { post -> RenderedPost in + let bodyHTML = markdownParser.html(from: post.bodyMarkdown) + return RenderedPost(post: post, body: bodyHTML) + } + sortedPostsByMonth[month.number] = renderedPosts + + let monthDir = yearDir.appendingPathComponent(month.padded) + try fileManager.createDirectory(at: monthDir, withIntermediateDirectories: true, attributes: nil) + let context: [String: Any] = ["path": path, "month": month, "posts": renderedPosts] + let monthHTML = try templateRenderer.renderTemplate(name: "posts-month", context: context) + let monthURL = monthDir.appendingPathComponent("index.html") + try monthHTML.write(to: monthURL, atomically: true, encoding: .utf8) + } + + try fileManager.createDirectory(at: yearDir, withIntermediateDirectories: true, attributes: nil) + let context: [String: Any] = [ + "path": path, + "year": year, + "months": sortedPostsByMonth.keys.sorted().reversed().map(Month.init), + "postsByMonth": sortedPostsByMonth, + ] + let yearHTML = try templateRenderer.renderTemplate(name: "posts-year", context: context) + let yearURL = yearDir.appendingPathComponent("index.html") + try yearHTML.write(to: yearURL, atomically: true, encoding: .utf8) + } + } + + private func renderPost(_ post: Post, monthDir: URL, templateRenderer: TemplateRenderer) throws { + print("renderPost(\(post), monthDir: \(monthDir), templateRenderer: \(templateRenderer)") + try fileManager.createDirectory(at: monthDir, withIntermediateDirectories: true, attributes: nil) + let filename = "\(post.slug).html" + let postURL = monthDir.appendingPathComponent(filename) + let templateName = self.templateName(for: post) + let bodyHTML = markdownParser.html(from: post.bodyMarkdown) + let renderedPost = RenderedPost(post: post, body: bodyHTML) + let postHTML = try templateRenderer.renderTemplate(name: templateName, context: ["post": renderedPost]) + try postHTML.write(to: postURL, atomically: true, encoding: .utf8) + } + + private func templateName(for post: Post) -> String { + post.isLink ? "post-link" : "post-text" + } + + private func enumerateMarkdownFiles(directory: URL) throws -> [URL] { + print("enumerateMarkdownFiles(directory: \(directory))") + return try fileManager.contentsOfDirectory(atPath: directory.path).flatMap { (filename: String) -> [URL] in + let fileURL = directory.appendingPathComponent(filename) + var isDir: ObjCBool = false + fileManager.fileExists(atPath: fileURL.path, isDirectory: &isDir) + if isDir.boolValue { + return try enumerateMarkdownFiles(directory: fileURL) + } + else { + return fileURL.pathExtension == "md" ? [fileURL] : [] + } + } + } +} diff --git a/SiteGenerator/Sources/SiteGenerator/Posts/RenderedPost.swift b/SiteGenerator/Sources/SiteGenerator/Posts/RenderedPost.swift new file mode 100644 index 0000000..2e5711a --- /dev/null +++ b/SiteGenerator/Sources/SiteGenerator/Posts/RenderedPost.swift @@ -0,0 +1,27 @@ +// +// RenderedPost.swift +// SiteGenerator +// +// Created by Sami Samhuri on 2019-12-03. +// + +import Foundation + +struct RenderedPost { + let post: Post + let body: String + + var author: String { post.author } + + var title: String { post.title } + + var date: Date { post.date } + + var formattedDate: String { post.formattedDate } + + var isLink: Bool { post.isLink } + + var link: URL? { post.link } + + var path: String { post.path } +} diff --git a/SiteGenerator/Sources/SiteGenerator/Projects/ProjectsPlugin.swift b/SiteGenerator/Sources/SiteGenerator/Projects/ProjectsPlugin.swift index 7eccefb..d69bc89 100644 --- a/SiteGenerator/Sources/SiteGenerator/Projects/ProjectsPlugin.swift +++ b/SiteGenerator/Sources/SiteGenerator/Projects/ProjectsPlugin.swift @@ -38,7 +38,7 @@ final class ProjectsPlugin: Plugin { } } - func render(targetURL: URL, delegate: PluginDelegate) throws { + func render(targetURL: URL, templateRenderer: TemplateRenderer) throws { guard !projects.isEmpty else { return } @@ -46,13 +46,13 @@ final class ProjectsPlugin: Plugin { let projectsDir = targetURL.appendingPathComponent(path) try fileManager.createDirectory(at: projectsDir, withIntermediateDirectories: true, attributes: nil) let projectsURL = projectsDir.appendingPathComponent("index.html") - let projectsHTML = try delegate.renderTemplate(name: "projects", context: ["projects": projects]) + let projectsHTML = try templateRenderer.renderTemplate(name: "projects", context: ["projects": projects]) try projectsHTML.write(to: projectsURL, atomically: true, encoding: .utf8) for project in projects { let filename = "\(project.title).html" let projectURL = projectsDir.appendingPathComponent(filename) - let projectHTML = try delegate.renderTemplate(name: "project", context: ["project": project]) + let projectHTML = try templateRenderer.renderTemplate(name: "project", context: ["project": project]) try projectHTML.write(to: projectURL, atomically: true, encoding: .utf8) } } diff --git a/SiteGenerator/Sources/SiteGenerator/Renderers/LessRenderer.swift b/SiteGenerator/Sources/SiteGenerator/Renderers/LessRenderer.swift index f0b1885..5b9ed9b 100644 --- a/SiteGenerator/Sources/SiteGenerator/Renderers/LessRenderer.swift +++ b/SiteGenerator/Sources/SiteGenerator/Renderers/LessRenderer.swift @@ -17,7 +17,7 @@ public final class LessRenderer: Renderer { } /// Parse Less and render it as CSS. - public func render(fileURL: URL, targetDir: URL, delegate: RendererDelegate) throws { + public func render(fileURL: URL, targetDir: URL, templateRenderer: TemplateRenderer) throws { let filename = fileURL.lastPathComponent let cssURL = targetDir.appendingPathComponent(filename.replacingOccurrences(of: ".less", with: ".css")) let less = try String(contentsOf: fileURL, encoding: .utf8) diff --git a/SiteGenerator/Sources/SiteGenerator/Renderers/MarkdownRenderer.swift b/SiteGenerator/Sources/SiteGenerator/Renderers/MarkdownRenderer.swift index 040359b..e416a1b 100644 --- a/SiteGenerator/Sources/SiteGenerator/Renderers/MarkdownRenderer.swift +++ b/SiteGenerator/Sources/SiteGenerator/Renderers/MarkdownRenderer.swift @@ -9,26 +9,26 @@ import Foundation import Ink public final class MarkdownRenderer: Renderer { - let mdParser = MarkdownParser() + let markdownParser = MarkdownParser() public func canRenderFile(named filename: String, withExtension ext: String) -> Bool { ext == "md" } /// Parse Markdown and render it as HTML, running it through a Stencil template. - public func render(fileURL: URL, targetDir: URL, delegate: RendererDelegate) throws { + public func render(fileURL: URL, targetDir: URL, templateRenderer: TemplateRenderer) throws { let mdFilename = fileURL.lastPathComponent let htmlFilename = mdFilename.replacingOccurrences(of: ".md", with: ".html") let htmlURL = targetDir.appendingPathComponent(htmlFilename) let bodyMarkdown = try String(contentsOf: fileURL, encoding: .utf8) - let bodyHTML = mdParser.html(from: bodyMarkdown).trimmingCharacters(in: .whitespacesAndNewlines) + let bodyHTML = markdownParser.html(from: bodyMarkdown).trimmingCharacters(in: .whitespacesAndNewlines) let metadata = try markdownMetadata(from: fileURL) - let pageHTML = try delegate.renderPage(bodyHTML: bodyHTML, metadata: metadata) + let pageHTML = try templateRenderer.renderPage(bodyHTML: bodyHTML, metadata: metadata) try pageHTML.write(to: htmlURL, atomically: true, encoding: .utf8) } func markdownMetadata(from url: URL) throws -> [String: String] { let md = try String(contentsOf: url, encoding: .utf8) - return mdParser.parse(md).metadata + return markdownParser.parse(md).metadata } } diff --git a/SiteGenerator/Sources/SiteGenerator/main.swift b/SiteGenerator/Sources/SiteGenerator/main.swift index fd740f5..41bbdf8 100644 --- a/SiteGenerator/Sources/SiteGenerator/main.swift +++ b/SiteGenerator/Sources/SiteGenerator/main.swift @@ -12,7 +12,7 @@ func main(sourcePath: String, targetPath: String) throws { let targetURL = URL(fileURLWithPath: targetPath) let generator = try Generator( sourceURL: sourceURL, - plugins: [ProjectsPlugin()], + plugins: [ProjectsPlugin(), PostsPlugin()], renderers: [LessRenderer(), MarkdownRenderer()] ) try generator.generate(targetURL: targetURL) diff --git a/templates/post-link.html b/templates/post-link.html index 6e3e6a6..42e5359 100644 --- a/templates/post-link.html +++ b/templates/post-link.html @@ -4,7 +4,7 @@

→ {{ post.title }}

- +
{{ post.body }} diff --git a/templates/post-text.html b/templates/post-text.html index 212b88f..e98ac06 100644 --- a/templates/post-text.html +++ b/templates/post-text.html @@ -4,7 +4,7 @@

{{ post.title }}

- +
{{ post.body }}
diff --git a/templates/posts-month.html b/templates/posts-month.html new file mode 100644 index 0000000..bb70580 --- /dev/null +++ b/templates/posts-month.html @@ -0,0 +1,24 @@ +{% extends "samhuri.net.html" %} + +{% block body %} +{% for post in posts %} +
+
+ {% if post.isLink %} +

→ {{ post.title }}

+ + + {% else %} +

{{ post.title }}

+ + {% endif %} +
+ + {{ post.body }} +
+
+

+
+ +{% endfor %} +{% endblock %} diff --git a/templates/posts-year.html b/templates/posts-year.html new file mode 100644 index 0000000..126f52c --- /dev/null +++ b/templates/posts-year.html @@ -0,0 +1,27 @@ +{% extends "samhuri.net.html" %} + +{% block body %} +
+

{{ year }}

+ + {% for month in months %} +

+ {{ month.name }} +

+ + + + {% endfor %} +
+{% endblock %}