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
|
/*
This source file is part of the Swift.org open source project
Copyright (c) 2021-2022 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 SymbolKit
/// A model type that encapsulates variants of documentation node data.
///
/// Use this type to represent a piece of information about a documentation node that can have different values depending on some trait,
/// e.g., the programming language the symbol was defined in.
public struct DocumentationDataVariants<Variant> {
/// The variant values for this collection of variants.
private var values: [DocumentationDataVariantsTrait: Variant]
/// The default value of the variant.
private var defaultVariantValue: Variant?
/// All the variants registered in this variant collection, including any default variant.
///
/// The default variant value, if one exists, is the last element of the returned array.
public var allValues: [(trait: DocumentationDataVariantsTrait, variant: Variant)] {
values.map { (trait: $0.key, variant: $0.value) }
// Append the default variant value if there is one.
+ (defaultVariantValue.map { [(.fallback, $0)] } ?? [])
}
/// Whether there are any variants for this piece of information about the documentation node
public var isEmpty: Bool {
values.isEmpty
}
/// Creates a variants value.
///
/// - Parameters:
/// - values: The variants for a piece of information about a documentation node, grouped by trait, e.g., programming language.
/// - defaultVariantValue: The default value for this piece of information about the documentation node, if no variants have been registered.
public init(values: [DocumentationDataVariantsTrait: Variant] = [:], defaultVariantValue: Variant? = nil) {
self.values = values
self.defaultVariantValue = defaultVariantValue
}
/// Accesses the variant for the given trait.
public subscript(trait: DocumentationDataVariantsTrait) -> Variant? {
get { values[trait] ?? defaultVariantValue }
set {
if trait == .fallback {
defaultVariantValue = newValue
} else {
values[trait] = newValue
}
}
}
/// Accesses the variant for the given trait,
/// falling back to the given default variant if the key isn’t found.
public subscript(trait: DocumentationDataVariantsTrait, default defaultValue: Variant) -> Variant {
get { values[trait] ?? defaultValue }
set {
if trait == .fallback {
defaultVariantValue = newValue
} else {
values[trait] = newValue
}
}
}
/// Whether a variant for the given trait has been registered.
///
/// - Parameter trait: The trait to look up a variant for.
public func hasVariant(for trait: DocumentationDataVariantsTrait) -> Bool {
values.keys.contains(trait)
}
func map<NewVariant>(transform: (Variant) -> NewVariant) -> DocumentationDataVariants<NewVariant> {
return DocumentationDataVariants<NewVariant>(
values: Dictionary(
uniqueKeysWithValues: values.map { (trait, variant) in
return (trait, transform(variant))
}
),
defaultVariantValue: defaultVariantValue.map(transform)
)
}
}
extension DocumentationDataVariants {
/// Convenience initializer to initialize a variants value with a Swift variant only.
init(swiftVariant: Variant?) {
if let swiftVariant {
self.init(values: [.swift: swiftVariant])
} else {
self.init()
}
}
static var empty: DocumentationDataVariants<Variant> {
return DocumentationDataVariants<Variant>(values: [:], defaultVariantValue: nil)
}
/// Convenience API to access the first variant, or the default value if there are no registered variants.
///
/// > Important:
/// > Do not use this property in new code.
/// > It exists to transition existing code from only working with Swift symbols to working with multi-language symbols.
/// > This property should be considered deprecated but isn't formally deprecated to avoid the ~50 warnings that would make it
/// > harder to spot new warnings. (rdar://86580516)
var firstValue: Variant? {
// A Dictionary's order isn't stable across program executions so accessing the `first` value would
// result in non-deterministic behavior and also flaky tests.
//
// Since this convenience accessor exist to transition existing code from only working with Swift symbols,
// it accesses the Swift value first, if it exist, and otherwise accesses the real non-deterministic first value.
// This assumes that variant only represents one non-Swift language.
get { self[.swift] ?? self.values.first?.value }
set { self[.swift] = newValue }
}
}
extension DocumentationDataVariants: Equatable where Variant: Equatable {}
/// The trait associated with a variant of some piece of information about a documentation node.
public struct DocumentationDataVariantsTrait: Hashable {
/// The Swift programming language.
public static var swift = DocumentationDataVariantsTrait(interfaceLanguage: SourceLanguage.swift.id)
/// The Objective-C programming language.
public static var objectiveC = DocumentationDataVariantsTrait(interfaceLanguage: SourceLanguage.objectiveC.id)
/// The language in which the documentation node is relevant.
public var interfaceLanguage: String?
/// A special trait that represents the fallback trait, which internal clients can use to access the default value of a collection of variants.
static var fallback = DocumentationDataVariantsTrait()
/// Creates a new trait given an interface language.
///
/// - Parameter interfaceLanguage: The language in which a documentation node is relevant.
public init(interfaceLanguage: String? = nil) {
self.interfaceLanguage = interfaceLanguage
}
/// Creates a new trait given a symbol graph selector.
///
/// - Parameter selector: The symbol graph selector to use when creating the trait.
public init(for selector: UnifiedSymbolGraph.Selector) {
self.init(
interfaceLanguage: SourceLanguage(knownLanguageIdentifier: selector.interfaceLanguage)?.id
?? selector.interfaceLanguage
)
}
}
extension Set<DocumentationDataVariantsTrait> {
/// Filters set to a subset of language traits that can coexist together.
///
/// - Parameter trait: The language variant being processed.
///
/// Due to the interoperability of Swift and Objective-C, these languages represent separate views into the same APIs rather than being disjoint APIs.
/// When constructing content, a page wants to display one or the other, not both, while other language variants are distinct and may be used concurrently.
/// Use `compatible(withTrait:)` to obtain a subset of the current set of variants that can coexist on a page in the context of the input `trait`.
func traitsCompatible(with trait: DocumentationDataVariantsTrait) -> Set<Element> {
let compatibleTraits: Set<Element>
if trait == DocumentationDataVariantsTrait.objectiveC {
// Objective-C pages should exclude Swift content.
compatibleTraits = self.filter { $0 != DocumentationDataVariantsTrait.swift }
} else if trait == DocumentationDataVariantsTrait.swift {
// Swift pages should exclude Objective-C content.
compatibleTraits = self.filter { $0 != DocumentationDataVariantsTrait.objectiveC }
} else {
// Other pages should allow either Swift (preferred) or Objective-C, but not both.
if self.contains(DocumentationDataVariantsTrait.swift) {
// A Swift variant is present, so exclude the Objective-C variant, if present.
compatibleTraits = self.filter { $0 != DocumentationDataVariantsTrait.objectiveC }
} else {
// Full list lacks Swift, so can be used as is.
compatibleTraits = self
}
}
return compatibleTraits
}
}
|