import Foundation
import HTML
import PathKit
@preconcurrency import Saga
import SagaParsleyMarkdownReader
import SagaSwimRenderer
enum SiteMetadata {
#if DEBUG
static let url = URL(string: "http://localhost:3000")!
#else
static let url = URL(string: "https://mhoush.com")!
#endif
static let name = "mhoush"
static let author = "Michael Housh"
}
struct ArticleMetadata: Metadata {
let tags: [String]
var summary: String?
let `public`: Bool?
let image: String?
}
struct AppMetadata: Metadata {
let url: URL?
let images: [String]?
}
struct PageMetadata: Metadata {
let section: String?
}
// An easy way to only get public articles, since ArticleMetadata.public is optional
extension Item where M == ArticleMetadata {
var `public`: Bool {
return metadata.public ?? true
}
}
func permalink(item: Item) {
// Insert the publication year into the permalink.
// If the `relativeDestination` was "articles/looking-for-django-cms/index.html", then it becomes "articles/2009/looking-for-django-cms/index.html"
var components = item.relativeDestination.components
components.insert("\(Calendar.current.component(.year, from: item.date))", at: 1)
item.relativeDestination = Path(components: components)
}
func removingBreaks(item: Item) {
// remove explicit
from items that show up likely due to how prettier formats
// markdown files inside of neovim.
item.body = item.body.removeBreaks
}
@main
struct Run {
static func main() async throws {
try await Saga(input: "content", output: "deploy")
// All markdown files within the "articles" subfolder will be parsed to html,
// using ArticleMetadata as the Item's metadata type.
// Furthermore we are only interested in public articles.
.register(
folder: "articles",
metadata: ArticleMetadata.self,
readers: [.parsleyMarkdownReader],
itemProcessor: sequence(removingBreaks, publicationDateInFilename, permalink),
filter: \.public,
writers: [
.itemWriter(swim(renderArticle)),
.listWriter(swim(renderArticles)),
.tagWriter(swim(renderTag), tags: \.metadata.tags),
.yearWriter(swim(renderYear)),
// Atom feed for all articles, and a feed per tag
.listWriter(
atomFeed(
title: SiteMetadata.name,
author: SiteMetadata.author,
baseURL: SiteMetadata.url,
summary: \.metadata.summary
),
output: "feed.xml"
),
.tagWriter(
atomFeed(
title: SiteMetadata.name,
author: SiteMetadata.author, baseURL: SiteMetadata.url, summary: \.metadata.summary
),
output: "tag/[key]/feed.xml",
tags: \.metadata.tags
)
]
)
// All the remaining markdown files will be parsed to html,
// using the default EmptyMetadata as the Item's metadata type.
.register(
metadata: PageMetadata.self,
readers: [.parsleyMarkdownReader],
itemWriteMode: .keepAsFile, // need to keep 404.md as 404.html, not 404/index.html
writers: [.itemWriter(swim(renderPage))]
)
// Run the steps we registered above
.run()
// All the remaining files that were not parsed to markdown, so for example images, raw html files and css,
// are copied as-is to the output folder.
.staticFiles()
}
}