mirror of
https://github.com/samsonjs/samhuri.net.git
synced 2026-04-27 14:57:40 +00:00
Render a JSON feed
This commit is contained in:
parent
1d0ffd52a2
commit
dd96d95fc4
6 changed files with 154 additions and 3 deletions
|
|
@ -0,0 +1,39 @@
|
||||||
|
//
|
||||||
|
// JSONFeedPlugin.swift
|
||||||
|
// SiteGenerator
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
final class JSONFeedPlugin: Plugin {
|
||||||
|
let postRepo: PostRepo
|
||||||
|
let jsonFeedWriter: JSONFeedWriter
|
||||||
|
|
||||||
|
init(
|
||||||
|
postRepo: PostRepo = PostRepo(),
|
||||||
|
jsonFeedWriter: JSONFeedWriter = JSONFeedWriter()
|
||||||
|
) {
|
||||||
|
self.postRepo = postRepo
|
||||||
|
self.jsonFeedWriter = jsonFeedWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Plugin methods
|
||||||
|
|
||||||
|
func setUp(site: Site, sourceURL: URL) throws {
|
||||||
|
guard postRepo.postDataExists(at: sourceURL) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try postRepo.readPosts(sourceURL: sourceURL, makePath: jsonFeedWriter.urlPathForPost)
|
||||||
|
}
|
||||||
|
|
||||||
|
func render(site: Site, targetURL: URL, templateRenderer: TemplateRenderer) throws {
|
||||||
|
guard !postRepo.isEmpty else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try jsonFeedWriter.writeFeed(postRepo.postsForFeed, site: site, to: targetURL, with: templateRenderer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
//
|
||||||
|
// JSONFeedWriter.swift
|
||||||
|
// SiteGenerator
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
private struct Feed: Codable {
|
||||||
|
let version = "https://jsonfeed.org/version/1"
|
||||||
|
let title: String
|
||||||
|
let home_page_url: String
|
||||||
|
let feed_url: String
|
||||||
|
let author: FeedAuthor
|
||||||
|
let icon: String?
|
||||||
|
let favicon: String?
|
||||||
|
let items: [FeedItem]
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct FeedAuthor: Codable {
|
||||||
|
let name: String
|
||||||
|
let avatar: String?
|
||||||
|
let url: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct FeedItem: Codable {
|
||||||
|
let title: String
|
||||||
|
let date_published: Date
|
||||||
|
let id: String
|
||||||
|
let url: String
|
||||||
|
let external_url: String?
|
||||||
|
let author: FeedAuthor
|
||||||
|
let content_html: String
|
||||||
|
let tags: [String]
|
||||||
|
}
|
||||||
|
|
||||||
|
final class JSONFeedWriter {
|
||||||
|
let fileManager: FileManager
|
||||||
|
let feedPath: String
|
||||||
|
let postsPath: String
|
||||||
|
|
||||||
|
var baseURL: URL!
|
||||||
|
|
||||||
|
init(fileManager: FileManager = .default, feedPath: String = "feed.json", postsPath: String = "posts") {
|
||||||
|
self.fileManager = fileManager
|
||||||
|
self.feedPath = feedPath
|
||||||
|
self.postsPath = postsPath
|
||||||
|
}
|
||||||
|
|
||||||
|
#warning("These urlPath methods were copied from PostsPlugin and should possibly be moved somewhere else")
|
||||||
|
|
||||||
|
func urlPath(year: Int) -> String {
|
||||||
|
"/\(postsPath)/\(year)"
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlPath(year: Int, month: Month) -> String {
|
||||||
|
urlPath(year: year).appending("/\(month.padded)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlPathForPost(date: Date, slug: String) -> String {
|
||||||
|
urlPath(year: date.year, month: Month(date.month)).appending("/\(slug)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFeed(_ posts: [Post], site: Site, to targetURL: URL, with templateRenderer: TemplateRenderer) throws {
|
||||||
|
let items: [FeedItem] = try posts.map { post in
|
||||||
|
let url = site.url.appendingPathComponent(post.path)
|
||||||
|
return FeedItem(
|
||||||
|
title: post.isLink ? "→ \(post.title)" : post.title,
|
||||||
|
date_published: post.date,
|
||||||
|
id: url.absoluteString,
|
||||||
|
url: url.absoluteString,
|
||||||
|
external_url: post.link?.absoluteString,
|
||||||
|
author: FeedAuthor(name: post.author, avatar: nil, url: nil),
|
||||||
|
content_html: try templateRenderer.renderTemplate(name: "feed-post.html", context: [
|
||||||
|
"post": post,
|
||||||
|
]),
|
||||||
|
tags: post.tags
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let avatar = site.avatarPath.map(site.url.appendingPathComponent)
|
||||||
|
let feed: Feed = Feed(
|
||||||
|
title: site.title,
|
||||||
|
home_page_url: site.url.absoluteString,
|
||||||
|
feed_url: site.url.appendingPathComponent(feedPath).absoluteString,
|
||||||
|
author: FeedAuthor(name: site.author, avatar: avatar?.absoluteString, url: site.url.absoluteString),
|
||||||
|
icon: site.iconPath.map(site.url.appendingPathComponent)?.absoluteString,
|
||||||
|
favicon: site.faviconPath.map(site.url.appendingPathComponent)?.absoluteString,
|
||||||
|
items: items
|
||||||
|
)
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
encoder.dateEncodingStrategy = .iso8601
|
||||||
|
encoder.outputFormatting = [.prettyPrinted, .withoutEscapingSlashes]
|
||||||
|
let feedJSON = try encoder.encode(feed)
|
||||||
|
let feedURL = targetURL.appendingPathComponent(feedPath)
|
||||||
|
try feedJSON.write(to: feedURL, options: [.atomic])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,9 @@ struct HumanSite: Codable {
|
||||||
let template: String?
|
let template: String?
|
||||||
let styles: [String]?
|
let styles: [String]?
|
||||||
let scripts: [String]?
|
let scripts: [String]?
|
||||||
|
let avatar: String?
|
||||||
|
let icon: String?
|
||||||
|
let favicon: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Site {
|
extension Site {
|
||||||
|
|
@ -29,7 +32,10 @@ extension Site {
|
||||||
url: humanSite.url,
|
url: humanSite.url,
|
||||||
template: humanSite.template ?? "page",
|
template: humanSite.template ?? "page",
|
||||||
styles: humanSite.styles ?? [],
|
styles: humanSite.styles ?? [],
|
||||||
scripts: humanSite.scripts ?? []
|
scripts: humanSite.scripts ?? [],
|
||||||
|
avatarPath: humanSite.avatar,
|
||||||
|
iconPath: humanSite.icon,
|
||||||
|
faviconPath: humanSite.favicon
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,11 @@ public struct Site {
|
||||||
public let template: String
|
public let template: String
|
||||||
public let styles: [String]
|
public let styles: [String]
|
||||||
public let scripts: [String]
|
public let scripts: [String]
|
||||||
|
|
||||||
|
// Used for JSON feed
|
||||||
|
public let avatarPath: String?
|
||||||
|
public let iconPath: String?
|
||||||
|
public let faviconPath: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Site {
|
extension Site {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ func main(sourcePath: String, targetPath: String) throws {
|
||||||
let targetURL = URL(fileURLWithPath: targetPath)
|
let targetURL = URL(fileURLWithPath: targetPath)
|
||||||
let generator = try Generator(
|
let generator = try Generator(
|
||||||
sourceURL: sourceURL,
|
sourceURL: sourceURL,
|
||||||
plugins: [ProjectsPlugin(), PostsPlugin(), RSSFeedPlugin()],
|
plugins: [ProjectsPlugin(), PostsPlugin(), RSSFeedPlugin(), JSONFeedPlugin()],
|
||||||
renderers: [LessRenderer(), MarkdownRenderer()]
|
renderers: [LessRenderer(), MarkdownRenderer()]
|
||||||
)
|
)
|
||||||
try generator.generate(targetURL: targetURL)
|
try generator.generate(targetURL: targetURL)
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,8 @@
|
||||||
"/css/normalize.css",
|
"/css/normalize.css",
|
||||||
"/css/style.css"
|
"/css/style.css"
|
||||||
],
|
],
|
||||||
"scripts": []
|
"scripts": [],
|
||||||
|
"icon": "images/apple-touch-icon-300.png",
|
||||||
|
"favicon": "images/apple-touch-icon-80.png",
|
||||||
|
"avatar": "images/me.jpg",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue