Skip to content

Commit

Permalink
Applying fixes from JohnSundell#142.
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim Hart committed Jan 27, 2023
1 parent 36c60df commit 7eaaa0e
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 27 deletions.
84 changes: 57 additions & 27 deletions Sources/Publish/Internal/HTMLGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,32 @@ internal struct HTMLGenerator<Site: Website> {
let context: PublishingContext<Site>

func generate() async throws {
try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask { try await copyThemeResources() }
group.addTask { try generateIndexHTML() }
group.addTask { try await generateSectionHTML() }
group.addTask { try await generatePageHTML() }
group.addTask { try await generateTagHTMLIfNeeded() }

// Throw any errors generated by the above set of operations:
for try await _ in group {}
try await withThrowingTaskGroup(of: (substep: String, paths: [Path]).self) { group in
group.addTask { (substep: "Copy theme resources", paths: try await copyThemeResources()) }
group.addTask { (substep: "Generate index", paths: try generateIndexHTML()) }
group.addTask { (substep: "Generate sections", paths: try await generateSectionHTML()) }
group.addTask { (substep: "Generate pages", paths: try await generatePageHTML()) }
group.addTask { (substep: "Generate tags", paths: try await generateTagHTMLIfNeeded()) }

try await validate(group)
}
}
}

private extension HTMLGenerator {
func copyThemeResources() async throws {
func copyThemeResources() async throws -> [Path] {
guard !theme.resourcePaths.isEmpty else {
return
return []
}

let creationFile = try File(path: theme.creationPath.string)
let packageFolder = try creationFile.resolveSwiftPackageFolder()

try await theme.resourcePaths.concurrentForEach { path in
return try await theme.resourcePaths.concurrentMap { path -> Path in
do {
let file = try packageFolder.file(at: path.string)
try context.copyFileToOutput(file, targetFolderPath: nil)
return path
} catch {
throw PublishingError(
path: path,
Expand All @@ -51,34 +51,42 @@ private extension HTMLGenerator {
}
}

func generateIndexHTML() throws {
func generateIndexHTML() throws -> [Path] {
let html = try theme.makeIndexHTML(context.index, context)
let indexFile = try context.createOutputFile(at: "index.html")
let path = Path("index.html")
let indexFile = try context.createOutputFile(at: path)
try indexFile.write(html.render(indentedBy: indentation))
return [path]
}

func generateSectionHTML() async throws {
try await context.sections.concurrentForEach { section in
try outputHTML(
func generateSectionHTML() async throws -> [Path] {
try await context.sections.concurrentFlatMap { section -> [Path] in
var allPaths = [Path]()

let sectionPath = try outputHTML(
for: section,
indentedBy: indentation,
using: theme.makeSectionHTML,
fileMode: .foldersAndIndexFiles
)

try await section.items.concurrentForEach { item in
allPaths.append(sectionPath)

let sectionItemPaths = try await section.items.concurrentMap { item -> Path in
try outputHTML(
for: item,
indentedBy: indentation,
using: theme.makeItemHTML,
fileMode: fileMode
)
}

allPaths.append(contentsOf: sectionItemPaths)
return allPaths
}
}

func generatePageHTML() async throws {
try await context.pages.values.concurrentForEach { page in
func generatePageHTML() async throws -> [Path] {
try await context.pages.values.concurrentMap { page -> Path in
try outputHTML(
for: page,
indentedBy: indentation,
Expand All @@ -88,9 +96,9 @@ private extension HTMLGenerator {
}
}

func generateTagHTMLIfNeeded() async throws {
func generateTagHTMLIfNeeded() async throws -> [Path] {
guard let config = context.site.tagHTMLConfig else {
return
return []
}

let listPage = TagListPage(
Expand All @@ -99,13 +107,15 @@ private extension HTMLGenerator {
content: config.listContent ?? .init()
)

var allPaths = [Path]()
if let listHTML = try theme.makeTagListHTML(listPage, context) {
let listPath = Path("\(config.basePath)/index.html")
let listFile = try context.createOutputFile(at: listPath)
try listFile.write(listHTML.render(indentedBy: indentation))
allPaths.append(listPath)
}

try await context.allTags.concurrentForEach { tag in
let tagPaths: [Path] = try await context.allTags.concurrentCompactMap { tag -> Path? in
let detailsPath = context.site.path(for: tag)
let detailsContent = config.detailsContentResolver(tag)

Expand All @@ -116,28 +126,48 @@ private extension HTMLGenerator {
)

guard let detailsHTML = try theme.makeTagDetailsHTML(detailsPage, context) else {
return
return nil
}

try outputHTML(
return try outputHTML(
for: detailsPage,
indentedBy: indentation,
using: { _, _ in detailsHTML },
fileMode: fileMode
)
}

allPaths.append(contentsOf: tagPaths)
return allPaths
}

func validate(_ group: ThrowingTaskGroup<(substep: String, paths: [Path]), Error>) async throws {
var pathSubsteps = [Path: [String]]()
for try await substepAndPaths in group {
for path in substepAndPaths.paths {
if let previousSubsteps = pathSubsteps[path] {
let substeps = previousSubsteps.appending(substepAndPaths.substep)
throw PublishingError(
path: path,
infoMessage: "Path conflict in substeps: \(substeps)"
)
}
pathSubsteps[path, default: []].append(substepAndPaths.substep)
}
}
}

func outputHTML<T: Location>(
for location: T,
indentedBy indentation: Indentation.Kind?,
using generator: (T, PublishingContext<Site>) throws -> HTML,
fileMode: HTMLFileMode
) throws {
) throws -> Path {
let html = try generator(location, context)
let path = filePath(for: location, fileMode: fileMode)
let file = try context.createOutputFile(at: path)
try file.write(html.render(indentedBy: indentation))
return path
}

func filePath(for location: Location, fileMode: HTMLFileMode) -> Path {
Expand Down
25 changes: 25 additions & 0 deletions Tests/PublishTests/Tests/HTMLGenerationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,31 @@ final class HTMLGenerationTests: PublishTestCase {
)
}

func testGeneratingConflictingFilesThrowsError() throws {
let folder = try Folder.createTemporary()

var thrownError: PublishingError?
do {
try publishWebsite(
in: folder,
using: [
.addMarkdownFiles(),
.generateHTML(withTheme: .foundation)
],
content: [
// This file has the same name as the `WebsiteStub.SectionID.one` case, which
// causes multiple outputs at the same location.
"one.md": "# One content",
]
)
} catch {
thrownError = error as? PublishingError
}

let path = try require(thrownError?.path)
XCTAssertEqual(path, "one/index.html")
}

func testFoundationTheme() throws {
let folder = try Folder.createTemporary()

Expand Down

0 comments on commit 7eaaa0e

Please sign in to comment.