1
1
/*
2
2
This source file is part of the Swift.org open source project
3
3
4
- Copyright (c) 2024 Apple Inc. and the Swift project authors
4
+ Copyright (c) 2024-2025 Apple Inc. and the Swift project authors
5
5
Licensed under Apache License v2.0 with Runtime Library Exception
6
6
7
7
See https://swift.org/LICENSE.txt for license information
@@ -13,9 +13,17 @@ import SwiftDocC
13
13
14
14
extension MergeAction {
15
15
struct RootRenderReferences {
16
- var documentation , tutorials : [ TopicRenderReference ]
16
+ fileprivate struct Information {
17
+ var reference : TopicRenderReference
18
+ var dependencies : [ any RenderReference ]
19
+
20
+ var rawIdentifier : String {
21
+ reference. identifier. identifier
22
+ }
23
+ }
24
+ fileprivate var documentation , tutorials : [ Information ]
17
25
18
- fileprivate var all : [ TopicRenderReference ] {
26
+ fileprivate var all : [ Information ] {
19
27
documentation + tutorials
20
28
}
21
29
var isEmpty : Bool {
@@ -27,19 +35,20 @@ extension MergeAction {
27
35
}
28
36
29
37
func readRootNodeRenderReferencesIn( dataDirectory: URL ) throws -> RootRenderReferences {
30
- func inner( url: URL ) throws -> [ TopicRenderReference ] {
38
+ func inner( url: URL ) throws -> [ RootRenderReferences . Information ] {
31
39
try fileManager. contentsOfDirectory ( at: url, includingPropertiesForKeys: nil , options: [ ] )
32
40
. compactMap {
33
41
guard $0. pathExtension == " json " else {
34
42
return nil
35
43
}
36
44
37
45
let data = try fileManager. contents ( of: $0)
38
- return try JSONDecoder ( ) . decode ( RootNodeRenderReference . self, from: data)
39
- . renderReference
46
+ let decoded = try JSONDecoder ( ) . decode ( RootNodeRenderReference . self, from: data)
47
+
48
+ return . init( reference: decoded. renderReference, dependencies: decoded. renderDependencies)
40
49
}
41
50
. sorted ( by: { lhs, rhs in
42
- lhs. title < rhs. title
51
+ lhs. reference . title < rhs. reference . title
43
52
} )
44
53
}
45
54
@@ -72,21 +81,26 @@ extension MergeAction {
72
81
if rootRenderReferences. containsBothKinds {
73
82
// If the combined archive contains both documentation and tutorial content, create separate topic sections for each.
74
83
renderNode. topicSections = [
75
- . init( title: " Modules " , abstract: nil , discussion: nil , identifiers: rootRenderReferences. documentation. map ( \. identifier . identifier ) ) ,
76
- . init( title: " Tutorials " , abstract: nil , discussion: nil , identifiers: rootRenderReferences. tutorials. map ( \. identifier . identifier ) ) ,
84
+ . init( title: " Modules " , abstract: nil , discussion: nil , identifiers: rootRenderReferences. documentation. map ( \. rawIdentifier ) ) ,
85
+ . init( title: " Tutorials " , abstract: nil , discussion: nil , identifiers: rootRenderReferences. tutorials. map ( \. rawIdentifier ) ) ,
77
86
]
78
87
} else {
79
88
// Otherwise, create a single unnamed topic section
80
89
renderNode. topicSections = [
81
- . init( title: nil , abstract: nil , discussion: nil , identifiers: ( rootRenderReferences. all) . map ( \. identifier . identifier ) ) ,
90
+ . init( title: nil , abstract: nil , discussion: nil , identifiers: ( rootRenderReferences. all) . map ( \. rawIdentifier ) ) ,
82
91
]
83
92
}
84
93
85
94
for renderReference in rootRenderReferences. documentation {
86
- renderNode. references [ renderReference. identifier. identifier] = renderReference
95
+ renderNode. references [ renderReference. rawIdentifier] = renderReference. reference
96
+
97
+ for dependencyReference in renderReference. dependencies {
98
+ renderNode. references [ dependencyReference. identifier. identifier] = dependencyReference
99
+ }
87
100
}
88
101
for renderReference in rootRenderReferences. tutorials {
89
- renderNode. references [ renderReference. identifier. identifier] = renderReference
102
+ renderNode. references [ renderReference. rawIdentifier] = renderReference. reference
103
+ // Tutorial pages don't have page images.
90
104
}
91
105
92
106
return renderNode
@@ -97,6 +111,7 @@ extension MergeAction {
97
111
private struct RootNodeRenderReference : Decodable {
98
112
/// The decoded root node render reference
99
113
var renderReference : TopicRenderReference
114
+ var renderDependencies : [ any RenderReference ]
100
115
101
116
enum CodingKeys : CodingKey {
102
117
// The only render node keys that should be needed
@@ -107,7 +122,7 @@ private struct RootNodeRenderReference: Decodable {
107
122
108
123
struct StringCodingKey : CodingKey {
109
124
var stringValue : String
110
- init ? ( stringValue: String ) {
125
+ init ( stringValue: String ) {
111
126
self . stringValue = stringValue
112
127
}
113
128
var intValue : Int ? = nil
@@ -122,6 +137,7 @@ private struct RootNodeRenderReference: Decodable {
122
137
123
138
let identifier = try container. decode ( ResolvedTopicReference . self, forKey: . identifier)
124
139
let rawIdentifier = identifier. url. absoluteString
140
+ let imageReferencePrefix = identifier. bundleID. rawValue + " / "
125
141
126
142
// Every node should include a reference to the root page.
127
143
// For reference documentation, this is because the root appears as a link in the breadcrumbs on every page.
@@ -130,8 +146,21 @@ private struct RootNodeRenderReference: Decodable {
130
146
// If the root page has a reference to itself, then that the fastest and easiest way to access the correct topic render reference.
131
147
if container. contains ( . references) {
132
148
let referencesContainer = try container. nestedContainer ( keyedBy: StringCodingKey . self, forKey: . references)
133
- if let selfReference = try referencesContainer. decodeIfPresent ( TopicRenderReference . self, forKey: . init( stringValue: rawIdentifier) !) {
149
+ if var selfReference = try referencesContainer. decodeIfPresent ( TopicRenderReference . self, forKey: . init( stringValue: rawIdentifier) ) {
150
+ renderDependencies = try Self . decodeDependencyReferences (
151
+ container: referencesContainer,
152
+ images: selfReference. images,
153
+ imageReferencePrefix: imageReferencePrefix,
154
+ abstract: selfReference. abstract
155
+ )
156
+
157
+ // Image references don't include the bundle ID that they're part of and can collide with other images.
158
+ for index in selfReference. images. indices {
159
+ selfReference. images [ index] . identifier. addPrefix ( imageReferencePrefix)
160
+ }
161
+
134
162
renderReference = selfReference
163
+
135
164
return
136
165
}
137
166
}
@@ -140,13 +169,62 @@ private struct RootNodeRenderReference: Decodable {
140
169
// we can create a new topic reference by decoding a little bit more information from the render node.
141
170
let metadata = try container. decode ( RenderMetadata . self, forKey: . metadata)
142
171
172
+ var prefixedImages = metadata. images
173
+ // Image references don't include the bundle ID that they're part of and can collide with other images.
174
+ for index in prefixedImages. indices {
175
+ prefixedImages [ index] . identifier. addPrefix ( imageReferencePrefix)
176
+ }
177
+
143
178
renderReference = TopicRenderReference (
144
179
identifier: RenderReferenceIdentifier ( rawIdentifier) ,
145
180
title: metadata. title ?? identifier. lastPathComponent,
146
181
abstract: try container. decodeIfPresent ( [ RenderInlineContent ] . self, forKey: . abstract) ?? [ ] ,
147
182
url: identifier. path. lowercased ( ) ,
148
183
kind: try container. decode ( RenderNode . Kind. self, forKey: . kind) ,
149
- images: metadata . images
184
+ images: prefixedImages
150
185
)
186
+
187
+ if container. contains ( . references) {
188
+ renderDependencies = try Self . decodeDependencyReferences (
189
+ container: try container. nestedContainer ( keyedBy: StringCodingKey . self, forKey: . references) ,
190
+ images: metadata. images,
191
+ imageReferencePrefix: imageReferencePrefix,
192
+ abstract: renderReference. abstract
193
+ )
194
+
195
+ } else {
196
+ renderDependencies = [ ]
197
+ }
198
+ }
199
+
200
+ private static func decodeDependencyReferences(
201
+ container: KeyedDecodingContainer < RootNodeRenderReference . StringCodingKey > ,
202
+ images: [ TopicImage ] ,
203
+ imageReferencePrefix: String ,
204
+ abstract: [ RenderInlineContent ]
205
+ ) throws -> [ any RenderReference ] {
206
+ var references : [ any RenderReference ] = [ ]
207
+
208
+ for image in images {
209
+ // Image references don't include the bundle ID that they're part of and can collide with other images.
210
+ var imageRef = try container. decode ( ImageReference . self, forKey: . init( stringValue: image. identifier. identifier) )
211
+ imageRef. identifier. addPrefix ( imageReferencePrefix)
212
+
213
+ references. append ( imageRef)
214
+ }
215
+
216
+ for case . reference( identifier: let identifier, isActive: _, overridingTitle: _, overridingTitleInlineContent: _) in abstract {
217
+ references. append (
218
+ try container. decode ( TopicRenderReference . self, forKey: . init( stringValue: identifier. identifier) )
219
+ )
220
+ }
221
+
222
+ return references
223
+ }
224
+ }
225
+
226
+ private extension RenderReferenceIdentifier {
227
+ mutating func addPrefix( _ prefix: String ) {
228
+ identifier = prefix + identifier
151
229
}
152
230
}
0 commit comments