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 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
|
/*
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
/// Arbitrary metadata for a render node.
public struct RenderMetadata: VariantContainer {
// MARK: Tutorials metadata
/// The name of technology associated with a tutorial.
public var category: String?
public var categoryPathComponent: String?
/// A description of the estimated time to complete the tutorials of a technology.
public var estimatedTime: String?
// MARK: Symbol metadata
/// The modules that the symbol is apart of.
public var modules: [Module]? {
get { getVariantDefaultValue(keyPath: \.modulesVariants) }
set { setVariantDefaultValue(newValue, keyPath: \.modulesVariants) }
}
/// The variants for the modules.
public var modulesVariants: VariantCollection<[Module]?> = .init(defaultValue: nil)
/// The name of the module extension in which the symbol is defined, if applicable.
public var extendedModule: String? {
get { getVariantDefaultValue(keyPath: \.extendedModuleVariants) }
set { setVariantDefaultValue(newValue, keyPath: \.extendedModuleVariants) }
}
/// The variants for the module extension.
public var extendedModuleVariants: VariantCollection<String?> = .init(defaultValue: nil)
/// The platform availability information about a symbol.
public var platforms: [AvailabilityRenderItem]? {
get { getVariantDefaultValue(keyPath: \.platformsVariants) }
set { setVariantDefaultValue(newValue, keyPath: \.platformsVariants) }
}
/// The variants for the platforms.
public var platformsVariants: VariantCollection<[AvailabilityRenderItem]?> = .init(defaultValue: nil)
/// Whether protocol method is required to be implemented by conforming types.
public var required: Bool {
get { getVariantDefaultValue(keyPath: \.requiredVariants) }
set { setVariantDefaultValue(newValue, keyPath: \.requiredVariants) }
}
/// The variants for the `required` property.
public var requiredVariants: VariantCollection<Bool> = .init(defaultValue: false)
/// A heading describing the type of the document.
public var roleHeading: String? {
get { getVariantDefaultValue(keyPath: \.roleHeadingVariants) }
set { setVariantDefaultValue(newValue, keyPath: \.roleHeadingVariants) }
}
/// The variants of the role heading.
public var roleHeadingVariants: VariantCollection<String?> = .init(defaultValue: nil)
/// The role of the document.
///
/// Examples of document roles include "symbol" or "sampleCode".
public var role: String?
/// Custom authored images that represent this page.
///
/// Authors can use the `@PageImage` metadata directive to provide custom icon and card images for a page.
public var images: [TopicImage] = []
/// Custom authored color that represents this page.
///
/// Authors can use the `@PageColor` metadata directive to provide a custom color for a page.
public var color: TopicColor?
/// Author provided custom metadata describing the page.
public var customMetadata: [String: String] = [:]
/// The title of the page.
public var title: String? {
get { getVariantDefaultValue(keyPath: \.titleVariants) }
set { setVariantDefaultValue(newValue, keyPath: \.titleVariants) }
}
/// The variants of the title.
public var titleVariants: VariantCollection<String?> = .init(defaultValue: nil)
/// An identifier for a symbol generated externally.
public var externalID: String? {
get { getVariantDefaultValue(keyPath: \.externalIDVariants) }
set { setVariantDefaultValue(newValue, keyPath: \.externalIDVariants) }
}
/// The variants of the external ID.
public var externalIDVariants: VariantCollection<String?> = .init(defaultValue: nil)
/// The kind of a symbol, e.g., "class" or "func".
public var symbolKind: String? {
get { getVariantDefaultValue(keyPath: \.symbolKindVariants) }
set { setVariantDefaultValue(newValue, keyPath: \.symbolKindVariants) }
}
/// The variants of the symbol kind.
public var symbolKindVariants: VariantCollection<String?> = .init(defaultValue: nil)
/// The access level of a symbol, e.g., "public" or "private".
public var symbolAccessLevel: String? {
get { getVariantDefaultValue(keyPath: \.symbolAccessLevelVariants) }
set { setVariantDefaultValue(newValue, keyPath: \.symbolAccessLevelVariants) }
}
/// The variants for the access level of a symbol.
public var symbolAccessLevelVariants: VariantCollection<String?> = .init(defaultValue: nil)
/// Abbreviated declaration to display in links.
public var fragments: [DeclarationRenderSection.Token]? {
get { getVariantDefaultValue(keyPath: \.fragmentsVariants) }
set { setVariantDefaultValue(newValue, keyPath: \.fragmentsVariants) }
}
/// The variants for the fragments of a page.
public var fragmentsVariants: VariantCollection<[DeclarationRenderSection.Token]?> = .init(defaultValue: nil)
/// Abbreviated declaration to display in navigators.
public var navigatorTitle: [DeclarationRenderSection.Token]? {
get { getVariantDefaultValue(keyPath: \.navigatorTitleVariants) }
set { setVariantDefaultValue(newValue, keyPath: \.navigatorTitleVariants) }
}
/// The variants for the navigator title of a page.
public var navigatorTitleVariants: VariantCollection<[DeclarationRenderSection.Token]?> = .init(defaultValue: nil)
/// Additional metadata associated with the render node.
public var extraMetadata: [CodingKeys: Any] = [:]
/// Information the availability of generic APIs.
public var conformance: ConformanceSection?
/// The URI of the source file in which the symbol was originally declared, suitable for display in a user interface.
///
/// This information may not (and should not) always be available for many reasons,
/// such as compiler infrastructure limitations, or filesystem privacy and security concerns.
public var sourceFileURI: String? {
get { getVariantDefaultValue(keyPath: \.sourceFileURIVariants) }
set { setVariantDefaultValue(newValue, keyPath: \.sourceFileURIVariants) }
}
/// The variants for the source file URI of a page.
public var sourceFileURIVariants: VariantCollection<String?> = .init(defaultValue: nil)
/// The remote location where the source declaration of the topic can be viewed.
public var remoteSource: RemoteSource? {
get { getVariantDefaultValue(keyPath: \.remoteSourceVariants) }
set { setVariantDefaultValue(newValue, keyPath: \.remoteSourceVariants) }
}
/// The variants for the topic's remote source.
public var remoteSourceVariants: VariantCollection<RemoteSource?> = .init(defaultValue: nil)
/// Any tags assigned to the node.
public var tags: [RenderNode.Tag]?
/// Whether there isn't a version of the page with more content that a renderer can link to.
///
/// This property indicates to renderers that an expanded version of the page does not exist for this render node,
/// which, for example, controls whether a 'View More' link should be displayed or not.
///
/// It's the renderer's responsibility to fetch the full version of the page, for example using
/// the ``RenderNode/variants`` property.
public var hasNoExpandedDocumentation: Bool = false
}
extension RenderMetadata: Codable {
/// A list of pre-defined roles to assign to nodes.
public enum Role: String, Equatable {
case symbol, containerSymbol, restRequestSymbol, dictionarySymbol, pseudoSymbol, pseudoCollection, collection, collectionGroup, article, sampleCode, unknown
case table, codeListing, link, subsection, task, overview
case tutorial = "project"
}
/// Metadata about a module dependency.
public struct Module: Codable, Equatable {
public let name: String
/// Possible dependencies to the module, we allow for those in the render JSON model
/// but have no authoring support at the moment.
public let relatedModules: [String]?
}
/// Describes the location of the topic's source code, hosted remotely by a source service.
public struct RemoteSource: Codable, Equatable {
/// The name of the file where the topic is declared.
public var fileName: String
/// The location of the topic's source code, hosted by a source service.
public var url: URL
/// Creates a topic's source given its source code's file name and URL.
public init(fileName: String, url: URL) {
self.fileName = fileName
self.url = url
}
}
public struct CodingKeys: CodingKey, Hashable, Equatable {
public var stringValue: String
public init(stringValue: String) {
self.stringValue = stringValue
}
public var intValue: Int? {
return nil
}
public init?(intValue: Int) {
return nil
}
public static let category = CodingKeys(stringValue: "category")
public static let categoryPathComponent = CodingKeys(stringValue: "categoryPathComponent")
public static let estimatedTime = CodingKeys(stringValue: "estimatedTime")
public static let modules = CodingKeys(stringValue: "modules")
public static let extendedModule = CodingKeys(stringValue: "extendedModule")
public static let platforms = CodingKeys(stringValue: "platforms")
public static let required = CodingKeys(stringValue: "required")
public static let roleHeading = CodingKeys(stringValue: "roleHeading")
public static let role = CodingKeys(stringValue: "role")
public static let title = CodingKeys(stringValue: "title")
public static let externalID = CodingKeys(stringValue: "externalID")
public static let symbolKind = CodingKeys(stringValue: "symbolKind")
public static let symbolAccessLevel = CodingKeys(stringValue: "symbolAccessLevel")
public static let conformance = CodingKeys(stringValue: "conformance")
public static let fragments = CodingKeys(stringValue: "fragments")
public static let navigatorTitle = CodingKeys(stringValue: "navigatorTitle")
public static let sourceFileURI = CodingKeys(stringValue: "sourceFileURI")
public static let remoteSource = CodingKeys(stringValue: "remoteSource")
public static let tags = CodingKeys(stringValue: "tags")
public static let images = CodingKeys(stringValue: "images")
public static let color = CodingKeys(stringValue: "color")
public static let customMetadata = CodingKeys(stringValue: "customMetadata")
public static let hasNoExpandedDocumentation = CodingKeys(stringValue: "hasNoExpandedDocumentation")
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
category = try container.decodeIfPresent(String.self, forKey: .category)
categoryPathComponent = try container.decodeIfPresent(String.self, forKey: .categoryPathComponent)
platformsVariants = try container.decodeVariantCollectionIfPresent(ofValueType: [AvailabilityRenderItem]?.self, forKey: .platforms)
modulesVariants = try container.decodeVariantCollectionIfPresent(ofValueType: [Module]?.self, forKey: .modules)
extendedModuleVariants = try container.decodeVariantCollectionIfPresent(ofValueType: String?.self, forKey: .extendedModule)
estimatedTime = try container.decodeIfPresent(String.self, forKey: .estimatedTime)
requiredVariants = try container.decodeVariantCollectionIfPresent(ofValueType: Bool.self, forKey: .required) ?? .init(defaultValue: false)
roleHeadingVariants = try container.decodeVariantCollectionIfPresent(ofValueType: String?.self, forKey: .roleHeading)
images = try container.decodeIfPresent([TopicImage].self, forKey: .images) ?? []
color = try container.decodeIfPresent(TopicColor.self, forKey: .color)
customMetadata = try container.decodeIfPresent([String: String].self, forKey: .customMetadata) ?? [:]
let rawRole = try container.decodeIfPresent(String.self, forKey: .role)
role = rawRole == "tutorial" ? Role.tutorial.rawValue : rawRole
titleVariants = try container.decodeVariantCollectionIfPresent(ofValueType: String?.self, forKey: .title)
externalID = try container.decodeIfPresent(String.self, forKey: .externalID)
symbolKindVariants = try container.decodeVariantCollectionIfPresent(ofValueType: String?.self, forKey: .symbolKind)
symbolAccessLevelVariants = try container.decodeVariantCollectionIfPresent(ofValueType: String?.self, forKey: .symbolAccessLevel)
conformance = try container.decodeIfPresent(ConformanceSection.self, forKey: .conformance)
fragmentsVariants = try container.decodeVariantCollectionIfPresent(ofValueType: [DeclarationRenderSection.Token]?.self, forKey: .fragments)
navigatorTitleVariants = try container.decodeVariantCollectionIfPresent(ofValueType: [DeclarationRenderSection.Token]?.self, forKey: .navigatorTitle)
sourceFileURIVariants = try container.decodeVariantCollectionIfPresent(ofValueType: String?.self, forKey: .sourceFileURI)
remoteSourceVariants = try container.decodeVariantCollectionIfPresent(ofValueType: RemoteSource?.self, forKey: .remoteSource)
tags = try container.decodeIfPresent([RenderNode.Tag].self, forKey: .tags)
hasNoExpandedDocumentation = try container.decodeIfPresent(Bool.self, forKey: .hasNoExpandedDocumentation) ?? false
let extraKeys = Set(container.allKeys).subtracting(
[
.category,
.categoryPathComponent,
.estimatedTime,
.modules,
.extendedModule,
.platforms,
.required,
.roleHeading,
.role,
.title,
.externalID,
.symbolKind,
.symbolAccessLevel,
.conformance,
.fragments,
.navigatorTitle,
.sourceFileURI,
.remoteSource,
.tags,
.hasNoExpandedDocumentation,
]
)
for extraKey in extraKeys {
extraMetadata[extraKey] = try container.decode(AnyMetadata.self, forKey: extraKey).value
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(category, forKey: .category)
try container.encodeIfPresent(categoryPathComponent, forKey: .categoryPathComponent)
try container.encodeVariantCollection(modulesVariants, forKey: .modules, encoder: encoder)
try container.encodeVariantCollection(extendedModuleVariants, forKey: .extendedModule, encoder: encoder)
try container.encodeIfPresent(estimatedTime, forKey: .estimatedTime)
try container.encodeVariantCollection(platformsVariants, forKey: .platforms, encoder: encoder)
try container.encodeVariantCollectionIfTrue(requiredVariants, forKey: .required, encoder: encoder)
try container.encodeVariantCollection(roleHeadingVariants, forKey: .roleHeading, encoder: encoder)
try container.encodeIfPresent(role, forKey: .role)
try container.encodeVariantCollection(titleVariants, forKey: .title, encoder: encoder)
try container.encodeIfPresent(externalID, forKey: .externalID)
try container.encodeIfPresent(symbolKindVariants.defaultValue, forKey: .symbolKind)
try container.encodeVariantCollection(symbolKindVariants, forKey: .symbolKind, encoder: encoder)
try container.encodeVariantCollection(symbolAccessLevelVariants, forKey: .symbolAccessLevel, encoder: encoder)
try container.encodeIfPresent(conformance, forKey: .conformance)
try container.encodeVariantCollection(fragmentsVariants, forKey: .fragments, encoder: encoder)
try container.encodeVariantCollection(navigatorTitleVariants, forKey: .navigatorTitle, encoder: encoder)
try container.encodeVariantCollection(sourceFileURIVariants, forKey: .sourceFileURI, encoder: encoder)
try container.encodeVariantCollection(remoteSourceVariants, forKey: .remoteSource, encoder: encoder)
if let tags = self.tags, !tags.isEmpty {
try container.encodeIfPresent(tags, forKey: .tags)
}
for (key, value) in extraMetadata {
try container.encode(AnyMetadata(value), forKey: key)
}
try container.encodeIfNotEmpty(images, forKey: .images)
try container.encodeIfPresent(color, forKey: .color)
try container.encodeIfNotEmpty(customMetadata, forKey: .customMetadata)
try container.encodeIfTrue(hasNoExpandedDocumentation, forKey: .hasNoExpandedDocumentation)
}
}
// Diffable conformance
extension RenderMetadata: RenderJSONDiffable {
/// Returns the differences between this RenderMetadata and the given one.
func difference(from other: RenderMetadata, at path: CodablePath) -> JSONPatchDifferences {
var diffBuilder = DifferenceBuilder(current: self, other: other, basePath: path)
diffBuilder.addDifferences(atKeyPath: \.category, forKey: CodingKeys.category)
diffBuilder.addDifferences(atKeyPath: \.categoryPathComponent, forKey: CodingKeys.categoryPathComponent)
diffBuilder.addDifferences(atKeyPath: \.estimatedTime, forKey: CodingKeys.estimatedTime)
diffBuilder.addDifferences(atKeyPath: \.modules, forKey: CodingKeys.modules)
diffBuilder.addDifferences(atKeyPath: \.extendedModule, forKey: CodingKeys.extendedModule)
diffBuilder.addDifferences(atKeyPath: \.modules, forKey: CodingKeys.modules)
diffBuilder.addDifferences(atKeyPath: \.platforms, forKey: CodingKeys.platforms)
diffBuilder.addDifferences(atKeyPath: \.required, forKey: CodingKeys.required)
diffBuilder.addDifferences(atKeyPath: \.roleHeading, forKey: CodingKeys.roleHeading)
diffBuilder.addDifferences(atKeyPath: \.role, forKey: CodingKeys.role)
diffBuilder.addDifferences(atKeyPath: \.images, forKey: CodingKeys.images)
diffBuilder.addDifferences(atKeyPath: \.color, forKey: CodingKeys.color)
diffBuilder.addDifferences(atKeyPath: \.customMetadata, forKey: CodingKeys.customMetadata)
diffBuilder.addDifferences(atKeyPath: \.title, forKey: CodingKeys.title)
diffBuilder.addDifferences(atKeyPath: \.externalID, forKey: CodingKeys.externalID)
diffBuilder.addDifferences(atKeyPath: \.symbolKind, forKey: CodingKeys.symbolKind)
diffBuilder.addDifferences(atKeyPath: \.symbolAccessLevel, forKey: CodingKeys.symbolAccessLevel)
diffBuilder.addDifferences(atKeyPath: \.fragments, forKey: CodingKeys.fragments)
diffBuilder.addDifferences(atKeyPath: \.navigatorTitle, forKey: CodingKeys.navigatorTitle)
diffBuilder.addDifferences(atKeyPath: \.conformance, forKey: CodingKeys.conformance)
diffBuilder.addDifferences(atKeyPath: \.sourceFileURI, forKey: CodingKeys.sourceFileURI)
diffBuilder.addDifferences(atKeyPath: \.remoteSource, forKey: CodingKeys.remoteSource)
diffBuilder.addDifferences(atKeyPath: \.tags, forKey: CodingKeys.tags)
diffBuilder.addDifferences(atKeyPath: \.hasNoExpandedDocumentation, forKey: CodingKeys.hasNoExpandedDocumentation)
return diffBuilder.differences
}
/// Returns if this RenderMetadata is similar enough to the given one.
func isSimilar(to other: RenderMetadata) -> Bool {
return self.title == other.title
}
}
// Diffable conformance
extension RenderMetadata.Module: RenderJSONDiffable {
/// Returns the difference between two RenderMetadata.Modules.
func difference(from other: RenderMetadata.Module, at path: CodablePath) -> JSONPatchDifferences {
var diffBuilder = DifferenceBuilder(current: self, other: other, basePath: path)
diffBuilder.addDifferences(atKeyPath: \.name, forKey: CodingKeys.name)
diffBuilder.addDifferences(atKeyPath: \.relatedModules, forKey: CodingKeys.relatedModules)
return diffBuilder.differences
}
}
|