File: RenderHierarchyTranslator.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (213 lines) | stat: -rw-r--r-- 11,970 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/*
 This source file is part of the Swift.org open source project

 Copyright (c) 2021 Apple Inc. and the Swift project authors
 Licensed under Apache License v2.0 with Runtime Library Exception

 See https://swift.org/LICENSE.txt for license information
 See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/

import Foundation

/// A hierarchy translator that converts a part of the topic graph into a hierarchy tree.
struct RenderHierarchyTranslator {
    var context: DocumentationContext
    var bundle: DocumentationBundle
    
    var collectedTopicReferences = Set<ResolvedTopicReference>()
    var linkReferences = [String: LinkReference]()
    
    /// Creates a new translator for the given bundle in the given context.
    /// - Parameters:
    ///   - context: The documentation context for the conversion.
    ///   - bundle: The documentation bundle for the conversion.
    init(context: DocumentationContext, bundle: DocumentationBundle) {
        self.context = context
        self.bundle = bundle
    }
    
    static let assessmentsAnchor = urlReadableFragment(TutorialAssessmentsRenderSection.title)
    let urlGenerator = NodeURLGenerator()
    
    /// Returns a complete hierarchy, starting at the given tutorials landing page and describing all
    /// contained volumes, chapters, and tutorials.
    /// - Parameters:
    ///   - reference: A reference to a tutorials-related topic.
    ///   - omittingChapters: If `true`, don't include chapters in the returned hierarchy.
    /// - Returns: A tuple of 1) a tutorials hierarchy and 2) the root reference of the tutorials hierarchy.
    mutating func visitTechnologyNode(_ reference: ResolvedTopicReference, omittingChapters: Bool = false) -> (hierarchy: RenderHierarchy, technology: ResolvedTopicReference)? {
        let paths = context.finitePaths(to: reference, options: [.preferTechnologyRoot])
        
        // If the node is a technology return immediately without generating breadcrumbs
        if let _ = (try? context.entity(with: reference))?.semantic as? Technology {
            let hierarchy = visitTechnology(reference, omittingChapters: omittingChapters)
            return (hierarchy: .tutorials(hierarchy), technology: reference)
        }
        
        guard let technologyPath = paths.mapFirst(where: { path -> [ResolvedTopicReference]? in
            guard let rootReference = path.first,
                let _ = try! context.entity(with: rootReference).semantic as? Technology else { return nil }
            return path
        }) else {
            // If there are no tutorials, return `nil`. We've already warned about uncurated tutorials.
            return nil
        }
        
        let technologyReference = technologyPath[0]
        var hierarchy = visitTechnology(technologyReference, omittingChapters: omittingChapters)
        
        hierarchy.paths = paths
            // Position the technology path as the canonical path for the node
            // in case it's curated multiple times under documentation symbols too.
            .sorted(by: { (lhs, rhs) -> Bool in
                return lhs == technologyPath
            })
            .map { $0.map { $0.absoluteString } }
        
        return (hierarchy: .tutorials(hierarchy), technology: technologyReference)
    }
    
    /// Returns the hierarchy under a given tutorials landing page.
    /// - Parameter technologyReference: The reference to the tutorials landing page.
    /// - Parameter omittingChapters: If `true`, don't include chapters in the returned hierarchy.
    /// - Returns: The hierarchy under the given landing page.
    mutating func visitTechnology(_ technologyReference: ResolvedTopicReference, omittingChapters: Bool = false) -> RenderTutorialsHierarchy {
        let technologyPath = urlGenerator.urlForReference(technologyReference, lowercased: true).path
        collectedTopicReferences.insert(technologyReference)
        // A technology is a root node in the bundle so passing empty breadcrumb paths
        var renderHierarchy = RenderTutorialsHierarchy(reference: RenderReferenceIdentifier(technologyReference.absoluteString), paths: [])

        if !omittingChapters {
            let children = context.children(of: technologyReference, kind: .volume)

            let renderChapters = children.compactMap { child in
                return visitVolume(child.reference, pathBreadcrumb: technologyPath)
            }.flatMap { $0 }

            renderHierarchy.modules = renderChapters
        }

        return renderHierarchy
    }
    
    /// Returns the hierarchy under a given tutorial series volume.
    /// - Parameter volumeReference: The reference to the volume.
    /// - Parameter pathBreadcrumb: The current path breadcrumb.
    /// - Returns: A list of hierarchy chapters contained in the volume, if any.
    mutating func visitVolume(_ volumeReference: ResolvedTopicReference, pathBreadcrumb: String) -> [RenderHierarchyChapter]? {
        let children = context.children(of: volumeReference, kind: .chapter)
        return children.compactMap { visitChapter($0.reference, pathBreadcrumb: pathBreadcrumb) }
    }
    
    /// Returns the hierarchy under a given chapter.
    /// - Parameter chapterReference: The reference to the chapter.
    /// - Parameter pathBreadcrumb: The current path breadcrumb.
    /// - Returns: The hierarchy under the given chapter.
    mutating func visitChapter(_ chapterReference: ResolvedTopicReference, pathBreadcrumb: String) -> RenderHierarchyChapter? {
        var renderHierarchyChapter = RenderHierarchyChapter(identifier: RenderReferenceIdentifier(chapterReference.absoluteString))
        collectedTopicReferences.insert(chapterReference)
        
        let children = context.children(of: chapterReference)
        
        renderHierarchyChapter.tutorials = children.compactMap { child in
            switch child.kind {
            case .tutorial:
                return visitTutorial(child.reference, pathBreadcrumb: pathBreadcrumb)
            case .tutorialArticle:
                return visitTutorialArticle(child.reference, pathBreadcrumb: pathBreadcrumb)
            default:
                fatalError("Unexpected child '\(child)' of chapter '\(chapterReference)', only tutorials and articles are expected.")
            }
            
        }
        return renderHierarchyChapter
    }
    
    /// Returns the hierarchy under a given tutorial article.
    /// - Parameter articleReference: The reference to the tutorial article.
    /// - Parameter pathBreadcrumb: The current path breadcrumb.
    /// - Returns: The hierarchy under the given tutorial article.
    mutating func visitTutorialArticle(_ articleReference: ResolvedTopicReference, pathBreadcrumb: String) -> RenderHierarchyTutorial? {
        let pathBreadcrumb = urlGenerator.urlForReference(articleReference, lowercased: true).path
        var renderHierarchyTutorial = RenderHierarchyTutorial(identifier: RenderReferenceIdentifier(articleReference.absoluteString))
        collectedTopicReferences.insert(articleReference)
        
        let children = context.children(of: articleReference, kind: .onPageLandmark)
    
        renderHierarchyTutorial.landmarks += children.compactMap { visitLandmark($0.reference, pathBreadcrumb: pathBreadcrumb) }
        
        return renderHierarchyTutorial
    }
    
    /// Returns the hierarchy under a given landmark.
    /// - Parameter landmarkReference: The reference to the landmark.
    /// - Parameter pathBreadcrumb: The current path breadcrumb.
    /// - Returns: The hierarchy under the given landmark.
    mutating func visitLandmark(_ landmarkReference: ResolvedTopicReference, pathBreadcrumb: String) -> RenderHierarchyLandmark {
        collectedTopicReferences.insert(landmarkReference)
        return RenderHierarchyLandmark(reference: RenderReferenceIdentifier(landmarkReference.absoluteString), kind: .heading)
    }
    
    /// Returns the hierarchy under a given tutorial.
    /// - Parameter tutorialReference: The reference to the tutorial.
    /// - Parameter pathBreadcrumb: The current path breadcrumb.
    /// - Returns: The hierarchy under the given tutorial.
    mutating func visitTutorial(_ tutorialReference: ResolvedTopicReference, pathBreadcrumb: String) -> RenderHierarchyTutorial? {
        let pathBreadcrumb = urlGenerator.urlForReference(tutorialReference, lowercased: true).path
        var renderHierarchyTutorial = RenderHierarchyTutorial(identifier: RenderReferenceIdentifier(tutorialReference.absoluteString))
        collectedTopicReferences.insert(tutorialReference)
        
        let children = context.children(of: tutorialReference, kind: .onPageLandmark)
        
        renderHierarchyTutorial.landmarks += children.compactMap { visitTutorialSection($0.reference, pathBreadcrumb: pathBreadcrumb) }
        
        if let tutorial = (try? context.entity(with: tutorialReference).semantic) as? Tutorial, let assessments = tutorial.assessments, !assessments.questions.isEmpty {
            // Add hardcoded assessment section.
            let assessmentReference = ResolvedTopicReference(bundleIdentifier: tutorialReference.bundleIdentifier, path: tutorialReference.path, fragment: RenderHierarchyTranslator.assessmentsAnchor, sourceLanguage: .swift)
            renderHierarchyTutorial.landmarks.append(RenderHierarchyLandmark(reference: RenderReferenceIdentifier(assessmentReference.absoluteString), kind: .assessment))
            
            let urlGenerator = PresentationURLGenerator(context: context, baseURL: bundle.baseURL)
            let assessmentLinkReference = LinkReference(
                identifier: RenderReferenceIdentifier(assessmentReference.absoluteString),
                title: "Check Your Understanding",
                url: urlGenerator.presentationURLForReference(assessmentReference).relativeString
            )
            linkReferences[assessmentReference.absoluteString] = assessmentLinkReference
        }

        return renderHierarchyTutorial
    }

    /// Returns the hierarchy under a given tutorial section.
    /// - Parameter tutorialSectionReference: The reference to the tutorial section.
    /// - Parameter pathBreadcrumb: The current path breadcrumb.
    /// - Returns: The hierarchy under the given tutorial section.
    mutating func visitTutorialSection(_ tutorialSectionReference: ResolvedTopicReference, pathBreadcrumb: String) -> RenderHierarchyLandmark {
        collectedTopicReferences.insert(tutorialSectionReference)
        return RenderHierarchyLandmark(reference: RenderReferenceIdentifier(tutorialSectionReference.absoluteString), kind: .task)
    }

    /// Returns the hierarchy under a given API symbol.
    /// - Parameter symbolReference: The reference to the API symbol.
    /// - Returns: The framework hierarchy that describes all paths where the symbol is curated.
    ///
    /// The documentation model is a graph (and not a tree) so you can curate API symbols
    /// multiple times under other API symbols, articles, or API collections. This method
    /// returns all the paths (breadcrumbs) between the framework landing page and the given symbol.
    mutating func visitSymbol(_ symbolReference: ResolvedTopicReference) -> RenderHierarchy {
        let pathReferences = context.finitePaths(to: symbolReference)
        pathReferences.forEach({
            collectedTopicReferences.formUnion($0)
        })
        let paths = pathReferences.map { $0.map { $0.absoluteString } }
        return .reference(RenderReferenceHierarchy(paths: paths))
    }
    
    /// Returns the hierarchy under a given article.
    /// - Parameter symbolReference: The reference to the article.
    /// - Returns: The framework hierarchy that describes all paths where the article is curated.
    mutating func visitArticle(_ symbolReference: ResolvedTopicReference) -> RenderHierarchy {
        return visitSymbol(symbolReference)
    }
}