File: AvailabilityRenderMetadataItem.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 (174 lines) | stat: -rw-r--r-- 8,213 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
/*
 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 SymbolKit

extension SemanticVersion {
    enum Precision: Int {
        case all = 0, patch, minor
        
        fileprivate var componentsToRemove: Int {
            switch self {
            case .minor:
                return 1
            case .all, .patch:
                return 0
            }
        }
    }
    
    /// Renders a lossless version string up to a given component precision.
    ///
    /// In case there are non-zero components after the `precisionUpToNonsignificant` index
    /// this method will still include them in the returned version string. For example:
    ///  - "1.2.0" rendered with .patch precision returns "1.2.0"
    ///  - "1.2.0" rendered with .minor precision returns "1.2"
    ///  - but "1.2.3" rendered with .minor precision returns "1.2.3";
    ///    the patch component is included to prevent data loss.
    /// - Parameter precision: A ``Precision`` index of the least-significant component to include.
    /// - Returns: A rendered version string.
    func stringRepresentation(precisionUpToNonsignificant precision: Precision = .all) -> String {
        let components = [major, minor, patch]
        let lastIndex = components.count - 1
        let lastNonZeroIndex = components.lastIndex(where: { $0>0 }) ?? 0
        let renderUpToIndex = max(lastIndex - precision.componentsToRemove, lastNonZeroIndex)
        return components[0...renderUpToIndex]
            .map { "\($0)" }
            .joined(separator: ".")
    }
    
    init(_ semanticVersion: SymbolGraph.SemanticVersion) {
        self.major = semanticVersion.major
        self.minor = semanticVersion.minor
        self.patch = semanticVersion.patch
        self.prerelease = semanticVersion.prerelease
        self.buildMetadata = semanticVersion.buildMetadata
    }
    
    /// Compares a version triplet to a semantic version.
    /// - Parameter version: A version triplet to compare to this semantic version.
    /// - Returns: Returns whether the given triple represents the same version as the current version.
    func isEqualToVersionTriplet(_ version: VersionTriplet) -> Bool {
        return major == version.major &&
            minor == version.minor &&
            patch == version.patch
    }
}

/// Availability information of a symbol on a specific platform.
public struct AvailabilityRenderItem: Codable, Hashable, Equatable {
    /// The name of the platform on which the symbol is available.
    public var name: String?
    
    /// The version of the platform SDK introducing the symbol.
    public var introduced: String?
    
    /// The version of the platform SDK deprecating the symbol.
    public var deprecated: String?
    
    /// The version of the platform SDK marking the symbol as obsolete.
    public var obsoleted: String?
    
    /// A message associated with the availability of the symbol.
    ///
    /// Use this property to provide a deprecation reason or instructions how to
    /// update code that uses this symbol.
    public var message: String?
    
    /// The new name of the symbol, if it was renamed.
    public var renamed: String?
    
    /// If `true`, the symbol is deprecated on this or all platforms.
    public var unconditionallyDeprecated: Bool?
    
    /// If `true`, the symbol is unavailable on this or all platforms.
    public var unconditionallyUnavailable: Bool?
    
    /// If `true`, the symbol is introduced in a beta version of this platform.
    public var isBeta: Bool?
    
    private enum CodingKeys: String, CodingKey {
        case name, introducedAt, deprecatedAt, obsoletedAt, message, renamed, deprecated, unavailable
        case beta
    }
    
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decodeIfPresent(String.self, forKey: .name)
        introduced = try container.decodeIfPresent(String.self, forKey: .introducedAt)
        deprecated = try container.decodeIfPresent(String.self, forKey: .deprecatedAt)
        obsoleted = try container.decodeIfPresent(String.self, forKey: .obsoletedAt)
        message = try container.decodeIfPresent(String.self, forKey: .message)
        renamed = try container.decodeIfPresent(String.self, forKey: .renamed)
        unconditionallyDeprecated = try container.decodeIfPresent(Bool.self, forKey: .deprecated)
        unconditionallyUnavailable = try container.decodeIfPresent(Bool.self, forKey: .unavailable)
        isBeta = try container.decodeIfPresent(Bool.self, forKey: .beta)
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        try container.encodeIfPresent(name, forKey: .name)
        try container.encodeIfPresent(introduced, forKey: .introducedAt)
        try container.encodeIfPresent(deprecated, forKey: .deprecatedAt)
        try container.encodeIfPresent(obsoleted, forKey: .obsoletedAt)
        try container.encodeIfPresent(message, forKey: .message)
        try container.encodeIfPresent(renamed, forKey: .renamed)
        try container.encodeIfPresent(unconditionallyDeprecated, forKey: .deprecated)
        try container.encodeIfPresent(unconditionallyUnavailable, forKey: .unavailable)
        try container.encodeIfPresent(isBeta, forKey: .beta)
    }
    
    /// Creates a new availability item with the given parameters.
    /// - Parameter availability: The symbol-graph availability item.
    /// - Parameter current: The target platform version, if available.
    init(_ availability: SymbolGraph.Symbol.Availability.AvailabilityItem, current: PlatformVersion?) {
        let platformName = availability.domain.map({ PlatformName(operatingSystemName: $0.rawValue) })
        name = platformName?.displayName
        
        let introducedVersion = availability.introducedVersion.flatMap { SemanticVersion($0) }
        introduced = introducedVersion?.stringRepresentation(precisionUpToNonsignificant: .minor)
        deprecated = availability.deprecatedVersion.flatMap { SemanticVersion($0).stringRepresentation(precisionUpToNonsignificant: .minor) }
        obsoleted = availability.obsoletedVersion.flatMap { SemanticVersion($0).stringRepresentation(precisionUpToNonsignificant: .minor) }
        message = availability.message
        renamed = availability.renamed
        unconditionallyUnavailable = availability.isUnconditionallyUnavailable
        unconditionallyDeprecated = availability.isUnconditionallyDeprecated
        isBeta = AvailabilityRenderItem.isBeta(introduced: introducedVersion, current: current)
    }

    init?(_ availability: Metadata.Availability, current: PlatformVersion?) {
        let platformName = PlatformName(metadataPlatform: availability.platform)
        name = platformName?.displayName
        introduced = availability.introduced.stringRepresentation(precisionUpToNonsignificant: .minor)
        deprecated = availability.deprecated.flatMap { $0.stringRepresentation(precisionUpToNonsignificant: .minor) }
        isBeta = AvailabilityRenderItem.isBeta(introduced: availability.introduced, current: current)
    }
    
    private static func isBeta(introduced: SemanticVersion?, current: PlatformVersion?) -> Bool {
        guard let introduced, let current, current.beta, introduced.isEqualToVersionTriplet(current.version) else {
            return false
        }
        
        return true
    }
    
    /// Creates a new item with the given platform name and version string.
    /// - Parameters:
    ///   - name: A platform name.
    ///   - introduced: A version string.
    ///   - isBeta: If `true`, the symbol is introduced in a beta version of the platform.
    init(name: String, introduced: String, isBeta: Bool) {
        self.name = name
        self.introduced = introduced
        self.isBeta = isBeta
    }
}