File: Tutorial.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 (167 lines) | stat: -rw-r--r-- 7,721 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
/*
 This source file is part of the Swift.org open source project

 Copyright (c) 2021-2023 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
import Markdown

/**
 A tutorial to complete in order to gain knowledge of a ``Technology``.
 */
public final class Tutorial: Semantic, AutomaticDirectiveConvertible, Abstracted, Titled, Timed, Redirected {
    public static let introducedVersion = "5.5"
    public let originalMarkup: BlockDirective
    
    /// The estimated time in minutes that the containing ``Tutorial`` will take.
    @DirectiveArgumentWrapped(name: .custom("time"))
    public private(set) var durationMinutes: Int? = nil
    
    /// Project files to download to get started with the ``Tutorial``.
    @DirectiveArgumentWrapped(
        parseArgument: { bundle, argumentValue in
            ResourceReference(bundleIdentifier: bundle.identifier, path: argumentValue)
        }
    )
    public private(set) var projectFiles: ResourceReference? = nil
    
    /// Informal requirements to complete the ``Tutorial``.
    @ChildDirective(requirements: .zeroOrOne)
    public private(set) var requirements: [XcodeRequirement]
    
    /// The Intro section, representing a slide that introduces the tutorial.
    @ChildDirective
    public private(set) var intro: Intro

    /// All of the sections to complete to finish the tutorial.
    @ChildDirective(requirements: .oneOrMore)
    public private(set) var sections: [TutorialSection]
    
    /// The linkable parts of the tutorial.
    ///
    /// Allows you to direct link to discrete sections within a tutorial.
    public var landmarks: [Landmark] {
        return sections
    }
    
    /// A section containing various questions to test the reader's knowledge.
    @ChildDirective
    public private(set) var assessments: Assessments? = nil
    
    /// An image for the final call to action, which directs the reader to the starting point to learn about this category.
    @ChildDirective
    public private(set) var callToActionImage: ImageMedia? = nil
    
    public var abstract: Paragraph? {
        return intro.content.first as? Paragraph
    }
    
    public var title: String? {
        return intro.title
    }
    
    override var children: [Semantic] {
        return [intro] +
            requirements as [Semantic] +
            sections as [Semantic] +
            (assessments.map({ [$0] }) ?? [])
    }
    
    @ChildDirective
    public private(set) var redirects: [Redirect]? = nil
    
    static var keyPaths: [String : AnyKeyPath] = [
        "durationMinutes"   :   \Tutorial._durationMinutes,
        "projectFiles"      :   \Tutorial._projectFiles,
        "requirements"      :   \Tutorial._requirements,
        "intro"             :   \Tutorial._intro,
        "sections"          :   \Tutorial._sections,
        "assessments"       :   \Tutorial._assessments,
        "callToActionImage" :   \Tutorial._callToActionImage,
        "redirects"         :   \Tutorial._redirects,
    ]
    
    init(originalMarkup: BlockDirective, durationMinutes: Int?, projectFiles: ResourceReference?, requirements: [XcodeRequirement], intro: Intro, sections: [TutorialSection], assessments: Assessments?, callToActionImage: ImageMedia?, redirects: [Redirect]?) {
        self.originalMarkup = originalMarkup
        self.durationMinutes = durationMinutes
        self.projectFiles = projectFiles
        super.init()
        self.requirements = requirements
        self.intro = intro
        self.sections = sections
        self.assessments = assessments
        self.callToActionImage = callToActionImage
        self.redirects = redirects
    }
    
    @available(*, deprecated, message: "Do not call directly. Required for 'AutomaticDirectiveConvertible'.")
    init(originalMarkup: BlockDirective) {
        self.originalMarkup = originalMarkup
    }
    
    func validate(
        source: URL?,
        for bundle: DocumentationBundle,
        in context: DocumentationContext,
        problems: inout [Problem]
    ) -> Bool {
        var seenSectionTitles = [String: SourceRange]()
        sections = sections.filter { section -> Bool in
            let arguments = section.originalMarkup.arguments()
            let thisTitleRange = arguments[TutorialSection.Semantics.Title.argumentName]?.valueRange
            if let previousRange = seenSectionTitles[section.title] {
                var diagnostic = Diagnostic(source: source, severity: .warning, range: thisTitleRange, identifier: "org.swift.docc.\(Tutorial.self).DuplicateSectionTitle", summary: "Duplicate title in \(TutorialSection.directiveName.singleQuoted) directive", explanation: "\(TutorialSection.directiveName.singleQuoted) directives are identified and linked using their titles and so must be unique within a \(Tutorial.directiveName.singleQuoted) directive; this directive will be dropped")
                if let source {
                    diagnostic.notes.append(DiagnosticNote(source: source, range: previousRange, message: "First \(TutorialSection.directiveName.singleQuoted) directive with the title '\(section.title)' written here"))
                }
                problems.append(Problem(diagnostic: diagnostic, possibleSolutions: []))
                return false
            }
            seenSectionTitles[section.title] = thisTitleRange
            return true
        }
        
        return true
    }
    
    public override func accept<V: SemanticVisitor>(_ visitor: inout V) -> V.Result {
        return visitor.visitTutorial(self)
    }
}

extension Tutorial {
    static func analyze(_ node: TopicGraph.Node, completedContext context: DocumentationContext, engine: DiagnosticEngine) {
        let url = context.documentURL(for: node.reference)

        if let project = try? context.entity(with: node.reference).semantic as? Tutorial, let projectFiles = project.projectFiles {
            if context.resolveAsset(named: projectFiles.url.lastPathComponent, in: node.reference) == nil {
                // The project download file is not found.
                engine.emit(.init(
                    diagnostic: Diagnostic(source: url, severity: .warning, range: nil, identifier: "org.swift.docc.Project.ProjectFilesNotFound", 
                        summary: "\(projectFiles.path) file reference not found in \(Tutorial.directiveName.singleQuoted) directive"), 
                    possibleSolutions: [
                        Solution(summary: "Copy the referenced file into the documentation bundle directory", replacements: [])
                    ]
                ))
            }
        }
        
        let technologyParent = context.parents(of: node.reference)
            .compactMap({ context.topicGraph.nodeWithReference($0) })
            .first(where: { $0.kind == .technology || $0.kind == .chapter || $0.kind == .volume })
        guard technologyParent != nil else {
            engine.emit(.init(
                diagnostic: Diagnostic(source: url, severity: .warning, range: nil, identifier: "org.swift.docc.Unreferenced\(Tutorial.self)", summary: "The tutorial \(node.reference.path.components(separatedBy: "/").last!.singleQuoted) must be referenced from a Tutorial Table of Contents"),
                possibleSolutions: [
                    Solution(summary: "Use a \(TutorialReference.directiveName.singleQuoted) directive inside \(Technology.directiveName.singleQuoted) to reference the tutorial.", replacements: [])
                ]
            ))
            return
        }
    }
}