Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/Publish/API/HTMLFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Site>,
context: PublishingContext<Site>) throws -> HTML

/// Create the HTML to use for the website's list of tags, if supported.
Expand Down
6 changes: 4 additions & 2 deletions Sources/Publish/API/Page.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Site: Website>: 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
}
}
2 changes: 1 addition & 1 deletion Sources/Publish/API/PublishedWebsite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ public struct PublishedWebsite<Base: Website> {
/// The sections that were published.
public let sections: SectionMap<Base>
/// The free-form pages that were published.
public let pages: [Path : Page]
public let pages: [Path : Page<Base>]
}
8 changes: 4 additions & 4 deletions Sources/Publish/API/PublishingContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public struct PublishingContext<Site: Website> {
/// The sections that the website contains.
public var sections = SectionMap<Site>() { 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<Site>]()
/// A set containing all tags that are currently being used website-wide.
public var allTags: Set<Tag> { tagCache.tags ?? gatherAllTags() }
/// Any date when the website was last generated.
Expand Down Expand Up @@ -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<Site>) {
pages[page.path] = page
}

Expand All @@ -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<Page> = .any,
using mutations: Mutations<Page>) throws {
matching predicate: Predicate<Page<Site>> = .any,
using mutations: Mutations<Page<Site>>) throws {
guard var page = pages[path] else {
throw ContentError(path: path, reason: .pageNotFound)
}
Expand Down
10 changes: 5 additions & 5 deletions Sources/Publish/API/PublishingStep.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Site>) -> Self {
step(named: "Add page '\(page.path)'") { context in
context.addPage(page)
}
Expand All @@ -118,7 +118,7 @@ public extension PublishingStep {
/// - parameter sequence: The pages to add.
static func addPages<S: Sequence>(
in sequence: S
) -> Self where S.Element == Page {
) -> Self where S.Element == Page<Site> {
step(named: "Add pages in sequence") { context in
sequence.forEach { context.addPage($0) }
}
Expand Down Expand Up @@ -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<Page>
using mutations: @escaping Mutations<Page<Site>>
) -> Self {
step(named: "Mutate page at '\(path)'") { context in
try context.mutatePage(at: path, using: mutations)
Expand All @@ -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<Page> = .any,
using mutations: @escaping Mutations<Page>
matching predicate: Predicate<Page<Site>> = .any,
using mutations: @escaping Mutations<Page<Site>>
) -> Self {
step(named: "Mutate all pages") { context in
for path in context.pages.keys {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Publish/API/Theme+Foundation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ private struct FoundationHTMLFactory<Site: Website>: HTMLFactory {
)
}

func makePageHTML(for page: Page,
func makePageHTML(for page: Page<Site>,
context: PublishingContext<Site>) throws -> HTML {
HTML(
.lang(context.site.language),
Expand Down
2 changes: 1 addition & 1 deletion Sources/Publish/API/Theme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public struct Theme<Site: Website> {
internal let makeIndexHTML: (Index, PublishingContext<Site>) throws -> HTML
internal let makeSectionHTML: (Section<Site>, PublishingContext<Site>) throws -> HTML
internal let makeItemHTML: (Item<Site>, PublishingContext<Site>) throws -> HTML
internal let makePageHTML: (Page, PublishingContext<Site>) throws -> HTML
internal let makePageHTML: (Page<Site>, PublishingContext<Site>) throws -> HTML
internal let makeTagListHTML: (TagListPage, PublishingContext<Site>) throws -> HTML?
internal let makeTagDetailsHTML: (TagDetailsPage, PublishingContext<Site>) throws -> HTML?
internal let resourcePaths: Set<Path>
Expand Down
8 changes: 7 additions & 1 deletion Sources/Publish/API/Website.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 }
Expand Down
5 changes: 3 additions & 2 deletions Sources/Publish/Internal/MarkdownContentFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@ internal struct MarkdownContentFactory<Site: Website> {
)
}

func makePage(fromFile file: File, at path: Path) throws -> Page {
func makePage(fromFile file: File, at path: Path) throws -> Page<Site> {
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)
}
}

Expand Down
6 changes: 3 additions & 3 deletions Sources/Publish/Internal/MarkdownFileHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ internal struct MarkdownFileHandler<Site: Website> {

private extension MarkdownFileHandler {
enum FolderResult {
case pages([Page])
case pages([Page<Site>])
case section(id: Site.SectionID, content: Content?, items: [Item<Site>])
}

Expand All @@ -107,8 +107,8 @@ private extension MarkdownFileHandler {
recursively: Bool,
parentPath: Path,
factory: MarkdownContentFactory<Site>
) async throws -> [Page] {
let pages: [Page] = try await folder.files.concurrentCompactMap { file in
) async throws -> [Page<Site>] {
let pages: [Page<Site>] = try await folder.files.concurrentCompactMap { file in
guard file.isMarkdown else { return nil }

if file.nameExcludingExtension == "index", !recursively {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Publish/Internal/SiteMapGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ private extension SiteMapGenerator {
})
}

func makeSiteMap(for sections: [Section<Site>], pages: [Page], site: Site) -> SiteMap {
func makeSiteMap(for sections: [Section<Site>], pages: [Page<Site>], site: Site) -> SiteMap {
SiteMap(
.forEach(sections) { section in
guard shouldIncludePath(section.path) else {
Expand Down
4 changes: 2 additions & 2 deletions Tests/PublishTests/Infrastructure/HTMLFactoryMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ final class HTMLFactoryMock<Site: Website>: HTMLFactory {
var makeIndexHTML: Closure<Index> = { _, _ in HTML(.body()) }
var makeSectionHTML: Closure<Section<Site>> = { _, _ in HTML(.body()) }
var makeItemHTML: Closure<Item<Site>> = { _, _ in HTML(.body()) }
var makePageHTML: Closure<Page> = { _, _ in HTML(.body()) }
var makePageHTML: Closure<Page<Site>> = { _, _ in HTML(.body()) }
var makeTagListHTML: Closure<TagListPage>? = { _, _ in HTML(.body()) }
var makeTagDetailsHTML: Closure<TagDetailsPage>? = { _, _ in HTML(.body()) }

Expand All @@ -32,7 +32,7 @@ final class HTMLFactoryMock<Site: Website>: HTMLFactory {
try makeItemHTML(item, context)
}

func makePageHTML(for page: Page,
func makePageHTML(for page: Page<Site>,
context: PublishingContext<Site>) throws -> HTML {
try makePageHTML(page, context)
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/PublishTests/Infrastructure/Item+Stubbable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion Tests/PublishTests/Infrastructure/Page+Stubbable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 19 additions & 16 deletions Tests/PublishTests/Infrastructure/PublishTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ class PublishTestCase: XCTestCase {
@discardableResult
func publishWebsite(
in folder: Folder? = nil,
using steps: [PublishingStep<WebsiteStub.WithoutItemMetadata>],
using steps: [PublishingStep<WebsiteStub.WithoutMetadata>],
content: [Path : String] = [:]
) throws -> PublishedWebsite<WebsiteStub.WithoutItemMetadata> {
) throws -> PublishedWebsite<WebsiteStub.WithoutMetadata> {
try performWebsitePublishing(
in: folder,
using: steps,
Expand All @@ -25,12 +25,12 @@ class PublishTestCase: XCTestCase {
}

func publishWebsite(
_ site: WebsiteStub.WithoutItemMetadata = .init(),
_ site: WebsiteStub.WithoutMetadata = .init(),
in folder: Folder? = nil,
using theme: Theme<WebsiteStub.WithoutItemMetadata>,
using theme: Theme<WebsiteStub.WithoutMetadata>,
content: [Path : String] = [:],
additionalSteps: [PublishingStep<WebsiteStub.WithoutItemMetadata>] = [],
plugins: [Plugin<WebsiteStub.WithoutItemMetadata>] = [],
additionalSteps: [PublishingStep<WebsiteStub.WithoutMetadata>] = [],
plugins: [Plugin<WebsiteStub.WithoutMetadata>] = [],
expectedHTML: [Path : String],
allowWhitelistedOutputFiles: Bool = true,
file: StaticString = #file,
Expand Down Expand Up @@ -133,11 +133,12 @@ class PublishTestCase: XCTestCase {
}

@discardableResult
func publishWebsite<T: WebsiteItemMetadata>(
withItemMetadataType itemMetadataType: T.Type,
using steps: [PublishingStep<WebsiteStub.WithItemMetadata<T>>],
func publishWebsite<IT: WebsiteItemMetadata, PT: WebsitePageMetadata>(
withItemMetadataType itemMetadataType: IT.Type,
pageMetadataType: PT.Type,
using steps: [PublishingStep<WebsiteStub.WithItemMetadata<IT, PT>>],
content: [Path : String] = [:]
) throws -> PublishedWebsite<WebsiteStub.WithItemMetadata<T>> {
) throws -> PublishedWebsite<WebsiteStub.WithItemMetadata<IT, PT>> {
try performWebsitePublishing(
using: steps,
files: content,
Expand All @@ -149,7 +150,7 @@ class PublishTestCase: XCTestCase {
in section: WebsiteStub.SectionID = .one,
fromMarkdown markdown: String,
fileName: String = "markdown.md"
) throws -> Item<WebsiteStub.WithoutItemMetadata> {
) throws -> Item<WebsiteStub.WithoutMetadata> {
let site = try publishWebsite(
using: [
.addMarkdownFiles()
Expand All @@ -161,15 +162,17 @@ class PublishTestCase: XCTestCase {

return try require(site.sections[section].items.first)
}

func generateItem<T: WebsiteItemMetadata>(
withMetadataType metadataType: T.Type,

struct EmptyPageMetadata: WebsitePageMetadata {}
func generateItem<IT: WebsiteItemMetadata>(
withMetadataType metadataType: IT.Type,
in section: WebsiteStub.SectionID = .one,
fromMarkdown markdown: String,
fileName: String = "markdown.md"
) throws -> Item<WebsiteStub.WithItemMetadata<T>> {
) throws -> Item<WebsiteStub.WithItemMetadata<IT, EmptyPageMetadata>> {
let site = try publishWebsite(
withItemMetadataType: T.self,
withItemMetadataType: IT.self,
pageMetadataType: EmptyPageMetadata.self,
using: [
.addMarkdownFiles()
],
Expand Down
6 changes: 4 additions & 2 deletions Tests/PublishTests/Infrastructure/WebsiteStub.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,17 @@ class WebsiteStub {
}

extension WebsiteStub {
final class WithItemMetadata<ItemMetadata: WebsiteItemMetadata>: WebsiteStub, Website {}
final class WithItemMetadata<ItemMetadata: WebsiteItemMetadata, PageMetadata: WebsitePageMetadata>: 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 {}
}
}
2 changes: 1 addition & 1 deletion Tests/PublishTests/Tests/ErrorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Publish
final class ErrorTests: PublishTestCase {
func testErrorForInvalidRootPath() throws {
assertErrorThrown(
try WebsiteStub.WithoutItemMetadata().publish(
try WebsiteStub.WithoutMetadata().publish(
at: "🤷‍♂️",
using: []
),
Expand Down
5 changes: 3 additions & 2 deletions Tests/PublishTests/Tests/HTMLGenerationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Plot
import Files

final class HTMLGenerationTests: PublishTestCase {
private var htmlFactory: HTMLFactoryMock<WebsiteStub.WithoutItemMetadata>!
private var htmlFactory: HTMLFactoryMock<WebsiteStub.WithoutMetadata>!

override func setUp() {
super.setUp()
Expand Down Expand Up @@ -115,6 +115,7 @@ final class HTMLGenerationTests: PublishTestCase {
additionalSteps: [
.addPage(Page(
path: "path/to/page3",
metadata: WebsiteStub.WithoutMetadata.PageMetadata(),
content: Content(title: "Page 3")
))
],
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions Tests/PublishTests/Tests/MarkdownTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -95,7 +95,7 @@ final class MarkdownTests: PublishTestCase {
}

let item = try generateItem(
withMetadataType: Metadata.self,
withMetadataType: ItemMetadata.self,
fromMarkdown: """
---
string: Hello, world!
Expand Down
4 changes: 2 additions & 2 deletions Tests/PublishTests/Tests/PlotComponentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<WebsiteStub.WithoutMetadata>(path: "path", metadata: WebsiteStub.WithoutMetadata.PageMetadata(), content: Content()),
on: WebsiteStub.WithoutMetadata(),
stylesheetPaths: [
"local-1.css",
"/local-2.css",
Expand Down
Loading