Render the post archive at /posts and redirect /archive

This commit is contained in:
Sami Samhuri 2019-12-04 23:16:31 -08:00
parent 5ed68c45f8
commit c8dc29a511
12 changed files with 169 additions and 148 deletions

View file

@ -15,7 +15,7 @@ This version will go back to its roots and use headers at the top of markdown fi
Execution, trying TDD for the first time:
- [ ] Replace harp with custom Swift code
- [x] Replace harp with custom Swift code
- [x] Write a test harness that renders a site and then checks the output with `diff -r`
@ -51,7 +51,7 @@ Execution, trying TDD for the first time:
- [x] Check and delete _data.json
- [ ] Migrate posts to markdown with headers somehow
- [x] Migrate posts to markdown with headers somehow
- [x] Define the new format
@ -63,15 +63,19 @@ Execution, trying TDD for the first time:
- [x] Migrate month indexes
- [ ] Migrate index / recent posts
- [x] Migrate index / recent posts
- [ ] Migrate archive and put it at /posts/index.html, duh!
- [x] Migrate archive and put it at /posts/index.html, duh!
- [ ] 301 redirect /archive to /posts, and update the header link
- [x] 301 redirect /archive to /posts, and update the header link
- [ ] Check and delete _data.json filse
- [x] Check and delete _data.json filse
- [ ] Search for other _data.json files and eliminate any that are found
- [x] Search for other _data.json and .ejs files and eliminate any that are found
- [ ] Find a way to add the site name to HTML titles rendered by plugins
- [ ] Clean up the posts plugin
- [ ] Consider using Swift for samhuri.net as well, and then making SiteGenerator a package that it uses ... then we can use Plot or pointfree.co's swift-html

View file

