Skip to content

Commit 590f6d5

Browse files
d-ronnqvistheckjpatshaughnessy
authored
[6.3] Integrate per-page HTML content into each "index.html" file" (#1402)
* Add a new target for rendering Markdown content into static HTML (#1369) * Add a new target for rendering content into static HTML rdar://163326857 * Include documentation comments with examples for the various `visit(_:)` methods. * Document what a `LinkedAsset` represents * Add code comments to explain how the inline HTML markup is parsed * Don't lowercase anchors for self-referencing headings * Avoid nested scopes when switching over names * Add a code comment about what an "autolink" is * Add a helper function for rendering availability information as HTML (#1376) rdar://163326857 * Add a helper function for rendering page breadcrumbs as HTML (#1378) * Add a helper function for rendering page breadcrumbs as HTML rdar://163326857 * Apply wording suggestion in documentation comment Co-authored-by: Joseph Heck <j_heck@apple.com> --------- Co-authored-by: Joseph Heck <j_heck@apple.com> * Add a helper function for rendering a parameters section as HTML (#1377) * Add a helper function for rendering a parameters section as HTML rdar://163326857 * Apply wording suggestions in documentation comments Co-authored-by: Pat Shaughnessy <pat_shaughnessy@apple.com> Co-authored-by: Joseph Heck <j_heck@apple.com> * Add TODO comment about a debug assertion for the parameter name order --------- Co-authored-by: Pat Shaughnessy <pat_shaughnessy@apple.com> Co-authored-by: Joseph Heck <j_heck@apple.com> * Add tests for parsing of inline HTML when rendering markdown as HTML (#1379) * Add test about parsing inline HTML except for comments * Extract inner HTML parsing code into a private function * Fix minor spelling in code comment Co-authored-by: Pat Shaughnessy <pat_shaughnessy@apple.com> --------- Co-authored-by: Pat Shaughnessy <pat_shaughnessy@apple.com> * Add a helper function for rendering a returns sections as HTML (#1381) * Add a helper function for rendering a returns sections as HTML rdar://163326857 * Minor phrasing updates to the parameter section helper's documentation * Add new source file to Windows CMake file list * Correct grammar in documentation comments Co-authored-by: Pat Shaughnessy <pat_shaughnessy@apple.com> --------- Co-authored-by: Pat Shaughnessy <pat_shaughnessy@apple.com> * Add a helper function for rendering symbol declarations as HTML (#1384) * Add a helper function for rendering symbol declarations as HTML rdar://163326857 * Add code comments to describe why the concise declaration: - Only includes the primary language - Joins the declaration fragments into a single string * Add a helper function for rendering a Topics/SeeAlso section as HTML (#1382) * Add a helper function for rendering a Topics/SeeAlso section as HTML rdar://163326857 * Add more code comments and internal documentation comments * Very slightly correct the test data * Minimally integrate per-page HTML content into each "index.html" file (#1383) * Minimally integrate per-page HTML content into each "index.html" file rdar://163326857 * Extract some common code into private helpers * Move a few common checks into the new private helpers * Add a helper function for rendering a discussion section as HTML (#1388) * Add a helper function for rendering a discussion section as HTML rdar://163326857 * Integrate the discussion section in the HTML renderer * Include Mentioned In and Relationships sections in the HTML output (#1391) * Include more content in the HTML output (#1390) * Include more page content in the HTML output * Also add deprecation summaries to the HTML output --------- Co-authored-by: Joseph Heck <j_heck@apple.com> Co-authored-by: Pat Shaughnessy <pat_shaughnessy@apple.com>
1 parent b0626e0 commit 590f6d5

32 files changed

+4859
-40
lines changed

Package.swift

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

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

88
See https://swift.org/LICENSE.txt for license information
@@ -44,6 +44,7 @@ let package = Package(
4444
name: "SwiftDocC",
4545
dependencies: [
4646
.target(name: "DocCCommon"),
47+
.target(name: "DocCHTML"),
4748
.product(name: "Markdown", package: "swift-markdown"),
4849
.product(name: "SymbolKit", package: "swift-docc-symbolkit"),
4950
.product(name: "CLMDB", package: "swift-lmdb"),
@@ -122,6 +123,7 @@ let package = Package(
122123
// This target shouldn't have any local dependencies so that all other targets can depend on it.
123124
// We can add dependencies on SymbolKit and Markdown here but they're not needed yet.
124125
],
126+
exclude: ["CMakeLists.txt"],
125127
swiftSettings: [.swiftLanguageMode(.v6)]
126128
),
127129

@@ -134,6 +136,27 @@ let package = Package(
134136
swiftSettings: [.swiftLanguageMode(.v6)]
135137
),
136138

139+
.target(
140+
name: "DocCHTML",
141+
dependencies: [
142+
.target(name: "DocCCommon"),
143+
.product(name: "Markdown", package: "swift-markdown"),
144+
.product(name: "SymbolKit", package: "swift-docc-symbolkit"),
145+
],
146+
exclude: ["CMakeLists.txt"],
147+
swiftSettings: [.swiftLanguageMode(.v6)]
148+
),
149+
.testTarget(
150+
name: "DocCHTMLTests",
151+
dependencies: [
152+
.target(name: "DocCHTML"),
153+
.target(name: "SwiftDocC"),
154+
.product(name: "Markdown", package: "swift-markdown"),
155+
.target(name: "SwiftDocCTestUtilities"),
156+
],
157+
swiftSettings: [.swiftLanguageMode(.v6)]
158+
),
159+
137160
// Test app for SwiftDocCUtilities
138161
.executableTarget(
139162
name: "signal-test-app",

Sources/DocCHTML/CMakeLists.txt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#[[
2+
This source file is part of the Swift open source project
3+
4+
Copyright © 2014 - 2025 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
#]]
9+
10+
add_library(DocCHTML STATIC
11+
LinkProvider.swift
12+
MarkdownRenderer+Availability.swift
13+
MarkdownRenderer+Breadcrumbs.swift
14+
MarkdownRenderer+Declaration.swift
15+
MarkdownRenderer+Discussion.swift
16+
MarkdownRenderer+Parameters.swift
17+
MarkdownRenderer+Relationships.swift
18+
MarkdownRenderer+Returns.swift
19+
MarkdownRenderer+Topics.swift
20+
MarkdownRenderer.swift
21+
WordBreak.swift
22+
XMLNode+element.swift)
23+
target_link_libraries(DocCHTML PRIVATE
24+
DocCCommon)
25+
target_link_libraries(DocCHTML PUBLIC
26+
SwiftMarkdown::Markdown
27+
DocC::SymbolKit)
28+
# FIXME(compnerd) workaround leaking dependencies
29+
target_link_libraries(DocCHTML PUBLIC
30+
libcmark-gfm
31+
libcmark-gfm-extensions)
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2025 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
package import Foundation
12+
package import Markdown
13+
package import DocCCommon
14+
15+
/// A type that provides information about other pages, and on-page elements, that the rendered page references.
16+
package protocol LinkProvider {
17+
/// Provide information about another page or on-page element, or `nil` if the other page can't be found.
18+
func element(for path: URL) -> LinkedElement?
19+
20+
/// Provide the path for a symbol based on its unique identifier, or `nil` if the other symbol with that identifier can't be found.
21+
func pathForSymbolID(_ usr: String) -> URL?
22+
23+
/// Provide information about an asset (for example an image or video), or `nil` if the asset can't be found.
24+
func assetNamed(_ assetName: String) -> LinkedAsset?
25+
26+
/// Fallback link text for a link string that the provider couldn't provide any information for.
27+
func fallbackLinkText(linkString: String) -> String
28+
}
29+
30+
package struct LinkedElement {
31+
/// The path within the output archive to the linked element.
32+
package var path: URL
33+
/// The names of the linked element, for display when the element is referenced in inline content.
34+
///
35+
/// Articles, headings, tutorials, and similar pages have a ``Names/single/conceptual(_:)`` name.
36+
/// Symbols can either have a ``Names/single/symbol(_:)`` name or have different names for each language representation (``Names/languageSpecificSymbol``).
37+
package var names: Names
38+
/// The subheadings of the linked element, for display when the element is referenced in either a Topics section, See Also section, or in a `@Links` directive.
39+
///
40+
/// Articles, headings, tutorials, and similar pages have a ``Names/single/conceptual(_:)`` name.
41+
/// Symbols can either have a ``Names/single/symbol(_:)`` name or have different names for each language representation (``Names/languageSpecificSymbol``).
42+
package var subheadings: Subheadings
43+
/// The abstract of the page—to be displayed in either a Topics section, See Also section, or in a `@Links` directive—or `nil` if the linked element doesn't have an abstract.
44+
package var abstract: Paragraph?
45+
46+
package init(path: URL, names: Names, subheadings: Subheadings, abstract: Paragraph?) {
47+
self.path = path
48+
self.names = names
49+
self.subheadings = subheadings
50+
self.abstract = abstract
51+
}
52+
53+
/// The single name or language-specific names to use when referring to a linked element in inline content.
54+
package enum Names {
55+
/// This element has the same name in all language representations
56+
case single(Name)
57+
/// This element is a symbol with different names in different languages.
58+
///
59+
/// Because `@DisplayName` applies to all language representations, these language specific names are always the symbol's subheading declaration and should display in a monospaced font.
60+
case languageSpecificSymbol([SourceLanguage: String])
61+
}
62+
package enum Name {
63+
/// The name refers to an article, heading, or custom `@DisplayName` and should display as regular text.
64+
case conceptual(String)
65+
/// The name refers to a symbol's subheading declaration and should display in a monospaced font.
66+
case symbol(String)
67+
}
68+
69+
/// The single subheading or language-specific subheadings to use when referring to a linked element in either a Topics section, See Also section, or in a `@Links` directive.
70+
package enum Subheadings {
71+
/// This element has the same name in all language representations
72+
case single(Subheading)
73+
/// This element is a symbol with different names in different languages.
74+
///
75+
/// Because `@DisplayName` applies to all language representations, these language specific names are always the symbol's subheading declaration and should display in a monospaced font.
76+
case languageSpecificSymbol([SourceLanguage: [SymbolNameFragment]])
77+
}
78+
package enum Subheading {
79+
/// The name refers to an article, heading, or custom `@DisplayName` and should display as regular text.
80+
case conceptual(String)
81+
/// The name refers to a symbol's subheading declaration and should display in a monospaced font.
82+
case symbol([SymbolNameFragment])
83+
}
84+
85+
/// A fragment in a symbol's name
86+
package struct SymbolNameFragment {
87+
/// The textual spelling of this fragment
88+
package var text: String
89+
/// The kind of fragment
90+
package var kind: Kind
91+
92+
/// The display kind of a single symbol name fragment
93+
package enum Kind: String {
94+
case identifier, decorator
95+
}
96+
97+
package init(text: String, kind: Kind) {
98+
self.text = text
99+
self.kind = kind
100+
}
101+
}
102+
}
103+
104+
/// Information about a referenced image, video, or download asset that may be represented by more than one file for different color styles and display scales.
105+
package struct LinkedAsset {
106+
/// The path within the output archive to each file for this asset, grouped by their light/dark style and display scale.
107+
package var files: [ColorStyle: [Int /* display scale*/: URL]]
108+
109+
package init(files: [ColorStyle : [Int /* display scale*/: URL]]) {
110+
self.files = files
111+
}
112+
113+
package enum ColorStyle: String {
114+
case light, dark
115+
}
116+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2025 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
#if canImport(FoundationXML)
12+
// TODO: Consider other HTML rendering options as a future improvement (rdar://165755530)
13+
package import FoundationXML
14+
#else
15+
package import Foundation
16+
#endif
17+
18+
package extension MarkdownRenderer {
19+
/// Information about the versions that a piece of API is available for a given platform.
20+
struct AvailabilityInfo {
21+
/// The name of the platform that this information applies to.
22+
package var name: String
23+
/// The pre-formatted version string that describes the version that this API was introduced in for this platform.
24+
package var introduced: String?
25+
/// The pre-formatted version string that describes the version that this API was deprecated in for this platform.
26+
package var deprecated: String?
27+
/// A Boolean value indicating if the platform is currently in beta.
28+
package var isBeta: Bool
29+
30+
package init(name: String, introduced: String? = nil, deprecated: String? = nil, isBeta: Bool) {
31+
self.name = name
32+
self.introduced = introduced
33+
self.deprecated = deprecated
34+
self.isBeta = isBeta
35+
}
36+
}
37+
38+
/// Creates an HTML element that describes the versions that a piece of API is available for the platforms described in the given availability information.
39+
func availability(_ info: [AvailabilityInfo]) -> XMLNode {
40+
let items: [XMLNode] = info.map {
41+
var text = $0.name
42+
43+
let description: String
44+
if let introduced = $0.introduced {
45+
if let deprecated = $0.deprecated{
46+
text += " \(introduced)\(deprecated)"
47+
description = "Introduced in \($0.name) \(introduced) and deprecated in \($0.name) \(deprecated)"
48+
} else {
49+
text += " \(introduced)+"
50+
description = "Available on \(introduced) and later"
51+
}
52+
} else {
53+
description = "Available on \($0.name)"
54+
}
55+
56+
var attributes = [
57+
"role": "text",
58+
"aria-label": "\(text), \(description)",
59+
"title": description
60+
]
61+
if $0.isBeta {
62+
attributes["class"] = "beta"
63+
} else if $0.deprecated != nil {
64+
attributes["class"] = "deprecated"
65+
}
66+
67+
return .element(named: "li", children: [.text(text)], attributes: goal == .richness ? attributes : [:])
68+
}
69+
70+
return .element(
71+
named: "ul",
72+
children: items,
73+
attributes: ["id": "availability"]
74+
)
75+
}
76+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2025 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
#if canImport(FoundationXML)
12+
// TODO: Consider other HTML rendering options as a future improvement (rdar://165755530)
13+
package import FoundationXML
14+
package import FoundationEssentials
15+
#else
16+
package import Foundation
17+
#endif
18+
19+
package extension MarkdownRenderer {
20+
/// Creates an HTML element for the breadcrumbs that lead to the renderer's current page.
21+
func breadcrumbs(references: [URL], currentPageNames: LinkedElement.Names) -> XMLNode {
22+
// Breadcrumbs handle symbols differently than most elements in that everything uses a default style (no "code voice")
23+
func nameElements(for names: LinkedElement.Names) -> [XMLNode] {
24+
switch names {
25+
case .single(.conceptual(let name)), .single(.symbol(let name)):
26+
return [.text(name)]
27+
28+
case .languageSpecificSymbol(let namesByLanguageID):
29+
let names = RenderHelpers.sortedLanguageSpecificValues(namesByLanguageID)
30+
return switch goal {
31+
case .richness:
32+
if names.count == 1 {
33+
[.text(names.first!.value)]
34+
} else {
35+
names.map { language, name in
36+
// Wrap the name in a span so that it can be given a language specific "class" attribute.
37+
.element(named: "span", children: [.text(name)], attributes: ["class": "\(language.id)-only"])
38+
}
39+
}
40+
case .conciseness:
41+
// If the goal is conciseness, only display the primary language's name
42+
names.first.map { _, name in [.text(name)] } ?? []
43+
}
44+
}
45+
}
46+
47+
// Create links for each of the breadcrumbs
48+
var items: [XMLNode] = references.compactMap {
49+
linkProvider.element(for: $0).map { page in
50+
.element(named: "li", children: [
51+
.element(named: "a", children: nameElements(for: page.names), attributes: ["href": self.path(to: page.path)])
52+
])
53+
}
54+
}
55+
56+
// Add the name of the current page. It doesn't display as a link because it would refer to the current page.
57+
items.append(
58+
.element(named: "li", children: nameElements(for: currentPageNames))
59+
)
60+
let list = XMLNode.element(named: "ul", children: items)
61+
62+
return switch goal {
63+
case .conciseness: list // If the goal is conciseness, don't wrap the list in a `<nav>` HTML element with an "id".
64+
case .richness: .element(named: "nav", children: [list], attributes: ["id": "breadcrumbs"])
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)