Skip to content

Commit e9ddb11

Browse files
Add custom favicon (#1400)
1 parent d842fdd commit e9ddb11

File tree

7 files changed

+49
-3
lines changed

7 files changed

+49
-3
lines changed

Sources/SwiftDocC/Infrastructure/DocumentationBundle.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ public struct DocumentationBundle {
9191

9292
/// A custom JSON settings file used to theme renderer output.
9393
public let themeSettings: URL?
94+
95+
/// A custom favicon file to use for rendered output.
96+
public let customFavicon: URL?
97+
9498
/// A URL prefix to be appended to the relative presentation URL.
9599
///
96100
/// This is used when a built documentation is hosted in a known location.
@@ -107,6 +111,7 @@ public struct DocumentationBundle {
107111
/// - customHeader: A custom HTML file to use as the header for rendered output.
108112
/// - customFooter: A custom HTML file to use as the footer for rendered output.
109113
/// - themeSettings: A custom JSON settings file used to theme renderer output.
114+
/// - customFavicon: A custom favicon file to use for rendered output.
110115
public init(
111116
info: Info,
112117
baseURL: URL = URL(string: "/")!,
@@ -115,7 +120,8 @@ public struct DocumentationBundle {
115120
miscResourceURLs: [URL],
116121
customHeader: URL? = nil,
117122
customFooter: URL? = nil,
118-
themeSettings: URL? = nil
123+
themeSettings: URL? = nil,
124+
customFavicon: URL? = nil
119125
) {
120126
self.info = info
121127
self.baseURL = baseURL
@@ -125,6 +131,7 @@ public struct DocumentationBundle {
125131
self.customHeader = customHeader
126132
self.customFooter = customFooter
127133
self.themeSettings = themeSettings
134+
self.customFavicon = customFavicon
128135
self.rootReference = ResolvedTopicReference(bundleID: info.id, path: "/", sourceLanguage: .swift)
129136
self.documentationRootReference = ResolvedTopicReference(bundleID: info.id, path: NodeURLGenerator.Path.documentationFolder, sourceLanguage: .swift)
130137
self.tutorialTableOfContentsContainer = ResolvedTopicReference(bundleID: info.id, 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
@@ -84,4 +84,12 @@ public enum DocumentationBundleFileTypes {
8484
public static func isThemeSettingsFile(_ url: URL) -> Bool {
8585
return url.lastPathComponent == themeSettingsFileName
8686
}
87+
88+
private static let customFaviconFileName = "favicon.ico"
89+
/// Checks if a file is a custom favicon.
90+
/// - Parameter url: The file to check.
91+
/// - Returns: Whether or not the file at `url` is a custom favicon.
92+
public static func isCustomFavicon(_ url: URL) -> Bool {
93+
return url.lastPathComponent.lowercased() == customFaviconFileName
94+
}
8795
}

Sources/SwiftDocC/Infrastructure/Input Discovery/DocumentationInputsProvider.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ extension DocumentationContext {
2727
/// ``DocumentationBundle/themeSettings`` | ``DocumentationBundleFileTypes/isThemeSettingsFile(_:)``
2828
/// ``DocumentationBundle/customHeader`` | ``DocumentationBundleFileTypes/isCustomHeader(_:)``
2929
/// ``DocumentationBundle/customFooter`` | ``DocumentationBundleFileTypes/isCustomFooter(_:)``
30+
/// ``DocumentationBundle/customFavicon`` | ``DocumentationBundleFileTypes/isCustomFavicon(_:)``
3031
/// ``DocumentationBundle/miscResourceURLs`` | Any file not already matched above.
3132
///
3233
/// ## Topics
@@ -165,7 +166,8 @@ extension DocumentationContext.InputsProvider {
165166
miscResourceURLs: foundContents.resources,
166167
customHeader: shallowContent.first(where: FileTypes.isCustomHeader),
167168
customFooter: shallowContent.first(where: FileTypes.isCustomFooter),
168-
themeSettings: shallowContent.first(where: FileTypes.isThemeSettingsFile)
169+
themeSettings: shallowContent.first(where: FileTypes.isThemeSettingsFile),
170+
customFavicon: shallowContent.first(where: FileTypes.isCustomFavicon)
169171
)
170172
}
171173

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,16 @@ struct ConvertFileWritingConsumer: ConvertOutputConsumer, ExternalNodeConsumer {
150150
}
151151
try fileManager._copyItem(at: themeSettings, to: targetFile)
152152
}
153+
154+
// If a custom favicon is provided, it will be copied in the output directory
155+
// The custom favicon will override the default one.
156+
if let customFavicon = bundle.customFavicon {
157+
let targetFile = targetFolder.appendingPathComponent(customFavicon.lastPathComponent, isDirectory: false)
158+
if fileManager.fileExists(atPath: targetFile.path) {
159+
try fileManager.removeItem(at: targetFile)
160+
}
161+
try fileManager._copyItem(at: customFavicon, to: targetFile)
162+
}
153163
}
154164

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

Sources/docc/DocCDocumentation.docc/customizing-the-appearance-of-your-documentation-pages.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ follow in order to override an icon:
166166
> You can find all the possible identifier values for every valid icon kind in
167167
> the Open API [specification][1] for `theme-settings.json`.
168168
169+
You can also specify a custom favicon for your documentation website by adding
170+
an optional `favicon.ico` file to the root of your documentation catalog.
171+
169172
**Example**
170173

171174
As a simple example, here is how you could update the icon used for articles in

Tests/SwiftDocCTests/Infrastructure/DocumentationBundleFileTypesTests.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,17 @@ class DocumentationBundleFileTypesTests: XCTestCase {
5555
XCTAssertFalse(DocumentationBundleFileTypes.isThemeSettingsFile(
5656
URL(fileURLWithPath: "/a/theme-settings.json/bar")))
5757
}
58+
59+
func testIsCustomFavicon() {
60+
XCTAssertTrue(DocumentationBundleFileTypes.isCustomFavicon(
61+
URL(fileURLWithPath: "favicon.ico")))
62+
XCTAssertTrue(DocumentationBundleFileTypes.isCustomFavicon(
63+
URL(fileURLWithPath: "/favicon.ico")))
64+
XCTAssertTrue(DocumentationBundleFileTypes.isCustomFavicon(
65+
URL(fileURLWithPath: "DocC.docc/favicon.ico")))
66+
XCTAssertFalse(DocumentationBundleFileTypes.isCustomFavicon(
67+
URL(fileURLWithPath: "favicon")))
68+
XCTAssertFalse(DocumentationBundleFileTypes.isCustomFavicon(
69+
URL(fileURLWithPath: "/favicon.ico/foo")))
70+
}
5871
}

Tests/SwiftDocCTests/Infrastructure/Input Discovery/DocumentationInputsProviderTests.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ class DocumentationInputsProviderTests: XCTestCase {
2828
// This top-level Info.plist will be read for bundle information
2929
InfoPlist(displayName: "CustomDisplayName"),
3030

31-
// These top-level files will be treated as a custom footer and a custom theme
31+
// These top-level files will be treated as a custom footer, custom theme, and custom favicon
3232
TextFile(name: "footer.html", utf8Content: ""),
3333
TextFile(name: "theme-settings.json", utf8Content: ""),
34+
DataFile(name: "favicon.ico", data: Data()),
3435

3536
// Top-level content will be found
3637
TextFile(name: "CCC.md", utf8Content: ""),
@@ -95,6 +96,7 @@ class DocumentationInputsProviderTests: XCTestCase {
9596
"Found.docc/Inner/Info.plist",
9697
"Found.docc/Inner/header.html",
9798
"Found.docc/Inner/second.png",
99+
"Found.docc/favicon.ico",
98100
"Found.docc/first.png",
99101
"Found.docc/footer.html",
100102
"Found.docc/theme-settings.json",
@@ -107,6 +109,7 @@ class DocumentationInputsProviderTests: XCTestCase {
107109
XCTAssertEqual(bundle.customFooter.map(relativePathString), "Found.docc/footer.html")
108110
XCTAssertEqual(bundle.customHeader.map(relativePathString), nil)
109111
XCTAssertEqual(bundle.themeSettings.map(relativePathString), "Found.docc/theme-settings.json")
112+
XCTAssertEqual(bundle.customFavicon.map(relativePathString), "Found.docc/favicon.ico")
110113
}
111114
}
112115

0 commit comments

Comments
 (0)