@ -50,19 +50,12 @@ final class PostsPlugin: Plugin {
return
}
let recentPostsURL = targetURL.appendingPathComponent(recentPostsPath)
try renderRecentPosts(targetFileURL: recentPostsURL, templateRenderer: templateRenderer)
let postsDir = targetURL.appendingPathComponent(postsPath)
try renderYearsAndMonths(postsDir: postsDir, templateRenderer: templateRenderer)
try renderPostsByDate(postsDir: postsDir, templateRenderer: templateRenderer)
}
func renderRecentPosts(targetFileURL: URL, templateRenderer: TemplateRenderer) throws {
print("renderRecentPosts(targetFileURL: \(targetFileURL), templateRenderer: \(templateRenderer)")
let recentPosts = posts.flattened().prefix(10)
let recentPostsHTML = try templateRenderer.renderTemplate(name: "recent-posts", context: ["recentPosts": recentPosts])
try recentPostsHTML.write(to: targetFileURL, atomically: true, encoding: .utf8)
try renderYears(postsDir: postsDir, templateRenderer: templateRenderer)
try renderMonths(postsDir: postsDir, templateRenderer: templateRenderer)
try renderArchive(postsDir: postsDir, templateRenderer: templateRenderer)
try renderRecentPosts(targetURL: targetURL, templateRenderer: templateRenderer)
}
func renderPostsByDate(postsDir: URL, templateRenderer: TemplateRenderer) throws {
@ -75,49 +68,78 @@ final class PostsPlugin: Plugin {
}
}
func renderYearsAndMonths(postsDir: URL, templateRenderer: TemplateRenderer) throws {
print("renderYearsAndMonths(postsDir: \(postsDir), templateRenderer: \(templateRenderer)")
func renderRecentPosts(targetURL: URL, templateRenderer: TemplateRenderer) throws {
print("renderRecentPosts(targetURL: \(targetURL), templateRenderer: \(templateRenderer)")
let recentPosts = posts.flattened().prefix(10)
let renderedRecentPosts: [[String: Any]] = recentPosts.map { post in
let html = markdownParser.html(from: post.bodyMarkdown)
return RenderedPost(post: post, body: html).dictionary
}
#warning("FIXME: get the site name out of here somehow")
let recentPostsHTML = try templateRenderer.renderTemplate(name: "recent-posts", context: [
"title": "samhuri.net",
"recentPosts": renderedRecentPosts,
])
let fileURL = targetURL.appendingPathComponent(recentPostsPath)
try recentPostsHTML.write(to: fileURL, atomically: true, encoding: .utf8)
}
func renderArchive(postsDir: URL, templateRenderer: TemplateRenderer) throws {
print("renderArchive(postsDir: \(postsDir), templateRenderer: \(templateRenderer)")
let allYears = posts.byYear.keys.sorted(by: >)
let allMonths = (1 ... 12).map(Month.init(_:))
let postsByMonthByYearForContext: [[String: Any]] = allYears.map { year in
[
"number": "\(year)",
"months": posts[year].byMonth.keys.sorted(by: >).map { month in
[
"number": month.padded,
"posts": posts[year][month].posts,
]
} as Any,
]
}
let context: [String: Any] = [
"title": "Archive",
"path": postsPath,
"years": postsByMonthByYearForContext,
"monthNames": allMonths.reduce(into: [String: String](), { dict, month in
dict[month.padded] = month.name
}),
"monthAbbreviations": allMonths.reduce(into: [String: String](), { dict, month in
dict[month.padded] = month.abbreviatedName
}),
]
let archiveHTML = try templateRenderer.renderTemplate(name: "posts-archive", context: context)
let archiveURL = postsDir.appendingPathComponent("index.html")
try archiveHTML.write(to: archiveURL, atomically: true, encoding: .utf8)
}
func renderYears(postsDir: URL, templateRenderer: TemplateRenderer) throws {
print("renderYears(postsDir: \(postsDir), templateRenderer: \(templateRenderer)")
let allMonths = (1 ... 12).map(Month.init(_:))
for (year, monthPosts) in posts.byYear.sorted(by: { $1.key < $0.key }) {
let yearDir = postsDir.appendingPathComponent("\(year)")
var sortedPostsByMonth: [Month: [RenderedPost]] = [:]
var sortedPostsByMonth: [Month: [Post]] = [:]
for month in allMonths {
let sortedPosts = monthPosts[month].posts.sorted(by: { $1.date < $0.date })
guard !sortedPosts.isEmpty else {
continue
if !sortedPosts.isEmpty {
sortedPostsByMonth[month] = sortedPosts
}
let renderedPosts = sortedPosts.map { post -> RenderedPost in
let bodyHTML = markdownParser.html(from: post.bodyMarkdown)
return RenderedPost(post: post, body: bodyHTML)
}
sortedPostsByMonth[month] = renderedPosts
let monthDir = yearDir.appendingPathComponent(month.padded)
try fileManager.createDirectory(at: monthDir, withIntermediateDirectories: true, attributes: nil)
#warning("FIXME: get the site name out of here somehow")
let context: [String: Any] = [
"title": "samhuri.net: \(month.name) \(year)",
"posts": renderedPosts.map { $0.dictionary },
]
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 months = Array(sortedPostsByMonth.keys.sorted().reversed())
let postsByMonthForContext: [String: [[String: Any]]] = sortedPostsByMonth.reduce(into: [:]) { dict, pair in
let (month, renderedPosts) = pair
dict[month.padded] = renderedPosts.map { $0.dictionary }
let postsByMonthForContext: [String: [Post]] = sortedPostsByMonth.reduce(into: [:]) { dict, pair in
let (month, posts) = pair
dict[month.padded] = posts
}
let monthsPadded = months.map { $0.padded }
#warning("FIXME: get the site name out of here somehow")
#warning("FIXME: get the site name in the head title but not the body title")
let context: [String: Any] = [
"title": "samhuri.net: \(year)",
"title": "\(year)",
"path": postsPath,
"year": year,
"months": monthsPadded,
"months": months.map { $0.padded },
"monthNames": months.reduce(into: [String: String](), { dict, month in
dict[month.padded] = month.name
}),
@ -132,6 +154,35 @@ final class PostsPlugin: Plugin {
}
}
func renderMonths(postsDir: URL, templateRenderer: TemplateRenderer) throws {
print("renderMonths(postsDir: \(postsDir), templateRenderer: \(templateRenderer)")
let allMonths = (1 ... 12).map(Month.init(_:))
for (year, monthPosts) in posts.byYear.sorted(by: { $1.key < $0.key }) {
let yearDir = postsDir.appendingPathComponent("\(year)")
for month in allMonths {
let sortedPosts = monthPosts[month].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)
}
let monthDir = yearDir.appendingPathComponent(month.padded)
try fileManager.createDirectory(at: monthDir, withIntermediateDirectories: true, attributes: nil)
#warning("FIXME: get the site name in the head title but not the body title")
let context: [String: Any] = [
"title": "\(month.name) \(year)",
"posts": renderedPosts.map { $0.dictionary },
]
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)
}
}
}
private func renderPost(_ post: Post, monthDir: URL, templateRenderer: TemplateRenderer) throws {
print("renderPost(\(post.debugDescription), monthDir: \(monthDir), templateRenderer: \(templateRenderer)")
try fileManager.createDirectory(at: monthDir, withIntermediateDirectories: true, attributes: nil)
@ -139,9 +190,9 @@ final class PostsPlugin: Plugin {
let postURL = monthDir.appendingPathComponent(filename)
let bodyHTML = markdownParser.html(from: post.bodyMarkdown)
let renderedPost = RenderedPost(post: post, body: bodyHTML)
#warning("FIXME: get the site name out of here somehow")
#warning("FIXME: get the site name in the head title but not the body title")
let postHTML = try templateRenderer.renderTemplate(name: "post", context: [
"title": "samhuri.net: \(renderedPost.post.title)",
"title": "\(renderedPost.post.title)",
"post": renderedPost.dictionary,
])
try postHTML.write(to: postURL, atomically: true, encoding: .utf8)

View file

@ -46,9 +46,9 @@ final class ProjectsPlugin: Plugin {
let projectsDir = targetURL.appendingPathComponent(path)
try fileManager.createDirectory(at: projectsDir, withIntermediateDirectories: true, attributes: nil)
let projectsURL = projectsDir.appendingPathComponent("index.html")
#warning("FIXME: get the site name out of here somehow")
#warning("FIXME: get the site name in the head title but not the body title")
let projectsHTML = try templateRenderer.renderTemplate(name: "projects", context: [
"title": "samhuri.net: Projects",
"title": "Projects",
"projects": projects,
])
try projectsHTML.write(to: projectsURL, atomically: true, encoding: .utf8)
@ -56,9 +56,9 @@ final class ProjectsPlugin: Plugin {
for project in projects {
let filename = "\(project.title).html"
let projectURL = projectsDir.appendingPathComponent(filename)
#warning("FIXME: get the site name out of here somehow")
#warning("FIXME: get the site name in the head title but not the body title")
let projectHTML = try templateRenderer.renderTemplate(name: "project", context: [
"title": "samhuri.net: \(project.title)",
"title": "\(project.title)",
"project": project,
])
try projectHTML.write(to: projectURL, atomically: true, encoding: .utf8)

View file

@ -1,39 +0,0 @@
{
"mach-o-symbol-and-relocation-tables": {
"id": "mach-o-symbol-and-relocation-tables",
"author": "Sami Samhuri",
"title": "Mach-O Symbol and Relocation Tables",
"date": "28th June, 2015",
"timestamp": 1435527198,
"link": null,
"url": "/posts/drafts/mach-o-symbol-and-relocation-tables",
"tags": [
]
},
"09c2b186-3ce7-445c-89bf-5d5b7f830cd7": {
"id": "09c2b186-3ce7-445c-89bf-5d5b7f830cd7",
"author": "Sami Samhuri",
"title": "The Case for Native",
"date": "27th June, 2015",
"timestamp": 1435424525,
"link": null,
"url": "/posts/drafts/09c2b186-3ce7-445c-89bf-5d5b7f830cd7",
"tags": [
]
},
"security-through-obscurity-is-still-not-a-best-practice": {
"id": "security-through-obscurity-is-still-not-a-best-practice",
"author": "Sami Samhuri",
"title": "security-through-obscurity-is-still-not-a-best-practice",
"date": "20th August, 2017",
"timestamp": 1503246688,
"link": null,
"url": "/posts/2017/08/security-through-obscurity-is-still-not-a-best-practice",
"tags": [
"rails",
"security"
]
}
}

View file

@ -1,3 +1,11 @@
---
Title: Mach-O Symbol and Relocation Tables
Author: Sami Samhuri
Date: 28th June, 2015
Timestamp: 1435527198
Tags:
---
The latest technology I've been learning is Palm's SDK for webOS,
Mojo. My first impression is that it's a great platform and
Palm could do a great job of 2.0 if they cut down on some of the
@ -32,4 +40,4 @@ support for Emacs](http://www.emacswiki.org/emacs/MojoSdk)
which provided a great base to get
started with. There are wrappers around (all?) of the Palm SDK
commands but it needed a bit of work to make it just do what I
wanted with as little input and thought as possible.
wanted with as little input and thought as possible.

View file

@ -1,3 +1,11 @@
---
Title: TBD
Author: Sami Samhuri
Date: 20th August, 2017
Timestamp: 1503246688
Tags: rails, security
---
A common way to configure a Rails server for different deployment environments is to use environment variables. This is a good practice as described by [The Twelve-Factor App][12factor] and can be applied to any server framework in any language running on a Unix OS. It keeps such secrets out of your code repository which is good, and it also makes it easy to customize your application for different environments. It's a pretty solid technique.
[12factor]: https://12factor.net

View file

@ -1,3 +1,11 @@
---
Title: The Case for Native
Author: Sami Samhuri
Date: 27th June, 2015
Timestamp: 1435424525
Tags:
---
For the past month I've been using [Appcelerator Titanium](http://www.appcelerator.com/products/) on a two man team. We made a simple iPhone app with a tab bar with embedded nav controllers, just 10 screens or so total. We started porting it to the iPad and Android so have some experience there. It's been a pretty frustrating exercise most days. I had a lot of little complaints but didn't take the time to step back and look at the bigger picture. I love JavaScript and in theory Titanium is awesome and a huge win. I wanted it to be a win but in reality it just hasn't been.
Here are 9 reasons why native is better in the short and long run.

View file

@ -79,8 +79,10 @@ Redirect 301 /json-diff http://tlrobinson.net/projects/javascript-fun/jsondiff
# The great vowel shortage of 1974 is over.
Redirect 301 /proj /projects
Redirect 301 /archive /posts
Redirect 301 /archive/index.html /posts
Redirect 301 /blog/sjs.rss /feed.xml
Redirect 301 /blog /posts
Redirect 301 /blog /
# Old naming scheme, incompatible with Harp.
Redirect 301 /blog/2006.02.08-first-post /posts/2006/02/first-post

View file

@ -1,53 +0,0 @@
<% function pad(n) { -%>
<% return +n < 10 ? '0' + n : String(n) -%>
<% } -%>
<% var _months = 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ') -%>
<% function formatDate(t) { -%>
<% var d = new Date(t) -%>
<% return d.getDate() + ' ' + _months[d.getMonth()] -%>
<% } -%>
<% function sortPosts(dir) { -%>
<% var slugs = Object.keys(dir._data) -%>
<% var posts = slugs.map(function(slug) { return dir._data[slug] }) -%>
<% posts.sort(function(a, b) { -%>
<% var t1 = a.timestamp -%>
<% var t2 = b.timestamp -%>
<% return t1 < t2 ? 1 : (t1 > t2 ? -1 : 0) -%>
<% }) -%>
<% return posts -%>
<% } -%>
<% function posts(dir, year) { -%>
<% if (!dir) return -%>
<h3>
<a href="/posts/<%= year %>"><%= year %></a>
</h3>
<% for (var month = 12; month >= 1; month--) { -%>
<% var monthDir = dir[pad(month)] -%>
<% if (!monthDir) continue -%>
<ul class="archive">
<% var posts = sortPosts(monthDir) -%>
<% for (var i in posts) { -%>
<% var post = posts[i] -%>
<% if (post.hidden) continue -%>
<li>
<% if (post.link) { -%>
<a href="<%= post.url %>">&rarr; <%= post.title %></a>
<% } else { -%>
<a href="<%= post.url %>"><%= post.title %></a>
<% } -%>
<time><%= formatDate(1000 * post.timestamp) %></time>
</li>
<% } -%>
</ul>
<% } -%>
<% } -%>
<% var year = new Date().getFullYear(); -%>
<% for (; year >= 2006; year--) { -%>
<% posts(public.posts[year], year) -%>
<% } -%>

View file

@ -2,6 +2,7 @@
<header>
<h2><a href="{{ post.path }}">{{ post.title }}</a></h2>
<time>{{ post.formattedDate }}</time>
<a class="permalink" href="{{ post.path }}">&infin;</a>
</header>
{{ post.body }}
</article>

View file

@ -0,0 +1,31 @@
{% extends "samhuri.net.html" %}
{% block body %}
{% for year in years %}
<div class="container">
<h1>{{ year.number }}</h1>
{% for month in year.months %}
<h2>
<a href="/{{ path }}/{{ year.number }}/{{ month.number }}">{{ monthNames[month.number] }}</a>
</h2>
<ul class="archive">
{% for post in month.posts %}
<li>
{% if post.isLink %}
<a href="{{ post.path }}">&rarr; {{ post.title }}</a>
{% else %}
<a href="{{ post.path }}">{{ post.title }}</a>
{% endif %}
<time>{{ post.day }} {{ monthAbbreviations[month.number] }}</time>
</li>
{% endfor %}
</ul>
{% endfor %}
</div>
{% endfor %}
{% endblock %}

View file

@ -46,7 +46,7 @@
<nav>
<ul>
<li><a href="/about">About</a></li>
<li><a href="/archive">Archive</a></li>
<li><a href="/posts">Archive</a></li>
<li><a href="/projects">Projects</a></li>
<li class="twitter"><a href="https://twitter.com/_sjs"><i class="fa fa-twitter"></i></a></li>
<li class="github"><a href="https://github.com/samsonjs"><i class="fa fa-github"></i></a></li>