From 4318c0b903ce37447dda7cf8e4bdfcaaaf6e4606 Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Sun, 22 Dec 2019 17:25:13 -0800 Subject: [PATCH] Render all posts pages and RSS feed with Plot and drop Stencil --- .tm_properties | 2 +- Readme.md | 22 ++--- SiteGenerator/.gitignore | 6 -- SiteGenerator/Package.resolved | 16 ---- SiteGenerator/Package.swift | 33 -------- SiteGenerator/Readme.md | 5 -- .../Sources/SiteGenerator/Date+Sugar.swift | 22 ----- .../Posts/Feeds/RSSFeedWriter.swift | 76 ----------------- .../Posts/PostsTemplateRenderer.swift | 22 ----- .../SiteGenerator/Projects/Project.swift | 20 ----- SiteGenerator/Tests/LinuxMain.swift | 7 -- .../SiteGeneratorTests.swift | 47 ----------- .../SiteGeneratorTests/XCTestManifests.swift | 9 -- gensite/Package.resolved | 27 ------ posts/2016/08/ios-git-pre-commit-hook.md | 40 +++++---- samhuri.net/Package.resolved | 27 ------ samhuri.net/Package.swift | 6 +- .../Sources/samhuri.net/Date+Sugar.swift | 8 ++ samhuri.net/Sources/samhuri.net/Page.swift | 1 - .../Sources/samhuri.net/PageRenderer.swift | 61 +------------- .../samhuri.net/Posts/FeedPostTemplate.swift | 30 +++++++ .../Posts/MonthPostsTemplate.swift | 22 +++++ .../Posts/PageRenderer+Posts.swift | 65 ++++++++++++++- .../samhuri.net/Posts/PostTemplate.swift | 34 ++++++++ .../Posts/PostsArchiveTemplate.swift | 22 +++++ .../Posts/RecentPostsTemplate.swift | 17 ++++ .../samhuri.net/Posts/YearPostsTemplate.swift | 51 ++++++++++++ .../Projects/PageRenderer+Projects.swift | 18 +++- .../samhuri.net/Projects/ProjectContext.swift | 1 - ...tTemplates.swift => ProjectTemplate.swift} | 24 +----- .../Projects/ProjectsTemplate.swift | 30 +++++++ .../SiteGenerator/BuiltSite.swift | 10 +-- .../SiteGenerator/MarkdownPageRenderer.swift | 4 +- .../SiteGenerator/MarkdownRenderer.swift | 10 +-- .../samhuri.net}/SiteGenerator/Plugin.swift | 4 +- .../SiteGenerator/Posts/Feeds/JSONFeed.swift | 2 +- .../Posts/Feeds/JSONFeedWriter.swift | 6 +- .../SiteGenerator/Posts/Feeds/RSSFeed.swift | 2 +- .../Posts/Feeds/RSSFeedWriter.swift | 25 ++++++ .../Posts/Feeds/String+EscapeXML.swift | 3 +- .../SiteGenerator/Posts/Month.swift | 2 +- .../SiteGenerator/Posts/Post.swift | 13 +-- .../SiteGenerator/Posts/PostRepo.swift | 2 +- .../SiteGenerator/Posts/PostTransformer.swift | 2 +- .../SiteGenerator/Posts/PostWriter.swift | 57 ++----------- .../SiteGenerator/Posts/PostsByYear.swift | 18 ++-- .../SiteGenerator/Posts/PostsPlugin.swift | 8 +- .../Posts/PostsPluginBuilder.swift | 14 ++-- .../Posts/PostsTemplateRenderer.swift | 26 ++++++ .../SiteGenerator/Projects/Project.swift | 20 +++++ .../Projects/ProjectsPlugin.swift | 8 +- .../Projects/ProjectsPluginBuilder.swift | 14 ++-- .../Projects/ProjectsTemplateRenderer.swift | 4 +- .../samhuri.net}/SiteGenerator/Renderer.swift | 4 +- .../samhuri.net}/SiteGenerator/Site.swift | 24 +++--- .../SiteGenerator/SiteBuilder.swift | 18 ++-- .../SiteGenerator/SiteGenerator.swift | 30 ++++--- .../SiteGenerator/TemplateAssets.swift | 12 +-- .../samhuri.net/Templates/HTMLElements.swift | 14 +++- .../samhuri.net/Templates/PageContext.swift | 39 --------- .../samhuri.net/Templates/PageTemplate.swift | 6 +- .../samhuri.net/Templates/SiteContext.swift | 1 - .../Templates/TemplateContext.swift | 1 - .../Sources/samhuri.net/samhuri.net.swift | 7 +- templates/feed-post.html | 7 -- templates/feed.xml | 23 ------ templates/partial-post-link.html | 11 --- templates/partial-post-text.html | 11 --- templates/partial-post.html | 5 -- templates/post.html | 5 -- templates/posts-archive.html | 38 --------- templates/posts-month.html | 13 --- templates/posts-year.html | 32 -------- templates/recent-posts.html | 11 --- templates/samhuri.net.html | 82 ------------------- 75 files changed, 541 insertions(+), 878 deletions(-) delete mode 100644 SiteGenerator/.gitignore delete mode 100644 SiteGenerator/Package.resolved delete mode 100644 SiteGenerator/Package.swift delete mode 100644 SiteGenerator/Readme.md delete mode 100644 SiteGenerator/Sources/SiteGenerator/Date+Sugar.swift delete mode 100644 SiteGenerator/Sources/SiteGenerator/Posts/Feeds/RSSFeedWriter.swift delete mode 100644 SiteGenerator/Sources/SiteGenerator/Posts/PostsTemplateRenderer.swift delete mode 100644 SiteGenerator/Sources/SiteGenerator/Projects/Project.swift delete mode 100644 SiteGenerator/Tests/LinuxMain.swift delete mode 100644 SiteGenerator/Tests/SiteGeneratorTests/SiteGeneratorTests.swift delete mode 100644 SiteGenerator/Tests/SiteGeneratorTests/XCTestManifests.swift create mode 100644 samhuri.net/Sources/samhuri.net/Posts/FeedPostTemplate.swift create mode 100644 samhuri.net/Sources/samhuri.net/Posts/MonthPostsTemplate.swift create mode 100644 samhuri.net/Sources/samhuri.net/Posts/PostTemplate.swift create mode 100644 samhuri.net/Sources/samhuri.net/Posts/PostsArchiveTemplate.swift create mode 100644 samhuri.net/Sources/samhuri.net/Posts/RecentPostsTemplate.swift create mode 100644 samhuri.net/Sources/samhuri.net/Posts/YearPostsTemplate.swift rename samhuri.net/Sources/samhuri.net/Projects/{ProjectTemplates.swift => ProjectTemplate.swift} (68%) create mode 100644 samhuri.net/Sources/samhuri.net/Projects/ProjectsTemplate.swift rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/BuiltSite.swift (64%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/MarkdownPageRenderer.swift (77%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/MarkdownRenderer.swift (84%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/Plugin.swift (81%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/Posts/Feeds/JSONFeed.swift (92%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/Posts/Feeds/JSONFeedWriter.swift (93%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/Posts/Feeds/RSSFeed.swift (87%) create mode 100644 samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Feeds/RSSFeedWriter.swift rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/Posts/Feeds/String+EscapeXML.swift (88%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/Posts/Month.swift (98%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/Posts/Post.swift (77%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/Posts/PostRepo.swift (99%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/Posts/PostTransformer.swift (99%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/Posts/PostWriter.swift (60%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/Posts/PostsByYear.swift (93%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/Posts/PostsPlugin.swift (90%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/Posts/PostsPluginBuilder.swift (86%) create mode 100644 samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostsTemplateRenderer.swift create mode 100644 samhuri.net/Sources/samhuri.net/SiteGenerator/Projects/Project.swift rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/Projects/ProjectsPlugin.swift (92%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/Projects/ProjectsPluginBuilder.swift (72%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/Projects/ProjectsTemplateRenderer.swift (83%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/Renderer.swift (84%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/Site.swift (64%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/SiteBuilder.swift (78%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/SiteGenerator.swift (74%) rename {SiteGenerator/Sources => samhuri.net/Sources/samhuri.net}/SiteGenerator/TemplateAssets.swift (50%) delete mode 100644 samhuri.net/Sources/samhuri.net/Templates/PageContext.swift delete mode 100644 templates/feed-post.html delete mode 100644 templates/feed.xml delete mode 100644 templates/partial-post-link.html delete mode 100644 templates/partial-post-text.html delete mode 100644 templates/partial-post.html delete mode 100644 templates/post.html delete mode 100644 templates/posts-archive.html delete mode 100644 templates/posts-month.html delete mode 100644 templates/posts-year.html delete mode 100644 templates/recent-posts.html delete mode 100644 templates/samhuri.net.html diff --git a/.tm_properties b/.tm_properties index c8da1d6..eb43dca 100644 --- a/.tm_properties +++ b/.tm_properties @@ -1,2 +1,2 @@ -exclude = "{$exclude,SiteGenerator,samhuri.net,gensite,www,tweets,wayback,actual}" +exclude = "{$exclude,samhuri.net,gensite,www,tweets,wayback,actual}" include = "{$include,.gitignore}" diff --git a/Readme.md b/Readme.md index d1fc494..edf6a50 100644 --- a/Readme.md +++ b/Readme.md @@ -111,7 +111,7 @@ Execution, trying TDD for the first time: - [x] Minify JS? Now that we're keeping node, why not ... Nope! Ditched node too -- [ ] Convert to a system of packages: SiteGenerator, samhuri_net, and gensite (executable) +- [x] Convert to a system of packages: SiteGenerator, samhuri_net, and gensite (executable) - [x] Create new packages and distribute the code accordingly @@ -125,27 +125,27 @@ Execution, trying TDD for the first time: - [x] Replace project templates with Swift code - - [ ] Replace post templates with Swift code + - [x] Replace post templates with Swift code - [x] Archive - - [ ] Year posts + - [x] Year posts - - [ ] Month posts + - [x] Month posts - - [ ] Post + - [x] Post - - [ ] Feed post + - [x] Recent posts - - [ ] Munge relative URLs in the RSS and JSON feeds to be absolute instead + - [x] Replace RSS feed with Swift code - - [ ] Recent posts + - [x] Feed post template - - [ ] RSS feed + - [x] RSS feed template - - [ ] Replace RSS feed template with Swift code + - [x] Munge relative URLs in the RSS and JSON feeds to be absolute instead - - [ ] Remove stencil + - [x] Remove stencil - [x] Add a server for local use and simple production setups (or use a file watcher + `python -m SimpleHTTPServer`?) diff --git a/SiteGenerator/.gitignore b/SiteGenerator/.gitignore deleted file mode 100644 index 51ef6c2..0000000 --- a/SiteGenerator/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -.DS_Store -/.build -/Packages -/*.xcodeproj -/.swiftpm -xcuserdata/ diff --git a/SiteGenerator/Package.resolved b/SiteGenerator/Package.resolved deleted file mode 100644 index d3725af..0000000 --- a/SiteGenerator/Package.resolved +++ /dev/null @@ -1,16 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "Ink", - "repositoryURL": "https://github.com/johnsundell/ink.git", - "state": { - "branch": null, - "revision": "c88bbce588a1ebfde2cf4d61eb9865a3edaa27d4", - "version": "0.2.0" - } - } - ] - }, - "version": 1 -} diff --git a/SiteGenerator/Package.swift b/SiteGenerator/Package.swift deleted file mode 100644 index 614671e..0000000 --- a/SiteGenerator/Package.swift +++ /dev/null @@ -1,33 +0,0 @@ -// swift-tools-version:5.1 -// The swift-tools-version declares the minimum version of Swift required to build this package. - -import PackageDescription - -let package = Package( - name: "SiteGenerator", - platforms: [ - .macOS(.v10_15), - .iOS(.v13), - ], - products: [ - // Products define the executables and libraries produced by a package, and make them visible to other packages. - .library( - name: "SiteGenerator", - targets: ["SiteGenerator"]), - ], - dependencies: [ - .package(url: "https://github.com/johnsundell/ink.git", from: "0.2.0"), - ], - targets: [ - // 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. - .target( - name: "SiteGenerator", - dependencies: [ - "Ink", - ]), - .testTarget( - name: "SiteGeneratorTests", - dependencies: ["SiteGenerator"]), - ] -) diff --git a/SiteGenerator/Readme.md b/SiteGenerator/Readme.md deleted file mode 100644 index dc6d75e..0000000 --- a/SiteGenerator/Readme.md +++ /dev/null @@ -1,5 +0,0 @@ -# SiteGenerator - -A static site generator. - -See https://github.com/samsonjs/samhuri.net for details. diff --git a/SiteGenerator/Sources/SiteGenerator/Date+Sugar.swift b/SiteGenerator/Sources/SiteGenerator/Date+Sugar.swift deleted file mode 100644 index 62dd663..0000000 --- a/SiteGenerator/Sources/SiteGenerator/Date+Sugar.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// 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/Posts/Feeds/RSSFeedWriter.swift b/SiteGenerator/Sources/SiteGenerator/Posts/Feeds/RSSFeedWriter.swift deleted file mode 100644 index f20df3c..0000000 --- a/SiteGenerator/Sources/SiteGenerator/Posts/Feeds/RSSFeedWriter.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// RSSFeedWriter.swift -// SiteGenerator -// -// Created by Sami Samhuri on 2019-12-10. -// - -import Foundation - -private struct FeedSite { - let title: String - let description: String? - let url: String -} - -private struct FeedPost { - let title: String - let date: String - let author: String - let link: String - let guid: String - let body: String -} - -private let rfc822Formatter: DateFormatter = { - let f = DateFormatter() - f.locale = Locale(identifier: "en_US_POSIX") - f.dateFormat = "EEE, d MMM yyyy HH:mm:ss ZZZ" - return f -}() - -private extension Date { - var rfc822: String { - rfc822Formatter.string(from: self) - } -} - -final class RSSFeedWriter { - let fileManager: FileManager - let feed: RSSFeed - - init(fileManager: FileManager = .default, feed: RSSFeed) { - self.fileManager = fileManager - self.feed = feed - } - - func writeFeed(_ posts: [Post], for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws { - let feedSite = FeedSite( - title: site.title.escapedForXML(), - description: site.description?.escapedForXML(), - url: site.url.absoluteString.escapedForXML() - ) - let renderedPosts: [FeedPost] = try posts.map { post in - let title = post.isLink ? "→ \(post.title)" : post.title - let author = "\(site.email) (\(post.author))" - let url = site.url.appendingPathComponent(post.path) - return FeedPost( - title: title.escapedForXML(), - date: post.date.rfc822.escapedForXML(), - author: author.escapedForXML(), - link: (post.link ?? url).absoluteString.escapedForXML(), - guid: url.absoluteString.escapedForXML(), - body: try templateRenderer.renderTemplate(.feedPost, site: site, context: [ - "post": post, - ]).escapedForXML() - ) - } - let feedXML = try templateRenderer.renderTemplate(.rssFeed, site: site, context: [ - "site": feedSite, - "feedURL": site.url.appendingPathComponent(feed.path).absoluteString.escapedForXML(), - "posts": renderedPosts, - ]) - let feedURL = targetURL.appendingPathComponent(feed.path) - try feedXML.write(to: feedURL, atomically: true, encoding: .utf8) - } -} diff --git a/SiteGenerator/Sources/SiteGenerator/Posts/PostsTemplateRenderer.swift b/SiteGenerator/Sources/SiteGenerator/Posts/PostsTemplateRenderer.swift deleted file mode 100644 index a2ddb61..0000000 --- a/SiteGenerator/Sources/SiteGenerator/Posts/PostsTemplateRenderer.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// 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 -} diff --git a/SiteGenerator/Sources/SiteGenerator/Projects/Project.swift b/SiteGenerator/Sources/SiteGenerator/Projects/Project.swift deleted file mode 100644 index c0d7d1d..0000000 --- a/SiteGenerator/Sources/SiteGenerator/Projects/Project.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// Project.swift -// SiteGenerator -// -// Created by Sami Samhuri on 2019-12-02. -// - -import Foundation - -public struct Project { - public let title: String - public let description: String - public let url: URL - - public init(title: String, description: String, url: URL) { - self.title = title - self.description = description - self.url = url - } -} diff --git a/SiteGenerator/Tests/LinuxMain.swift b/SiteGenerator/Tests/LinuxMain.swift deleted file mode 100644 index 8b7ffc0..0000000 --- a/SiteGenerator/Tests/LinuxMain.swift +++ /dev/null @@ -1,7 +0,0 @@ -import XCTest - -import SiteGeneratorTests - -var tests = [XCTestCaseEntry]() -tests += SiteGeneratorTests.allTests() -XCTMain(tests) diff --git a/SiteGenerator/Tests/SiteGeneratorTests/SiteGeneratorTests.swift b/SiteGenerator/Tests/SiteGeneratorTests/SiteGeneratorTests.swift deleted file mode 100644 index c2062e6..0000000 --- a/SiteGenerator/Tests/SiteGeneratorTests/SiteGeneratorTests.swift +++ /dev/null @@ -1,47 +0,0 @@ -import XCTest -@testable import SiteGenerator - -final class SiteGeneratorTests: XCTestCase { - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct - // results. - - // Some of the APIs that we use below are available in macOS 10.13 and above. - guard #available(macOS 10.13, *) else { - return - } - - let fooBinary = productsDirectory.appendingPathComponent("SiteGenerator") - - let process = Process() - process.executableURL = fooBinary - - let pipe = Pipe() - process.standardOutput = pipe - - try process.run() - process.waitUntilExit() - - let data = pipe.fileHandleForReading.readDataToEndOfFile() - let output = String(data: data, encoding: .utf8) - - XCTAssertEqual(output, "") - } - - /// Returns path to the built products directory. - var productsDirectory: URL { - #if os(macOS) - for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") { - return bundle.bundleURL.deletingLastPathComponent() - } - fatalError("couldn't find the products directory") - #else - return Bundle.main.bundleURL - #endif - } - - static var allTests = [ - ("testExample", testExample), - ] -} diff --git a/SiteGenerator/Tests/SiteGeneratorTests/XCTestManifests.swift b/SiteGenerator/Tests/SiteGeneratorTests/XCTestManifests.swift deleted file mode 100644 index 2f0d547..0000000 --- a/SiteGenerator/Tests/SiteGeneratorTests/XCTestManifests.swift +++ /dev/null @@ -1,9 +0,0 @@ -import XCTest - -#if !canImport(ObjectiveC) -public func allTests() -> [XCTestCaseEntry] { - return [ - testCase(SiteGeneratorTests.allTests), - ] -} -#endif diff --git a/gensite/Package.resolved b/gensite/Package.resolved index 0784686..b3cfe99 100644 --- a/gensite/Package.resolved +++ b/gensite/Package.resolved @@ -10,15 +10,6 @@ "version": "0.2.0" } }, - { - "package": "PathKit", - "repositoryURL": "https://github.com/kylef/PathKit.git", - "state": { - "branch": null, - "revision": "e2f5be30e4c8f531c9c1e8765aa7b71c0a45d7a0", - "version": "0.9.2" - } - }, { "package": "Plot", "repositoryURL": "https://github.com/johnsundell/plot.git", @@ -27,24 +18,6 @@ "revision": "dd7fce79ce4802afdc7d45ce34bddc5cea566202", "version": "0.2.0" } - }, - { - "package": "Spectre", - "repositoryURL": "https://github.com/kylef/Spectre.git", - "state": { - "branch": null, - "revision": "f14ff47f45642aa5703900980b014c2e9394b6e5", - "version": "0.9.0" - } - }, - { - "package": "Stencil", - "repositoryURL": "https://github.com/stencilproject/Stencil.git", - "state": { - "branch": null, - "revision": "0e9a78d6584e3812cd9c09494d5c7b483e8f533c", - "version": "0.13.1" - } } ] }, diff --git a/posts/2016/08/ios-git-pre-commit-hook.md b/posts/2016/08/ios-git-pre-commit-hook.md index 15c53ec..3ac4729 100644 --- a/posts/2016/08/ios-git-pre-commit-hook.md +++ b/posts/2016/08/ios-git-pre-commit-hook.md @@ -18,13 +18,13 @@ If you don't care what I did or why then you can just [see the updated script][g The diff command is repeated. This is any easy win: -
+```bash
 diff-index() {
     git diff-index -p -M --cached HEAD -- "$@"
 }
 
 if diff-index '*Tests.swift' | ...
-
+``` You get the idea. @@ -34,7 +34,9 @@ One problem is that the bootstrap script uses an absolute path when creating a s That's easily fixed by using a relative path to your pre-commit hook, like so: -
ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
+```bash +ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit +``` Ah, this is more flexible! Of course if you ever move the script itself then it's on you to update the symlink and bootstrap.sh, but that was already the case anyway. @@ -46,42 +48,42 @@ Ok great so this script tells me there are errors. Well, script, what exactly _a First ignore the fact I'm talking to a shell script. I don't get out much. Anyway... now we need to pull out the regular expressions and globs so we can reuse them to show what the actual errors are if we find any. -
+```bash
 test_pattern='^\+\s*\b(fdescribe|fit|fcontext|xdescribe|xit|xcontext)\('
 test_glob='*Tests.swift *Specs.swift'
 if diff-index $test_glob | egrep "$test_pattern" >/dev/null 2>&1
 ...
-
+``` _Pro tip: I prefixed test\_pattern with `\b` to only match word boundaries to reduce false positives._ And: -
+```bash
 misplaced_pattern='misplaced="YES"'
 misplaced_glob='*.xib *.storyboard'
 if diff-index $misplaced_glob | grep '^+' | egrep "$misplaced_pattern" >/dev/null 2>&1
 ...
-
+``` You may notice that I snuck in `*Specs.swift` as well. Let's not be choosy about file naming. Then we need to show where the errors are by using `diff-indef`, with an `|| true` at the end because the whole script fails if any single command fails, and `git diff-index` regularly exits with non-zero status (I didn't look into why that is). -
+```bash
 echo "COMMIT REJECTED for fdescribe/fit/fcontext/xdescribe/xit/xcontext." >&2
 echo "Remove focused and disabled tests before committing." >&2
 diff-index $test_glob | egrep -2 "$test_pattern" || true >&2
 echo '----' >&2
-
+``` And for misplaced views: -
+```bash
 echo "COMMIT REJECTED for misplaced views. Correct them before committing." >&2
 git grep -E "$misplaced_pattern" $misplaced_glob || true >&2
 echo '----' >&2
-
+``` ## Fix all the things, at once @@ -91,15 +93,21 @@ The first step is to exit at the end using a code in a variable that is set to 1 Up top: -
failed=0
+```bash +failed=0 +``` In the middle, where we detect errors: -
failed=1
+```bash +failed=1 +``` And at the bottom: -
exit $failed
+```bash +exit $failed +``` That's all there is to it. If we don't exit early then all the code runs. @@ -113,7 +121,7 @@ Those were all the obvious improvements in my mind and now I'm using this modifi Here's the whole thing put together: -
+```bash
 #!/usr/bin/env bash
 #
 # Based on http://merowing.info/2016/08/setting-up-pre-commit-hook-for-ios/
@@ -148,4 +156,4 @@ then
 fi
 
 exit $failed
-
\ No newline at end of file +``` \ No newline at end of file diff --git a/samhuri.net/Package.resolved b/samhuri.net/Package.resolved index 0784686..b3cfe99 100644 --- a/samhuri.net/Package.resolved +++ b/samhuri.net/Package.resolved @@ -10,15 +10,6 @@ "version": "0.2.0" } }, - { - "package": "PathKit", - "repositoryURL": "https://github.com/kylef/PathKit.git", - "state": { - "branch": null, - "revision": "e2f5be30e4c8f531c9c1e8765aa7b71c0a45d7a0", - "version": "0.9.2" - } - }, { "package": "Plot", "repositoryURL": "https://github.com/johnsundell/plot.git", @@ -27,24 +18,6 @@ "revision": "dd7fce79ce4802afdc7d45ce34bddc5cea566202", "version": "0.2.0" } - }, - { - "package": "Spectre", - "repositoryURL": "https://github.com/kylef/Spectre.git", - "state": { - "branch": null, - "revision": "f14ff47f45642aa5703900980b014c2e9394b6e5", - "version": "0.9.0" - } - }, - { - "package": "Stencil", - "repositoryURL": "https://github.com/stencilproject/Stencil.git", - "state": { - "branch": null, - "revision": "0e9a78d6584e3812cd9c09494d5c7b483e8f533c", - "version": "0.13.1" - } } ] }, diff --git a/samhuri.net/Package.swift b/samhuri.net/Package.swift index 2da060c..da3f0b5 100644 --- a/samhuri.net/Package.swift +++ b/samhuri.net/Package.swift @@ -16,8 +16,7 @@ let package = Package( targets: ["samhuri.net"]), ], dependencies: [ - .package(path: "../SiteGenerator"), - .package(url: "https://github.com/stencilproject/Stencil.git", from: "0.13.0"), + .package(url: "https://github.com/johnsundell/ink.git", from: "0.2.0"), .package(url: "https://github.com/johnsundell/plot.git", from: "0.2.0"), ], targets: [ @@ -26,9 +25,8 @@ let package = Package( .target( name: "samhuri.net", dependencies: [ + "Ink", "Plot", - "SiteGenerator", - "Stencil", ]), .testTarget( name: "samhuri.netTests", diff --git a/samhuri.net/Sources/samhuri.net/Date+Sugar.swift b/samhuri.net/Sources/samhuri.net/Date+Sugar.swift index 82c32b4..1483e3c 100644 --- a/samhuri.net/Sources/samhuri.net/Date+Sugar.swift +++ b/samhuri.net/Sources/samhuri.net/Date+Sugar.swift @@ -11,4 +11,12 @@ 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/samhuri.net/Sources/samhuri.net/Page.swift b/samhuri.net/Sources/samhuri.net/Page.swift index 488da84..2246f9a 100644 --- a/samhuri.net/Sources/samhuri.net/Page.swift +++ b/samhuri.net/Sources/samhuri.net/Page.swift @@ -6,7 +6,6 @@ // import Foundation -import SiteGenerator struct Page { let title: String diff --git a/samhuri.net/Sources/samhuri.net/PageRenderer.swift b/samhuri.net/Sources/samhuri.net/PageRenderer.swift index 16e6768..9bcb22a 100644 --- a/samhuri.net/Sources/samhuri.net/PageRenderer.swift +++ b/samhuri.net/Sources/samhuri.net/PageRenderer.swift @@ -7,22 +7,8 @@ import Foundation import Plot -import SiteGenerator - -#warning("Deprecated imports") -import PathKit -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) - } - func render(_ body: Node, context: TemplateContext) -> String { Template.site(body: body, context: context).render(indentedBy: .spaces(2)) } @@ -30,51 +16,8 @@ final class PageRenderer { extension PageRenderer: MarkdownPageRenderer { func renderPage(site: Site, bodyHTML: String, metadata: [String: String]) throws -> String { - let pageTitle = metadata["Title", default: ""] + let pageTitle = metadata["Title"] let context = SiteContext(site: site, subtitle: pageTitle, templateAssets: .none()) - return render(.page(title: pageTitle, bodyHTML: bodyHTML), context: context) - } -} - -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, subtitle: nil, templateAssets: .none()) - let contextDict = siteContext.dictionary.merging(context, uniquingKeysWith: { _, new in new }) - return try stencil.renderTemplate(name: template.htmlFilename, context: contextDict) - } -} - -extension PageRenderer: ProjectsTemplateRenderer { - func renderProjects(_ projects: [Project], site: Site, assets: TemplateAssets) throws -> String { - let context = SiteContext(site: site, subtitle: "Projects", templateAssets: assets) - return render(.projects(projects), context: context) - } - - func renderProject(_ project: Project, site: Site, assets: TemplateAssets) throws -> String { - let projectContext = ProjectContext(project: project, site: site, templateAssets: assets) - let context = SiteContext(site: site, subtitle: project.title, templateAssets: assets) - return render(.project(projectContext), context: context) + return render(.page(title: pageTitle ?? "", bodyHTML: bodyHTML), context: context) } } diff --git a/samhuri.net/Sources/samhuri.net/Posts/FeedPostTemplate.swift b/samhuri.net/Sources/samhuri.net/Posts/FeedPostTemplate.swift new file mode 100644 index 0000000..7ec54ae --- /dev/null +++ b/samhuri.net/Sources/samhuri.net/Posts/FeedPostTemplate.swift @@ -0,0 +1,30 @@ +// +// FeedPostTemplate.swift +// samhuri.net +// +// Created by Sami Samhuri on 2019-12-22. +// + +import Foundation +import Plot + +private extension Node where Context == HTML.BodyContext { + static func link(_ attributes: Attribute...) -> Self { + .element(named: "link", attributes: attributes) + } +} + +extension Node where Context == HTML.BodyContext { + static func feedPost(_ post: Post, url: URL, styles: [URL]) -> Self { + .group([ + .group(styles.map { style in + .link(.rel(.stylesheet), .href(style), .type("text/css")) + }), + .div( + .p(.class("time"), .text(post.formattedDate)), + .raw(post.body), + .p(.a(.class("permalink"), .href(url), "∞")) + ), + ]) + } +} diff --git a/samhuri.net/Sources/samhuri.net/Posts/MonthPostsTemplate.swift b/samhuri.net/Sources/samhuri.net/Posts/MonthPostsTemplate.swift new file mode 100644 index 0000000..d04f7c0 --- /dev/null +++ b/samhuri.net/Sources/samhuri.net/Posts/MonthPostsTemplate.swift @@ -0,0 +1,22 @@ +// +// MonthPostsTemplate.swift +// samhuri.net +// +// Created by Sami Samhuri on 2019-12-22. +// + +import Foundation +import Plot + +extension Node where Context == HTML.BodyContext { + static func monthPosts(_ posts: MonthPosts) -> Self { + .group([ + .div(.class("container"), + .h1("\(posts.month.name) \(posts.year)") + ), + .group(posts.posts.sorted(by: >).map { post in + .div(.class("container"), self.post(post)) + }) + ]) + } +} diff --git a/samhuri.net/Sources/samhuri.net/Posts/PageRenderer+Posts.swift b/samhuri.net/Sources/samhuri.net/Posts/PageRenderer+Posts.swift index 4559749..9899bbc 100644 --- a/samhuri.net/Sources/samhuri.net/Posts/PageRenderer+Posts.swift +++ b/samhuri.net/Sources/samhuri.net/Posts/PageRenderer+Posts.swift @@ -1,8 +1,69 @@ // -// File.swift -// +// 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, assets: TemplateAssets) throws -> String { + let context = SiteContext(site: site, subtitle: "Archive", templateAssets: assets) + return render(.archive(postsByYear), context: context) + } + + func renderYearPosts(_ yearPosts: YearPosts, site: Site, assets: TemplateAssets) throws -> String { + let context = SiteContext(site: site, subtitle: yearPosts.title, templateAssets: assets) + return render(.yearPosts(yearPosts), context: context) + } + + func renderMonthPosts(_ posts: MonthPosts, site: Site, assets: TemplateAssets) throws -> String { + 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, assets: TemplateAssets) throws -> String { + let context = SiteContext(site: site, subtitle: post.title, templateAssets: assets) + return render(.post(post), context: context) + } + + func renderRecentPosts(_ posts: [Post], site: Site, assets: TemplateAssets) throws -> String { + let context = SiteContext(site: site, subtitle: nil, templateAssets: assets) + return render(.recentPosts(posts), context: context) + } + + // MARK: - Feeds + + func renderFeedPost(_ post: Post, site: Site, assets: TemplateAssets) throws -> String { + let context = SiteContext(site: site, subtitle: post.title, templateAssets: assets) + 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, assets: TemplateAssets) 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, assets: assets)) + ) + }) + ).render(indentedBy: .spaces(2)) + } +} diff --git a/samhuri.net/Sources/samhuri.net/Posts/PostTemplate.swift b/samhuri.net/Sources/samhuri.net/Posts/PostTemplate.swift new file mode 100644 index 0000000..f743cd6 --- /dev/null +++ b/samhuri.net/Sources/samhuri.net/Posts/PostTemplate.swift @@ -0,0 +1,34 @@ +// +// PostTemplate.swift +// samhuri.net +// +// Created by Sami Samhuri on 2019-12-22. +// + +import Foundation +import Plot + +extension Node where Context == HTML.BodyContext { + static func post(_ post: Post) -> Self { + .group([ + .article( + .header( + .h2(postTitleLink(post)), + .time(.text(post.formattedDate)), + .a(.class("permalink"), .href(post.path), "∞") + ), + .raw(post.body) + ), + .div(.class("row clearfix"), + .p(.class("fin"), .i(.class("fa fa-code"))) + ) + ]) + } + + static func postTitleLink(_ post: Post) -> Self { + .if(post.isLink, + .a(.href(post.link?.absoluteString ?? post.path), "→ \(post.title)"), + else: .a(.href(post.path), .text(post.title)) + ) + } +} diff --git a/samhuri.net/Sources/samhuri.net/Posts/PostsArchiveTemplate.swift b/samhuri.net/Sources/samhuri.net/Posts/PostsArchiveTemplate.swift new file mode 100644 index 0000000..bbbe2c8 --- /dev/null +++ b/samhuri.net/Sources/samhuri.net/Posts/PostsArchiveTemplate.swift @@ -0,0 +1,22 @@ +// +// ArchiveTemplate.swift +// samhuri.net +// +// Created by Sami Samhuri on 2019-12-21. +// + +import Foundation +import Plot + +extension Node where Context == HTML.BodyContext { + static func archive(_ postsByYear: PostsByYear) -> Self { + .group([ + .div(.class("container"), + .h1("Archive") + ), + .group(postsByYear.years.sorted(by: >).map { year in + .yearPosts(postsByYear[year]) + }), + ]) + } +} diff --git a/samhuri.net/Sources/samhuri.net/Posts/RecentPostsTemplate.swift b/samhuri.net/Sources/samhuri.net/Posts/RecentPostsTemplate.swift new file mode 100644 index 0000000..98a4945 --- /dev/null +++ b/samhuri.net/Sources/samhuri.net/Posts/RecentPostsTemplate.swift @@ -0,0 +1,17 @@ +// +// RecentPostsTemplate.swift +// samhuri.net +// +// Created by Sami Samhuri on 2019-12-22. +// + +import Foundation +import Plot + +extension Node where Context == HTML.BodyContext { + static func recentPosts(_ posts: [Post]) -> Self { + .div(.class("container"), + .group(posts.map(post)) + ) + } +} diff --git a/samhuri.net/Sources/samhuri.net/Posts/YearPostsTemplate.swift b/samhuri.net/Sources/samhuri.net/Posts/YearPostsTemplate.swift new file mode 100644 index 0000000..677233b --- /dev/null +++ b/samhuri.net/Sources/samhuri.net/Posts/YearPostsTemplate.swift @@ -0,0 +1,51 @@ +// +// YearPostsTemplate.swift +// samhuri.net +// +// Created by Sami Samhuri on 2019-12-21. +// + +import Foundation +import Plot + +extension Node where Context == HTML.BodyContext { + static func yearPosts(_ posts: YearPosts) -> Self { + .div(.class("container"), + .h2(.class("year"), + .a(.href(posts.path), .text(posts.title)) + ), + + .group(posts.months.sorted(by: >).map { month in + .monthTitles(posts[month]) + }) + ) + } + + static func monthTitles(_ posts: MonthPosts) -> Self { + .group([ + .h3(.class("month"), + .a(.href(posts.path), "\(posts.month.name)") + ), + .ul(.class("archive"), + .group(posts.posts.sorted(by: >).map { post in + .postItem(post, date: "\(post.date.day) \(posts.month.abbreviation)") + }) + ), + ]) + } +} + +extension Node where Context == HTML.ListContext { + static func postItem(_ post: Post, date: Node) -> Self { + .if(post.isLink, .li( + .a(.href(post.link?.absoluteString ?? post.path), "→ \(post.title)"), + .time(date), + .a(.class("permalink"), .href(post.path), "∞") + ), + else: .li( + .a(.href(post.path), .text(post.title)), + .time(date) + ) + ) + } +} diff --git a/samhuri.net/Sources/samhuri.net/Projects/PageRenderer+Projects.swift b/samhuri.net/Sources/samhuri.net/Projects/PageRenderer+Projects.swift index 4559749..e0169d8 100644 --- a/samhuri.net/Sources/samhuri.net/Projects/PageRenderer+Projects.swift +++ b/samhuri.net/Sources/samhuri.net/Projects/PageRenderer+Projects.swift @@ -1,8 +1,22 @@ // -// File.swift -// +// PageRenderer+Projects.swift +// samhuri.net // // Created by Sami Samhuri on 2019-12-22. // import Foundation +import Plot + +extension PageRenderer: ProjectsTemplateRenderer { + func renderProjects(_ projects: [Project], site: Site, assets: TemplateAssets) throws -> String { + let context = SiteContext(site: site, subtitle: "Projects", templateAssets: assets) + return render(.projects(projects), context: context) + } + + func renderProject(_ project: Project, site: Site, assets: TemplateAssets) throws -> String { + let projectContext = ProjectContext(project: project, site: site, templateAssets: assets) + let context = SiteContext(site: site, subtitle: project.title, templateAssets: assets) + return render(.project(projectContext), context: context) + } +} diff --git a/samhuri.net/Sources/samhuri.net/Projects/ProjectContext.swift b/samhuri.net/Sources/samhuri.net/Projects/ProjectContext.swift index 4e06b40..1ac3cce 100644 --- a/samhuri.net/Sources/samhuri.net/Projects/ProjectContext.swift +++ b/samhuri.net/Sources/samhuri.net/Projects/ProjectContext.swift @@ -6,7 +6,6 @@ // import Foundation -import SiteGenerator struct ProjectContext: TemplateContext { let site: Site diff --git a/samhuri.net/Sources/samhuri.net/Projects/ProjectTemplates.swift b/samhuri.net/Sources/samhuri.net/Projects/ProjectTemplate.swift similarity index 68% rename from samhuri.net/Sources/samhuri.net/Projects/ProjectTemplates.swift rename to samhuri.net/Sources/samhuri.net/Projects/ProjectTemplate.swift index 86ccbb6..b487cf0 100644 --- a/samhuri.net/Sources/samhuri.net/Projects/ProjectTemplates.swift +++ b/samhuri.net/Sources/samhuri.net/Projects/ProjectTemplate.swift @@ -1,5 +1,5 @@ // -// ProjectTemplates.swift +// ProjectTemplate.swift // samhuri.net // // Created by Sami Samhuri on 2019-12-19. @@ -7,29 +7,9 @@ import Foundation import Plot -import SiteGenerator extension Node where Context == HTML.BodyContext { - static func projects(_ projects: [Project]) -> Node { - .group([ - .article(.class("container"), - .h1("Projects"), - - .group(projects.map { project in - .div(.class("project-listing"), - .h4(.a(.href(project.url), .text(project.title))), - .p(.class("description"), .text(project.description)) - ) - }) - ), - - .div(.class("row clearfix"), - .p(.class("fin"), .i(.class("fa fa-code"))) - ) - ]) - } - - static func project(_ context: ProjectContext) -> Node { + static func project(_ context: ProjectContext) -> Self { .group([ .article(.class("container project"), // projects.js picks up this data-title attribute and uses it to render all the Github stuff diff --git a/samhuri.net/Sources/samhuri.net/Projects/ProjectsTemplate.swift b/samhuri.net/Sources/samhuri.net/Projects/ProjectsTemplate.swift new file mode 100644 index 0000000..0c50152 --- /dev/null +++ b/samhuri.net/Sources/samhuri.net/Projects/ProjectsTemplate.swift @@ -0,0 +1,30 @@ +// +// ProjectsTemplate.swift +// samhuri.net +// +// Created by Sami Samhuri on 2019-12-19. +// + +import Foundation +import Plot + +extension Node where Context == HTML.BodyContext { + static func projects(_ projects: [Project]) -> Self { + .group([ + .article(.class("container"), + .h1("Projects"), + + .group(projects.map { project in + .div(.class("project-listing"), + .h4(.a(.href(project.url), .text(project.title))), + .p(.class("description"), .text(project.description)) + ) + }) + ), + + .div(.class("row clearfix"), + .p(.class("fin"), .i(.class("fa fa-code"))) + ) + ]) + } +} diff --git a/SiteGenerator/Sources/SiteGenerator/BuiltSite.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/BuiltSite.swift similarity index 64% rename from SiteGenerator/Sources/SiteGenerator/BuiltSite.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/BuiltSite.swift index ef9733d..fb983b7 100644 --- a/SiteGenerator/Sources/SiteGenerator/BuiltSite.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/BuiltSite.swift @@ -1,16 +1,16 @@ // // BuiltSite.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-15. // import Foundation -public struct BuiltSite { - public let site: Site - public let plugins: [Plugin] - public let renderers: [Renderer] +struct BuiltSite { + let site: Site + let plugins: [Plugin] + let renderers: [Renderer] init(site: Site, plugins: [Plugin], renderers: [Renderer]) { self.site = site diff --git a/SiteGenerator/Sources/SiteGenerator/MarkdownPageRenderer.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/MarkdownPageRenderer.swift similarity index 77% rename from SiteGenerator/Sources/SiteGenerator/MarkdownPageRenderer.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/MarkdownPageRenderer.swift index 61c7813..0de70b2 100644 --- a/SiteGenerator/Sources/SiteGenerator/MarkdownPageRenderer.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/MarkdownPageRenderer.swift @@ -1,12 +1,12 @@ // // MarkdownPageRenderer.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-03. // import Foundation -public protocol MarkdownPageRenderer { +protocol MarkdownPageRenderer { func renderPage(site: Site, bodyHTML: String, metadata: [String: String]) throws -> String } diff --git a/SiteGenerator/Sources/SiteGenerator/MarkdownRenderer.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/MarkdownRenderer.swift similarity index 84% rename from SiteGenerator/Sources/SiteGenerator/MarkdownRenderer.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/MarkdownRenderer.swift index 251b005..31ad658 100644 --- a/SiteGenerator/Sources/SiteGenerator/MarkdownRenderer.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/MarkdownRenderer.swift @@ -1,6 +1,6 @@ // // MarkdownRenderer.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-02. // @@ -8,21 +8,21 @@ import Foundation import Ink -public final class MarkdownRenderer: Renderer { +final class MarkdownRenderer: Renderer { let fileManager: FileManager = .default let markdownParser = MarkdownParser() let pageRenderer: MarkdownPageRenderer - public init(pageRenderer: MarkdownPageRenderer) { + init(pageRenderer: MarkdownPageRenderer) { self.pageRenderer = pageRenderer } - public func canRenderFile(named filename: String, withExtension ext: String) -> Bool { + 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(site: Site, fileURL: URL, targetDir: URL) throws { + func render(site: Site, fileURL: URL, targetDir: URL) throws { let bodyMarkdown = try String(contentsOf: fileURL, encoding: .utf8) let bodyHTML = markdownParser.html(from: bodyMarkdown).trimmingCharacters(in: .whitespacesAndNewlines) let metadata = try markdownMetadata(from: fileURL) diff --git a/SiteGenerator/Sources/SiteGenerator/Plugin.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Plugin.swift similarity index 81% rename from SiteGenerator/Sources/SiteGenerator/Plugin.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/Plugin.swift index 1dd1e99..5f02ef2 100644 --- a/SiteGenerator/Sources/SiteGenerator/Plugin.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Plugin.swift @@ -1,13 +1,13 @@ // // Plugin.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-02. // import Foundation -public protocol Plugin { +protocol Plugin { func setUp(site: Site, sourceURL: URL) throws func render(site: Site, targetURL: URL) throws diff --git a/SiteGenerator/Sources/SiteGenerator/Posts/Feeds/JSONFeed.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Feeds/JSONFeed.swift similarity index 92% rename from SiteGenerator/Sources/SiteGenerator/Posts/Feeds/JSONFeed.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Feeds/JSONFeed.swift index adeb06f..4e0569d 100644 --- a/SiteGenerator/Sources/SiteGenerator/Posts/Feeds/JSONFeed.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Feeds/JSONFeed.swift @@ -1,6 +1,6 @@ // // JSONFeed.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-15. // diff --git a/SiteGenerator/Sources/SiteGenerator/Posts/Feeds/JSONFeedWriter.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Feeds/JSONFeedWriter.swift similarity index 93% rename from SiteGenerator/Sources/SiteGenerator/Posts/Feeds/JSONFeedWriter.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Feeds/JSONFeedWriter.swift index f6268b8..357c11d 100644 --- a/SiteGenerator/Sources/SiteGenerator/Posts/Feeds/JSONFeedWriter.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Feeds/JSONFeedWriter.swift @@ -1,6 +1,6 @@ // // JSONFeedWriter.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-10. // @@ -54,9 +54,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.renderTemplate(.feedPost, site: site, context: [ - "post": post, - ]), + content_html: try templateRenderer.renderFeedPost(post, site: site, assets: .none()), tags: post.tags ) } diff --git a/SiteGenerator/Sources/SiteGenerator/Posts/Feeds/RSSFeed.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Feeds/RSSFeed.swift similarity index 87% rename from SiteGenerator/Sources/SiteGenerator/Posts/Feeds/RSSFeed.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Feeds/RSSFeed.swift index 38013fd..b41b928 100644 --- a/SiteGenerator/Sources/SiteGenerator/Posts/Feeds/RSSFeed.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Feeds/RSSFeed.swift @@ -1,6 +1,6 @@ // // RSSFeed.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-15. // diff --git a/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Feeds/RSSFeedWriter.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Feeds/RSSFeedWriter.swift new file mode 100644 index 0000000..58eabf4 --- /dev/null +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Feeds/RSSFeedWriter.swift @@ -0,0 +1,25 @@ +// +// RSSFeedWriter.swift +// samhuri.net +// +// Created by Sami Samhuri on 2019-12-10. +// + +import Foundation + +final class RSSFeedWriter { + let fileManager: FileManager + let feed: RSSFeed + + init(fileManager: FileManager = .default, feed: RSSFeed) { + self.fileManager = fileManager + self.feed = feed + } + + func writeFeed(_ posts: [Post], for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws { + let feedURL = site.url.appendingPathComponent(feed.path) + let feedXML = try templateRenderer.renderRSSFeed(posts: posts, feedURL: feedURL, site: site, assets: .none()) + let feedFileURL = targetURL.appendingPathComponent(feed.path) + try feedXML.write(to: feedFileURL, atomically: true, encoding: .utf8) + } +} diff --git a/SiteGenerator/Sources/SiteGenerator/Posts/Feeds/String+EscapeXML.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Feeds/String+EscapeXML.swift similarity index 88% rename from SiteGenerator/Sources/SiteGenerator/Posts/Feeds/String+EscapeXML.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Feeds/String+EscapeXML.swift index 48c411b..13155c8 100644 --- a/SiteGenerator/Sources/SiteGenerator/Posts/Feeds/String+EscapeXML.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Feeds/String+EscapeXML.swift @@ -1,6 +1,6 @@ // // XMLEscape.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-12. // @@ -8,6 +8,7 @@ import Foundation extension String { + @available(*, deprecated) func escapedForXML() -> String { replacingOccurrences(of: "&", with: "&") .replacingOccurrences(of: "<", with: "<") diff --git a/SiteGenerator/Sources/SiteGenerator/Posts/Month.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Month.swift similarity index 98% rename from SiteGenerator/Sources/SiteGenerator/Posts/Month.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Month.swift index 6d6e92a..39111e5 100644 --- a/SiteGenerator/Sources/SiteGenerator/Posts/Month.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Month.swift @@ -1,6 +1,6 @@ // // Month.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-03. // diff --git a/SiteGenerator/Sources/SiteGenerator/Posts/Post.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Post.swift similarity index 77% rename from SiteGenerator/Sources/SiteGenerator/Posts/Post.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Post.swift index f492048..ec2b005 100644 --- a/SiteGenerator/Sources/SiteGenerator/Posts/Post.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/Post.swift @@ -1,6 +1,6 @@ // // Post.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-01. // @@ -18,11 +18,6 @@ struct Post { let body: String let path: String - // These are computed properties but are computed eagerly because - // Stencil is unable to use real computed properties at this time. - let isLink: Bool - let day: Int - init(slug: String, title: String, author: String, date: Date, formattedDate: String, link: URL?, tags: [String], body: String, path: String) { self.slug = slug self.title = title @@ -33,10 +28,10 @@ struct Post { self.tags = tags self.body = body self.path = path + } - // Eagerly computed properties - self.isLink = link != nil - self.day = date.day + var isLink: Bool { + link != nil } } diff --git a/SiteGenerator/Sources/SiteGenerator/Posts/PostRepo.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostRepo.swift similarity index 99% rename from SiteGenerator/Sources/SiteGenerator/Posts/PostRepo.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostRepo.swift index 2e7b292..535aad9 100644 --- a/SiteGenerator/Sources/SiteGenerator/Posts/PostRepo.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostRepo.swift @@ -1,6 +1,6 @@ // // PostRepo.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-09. // diff --git a/SiteGenerator/Sources/SiteGenerator/Posts/PostTransformer.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostTransformer.swift similarity index 99% rename from SiteGenerator/Sources/SiteGenerator/Posts/PostTransformer.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostTransformer.swift index 257f2fd..a506ded 100644 --- a/SiteGenerator/Sources/SiteGenerator/Posts/PostTransformer.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostTransformer.swift @@ -1,6 +1,6 @@ // // PostTransformer.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-09. // diff --git a/SiteGenerator/Sources/SiteGenerator/Posts/PostWriter.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostWriter.swift similarity index 60% rename from SiteGenerator/Sources/SiteGenerator/Posts/PostWriter.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostWriter.swift index b9d83a0..08b9946 100644 --- a/SiteGenerator/Sources/SiteGenerator/Posts/PostWriter.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostWriter.swift @@ -1,6 +1,6 @@ // // PostWriter.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-09. // @@ -22,10 +22,7 @@ final class PostWriter { extension PostWriter { func writePosts(_ posts: [Post], for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws { for post in posts { - let postHTML = try templateRenderer.renderTemplate(.post, site: site, context: [ - "title": post.title, - "post": post, - ]) + let postHTML = try templateRenderer.renderPost(post, site: site, assets: .none()) let postURL = targetURL .appendingPathComponent(outputPath) .appendingPathComponent(filePath(date: post.date, slug: post.slug)) @@ -43,9 +40,7 @@ extension PostWriter { extension PostWriter { func writeRecentPosts(_ recentPosts: [Post], for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws { - let recentPostsHTML = try templateRenderer.renderTemplate(.recentPosts, site: site, context: [ - "recentPosts": recentPosts, - ]) + let recentPostsHTML = try templateRenderer.renderRecentPosts(recentPosts, site: site, assets: .none()) let fileURL = targetURL.appendingPathComponent("index.html") try fileManager.createDirectory(at: fileURL.deletingLastPathComponent(), withIntermediateDirectories: true, attributes: nil) try recentPostsHTML.write(to: fileURL, atomically: true, encoding: .utf8) @@ -56,50 +51,20 @@ extension PostWriter { extension PostWriter { func writeArchive(posts: PostsByYear, for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws { - let allYears = posts.byYear.keys.sorted(by: >) - let archiveHTML = try templateRenderer.renderTemplate(.archive, site: site, context: [ - "title": "Archive", - "years": allYears.map { contextDictionaryForYearPosts(posts[$0]) }, - ]) + let archiveHTML = try templateRenderer.renderArchive(postsByYear: posts, site: site, assets: .none()) let archiveURL = targetURL.appendingPathComponent(outputPath).appendingPathComponent("index.html") try fileManager.createDirectory(at: archiveURL.deletingLastPathComponent(), withIntermediateDirectories: true, attributes: nil) try archiveHTML.write(to: archiveURL, atomically: true, encoding: .utf8) } - - private func contextDictionaryForYearPosts(_ posts: YearPosts) -> [String: Any] { - [ - "path": posts.path, - "title": posts.title, - "months": posts.months.sorted(by: >).map { month in - contextDictionaryForMonthPosts(posts[month], year: posts.year) - }, - ] - } - - private func contextDictionaryForMonthPosts(_ posts: MonthPosts, year: Int) -> [String: Any] { - [ - "path": posts.path, - "name": posts.month.name, - "abbreviation": posts.month.abbreviation, - "posts": posts.posts.sorted(by: >), - ] - } } // MARK: - Yearly post index pages extension PostWriter { func writeYearIndexes(posts: PostsByYear, for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws { - for (year, yearPosts) in posts.byYear { - let months = yearPosts.months.sorted(by: >) + for yearPosts in posts.byYear.values { let yearDir = targetURL.appendingPathComponent(yearPosts.path) - let context: [String: Any] = [ - "title": yearPosts.title, - "path": yearPosts.path, - "year": year, - "months": months.map { contextDictionaryForMonthPosts(posts[year][$0], year: year) }, - ] - let yearHTML = try templateRenderer.renderTemplate(.yearPosts, site: site, context: context) + let yearHTML = try templateRenderer.renderYearPosts(yearPosts, site: site, assets: .none()) let yearURL = yearDir.appendingPathComponent("index.html") try fileManager.createDirectory(at: yearDir, withIntermediateDirectories: true, attributes: nil) try yearHTML.write(to: yearURL, atomically: true, encoding: .utf8) @@ -111,14 +76,10 @@ extension PostWriter { extension PostWriter { func writeMonthRollups(posts: PostsByYear, for site: Site, to targetURL: URL, with templateRenderer: PostsTemplateRenderer) throws { - for (year, yearPosts) in posts.byYear { - for month in yearPosts.months { - let monthPosts = yearPosts[month] + for yearPosts in posts.byYear.values { + for monthPosts in yearPosts.byMonth.values { let monthDir = targetURL.appendingPathComponent(monthPosts.path) - let monthHTML = try templateRenderer.renderTemplate(.monthPosts, site: site, context: [ - "title": "\(month.name) \(year)", - "posts": monthPosts.posts.sorted(by: >), - ]) + let monthHTML = try templateRenderer.renderMonthPosts(monthPosts, site: site, assets: .none()) let monthURL = monthDir.appendingPathComponent("index.html") try fileManager.createDirectory(at: monthDir, withIntermediateDirectories: true, attributes: nil) try monthHTML.write(to: monthURL, atomically: true, encoding: .utf8) diff --git a/SiteGenerator/Sources/SiteGenerator/Posts/PostsByYear.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostsByYear.swift similarity index 93% rename from SiteGenerator/Sources/SiteGenerator/Posts/PostsByYear.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostsByYear.swift index 9ec1ba1..6c40788 100644 --- a/SiteGenerator/Sources/SiteGenerator/Posts/PostsByYear.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostsByYear.swift @@ -1,6 +1,6 @@ // // Posts.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-03. // @@ -19,6 +19,10 @@ struct MonthPosts { var isEmpty: Bool { posts.isEmpty } + + var year: Int { + posts[0].date.year + } } // MARK: - @@ -62,6 +66,14 @@ struct PostsByYear { posts.forEach { add(post: $0) } } + var isEmpty: Bool { + byYear.isEmpty || byYear.values.allSatisfy { $0.isEmpty } + } + + var years: [Int] { + Array(byYear.keys) + } + subscript(year: Int) -> YearPosts { get { byYear[year, default: YearPosts(year: year, byMonth: [:], path: "\(path)/\(year)")] @@ -71,10 +83,6 @@ struct PostsByYear { } } - var isEmpty: Bool { - byYear.isEmpty || byYear.values.allSatisfy { $0.isEmpty } - } - mutating func add(post: Post) { let (year, month) = (post.date.year, Month(post.date.month)) self[year][month].posts.append(post) diff --git a/SiteGenerator/Sources/SiteGenerator/Posts/PostsPlugin.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostsPlugin.swift similarity index 90% rename from SiteGenerator/Sources/SiteGenerator/Posts/PostsPlugin.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostsPlugin.swift index 77b9a6d..ca933de 100644 --- a/SiteGenerator/Sources/SiteGenerator/Posts/PostsPlugin.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostsPlugin.swift @@ -1,13 +1,13 @@ // // PostsPlugin.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-03. // import Foundation -public final class PostsPlugin: Plugin { +final class PostsPlugin: Plugin { let templateRenderer: PostsTemplateRenderer let postRepo: PostRepo let postWriter: PostWriter @@ -30,7 +30,7 @@ public final class PostsPlugin: Plugin { // MARK: - Plugin methods - public func setUp(site: Site, sourceURL: URL) throws { + func setUp(site: Site, sourceURL: URL) throws { guard postRepo.postDataExists(at: sourceURL) else { return } @@ -38,7 +38,7 @@ public final class PostsPlugin: Plugin { try postRepo.readPosts(sourceURL: sourceURL) } - public func render(site: Site, targetURL: URL) throws { + func render(site: Site, targetURL: URL) throws { guard !postRepo.isEmpty else { return } diff --git a/SiteGenerator/Sources/SiteGenerator/Posts/PostsPluginBuilder.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostsPluginBuilder.swift similarity index 86% rename from SiteGenerator/Sources/SiteGenerator/Posts/PostsPluginBuilder.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostsPluginBuilder.swift index 4126351..d0680b0 100644 --- a/SiteGenerator/Sources/SiteGenerator/Posts/PostsPluginBuilder.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostsPluginBuilder.swift @@ -1,29 +1,29 @@ // // PostsPluginBuilder.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-15. // import Foundation -public final class PostsPluginBuilder { +final class PostsPluginBuilder { private let templateRenderer: PostsTemplateRenderer private var path: String? private var jsonFeed: JSONFeed? private var rssFeed: RSSFeed? - public init(templateRenderer: PostsTemplateRenderer) { + init(templateRenderer: PostsTemplateRenderer) { self.templateRenderer = templateRenderer } - public func path(_ path: String) -> PostsPluginBuilder { + func path(_ path: String) -> PostsPluginBuilder { precondition(self.path == nil, "path is already defined") self.path = path return self } - public func jsonFeed( + func jsonFeed( path: String? = nil, avatarPath: String? = nil, iconPath: String? = nil, @@ -39,13 +39,13 @@ public final class PostsPluginBuilder { return self } - public func rssFeed(path: String? = nil) -> PostsPluginBuilder { + func rssFeed(path: String? = nil) -> PostsPluginBuilder { precondition(rssFeed == nil, "RSS feed is already defined") rssFeed = RSSFeed(path: path ?? "feed.xml") return self } - public func build() -> PostsPlugin { + func build() -> PostsPlugin { let postRepo: PostRepo let postWriter: PostWriter if let outputPath = path { diff --git a/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostsTemplateRenderer.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostsTemplateRenderer.swift new file mode 100644 index 0000000..104ddcf --- /dev/null +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Posts/PostsTemplateRenderer.swift @@ -0,0 +1,26 @@ +// +// PostsTemplateRenderer.swift +// samhuri.net +// +// Created by Sami Samhuri on 2019-12-17. +// + +import Foundation + +protocol PostsTemplateRenderer { + func renderArchive(postsByYear: PostsByYear, site: Site, assets: TemplateAssets) throws -> String + + func renderYearPosts(_ yearPosts: YearPosts, site: Site, assets: TemplateAssets) throws -> String + + func renderMonthPosts(_ posts: MonthPosts, site: Site, assets: TemplateAssets) throws -> String + + func renderPost(_ post: Post, site: Site, assets: TemplateAssets) throws -> String + + func renderRecentPosts(_ posts: [Post], site: Site, assets: TemplateAssets) throws -> String + + // MARK: - Feeds + + func renderFeedPost(_ post: Post, site: Site, assets: TemplateAssets) throws -> String + + func renderRSSFeed(posts: [Post], feedURL: URL, site: Site, assets: TemplateAssets) throws -> String +} diff --git a/samhuri.net/Sources/samhuri.net/SiteGenerator/Projects/Project.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Projects/Project.swift new file mode 100644 index 0000000..3f83e64 --- /dev/null +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Projects/Project.swift @@ -0,0 +1,20 @@ +// +// Project.swift +// samhuri.net +// +// Created by Sami Samhuri on 2019-12-02. +// + +import Foundation + +struct Project { + let title: String + let description: String + let url: URL + + init(title: String, description: String, url: URL) { + self.title = title + self.description = description + self.url = url + } +} diff --git a/SiteGenerator/Sources/SiteGenerator/Projects/ProjectsPlugin.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Projects/ProjectsPlugin.swift similarity index 92% rename from SiteGenerator/Sources/SiteGenerator/Projects/ProjectsPlugin.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/Projects/ProjectsPlugin.swift index 1598ed5..5361bdf 100644 --- a/SiteGenerator/Sources/SiteGenerator/Projects/ProjectsPlugin.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Projects/ProjectsPlugin.swift @@ -1,6 +1,6 @@ // // ProjectsPlugin.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-02. // @@ -12,7 +12,7 @@ struct PartialProject { let description: String } -public final class ProjectsPlugin: Plugin { +final class ProjectsPlugin: Plugin { let fileManager: FileManager = .default let outputPath: String let partialProjects: [PartialProject] @@ -36,7 +36,7 @@ public final class ProjectsPlugin: Plugin { // MARK: - Plugin methods - public func setUp(site: Site, sourceURL: URL) throws { + func setUp(site: Site, sourceURL: URL) throws { self.sourceURL = sourceURL projects = partialProjects.map { partial in Project( @@ -47,7 +47,7 @@ public final class ProjectsPlugin: Plugin { } } - public func render(site: Site, targetURL: URL) throws { + func render(site: Site, targetURL: URL) throws { guard !projects.isEmpty else { return } diff --git a/SiteGenerator/Sources/SiteGenerator/Projects/ProjectsPluginBuilder.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Projects/ProjectsPluginBuilder.swift similarity index 72% rename from SiteGenerator/Sources/SiteGenerator/Projects/ProjectsPluginBuilder.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/Projects/ProjectsPluginBuilder.swift index a3706fd..ed44b03 100644 --- a/SiteGenerator/Sources/SiteGenerator/Projects/ProjectsPluginBuilder.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Projects/ProjectsPluginBuilder.swift @@ -1,41 +1,41 @@ // // ProjectsPluginBuilder.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-19. // import Foundation -public final class ProjectsPluginBuilder { +final class ProjectsPluginBuilder { let templateRenderer: ProjectsTemplateRenderer private var path: String? private var projects: [PartialProject] = [] private var projectAssets: TemplateAssets? - public init(templateRenderer: ProjectsTemplateRenderer) { + init(templateRenderer: ProjectsTemplateRenderer) { self.templateRenderer = templateRenderer } - public func path(_ path: String) -> ProjectsPluginBuilder { + func path(_ path: String) -> ProjectsPluginBuilder { precondition(self.path == nil, "path is already defined") self.path = path return self } - public func projectAssets(_ projectAssets: TemplateAssets) -> ProjectsPluginBuilder { + func projectAssets(_ projectAssets: TemplateAssets) -> ProjectsPluginBuilder { precondition(self.projectAssets == nil, "projectAssets are already defined") self.projectAssets = projectAssets return self } - public func add(_ title: String, description: String) -> ProjectsPluginBuilder { + func add(_ title: String, description: String) -> ProjectsPluginBuilder { let project = PartialProject(title: title, description: description) projects.append(project) return self } - public func build() -> ProjectsPlugin { + func build() -> ProjectsPlugin { if projects.isEmpty { print("WARNING: No projects have been added") } diff --git a/SiteGenerator/Sources/SiteGenerator/Projects/ProjectsTemplateRenderer.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Projects/ProjectsTemplateRenderer.swift similarity index 83% rename from SiteGenerator/Sources/SiteGenerator/Projects/ProjectsTemplateRenderer.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/Projects/ProjectsTemplateRenderer.swift index 6d888d2..78da515 100644 --- a/SiteGenerator/Sources/SiteGenerator/Projects/ProjectsTemplateRenderer.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Projects/ProjectsTemplateRenderer.swift @@ -1,13 +1,13 @@ // // ProjectsTemplateRenderer.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-17. // import Foundation -public protocol ProjectsTemplateRenderer { +protocol ProjectsTemplateRenderer { func renderProjects(_ projects: [Project], site: Site, assets: TemplateAssets) throws -> String func renderProject(_ project: Project, site: Site, assets: TemplateAssets) throws -> String } diff --git a/SiteGenerator/Sources/SiteGenerator/Renderer.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Renderer.swift similarity index 84% rename from SiteGenerator/Sources/SiteGenerator/Renderer.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/Renderer.swift index 674d4b2..541ca09 100644 --- a/SiteGenerator/Sources/SiteGenerator/Renderer.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Renderer.swift @@ -1,13 +1,13 @@ // // Renderer.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-02. // import Foundation -public protocol Renderer { +protocol Renderer { func canRenderFile(named filename: String, withExtension ext: String) -> Bool func render(site: Site, fileURL: URL, targetDir: URL) throws diff --git a/SiteGenerator/Sources/SiteGenerator/Site.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/Site.swift similarity index 64% rename from SiteGenerator/Sources/SiteGenerator/Site.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/Site.swift index 29d198a..55326d2 100644 --- a/SiteGenerator/Sources/SiteGenerator/Site.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/Site.swift @@ -1,24 +1,24 @@ // // Site.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-01. // import Foundation -public struct Site { - public let author: String - public let email: String - public let title: String - public let description: String? - public let url: URL - public let styles: [String] - public let scripts: [String] - public let renderers: [Renderer] - public let plugins: [Plugin] +struct Site { + let author: String + let email: String + let title: String + let description: String? + let url: URL + let styles: [String] + let scripts: [String] + let renderers: [Renderer] + let plugins: [Plugin] - public init( + init( author: String, email: String, title: String, diff --git a/SiteGenerator/Sources/SiteGenerator/SiteBuilder.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/SiteBuilder.swift similarity index 78% rename from SiteGenerator/Sources/SiteGenerator/SiteBuilder.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/SiteBuilder.swift index ee82d53..b8de1bd 100644 --- a/SiteGenerator/Sources/SiteGenerator/SiteBuilder.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/SiteBuilder.swift @@ -1,13 +1,13 @@ // // SiteBuilder.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-15. // import Foundation -public final class SiteBuilder { +final class SiteBuilder { private let title: String private let description: String? private let author: String @@ -20,7 +20,7 @@ public final class SiteBuilder { private var plugins: [Plugin] = [] private var renderers: [Renderer] = [] - public init( + init( title: String, description: String? = nil, author: String, @@ -34,27 +34,27 @@ public final class SiteBuilder { self.url = url } - public func styles(_ styles: String...) -> SiteBuilder { + func styles(_ styles: String...) -> SiteBuilder { self.styles.append(contentsOf: styles) return self } - public func scripts(_ scripts: String...) -> SiteBuilder { + func scripts(_ scripts: String...) -> SiteBuilder { self.scripts.append(contentsOf: scripts) return self } - public func plugin(_ plugin: Plugin) -> SiteBuilder { + func plugin(_ plugin: Plugin) -> SiteBuilder { plugins.append(plugin) return self } - public func renderer(_ renderer: Renderer) -> SiteBuilder { + func renderer(_ renderer: Renderer) -> SiteBuilder { renderers.append(renderer) return self } - public func build() -> Site { + func build() -> Site { Site( author: author, email: email, @@ -71,7 +71,7 @@ public final class SiteBuilder { // MARK: - Markdown -public extension SiteBuilder { +extension SiteBuilder { func renderMarkdown(pageRenderer: MarkdownPageRenderer) -> SiteBuilder { renderer(MarkdownRenderer(pageRenderer: pageRenderer)) } diff --git a/SiteGenerator/Sources/SiteGenerator/SiteGenerator.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/SiteGenerator.swift similarity index 74% rename from SiteGenerator/Sources/SiteGenerator/SiteGenerator.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/SiteGenerator.swift index 6ef37a7..2c441cb 100644 --- a/SiteGenerator/Sources/SiteGenerator/SiteGenerator.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/SiteGenerator.swift @@ -1,23 +1,23 @@ // -// SiteGenerator.swift -// SiteGenerator +// samhuri.net.swift +// samhuri.net // // Created by Sami Samhuri on 2019-12-01. // import Foundation -public final class SiteGenerator { +final class SiteGenerator { // Dependencies let fileManager: FileManager = .default // Site properties - public let site: Site - public let sourceURL: URL + let site: Site + let sourceURL: URL let ignoredFilenames = [".DS_Store", ".gitkeep"] - public init(sourceURL: URL, site: Site) throws { + init(sourceURL: URL, site: Site) throws { self.site = site self.sourceURL = sourceURL @@ -30,7 +30,7 @@ public final class SiteGenerator { } } - public func generate(targetURL: URL) throws { + func generate(targetURL: URL) throws { for plugin in site.plugins { try plugin.render(site: site, targetURL: targetURL) } @@ -63,18 +63,24 @@ public final class SiteGenerator { } } - func renderOrCopyFile(url fileURL: URL, targetDir: URL) throws { - let filename = fileURL.lastPathComponent + func renderOrCopyFile(url sourceURL: URL, targetDir: URL) throws { + let filename = sourceURL.lastPathComponent + let targetURL = targetDir.appendingPathComponent(filename) + + // Clear the way so write operations don't fail later on. + if fileManager.fileExists(atPath: targetURL.path) { + try fileManager.removeItem(at: targetURL) + } + let ext = String(filename.split(separator: ".").last!) for renderer in site.renderers { if renderer.canRenderFile(named: filename, withExtension: ext) { - try renderer.render(site: site, fileURL: fileURL, targetDir: targetDir) + try renderer.render(site: site, fileURL: sourceURL, targetDir: targetDir) return } } // Not handled by any renderer. Copy the file unchanged. - let dest = targetDir.appendingPathComponent(filename) - try fileManager.copyItem(at: fileURL, to: dest) + try fileManager.copyItem(at: sourceURL, to: targetURL) } } diff --git a/SiteGenerator/Sources/SiteGenerator/TemplateAssets.swift b/samhuri.net/Sources/samhuri.net/SiteGenerator/TemplateAssets.swift similarity index 50% rename from SiteGenerator/Sources/SiteGenerator/TemplateAssets.swift rename to samhuri.net/Sources/samhuri.net/SiteGenerator/TemplateAssets.swift index 64204ba..97768d0 100644 --- a/SiteGenerator/Sources/SiteGenerator/TemplateAssets.swift +++ b/samhuri.net/Sources/samhuri.net/SiteGenerator/TemplateAssets.swift @@ -1,22 +1,22 @@ // // TemplateAssets.swift -// SiteGenerator +// samhuri.net // // Created by Sami Samhuri on 2019-12-20. // import Foundation -public struct TemplateAssets { - public let scripts: [String] - public let styles: [String] +struct TemplateAssets { + let scripts: [String] + let styles: [String] - public init(scripts: [String], styles: [String]) { + init(scripts: [String], styles: [String]) { self.scripts = scripts self.styles = styles } - public static func none() -> TemplateAssets { + static func none() -> TemplateAssets { TemplateAssets(scripts: [], styles: []) } } diff --git a/samhuri.net/Sources/samhuri.net/Templates/HTMLElements.swift b/samhuri.net/Sources/samhuri.net/Templates/HTMLElements.swift index 0f9591e..6df1879 100644 --- a/samhuri.net/Sources/samhuri.net/Templates/HTMLElements.swift +++ b/samhuri.net/Sources/samhuri.net/Templates/HTMLElements.swift @@ -9,23 +9,23 @@ import Foundation import Plot extension Node where Context == HTML.HeadContext { - static func jsonFeedLink(_ url: URLRepresentable, title: String) -> Node { + static func jsonFeedLink(_ url: URLRepresentable, title: String) -> Self { .link(.rel(.alternate), .href(url), .type("application/json"), .attribute(named: "title", value: title)) } } extension Node where Context == HTML.HeadContext { - static func appleTouchIcon(_ url: URLRepresentable) -> Node { + static func appleTouchIcon(_ url: URLRepresentable) -> Self { .link(.attribute(named: "rel", value: "apple-touch-icon"), .href(url)) } - static func safariPinnedTabIcon(_ url: URLRepresentable, color: String) -> Node { + static func safariPinnedTabIcon(_ url: URLRepresentable, color: String) -> Self { .link(.attribute(named: "rel", value: "mask-icon"), .attribute(named: "color", value: color), .href(url)) } } extension Node where Context == HTML.BodyContext { - static func asyncStylesheetLinks(_ urls: [URLRepresentable]) -> Node { + static func asyncStylesheetLinks(_ urls: [URLRepresentable]) -> Self { .script(""" (function() { var urls = [\(urls.map { "'\($0)'" }.joined(separator: ", "))]; @@ -40,3 +40,9 @@ extension Node where Context == HTML.BodyContext { """) } } + +extension Node where Context == HTML.BodyContext { + static func time(_ nodes: Node...) -> Self { + .element(named: "time", nodes: nodes) + } +} diff --git a/samhuri.net/Sources/samhuri.net/Templates/PageContext.swift b/samhuri.net/Sources/samhuri.net/Templates/PageContext.swift deleted file mode 100644 index 11d568e..0000000 --- a/samhuri.net/Sources/samhuri.net/Templates/PageContext.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// PageContext.swift -// samhuri.net -// -// Created by Sami Samhuri on 2019-12-02. -// - -import Foundation -import SiteGenerator - -struct PageContext: TemplateContext { - let site: Site - @available(*, deprecated) let body: String - let page: Page - @available(*, deprecated) let metadata: [String: String] - - var title: String { - "\(site.title): \(page.title)" - } - - var templateAssets: TemplateAssets { - page.templateAssets - } -} - -extension PageContext { - @available(*, deprecated) - var dictionary: [String: Any] { - [ - "site": site, - "body": body, - "page": page, - "metadata": metadata, - "styles": site.styles + templateAssets.styles, - "scripts": site.scripts + templateAssets.scripts, - "currentYear": Date().year, - ] - } -} diff --git a/samhuri.net/Sources/samhuri.net/Templates/PageTemplate.swift b/samhuri.net/Sources/samhuri.net/Templates/PageTemplate.swift index 67709b3..f62a86a 100644 --- a/samhuri.net/Sources/samhuri.net/Templates/PageTemplate.swift +++ b/samhuri.net/Sources/samhuri.net/Templates/PageTemplate.swift @@ -1,6 +1,6 @@ // -// PartialTemplates.swift -// +// PageTemplate.swift +// samhuri.net // // Created by Sami Samhuri on 2019-12-19. // @@ -9,7 +9,7 @@ import Foundation import Plot extension Node where Context == HTML.BodyContext { - static func page(title: String, bodyHTML: String) -> Node { + static func page(title: String, bodyHTML: String) -> Self { .group([ .article(.class("container"), .h1(.text(title)), diff --git a/samhuri.net/Sources/samhuri.net/Templates/SiteContext.swift b/samhuri.net/Sources/samhuri.net/Templates/SiteContext.swift index 770b612..b8e79b9 100644 --- a/samhuri.net/Sources/samhuri.net/Templates/SiteContext.swift +++ b/samhuri.net/Sources/samhuri.net/Templates/SiteContext.swift @@ -6,7 +6,6 @@ // import Foundation -import SiteGenerator struct SiteContext: TemplateContext { let site: Site diff --git a/samhuri.net/Sources/samhuri.net/Templates/TemplateContext.swift b/samhuri.net/Sources/samhuri.net/Templates/TemplateContext.swift index 4933e2f..00c1eff 100644 --- a/samhuri.net/Sources/samhuri.net/Templates/TemplateContext.swift +++ b/samhuri.net/Sources/samhuri.net/Templates/TemplateContext.swift @@ -6,7 +6,6 @@ // import Foundation -import SiteGenerator protocol TemplateContext { // Concrete requirements, must be implemented diff --git a/samhuri.net/Sources/samhuri.net/samhuri.net.swift b/samhuri.net/Sources/samhuri.net/samhuri.net.swift index 52a2f0d..0d91be6 100644 --- a/samhuri.net/Sources/samhuri.net/samhuri.net.swift +++ b/samhuri.net/Sources/samhuri.net/samhuri.net.swift @@ -1,5 +1,4 @@ import Foundation -import SiteGenerator public enum samhuri {} @@ -53,8 +52,7 @@ public extension samhuri { email: "sami@samhuri.net", url: siteURLOverride ?? URL(string: "https://samhuri.net")! ) - .styles("normalize.css", "style.css") - .styles("https://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css") + .styles("normalize.css", "style.css", "font-awesome.min.css") .renderMarkdown(pageRenderer: renderer) .plugin(projectsPlugin) .plugin(postsPlugin) @@ -62,8 +60,7 @@ public extension samhuri { } public func generate(sourceURL: URL, targetURL: URL) throws { - let templatesURL = sourceURL.appendingPathComponent("templates") - let renderer = PageRenderer(templatesURL: templatesURL) + let renderer = PageRenderer() let site = buildSite(renderer: renderer) let generator = try SiteGenerator(sourceURL: sourceURL, site: site) try generator.generate(targetURL: targetURL) diff --git a/templates/feed-post.html b/templates/feed-post.html deleted file mode 100644 index 1c4a394..0000000 --- a/templates/feed-post.html +++ /dev/null @@ -1,7 +0,0 @@ -
-

{{ post.date }}

- {{ post.body }} - {% if post.isLink %} -

- {% endif %} -
diff --git a/templates/feed.xml b/templates/feed.xml deleted file mode 100644 index a820a8b..0000000 --- a/templates/feed.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - {{ site.title }} - {{ site.description }} - {{ site.url }} - {{ posts[0].date }} - - - {% for post in posts %} - - {{ post.title }} - {{ post.body }} - {{ post.date }} - {{ post.author }} - {{ post.link }} - {{ post.guid }} - - {% endfor %} - - diff --git a/templates/partial-post-link.html b/templates/partial-post-link.html deleted file mode 100644 index ae279ae..0000000 --- a/templates/partial-post-link.html +++ /dev/null @@ -1,11 +0,0 @@ - -
-

-
diff --git a/templates/partial-post-text.html b/templates/partial-post-text.html deleted file mode 100644 index 11e0140..0000000 --- a/templates/partial-post-text.html +++ /dev/null @@ -1,11 +0,0 @@ - -
-

-
diff --git a/templates/partial-post.html b/templates/partial-post.html deleted file mode 100644 index 92e36c0..0000000 --- a/templates/partial-post.html +++ /dev/null @@ -1,5 +0,0 @@ -{% if post.isLink %} - {% include "partial-post-link.html" %} -{% else %} - {% include "partial-post-text.html" %} -{% endif %} diff --git a/templates/post.html b/templates/post.html deleted file mode 100644 index a6491fd..0000000 --- a/templates/post.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "samhuri.net.html" %} - -{% block body %} -{% include "partial-post.html" %} -{% endblock %} diff --git a/templates/posts-archive.html b/templates/posts-archive.html deleted file mode 100644 index f6e837b..0000000 --- a/templates/posts-archive.html +++ /dev/null @@ -1,38 +0,0 @@ -{% extends "samhuri.net.html" %} - -{% block body %} - -
-

{{ title }}

-
- -{% for year in years %} -
-

{{ year.title }}

- - {% for month in year.months %} -

- {{ month.name }} -

- -
    - {% for post in month.posts %} -
  • - {% if post.isLink %} - → {{ post.title }} - {% else %} - {{ post.title }} - {% endif %} - - {% if post.isLink %} - - {% endif %} -
  • - {% endfor %} -
- - {% endfor %} -
-{% endfor %} - -{% endblock %} diff --git a/templates/posts-month.html b/templates/posts-month.html deleted file mode 100644 index 4b4c21f..0000000 --- a/templates/posts-month.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends "samhuri.net.html" %} - -{% block body %} - -
-

{{ title }}

-
- -{% for post in posts %} - {% include "partial-post.html" post %} -{% endfor %} - -{% endblock %} diff --git a/templates/posts-year.html b/templates/posts-year.html deleted file mode 100644 index cee641c..0000000 --- a/templates/posts-year.html +++ /dev/null @@ -1,32 +0,0 @@ -{% extends "samhuri.net.html" %} - -{% block body %} - -
-

{{ title }}

- -{% for month in months %} -

- {{ month.name }} -

- -
    - {% for post in month.posts %} -
  • - {% if post.isLink %} - → {{ post.title }} - {% else %} - {{ post.title }} - {% endif %} - - {% if post.isLink %} - - {% endif %} -
  • - {% endfor %} -
- -{% endfor %} -
- -{% endblock %} diff --git a/templates/recent-posts.html b/templates/recent-posts.html deleted file mode 100644 index 6ad4593..0000000 --- a/templates/recent-posts.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "samhuri.net.html" %} - -{% block body %} - -
-{% for post in recentPosts %} - {% include "partial-post.html" post %} -{% endfor %} -
- -{% endblock %} diff --git a/templates/samhuri.net.html b/templates/samhuri.net.html deleted file mode 100644 index 0d8e6f9..0000000 --- a/templates/samhuri.net.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - {% if title %} - {{ site.title }}: {{ title }} - {% elif page.title %} - {{ site.title }}: {{ page.title }} - {% else %} - {{ site.title }} - {% endif %} - - - - - - - - - - - - - - - - - - - -
- - - - -
-
- - {% block body %}{{ body }}{% endblock %} - - - -{% for style in styles %} - -{% endfor %} - - - - -{% for script in scripts %} - -{% endfor %} - -