Skip to content

Commit 4c60f0c

Browse files
committed
Share logic with the JSON file writer for creating files and directories
1 parent 43018c8 commit 4c60f0c

File tree

4 files changed

+51
-49
lines changed

4 files changed

+51
-49
lines changed

Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ public struct ConvertAction: AsyncAction {
252252
// }
253253

254254
let indexHTML: URL?
255-
if let htmlTemplateDirectory, transformForStaticHosting, !includeContentInEachHTMLFile {
255+
if let htmlTemplateDirectory, transformForStaticHosting || includeContentInEachHTMLFile {
256256
let indexHTMLUrl = temporaryFolder.appendingPathComponent(
257257
HTMLTemplate.indexFileName.rawValue,
258258
isDirectory: false
@@ -303,7 +303,7 @@ public struct ConvertAction: AsyncAction {
303303
context: context,
304304
indexer: indexer,
305305
enableCustomTemplates: experimentalEnableCustomTemplates,
306-
transformForStaticHostingIndexHTML: indexHTML,
306+
transformForStaticHostingIndexHTML: includeContentInEachHTMLFile ? nil : indexHTML,
307307
bundleID: inputs.id
308308
)
309309

Sources/SwiftDocCUtilities/Action/Actions/Convert/FileWritingHTMLContentConsumer.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ struct FileWritingHTMLContentConsumer: HTMLContentConsumer {
6161
}
6262
}
6363
private var htmlTemplate: HTMLTemplate
64+
private let fileWriter: JSONEncodingRenderNodeWriter
6465

6566
init(
6667
targetFolder: URL,
@@ -72,6 +73,11 @@ struct FileWritingHTMLContentConsumer: HTMLContentConsumer {
7273
self.fileManager = fileManager
7374
self.htmlTemplate = try HTMLTemplate(data: fileManager.contents(of: htmlTemplate))
7475
self.prettyPrintOutput = prettyPrintOutput
76+
self.fileWriter = JSONEncodingRenderNodeWriter(
77+
targetFolder: targetFolder,
78+
fileManager: fileManager,
79+
transformForStaticHostingIndexHTML: nil
80+
)
7581
}
7682

7783
func consume(
@@ -86,9 +92,8 @@ struct FileWritingHTMLContentConsumer: HTMLContentConsumer {
8692
prettyPrint: prettyPrintOutput
8793
)
8894

89-
let url = targetFolder.appendingPathComponent(reference.path)
90-
try? fileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
91-
try fileManager.createFile(at: url.appendingPathComponent("index.html"), contents: Data(htmlString.utf8), options: .atomic)
95+
let relativeFilePath = NodeURLGenerator.fileSafeReferencePath(reference, lowercased: true) + "/index.html"
96+
try fileWriter.write(Data(htmlString.utf8), toFileSafePath: relativeFilePath)
9297
}
9398
}
9499

Sources/SwiftDocCUtilities/Action/Actions/Convert/JSONEncodingRenderNodeWriter.swift

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2021-2024 Apple Inc. and the Swift project authors
4+
Copyright (c) 2021-2025 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See https://swift.org/LICENSE.txt for license information
@@ -15,7 +15,6 @@ import SwiftDocC
1515
///
1616
/// The render node writer writes the JSON files into a hierarchy of folders and subfolders based on the relative URL for each node.
1717
class JSONEncodingRenderNodeWriter {
18-
private let renderNodeURLGenerator: NodeURLGenerator
1918
private let targetFolder: URL
2019
private let transformForStaticHostingIndexHTML: URL?
2120
private let fileManager: any FileManagerProtocol
@@ -27,9 +26,6 @@ class JSONEncodingRenderNodeWriter {
2726
/// - targetFolder: The folder to which the writer object writes the files.
2827
/// - fileManager: The file manager with which the writer object writes data to files.
2928
init(targetFolder: URL, fileManager: any FileManagerProtocol, transformForStaticHostingIndexHTML: URL?) {
30-
self.renderNodeURLGenerator = NodeURLGenerator(
31-
baseURL: targetFolder.appendingPathComponent("data", isDirectory: true)
32-
)
3329
self.targetFolder = targetFolder
3430
self.transformForStaticHostingIndexHTML = transformForStaticHostingIndexHTML
3531
self.fileManager = fileManager
@@ -52,34 +48,11 @@ class JSONEncodingRenderNodeWriter {
5248
lowercased: true
5349
)
5450

55-
// The path on disk to write the render node JSON file at.
56-
let renderNodeTargetFileURL = renderNodeURLGenerator
57-
.urlForReference(
58-
renderNode.identifier,
59-
fileSafePath: fileSafePath
60-
)
61-
.appendingPathExtension("json")
62-
63-
let renderNodeTargetFolderURL = renderNodeTargetFileURL.deletingLastPathComponent()
64-
65-
// On Linux sometimes it takes a moment for the directory to be created and that leads to
66-
// errors when trying to write files concurrently in the same target location.
67-
// We keep an index in `directoryIndex` and create new sub-directories as needed.
68-
// When the symbol's directory already exists no code is executed during the lock below
69-
// besides the set lookup.
70-
try directoryIndex.sync { directoryIndex in
71-
let (insertedRenderNodeTargetFolderURL, _) = directoryIndex.insert(renderNodeTargetFolderURL)
72-
if insertedRenderNodeTargetFolderURL {
73-
try fileManager.createDirectory(
74-
at: renderNodeTargetFolderURL,
75-
withIntermediateDirectories: true,
76-
attributes: nil
77-
)
78-
}
79-
}
80-
81-
let data = try renderNode.encodeToJSON(with: encoder, renderReferenceCache: renderReferenceCache)
82-
try fileManager.createFile(at: renderNodeTargetFileURL, contents: data, options: nil)
51+
try write(
52+
renderNode.encodeToJSON(with: encoder, renderReferenceCache: renderReferenceCache),
53+
// The path on disk to write the render node JSON file at.
54+
toFileSafePath: "data/\(fileSafePath).json"
55+
)
8356

8457
guard let indexHTML = transformForStaticHostingIndexHTML else {
8558
return
@@ -115,4 +88,28 @@ class JSONEncodingRenderNodeWriter {
11588
try fileManager._copyItem(at: indexHTML, to: htmlTargetFileURL)
11689
}
11790
}
91+
92+
func write(_ data: Data, toFileSafePath fileSafePath: String) throws {
93+
// The path on disk to write the data at.
94+
let fileURL = targetFolder.appendingPathComponent(fileSafePath, isDirectory: false)
95+
let containingFolderURL = fileURL.deletingLastPathComponent()
96+
97+
// On Linux sometimes it takes a moment for the directory to be created and that leads to
98+
// errors when trying to write files concurrently in the same target location.
99+
// We keep an index in `directoryIndex` and create new sub-directories as needed.
100+
// When the symbol's directory already exists no code is executed during the lock below
101+
// besides the set lookup.
102+
try directoryIndex.sync { directoryIndex in
103+
let (insertedRenderNodeTargetFolderURL, _) = directoryIndex.insert(containingFolderURL)
104+
if insertedRenderNodeTargetFolderURL {
105+
try fileManager.createDirectory(
106+
at: containingFolderURL,
107+
withIntermediateDirectories: true,
108+
attributes: nil
109+
)
110+
}
111+
}
112+
113+
try fileManager.createFile(at: fileURL, contents: data, options: nil)
114+
}
118115
}

Tests/SwiftDocCUtilitiesTests/FileWritingHTMLContentConsumerTests.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -173,18 +173,18 @@ final class FileWritingHTMLContentConsumerTests: XCTestCase {
173173
XCTAssertEqual(fileSystem.dump(subHierarchyFrom: "/output-dir"), """
174174
output-dir/
175175
╰─ documentation/
176-
╰─ ModuleName/
177-
├─ SomeArticle/
176+
╰─ modulename/
177+
├─ index.html
178+
├─ somearticle/
178179
│ ╰─ index.html
179-
├─ SomeClass/
180-
│ ├─ index.html
181-
│ ╰─ someMethod(with:and:)/
182-
│ ╰─ index.html
183-
╰─ index.html
180+
╰─ someclass/
181+
├─ index.html
182+
╰─ somemethod(with:and:)/
183+
╰─ index.html
184184
""")
185185

186186

187-
XCTAssertEqual(try XCTUnwrap(String(data: fileSystem.contents(of: URL(fileURLWithPath: "/output-dir/documentation/ModuleName/index.html")), encoding: .utf8)), """
187+
XCTAssertEqual(try XCTUnwrap(String(data: fileSystem.contents(of: URL(fileURLWithPath: "/output-dir/documentation/modulename/index.html")), encoding: .utf8)), """
188188
<html>
189189
<head>
190190
<meta charset="utf-8" />
@@ -230,7 +230,7 @@ final class FileWritingHTMLContentConsumerTests: XCTestCase {
230230
</html>
231231
""")
232232

233-
XCTAssertEqual(try XCTUnwrap(String(data: fileSystem.contents(of: URL(fileURLWithPath: "/output-dir/documentation/ModuleName/SomeClass/index.html")), encoding: .utf8)), """
233+
XCTAssertEqual(try XCTUnwrap(String(data: fileSystem.contents(of: URL(fileURLWithPath: "/output-dir/documentation/modulename/someclass/index.html")), encoding: .utf8)), """
234234
<html>
235235
<head>
236236
<meta charset="utf-8" />
@@ -272,7 +272,7 @@ final class FileWritingHTMLContentConsumerTests: XCTestCase {
272272
</html>
273273
""")
274274

275-
XCTAssertEqual(try XCTUnwrap(String(data: fileSystem.contents(of: URL(fileURLWithPath: "/output-dir/documentation/ModuleName/SomeClass/someMethod(with:and:)/index.html")), encoding: .utf8)), """
275+
XCTAssertEqual(try XCTUnwrap(String(data: fileSystem.contents(of: URL(fileURLWithPath: "/output-dir/documentation/modulename/someclass/somemethod(with:and:)/index.html")), encoding: .utf8)), """
276276
<html>
277277
<head>
278278
<meta charset="utf-8" />
@@ -345,7 +345,7 @@ final class FileWritingHTMLContentConsumerTests: XCTestCase {
345345
</html>
346346
""")
347347

348-
XCTAssertEqual(try XCTUnwrap(String(data: fileSystem.contents(of: URL(fileURLWithPath: "/output-dir/documentation/ModuleName/SomeArticle/index.html")), encoding: .utf8)), """
348+
XCTAssertEqual(try XCTUnwrap(String(data: fileSystem.contents(of: URL(fileURLWithPath: "/output-dir/documentation/modulename/somearticle/index.html")), encoding: .utf8)), """
349349
<html>
350350
<head>
351351
<meta charset="utf-8" />

0 commit comments

Comments
 (0)