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

 Copyright (c) 2021-2024 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

/// The documentation workspace provides a unified interface for accessing serialized documentation bundles and their files, from a variety of sources.
///
/// The ``DocumentationContext`` and the workspace that the context is operating in are connected in two ways:
///  - The workspace is the context's data provider.
///  - The context is the workspace's ``DocumentationContextDataProviderDelegate``.
///
/// The first lets the workspace multiplex the bundles from any number of data providers (``DocumentationWorkspaceDataProvider``) into a single list of
/// ``DocumentationContextDataProvider/bundles`` and allows the context to access the contents of the various bundles without knowing any specifics
/// of its source (files on disk, a database, or a web services).
///
/// The second lets the workspace notify the context when bundles are added or removed so that the context stays up to date, even after the context is created.
///
/// ```
///                                                                                       ┌─────┐
///                                                      ┌────────────────────────────────│ IDE │─────────────────────────────┐
///       ┌──────────┐  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐          │                                └─────┘                             │
///       │FileSystem│─▶ WorkspaceDataProvider ─┐        │                                                                    │
///       └──────────┘  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │        │                                                                    │
///                                             │        │                                                                    │
///                                             │        │                                                                    │
///       ┌──────────┐  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │  ┌───────────┐     Read-only     ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐                 ┌─────────┐
///       │WebService│─▶ WorkspaceDataProvider ─┼─▶│ Workspace │◀────interface───── ContextDataProvider ◀────get data────│ Context │
///       └──────────┘  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │  └───────────┘                   └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘                 └─────────┘
///                                             │        │                                                                    ▲
///                                             │        │                                                                    │
/// ┌────────────────┐  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │        │                                                                    │
/// │MyCustomDatabase│─▶ WorkspaceDataProvider ─┘        │    Bundle or       ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐     Event push    │
/// └────────────────┘  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘          └───────file ───────▶ ContextDataProviderDelegate ─────interface─────┘
///                                                             change        └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
/// ```
///
/// > Note: Each data provider is treated as a separate file system. A single documentation bundle may not span multiple data providers.
///
/// ## Topics
///
/// ### Data Providers
///
/// - ``DocumentationWorkspaceDataProvider``
/// - ``LocalFileSystemDataProvider``
/// - ``PrebuiltLocalFileSystemDataProvider``
///
/// ## See Also
///
/// - ``DocumentationContext``
/// - ``DocumentationContextDataProvider``
/// - ``DocumentationContextDataProviderDelegate``
///
public class DocumentationWorkspace: DocumentationContextDataProvider {
    /// An error when requesting information from a workspace.
    public enum WorkspaceError: DescribedError {
        /// A bundle with the provided ID wasn't found in the workspace.
        case unknownBundle(id: String)
        /// A data provider with the provided ID wasn't found in the workspace.
        case unknownProvider(id: String)
        
        /// A plain-text description of the error.
        public var errorDescription: String {
            switch self {
            case .unknownBundle(let id):
                return "The requested data could not be located because a containing bundle with id '\(id)' could not be found in the workspace."
            case .unknownProvider(let id):
                return "The requested data could not be located because a containing data provider with id '\(id)' could not be found in the workspace."
            }
        }
    }
    
    /// Reads the data for a given file in a given documentation bundle.
    ///
    /// - Parameters:
    ///   - url: The URL of the file to read.
    ///   - bundle: The documentation bundle that the file belongs to.
    /// - Throws: A ``WorkspaceError/unknownBundle(id:)`` error if the bundle doesn't exist in the workspace or
    ///           a ``WorkspaceError/unknownProvider(id:)`` error if the bundle's data provider doesn't exist in the workspace.
    /// - Returns: The raw data for the given file.
    public func contentsOfURL(_ url: URL, in bundle: DocumentationBundle) throws -> Data {
        guard let providerID = bundleToProvider[bundle.identifier] else {
            throw WorkspaceError.unknownBundle(id: bundle.identifier)
        }
        
        guard let provider = providers[providerID] else {
            throw WorkspaceError.unknownProvider(id: providerID)
        }
        
        return try provider.contentsOfURL(url)
    }

    /// A map of bundle identifiers to documentation bundles.
    public var bundles: [String: DocumentationBundle] = [:]
    /// A map of provider identifiers to data providers.
    private var providers: [String: DocumentationWorkspaceDataProvider] = [:]
    /// A map of bundle identifiers to provider identifiers (in other words, a map from a bundle to the provider that vends the bundle).
    private var bundleToProvider: [String: String] = [:]
    /// The delegate to notify when documentation bundles are added or removed from this workspace.
    public weak var delegate: DocumentationContextDataProviderDelegate?
    /// Creates a new, empty documentation workspace.
    public init() {}
    
    /// Adds a new data provider to the workspace.
    ///
    /// Adding a data provider also adds the documentation bundles that it provides, and notifies the ``delegate`` of the added bundles.
    ///
    /// - Parameters:
    ///   - provider: The workspace data provider to add to the workspace.
    ///   - options: The options that the data provider uses to discover documentation bundles that it provides to the delegate.
    public func registerProvider(_ provider: DocumentationWorkspaceDataProvider, options: BundleDiscoveryOptions = .init()) throws {
        // We must add the provider before adding the bundle so that the delegate
        // may start making requests immediately.
        providers[provider.identifier] = provider
        
        for bundle in try provider.bundles(options: options) {
            bundles[bundle.identifier] = bundle
            bundleToProvider[bundle.identifier] = provider.identifier
            try delegate?.dataProvider(self, didAddBundle: bundle)
        }
    }

    /// Removes a given data provider from the workspace.
    ///
    /// Removing a data provider also removes all its provided documentation bundles and notifies the ``delegate`` of the removed bundles.
    ///
    /// - Parameters:
    ///   - provider: The workspace data provider to remove from the workspace.
    ///   - options: The options that the data provider uses to discover documentation bundles that it removes from the delegate.
    public func unregisterProvider(_ provider: DocumentationWorkspaceDataProvider, options: BundleDiscoveryOptions = .init()) throws {
        for bundle in try provider.bundles(options: options) {
            bundles[bundle.identifier] = nil
            bundleToProvider[bundle.identifier] = nil
            try delegate?.dataProvider(self, didRemoveBundle: bundle)
        }
        
        // The provider must be removed after removing the bundle so that the delegate
        // may continue making requests as part of removing the bundle.
        providers[provider.identifier] = nil
    }
}