Skip to content

Commit 10994be

Browse files
authored
Include user provided theme-settings.json file in compiled output (#339)
Fixes: * GitHub Issue #336 * rdar://96976784
1 parent 7a9b539 commit 10994be

File tree

7 files changed

+146
-2
lines changed

7 files changed

+146
-2
lines changed

Sources/SwiftDocC/Infrastructure/DocumentationBundle.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ public struct DocumentationBundle {
8787
/// Custom HTML file to use as the footer for rendered output.
8888
public let customFooter: URL?
8989

90+
/// JSON settings file used to theme renderer output.
91+
public let themeSettings: URL?
92+
9093
/// Default syntax highlighting to use for code samples in this bundle.
9194
@available(*, deprecated, message: "Use 'info.defaultCodeListingLanguage' instead.")
9295
public var defaultCodeListingLanguage: String? {
@@ -156,7 +159,8 @@ public struct DocumentationBundle {
156159
markupURLs: [URL],
157160
miscResourceURLs: [URL],
158161
customHeader: URL? = nil,
159-
customFooter: URL? = nil
162+
customFooter: URL? = nil,
163+
themeSettings: URL? = nil
160164
) {
161165
self.info = info
162166
self.baseURL = baseURL
@@ -166,6 +170,7 @@ public struct DocumentationBundle {
166170
self.miscResourceURLs = miscResourceURLs
167171
self.customHeader = customHeader
168172
self.customFooter = customFooter
173+
self.themeSettings = themeSettings
169174
self.rootReference = ResolvedTopicReference(bundleIdentifier: info.identifier, path: "/", sourceLanguage: .swift)
170175
self.documentationRootReference = ResolvedTopicReference(bundleIdentifier: info.identifier, path: NodeURLGenerator.Path.documentationFolder, sourceLanguage: .swift)
171176
self.tutorialsRootReference = ResolvedTopicReference(bundleIdentifier: info.identifier, path: NodeURLGenerator.Path.tutorialsFolder, sourceLanguage: .swift)

Sources/SwiftDocC/Infrastructure/DocumentationBundleFileTypes.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,12 @@ public enum DocumentationBundleFileTypes {
7676
public static func isCustomFooter(_ url: URL) -> Bool {
7777
return url.lastPathComponent == customFooterFileName
7878
}
79+
80+
private static let themeSettingsFileName = "theme-settings.json"
81+
/// Checks if a file is `theme-settings.json`.
82+
/// - Parameter url: The file to check.
83+
/// - Returns: Whether or not the file at `url` is `theme-settings.json`.
84+
public static func isThemeSettingsFile(_ url: URL) -> Bool {
85+
return url.lastPathComponent == themeSettingsFileName
86+
}
7987
}

Sources/SwiftDocC/Infrastructure/Workspace/DataProviderBundleDiscovery.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,17 @@ extension DocumentationWorkspaceDataProvider where Self: FileSystemProvider {
6666

6767
let customHeader = findCustomHeader(bundleChildren)?.url
6868
let customFooter = findCustomFooter(bundleChildren)?.url
69+
let themeSettings = findThemeSettings(bundleChildren)?.url
6970

70-
return DocumentationBundle(info: info, symbolGraphURLs: symbolGraphFiles, markupURLs: markupFiles, miscResourceURLs: miscResources, customHeader: customHeader, customFooter: customFooter)
71+
return DocumentationBundle(
72+
info: info,
73+
symbolGraphURLs: symbolGraphFiles,
74+
markupURLs: markupFiles,
75+
miscResourceURLs: miscResources,
76+
customHeader: customHeader,
77+
customFooter: customFooter,
78+
themeSettings: themeSettings
79+
)
7180
}
7281

7382
/// Performs a shallow search for the first Info.plist file in the given list of files and directories.
@@ -111,6 +120,10 @@ extension DocumentationWorkspaceDataProvider where Self: FileSystemProvider {
111120
private func findCustomFooter(_ bundleChildren: [FSNode]) -> FSNode.File? {
112121
return bundleChildren.firstFile { DocumentationBundleFileTypes.isCustomFooter($0.url) }
113122
}
123+
124+
private func findThemeSettings(_ bundleChildren: [FSNode]) -> FSNode.File? {
125+
return bundleChildren.firstFile { DocumentationBundleFileTypes.isThemeSettingsFile($0.url) }
126+
}
114127
}
115128

116129
fileprivate extension Array where Element == FSNode {

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,17 @@ struct ConvertFileWritingConsumer: ConvertOutputConsumer {
124124
if let customFooter = bundle.customFooter, enableCustomTemplates {
125125
try injectCustomTemplate(customFooter, identifiedBy: .footer)
126126
}
127+
128+
// Copy the `theme-settings.json` file into the output directory if one
129+
// is provided. It will override any default `theme-settings.json` file
130+
// that the renderer template may already contain.
131+
if let themeSettings = bundle.themeSettings {
132+
let targetFile = targetFolder.appendingPathComponent(themeSettings.lastPathComponent, isDirectory: false)
133+
if fileManager.fileExists(atPath: targetFile.path) {
134+
try fileManager.removeItem(at: targetFile)
135+
}
136+
try fileManager.copyItem(at: themeSettings, to: targetFile)
137+
}
127138
}
128139

129140
func consume(linkableElementSummaries summaries: [LinkDestinationSummary]) throws {

Tests/SwiftDocCTests/Infrastructure/BundleDiscoveryTests.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,9 @@ class BundleDiscoveryTests: XCTestCase {
294294
// Ensure that `customFooter` is `nil` if no top level `footer.html`
295295
// file was found in the bundle
296296
XCTAssertNil(bundle.customFooter)
297+
// Ensure that `themeSettings` is `nil` if no `theme-settings.json`
298+
// file was found in the bundle
299+
XCTAssertNil(bundle.themeSettings)
297300
}
298301

299302
func testCustomTemplatesFound() throws {
@@ -325,4 +328,36 @@ class BundleDiscoveryTests: XCTestCase {
325328
// `footer.html` file if one is found in the bundle
326329
XCTAssertEqual(bundle.customFooter?.lastPathComponent, "footer.html")
327330
}
331+
332+
func testThemeSettingsFound() throws {
333+
let workspace = Folder(name: "TestBundle.docc", content:
334+
allFiles.map { CopyOfFile(original: $0) } + [
335+
TextFile(name: "theme-settings.json", utf8Content: """
336+
{
337+
"meta": {},
338+
"theme": {
339+
"colors": {
340+
"text": "#ff0000"
341+
}
342+
},
343+
"features": {}
344+
}
345+
"""),
346+
]
347+
)
348+
349+
let tempURL = try createTemporaryDirectory()
350+
351+
let workspaceURL = try workspace.write(inside: tempURL)
352+
let dataProvider = try LocalFileSystemDataProvider(rootURL: workspaceURL)
353+
354+
let bundles = try dataProvider.bundles(options: BundleDiscoveryOptions())
355+
356+
XCTAssertEqual(bundles.count, 1)
357+
guard let bundle = bundles.first else { return }
358+
359+
// Ensure that `themeSettings` points to the location of a
360+
// `theme-settings.json` file if one is found in the bundle
361+
XCTAssertEqual(bundle.themeSettings?.lastPathComponent, "theme-settings.json")
362+
}
328363
}

Tests/SwiftDocCTests/Infrastructure/DocumentationBundleFileTypesTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,18 @@ class DocumentationBundleFileTypesTests: XCTestCase {
4141
XCTAssertTrue(DocumentationBundleFileTypes.isCustomFooter(
4242
URL(fileURLWithPath: "DocC.docc/footer.html")))
4343
}
44+
45+
func testIsThemeSettingsFile() {
46+
XCTAssertTrue(DocumentationBundleFileTypes.isThemeSettingsFile(
47+
URL(fileURLWithPath: "theme-settings.json")))
48+
XCTAssertTrue(DocumentationBundleFileTypes.isThemeSettingsFile(
49+
URL(fileURLWithPath: "/a/b/theme-settings.json")))
50+
51+
XCTAssertFalse(DocumentationBundleFileTypes.isThemeSettingsFile(
52+
URL(fileURLWithPath: "theme-settings.txt")))
53+
XCTAssertFalse(DocumentationBundleFileTypes.isThemeSettingsFile(
54+
URL(fileURLWithPath: "not-theme-settings.json")))
55+
XCTAssertFalse(DocumentationBundleFileTypes.isThemeSettingsFile(
56+
URL(fileURLWithPath: "/a/theme-settings.json/bar")))
57+
}
4458
}

Tests/SwiftDocCUtilitiesTests/ConvertActionTests.swift

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2498,6 +2498,64 @@ class ConvertActionTests: XCTestCase {
24982498
let expectedOutput = Folder(name: ".docc-build", content: [expectedIndex])
24992499
expectedOutput.assertExist(at: result.outputs[0], fileManager: FileManager.default)
25002500
}
2501+
2502+
func testConvertWithThemeSettings() throws {
2503+
let info = InfoPlist(displayName: "TestConvertWithThemeSettings", identifier: "com.test.example")
2504+
let index = TextFile(name: "index.html", utf8Content: """
2505+
<!DOCTYPE html>
2506+
<html lang="en">
2507+
<head>
2508+
<title>Test</title>
2509+
</head>
2510+
<body data-color-scheme="auto"><p>hello</p></body>
2511+
</html>
2512+
""")
2513+
let themeSettings = TextFile(name: "theme-settings.json", utf8Content: """
2514+
{
2515+
"meta": {},
2516+
"theme": {
2517+
"colors": {
2518+
"text": "#ff0000"
2519+
}
2520+
},
2521+
"features": {}
2522+
}
2523+
""")
2524+
let template = Folder(name: "template", content: [index])
2525+
let bundle = Folder(name: "TestConvertWithThemeSettings.docc", content: [
2526+
info,
2527+
themeSettings,
2528+
])
2529+
2530+
let tempURL = try createTemporaryDirectory()
2531+
let targetURL = tempURL.appendingPathComponent("target", isDirectory: true)
2532+
2533+
let bundleURL = try bundle.write(inside: tempURL)
2534+
let templateURL = try template.write(inside: tempURL)
2535+
2536+
let dataProvider = try LocalFileSystemDataProvider(rootURL: bundleURL)
2537+
2538+
var action = try ConvertAction(
2539+
documentationBundleURL: bundleURL,
2540+
outOfProcessResolver: nil,
2541+
analyze: false,
2542+
targetDirectory: targetURL,
2543+
htmlTemplateDirectory: templateURL,
2544+
emitDigest: false,
2545+
currentPlatforms: nil,
2546+
dataProvider: dataProvider,
2547+
fileManager: FileManager.default,
2548+
temporaryDirectory: createTemporaryDirectory(),
2549+
experimentalEnableCustomTemplates: true
2550+
)
2551+
let result = try action.perform(logHandle: .standardOutput)
2552+
2553+
let expectedOutput = Folder(name: ".docc-build", content: [
2554+
index,
2555+
themeSettings,
2556+
])
2557+
expectedOutput.assertExist(at: result.outputs[0], fileManager: FileManager.default)
2558+
}
25012559

25022560
private func uniformlyPrintDiagnosticMessages(_ problems: [Problem]) -> String {
25032561
return problems.sorted(by: { (lhs, rhs) -> Bool in

0 commit comments

Comments
 (0)