diff --git a/Sources/Publish/API/HTMLFactory.swift b/Sources/Publish/API/HTMLFactory.swift
index a03888af..81e4bf94 100644
--- a/Sources/Publish/API/HTMLFactory.swift
+++ b/Sources/Publish/API/HTMLFactory.swift
@@ -35,7 +35,7 @@ public protocol HTMLFactory {
/// Create the HTML to use for a page.
/// - parameter page: The page to generate HTML for.
/// - parameter context: The current publishing context.
- func makePageHTML(for page: Page,
+ func makePageHTML(for page: Page,
context: PublishingContext) throws -> HTML
/// Create the HTML to use for the website's list of tags, if supported.
diff --git a/Sources/Publish/API/Page.swift b/Sources/Publish/API/Page.swift
index 59d6533b..6a95d4ab 100644
--- a/Sources/Publish/API/Page.swift
+++ b/Sources/Publish/API/Page.swift
@@ -11,16 +11,18 @@ import Foundation
/// or lists of pages, that should be organized within sections, use `Section`
/// and `Item` instead. Pages can either be added programmatically, or through
/// Markdown files placed within the root of the website's content folder.
-public struct Page: Location, Equatable {
+public struct Page: Location, Equatable {
public var path: Path
public var content: Content
+ public var metadata: Site.PageMetadata
/// Initialize a new page programmatically. You can also create pages from
/// Markdown using the `addMarkdownFiles` step.
/// - Parameter path: The absolute path of the page.
/// - Parameter content: The page's content.
- public init(path: Path, content: Content) {
+ public init(path: Path, metadata: Site.PageMetadata, content: Content) {
self.path = path
+ self.metadata = metadata
self.content = content
}
}
diff --git a/Sources/Publish/API/PublishedWebsite.swift b/Sources/Publish/API/PublishedWebsite.swift
index 8baa54b7..fe4daf70 100644
--- a/Sources/Publish/API/PublishedWebsite.swift
+++ b/Sources/Publish/API/PublishedWebsite.swift
@@ -15,5 +15,5 @@ public struct PublishedWebsite {
/// The sections that were published.
public let sections: SectionMap
/// The free-form pages that were published.
- public let pages: [Path : Page]
+ public let pages: [Path : Page]
}
diff --git a/Sources/Publish/API/PublishingContext.swift b/Sources/Publish/API/PublishingContext.swift
index 4dd14caa..c261a86e 100644
--- a/Sources/Publish/API/PublishingContext.swift
+++ b/Sources/Publish/API/PublishingContext.swift
@@ -28,7 +28,7 @@ public struct PublishingContext {
/// The sections that the website contains.
public var sections = SectionMap() { didSet { tagCache.tags = nil } }
/// The free-form pages that the website contains.
- public private(set) var pages = [Path : Page]()
+ public private(set) var pages = [Path : Page]()
/// A set containing all tags that are currently being used website-wide.
public var allTags: Set { tagCache.tags ?? gatherAllTags() }
/// Any date when the website was last generated.
@@ -230,7 +230,7 @@ public extension PublishingContext {
/// Add a page to the website programmatically.
/// - parameter page: The page to add.
- mutating func addPage(_ page: Page) {
+ mutating func addPage(_ page: Page) {
pages[page.path] = page
}
@@ -249,8 +249,8 @@ public extension PublishingContext {
/// - throws: An error in case the page couldn't be found, or
/// if the mutation close itself threw an error.
mutating func mutatePage(at path: Path,
- matching predicate: Predicate = .any,
- using mutations: Mutations) throws {
+ matching predicate: Predicate> = .any,
+ using mutations: Mutations>) throws {
guard var page = pages[path] else {
throw ContentError(path: path, reason: .pageNotFound)
}
diff --git a/Sources/Publish/API/PublishingStep.swift b/Sources/Publish/API/PublishingStep.swift
index 63e8a7b0..fd6983c9 100644
--- a/Sources/Publish/API/PublishingStep.swift
+++ b/Sources/Publish/API/PublishingStep.swift
@@ -108,7 +108,7 @@ public extension PublishingStep {
/// Add a page to website programmatically.
/// - parameter page: The page to add.
- static func addPage(_ page: Page) -> Self {
+ static func addPage(_ page: Page) -> Self {
step(named: "Add page '\(page.path)'") { context in
context.addPage(page)
}
@@ -118,7 +118,7 @@ public extension PublishingStep {
/// - parameter sequence: The pages to add.
static func addPages(
in sequence: S
- ) -> Self where S.Element == Page {
+ ) -> Self where S.Element == Page {
step(named: "Add pages in sequence") { context in
sequence.forEach { context.addPage($0) }
}
@@ -233,7 +233,7 @@ public extension PublishingStep {
/// - parameter mutations: The mutations to apply to the page.
static func mutatePage(
at path: Path,
- using mutations: @escaping Mutations
+ using mutations: @escaping Mutations>
) -> Self {
step(named: "Mutate page at '\(path)'") { context in
try context.mutatePage(at: path, using: mutations)
@@ -244,8 +244,8 @@ public extension PublishingStep {
/// - parameter predicate: Any predicate to filter the items using.
/// - parameter mutations: The mutations to apply to the page.
static func mutateAllPages(
- matching predicate: Predicate = .any,
- using mutations: @escaping Mutations
+ matching predicate: Predicate> = .any,
+ using mutations: @escaping Mutations>
) -> Self {
step(named: "Mutate all pages") { context in
for path in context.pages.keys {
diff --git a/Sources/Publish/API/Theme+Foundation.swift b/Sources/Publish/API/Theme+Foundation.swift
index 39cdcbb4..da686c2e 100644
--- a/Sources/Publish/API/Theme+Foundation.swift
+++ b/Sources/Publish/API/Theme+Foundation.swift
@@ -81,7 +81,7 @@ private struct FoundationHTMLFactory: HTMLFactory {
)
}
- func makePageHTML(for page: Page,
+ func makePageHTML(for page: Page,
context: PublishingContext) throws -> HTML {
HTML(
.lang(context.site.language),
diff --git a/Sources/Publish/API/Theme.swift b/Sources/Publish/API/Theme.swift
index f083976f..162dd43b 100644
--- a/Sources/Publish/API/Theme.swift
+++ b/Sources/Publish/API/Theme.swift
@@ -14,7 +14,7 @@ public struct Theme {
internal let makeIndexHTML: (Index, PublishingContext) throws -> HTML
internal let makeSectionHTML: (Section, PublishingContext) throws -> HTML
internal let makeItemHTML: (Item, PublishingContext) throws -> HTML
- internal let makePageHTML: (Page, PublishingContext) throws -> HTML
+ internal let makePageHTML: (Page, PublishingContext) throws -> HTML
internal let makeTagListHTML: (TagListPage, PublishingContext) throws -> HTML?
internal let makeTagDetailsHTML: (TagDetailsPage, PublishingContext) throws -> HTML?
internal let resourcePaths: Set
diff --git a/Sources/Publish/API/Website.swift b/Sources/Publish/API/Website.swift
index fd4c5b8c..45515cd7 100644
--- a/Sources/Publish/API/Website.swift
+++ b/Sources/Publish/API/Website.swift
@@ -12,6 +12,10 @@ import Dispatch
public protocol WebsiteSectionID: Decodable, Hashable, CaseIterable, RawRepresentable where RawValue == String {}
/// Protocol that all `Website.ItemMetadata` implementations must conform to.
public typealias WebsiteItemMetadata = Decodable & Hashable
+/// Protocol that all `Website.PageMetadata` implementations must conform to.
+public typealias WebsitePageMetadata = Decodable & Hashable
+/// Default type for `Website.PageMetadata` that includes no metadata.
+public struct NoPageMetadata: WebsitePageMetadata {}
/// Protocol used to define a Publish-based website.
/// You conform to this protocol using a custom type, which is then used to
@@ -23,8 +27,10 @@ public typealias WebsiteItemMetadata = Decodable & Hashable
public protocol Website {
/// The enum type used to represent the website's section IDs.
associatedtype SectionID: WebsiteSectionID
- /// The type that defines any custom metadata for the website.
+ /// The type that defines any custom metadata for the website's items.
associatedtype ItemMetadata: WebsiteItemMetadata
+ /// The type that defines any custom metadata for the website's pages.
+ associatedtype PageMetadata: WebsitePageMetadata = NoPageMetadata
/// The absolute URL that the website will be hosted at.
var url: URL { get }
diff --git a/Sources/Publish/Internal/MarkdownContentFactory.swift b/Sources/Publish/Internal/MarkdownContentFactory.swift
index 74dfd60e..5462a4c4 100644
--- a/Sources/Publish/Internal/MarkdownContentFactory.swift
+++ b/Sources/Publish/Internal/MarkdownContentFactory.swift
@@ -41,11 +41,12 @@ internal struct MarkdownContentFactory {
)
}
- func makePage(fromFile file: File, at path: Path) throws -> Page {
+ func makePage(fromFile file: File, at path: Path) throws -> Page {
let markdown = try parser.parse(file.readAsString())
let decoder = makeMetadataDecoder(for: markdown)
+ let metadata = try Site.PageMetadata(from: decoder)
let content = try makeContent(fromMarkdown: markdown, file: file, decoder: decoder)
- return Page(path: path, content: content)
+ return Page(path: path, metadata: metadata, content: content)
}
}
diff --git a/Sources/Publish/Internal/MarkdownFileHandler.swift b/Sources/Publish/Internal/MarkdownFileHandler.swift
index ffeaaaa3..e7d24b73 100644
--- a/Sources/Publish/Internal/MarkdownFileHandler.swift
+++ b/Sources/Publish/Internal/MarkdownFileHandler.swift
@@ -98,7 +98,7 @@ internal struct MarkdownFileHandler {
private extension MarkdownFileHandler {
enum FolderResult {
- case pages([Page])
+ case pages([Page])
case section(id: Site.SectionID, content: Content?, items: [Item])
}
@@ -107,8 +107,8 @@ private extension MarkdownFileHandler {
recursively: Bool,
parentPath: Path,
factory: MarkdownContentFactory
- ) async throws -> [Page] {
- let pages: [Page] = try await folder.files.concurrentCompactMap { file in
+ ) async throws -> [Page] {
+ let pages: [Page] = try await folder.files.concurrentCompactMap { file in
guard file.isMarkdown else { return nil }
if file.nameExcludingExtension == "index", !recursively {
diff --git a/Sources/Publish/Internal/SiteMapGenerator.swift b/Sources/Publish/Internal/SiteMapGenerator.swift
index 02463eac..f09a1aee 100644
--- a/Sources/Publish/Internal/SiteMapGenerator.swift
+++ b/Sources/Publish/Internal/SiteMapGenerator.swift
@@ -34,7 +34,7 @@ private extension SiteMapGenerator {
})
}
- func makeSiteMap(for sections: [Section], pages: [Page], site: Site) -> SiteMap {
+ func makeSiteMap(for sections: [Section], pages: [Page], site: Site) -> SiteMap {
SiteMap(
.forEach(sections) { section in
guard shouldIncludePath(section.path) else {
diff --git a/Tests/PublishTests/Infrastructure/HTMLFactoryMock.swift b/Tests/PublishTests/Infrastructure/HTMLFactoryMock.swift
index 5f63fe92..7ac04996 100644
--- a/Tests/PublishTests/Infrastructure/HTMLFactoryMock.swift
+++ b/Tests/PublishTests/Infrastructure/HTMLFactoryMock.swift
@@ -13,7 +13,7 @@ final class HTMLFactoryMock: HTMLFactory {
var makeIndexHTML: Closure = { _, _ in HTML(.body()) }
var makeSectionHTML: Closure> = { _, _ in HTML(.body()) }
var makeItemHTML: Closure- > = { _, _ in HTML(.body()) }
- var makePageHTML: Closure = { _, _ in HTML(.body()) }
+ var makePageHTML: Closure> = { _, _ in HTML(.body()) }
var makeTagListHTML: Closure? = { _, _ in HTML(.body()) }
var makeTagDetailsHTML: Closure? = { _, _ in HTML(.body()) }
@@ -32,7 +32,7 @@ final class HTMLFactoryMock: HTMLFactory {
try makeItemHTML(item, context)
}
- func makePageHTML(for page: Page,
+ func makePageHTML(for page: Page,
context: PublishingContext) throws -> HTML {
try makePageHTML(page, context)
}
diff --git a/Tests/PublishTests/Infrastructure/Item+Stubbable.swift b/Tests/PublishTests/Infrastructure/Item+Stubbable.swift
index 0a80ba56..0e38da76 100644
--- a/Tests/PublishTests/Infrastructure/Item+Stubbable.swift
+++ b/Tests/PublishTests/Infrastructure/Item+Stubbable.swift
@@ -7,7 +7,7 @@
import Foundation
import Publish
-extension Item: Stubbable where Site == WebsiteStub.WithoutItemMetadata {
+extension Item: Stubbable where Site == WebsiteStub.WithoutMetadata {
private static let defaultDate = Date()
static func stub(withPath path: Path) -> Self {
diff --git a/Tests/PublishTests/Infrastructure/Page+Stubbable.swift b/Tests/PublishTests/Infrastructure/Page+Stubbable.swift
index cdcede20..549cac20 100644
--- a/Tests/PublishTests/Infrastructure/Page+Stubbable.swift
+++ b/Tests/PublishTests/Infrastructure/Page+Stubbable.swift
@@ -7,12 +7,13 @@
import Foundation
import Publish
-extension Page: Stubbable {
+extension Page: Stubbable where Site == WebsiteStub.WithoutMetadata {
private static let defaultDate = Date()
static func stub(withPath path: Path) -> Self {
Page(
path: path,
+ metadata: Site.PageMetadata(),
content: Content(
date: defaultDate,
lastModified: defaultDate
diff --git a/Tests/PublishTests/Infrastructure/PublishTestCase.swift b/Tests/PublishTests/Infrastructure/PublishTestCase.swift
index c9f12ef5..9cf98c83 100644
--- a/Tests/PublishTests/Infrastructure/PublishTestCase.swift
+++ b/Tests/PublishTests/Infrastructure/PublishTestCase.swift
@@ -13,9 +13,9 @@ class PublishTestCase: XCTestCase {
@discardableResult
func publishWebsite(
in folder: Folder? = nil,
- using steps: [PublishingStep],
+ using steps: [PublishingStep],
content: [Path : String] = [:]
- ) throws -> PublishedWebsite {
+ ) throws -> PublishedWebsite {
try performWebsitePublishing(
in: folder,
using: steps,
@@ -25,12 +25,12 @@ class PublishTestCase: XCTestCase {
}
func publishWebsite(
- _ site: WebsiteStub.WithoutItemMetadata = .init(),
+ _ site: WebsiteStub.WithoutMetadata = .init(),
in folder: Folder? = nil,
- using theme: Theme,
+ using theme: Theme,
content: [Path : String] = [:],
- additionalSteps: [PublishingStep] = [],
- plugins: [Plugin] = [],
+ additionalSteps: [PublishingStep] = [],
+ plugins: [Plugin] = [],
expectedHTML: [Path : String],
allowWhitelistedOutputFiles: Bool = true,
file: StaticString = #file,
@@ -133,11 +133,12 @@ class PublishTestCase: XCTestCase {
}
@discardableResult
- func publishWebsite(
- withItemMetadataType itemMetadataType: T.Type,
- using steps: [PublishingStep>],
+ func publishWebsite(
+ withItemMetadataType itemMetadataType: IT.Type,
+ pageMetadataType: PT.Type,
+ using steps: [PublishingStep>],
content: [Path : String] = [:]
- ) throws -> PublishedWebsite> {
+ ) throws -> PublishedWebsite> {
try performWebsitePublishing(
using: steps,
files: content,
@@ -149,7 +150,7 @@ class PublishTestCase: XCTestCase {
in section: WebsiteStub.SectionID = .one,
fromMarkdown markdown: String,
fileName: String = "markdown.md"
- ) throws -> Item {
+ ) throws -> Item {
let site = try publishWebsite(
using: [
.addMarkdownFiles()
@@ -161,15 +162,17 @@ class PublishTestCase: XCTestCase {
return try require(site.sections[section].items.first)
}
-
- func generateItem(
- withMetadataType metadataType: T.Type,
+
+ struct EmptyPageMetadata: WebsitePageMetadata {}
+ func generateItem(
+ withMetadataType metadataType: IT.Type,
in section: WebsiteStub.SectionID = .one,
fromMarkdown markdown: String,
fileName: String = "markdown.md"
- ) throws -> Item> {
+ ) throws -> Item> {
let site = try publishWebsite(
- withItemMetadataType: T.self,
+ withItemMetadataType: IT.self,
+ pageMetadataType: EmptyPageMetadata.self,
using: [
.addMarkdownFiles()
],
diff --git a/Tests/PublishTests/Infrastructure/WebsiteStub.swift b/Tests/PublishTests/Infrastructure/WebsiteStub.swift
index 09c3638f..64717dcd 100644
--- a/Tests/PublishTests/Infrastructure/WebsiteStub.swift
+++ b/Tests/PublishTests/Infrastructure/WebsiteStub.swift
@@ -29,15 +29,17 @@ class WebsiteStub {
}
extension WebsiteStub {
- final class WithItemMetadata: WebsiteStub, Website {}
+ final class WithItemMetadata: WebsiteStub, Website {}
final class WithPodcastMetadata: WebsiteStub, Website {
struct ItemMetadata: PodcastCompatibleWebsiteItemMetadata {
var podcast: PodcastEpisodeMetadata?
}
+ struct PageMetadata: WebsitePageMetadata {}
}
- final class WithoutItemMetadata: WebsiteStub, Website {
+ final class WithoutMetadata: WebsiteStub, Website {
struct ItemMetadata: WebsiteItemMetadata {}
+ struct PageMetadata: WebsitePageMetadata {}
}
}
diff --git a/Tests/PublishTests/Tests/ErrorTests.swift b/Tests/PublishTests/Tests/ErrorTests.swift
index bda3c915..d14b1978 100644
--- a/Tests/PublishTests/Tests/ErrorTests.swift
+++ b/Tests/PublishTests/Tests/ErrorTests.swift
@@ -10,7 +10,7 @@ import Publish
final class ErrorTests: PublishTestCase {
func testErrorForInvalidRootPath() throws {
assertErrorThrown(
- try WebsiteStub.WithoutItemMetadata().publish(
+ try WebsiteStub.WithoutMetadata().publish(
at: "🤷♂️",
using: []
),
diff --git a/Tests/PublishTests/Tests/HTMLGenerationTests.swift b/Tests/PublishTests/Tests/HTMLGenerationTests.swift
index 2afca997..3e4c90cc 100644
--- a/Tests/PublishTests/Tests/HTMLGenerationTests.swift
+++ b/Tests/PublishTests/Tests/HTMLGenerationTests.swift
@@ -10,7 +10,7 @@ import Plot
import Files
final class HTMLGenerationTests: PublishTestCase {
- private var htmlFactory: HTMLFactoryMock!
+ private var htmlFactory: HTMLFactoryMock!
override func setUp() {
super.setUp()
@@ -115,6 +115,7 @@ final class HTMLGenerationTests: PublishTestCase {
additionalSteps: [
.addPage(Page(
path: "path/to/page3",
+ metadata: WebsiteStub.WithoutMetadata.PageMetadata(),
content: Content(title: "Page 3")
))
],
@@ -235,7 +236,7 @@ final class HTMLGenerationTests: PublishTestCase {
}
func testNotGeneratingTagHTMLWhenDisabled() throws {
- let site = WebsiteStub.WithoutItemMetadata()
+ let site = WebsiteStub.WithoutMetadata()
site.tagHTMLConfig = nil
try publishWebsite(site,
diff --git a/Tests/PublishTests/Tests/MarkdownTests.swift b/Tests/PublishTests/Tests/MarkdownTests.swift
index 28df7927..ea571084 100644
--- a/Tests/PublishTests/Tests/MarkdownTests.swift
+++ b/Tests/PublishTests/Tests/MarkdownTests.swift
@@ -78,7 +78,7 @@ final class MarkdownTests: PublishTestCase {
}
func testParsingFileWithCustomMetadata() throws {
- struct Metadata: WebsiteItemMetadata {
+ struct ItemMetadata: WebsiteItemMetadata {
struct Nested: WebsiteItemMetadata {
var string: String
var url: URL
@@ -95,7 +95,7 @@ final class MarkdownTests: PublishTestCase {
}
let item = try generateItem(
- withMetadataType: Metadata.self,
+ withMetadataType: ItemMetadata.self,
fromMarkdown: """
---
string: Hello, world!
diff --git a/Tests/PublishTests/Tests/PlotComponentTests.swift b/Tests/PublishTests/Tests/PlotComponentTests.swift
index 5269da15..85383b06 100644
--- a/Tests/PublishTests/Tests/PlotComponentTests.swift
+++ b/Tests/PublishTests/Tests/PlotComponentTests.swift
@@ -12,8 +12,8 @@ import Ink
final class PlotComponentTests: PublishTestCase {
func testStylesheetPaths() {
let html = Node.head(
- for: Page(path: "path", content: Content()),
- on: WebsiteStub.WithoutItemMetadata(),
+ for: Page(path: "path", metadata: WebsiteStub.WithoutMetadata.PageMetadata(), content: Content()),
+ on: WebsiteStub.WithoutMetadata(),
stylesheetPaths: [
"local-1.css",
"/local-2.css",
diff --git a/Tests/PublishTests/Tests/PluginTests.swift b/Tests/PublishTests/Tests/PluginTests.swift
index 14ead1eb..52df479b 100644
--- a/Tests/PublishTests/Tests/PluginTests.swift
+++ b/Tests/PublishTests/Tests/PluginTests.swift
@@ -42,7 +42,7 @@ final class PluginTests: PublishTestCase {
}
func testAddingPluginToDefaultPipeline() throws {
- let htmlFactory = HTMLFactoryMock()
+ let htmlFactory = HTMLFactoryMock()
htmlFactory.makeIndexHTML = { content, _ in
HTML(.body(content.body.node))
}
diff --git a/Tests/PublishTests/Tests/PodcastFeedGenerationTests.swift b/Tests/PublishTests/Tests/PodcastFeedGenerationTests.swift
index e836f52d..21e0b689 100644
--- a/Tests/PublishTests/Tests/PodcastFeedGenerationTests.swift
+++ b/Tests/PublishTests/Tests/PodcastFeedGenerationTests.swift
@@ -197,7 +197,7 @@ private extension PodcastFeedGenerationTests {
func generateFeed(
in folder: Folder,
config: Configuration? = nil,
- itemPredicate: Predicate
- >? = nil,
+ itemPredicate: Publish.Predicate
- >? = nil,
generationSteps: [PublishingStep] = [
.addMarkdownFiles()
],
diff --git a/Tests/PublishTests/Tests/RSSFeedGenerationTests.swift b/Tests/PublishTests/Tests/RSSFeedGenerationTests.swift
index 52cd192d..3d472e2a 100644
--- a/Tests/PublishTests/Tests/RSSFeedGenerationTests.swift
+++ b/Tests/PublishTests/Tests/RSSFeedGenerationTests.swift
@@ -174,12 +174,12 @@ final class RSSFeedGenerationTests: PublishTestCase {
}
private extension RSSFeedGenerationTests {
- typealias Site = WebsiteStub.WithoutItemMetadata
+ typealias Site = WebsiteStub.WithoutMetadata
func generateFeed(
in folder: Folder,
config: RSSFeedConfiguration = .default,
- itemPredicate: Predicate
- >? = nil,
+ itemPredicate: Publish.Predicate
- >? = nil,
generationSteps: [PublishingStep] = [
.addMarkdownFiles()
],
diff --git a/Tests/PublishTests/Tests/WebsiteTests.swift b/Tests/PublishTests/Tests/WebsiteTests.swift
index 1ab15744..face4566 100644
--- a/Tests/PublishTests/Tests/WebsiteTests.swift
+++ b/Tests/PublishTests/Tests/WebsiteTests.swift
@@ -8,7 +8,7 @@ import XCTest
import Publish
final class WebsiteTests: PublishTestCase {
- private var website: WebsiteStub.WithoutItemMetadata!
+ private var website: WebsiteStub.WithoutMetadata!
override func setUp() {
super.setUp()
@@ -74,7 +74,7 @@ final class WebsiteTests: PublishTestCase {
}
func testURLForLocation() {
- let page = Page(path: "mypage", content: Content())
+ let page = Page(path: "mypage", metadata: WebsiteStub.WithoutMetadata.PageMetadata(), content: Content())
XCTAssertEqual(
website.url(for: page),