diff --git a/Xcodes/Backend/AppState+Update.swift b/Xcodes/Backend/AppState+Update.swift
index dd5b7d7..c8124f2 100644
--- a/Xcodes/Backend/AppState+Update.swift
+++ b/Xcodes/Backend/AppState+Update.swift
@@ -6,10 +6,6 @@ import SwiftSoup
import struct XCModel.Xcode
extension AppState {
- private var dataSource: DataSource {
- Current.defaults.string(forKey: "dataSource").flatMap(DataSource.init(rawValue:)) ?? .default
- }
-
func updateIfNeeded() {
guard
let lastUpdated = Current.defaults.date(forKey: "lastUpdated"),
@@ -200,31 +196,6 @@ extension AppState {
}
return xcodes
}
- .map(filterPrereleasesThatMatchReleaseBuildMetadataIdentifiers)
.eraseToAnyPublisher()
}
-
- /// Xcode Releases may have multiple releases with the same build metadata when a build doesn't change between candidate and final releases.
- /// For example, 12.3 RC and 12.3 are both build 12C33
- /// We don't care about that difference, so only keep the final release (GM or Release, in XCModel terms).
- /// The downside of this is that a user could technically have both releases installed, and so they won't both be shown in the list, but I think most users wouldn't do this.
- func filterPrereleasesThatMatchReleaseBuildMetadataIdentifiers(_ availableXcodes: [AvailableXcode]) -> [AvailableXcode] {
- var filteredAvailableXcodes: [AvailableXcode] = []
- for availableXcode in availableXcodes {
- if availableXcode.version.buildMetadataIdentifiers.isEmpty {
- filteredAvailableXcodes.append(availableXcode)
- continue
- }
-
- let availableXcodesWithSameBuildMetadataIdentifiers = availableXcodes
- .filter({ $0.version.buildMetadataIdentifiers == availableXcode.version.buildMetadataIdentifiers })
- if availableXcodesWithSameBuildMetadataIdentifiers.count > 1,
- availableXcode.version.prereleaseIdentifiers.isEmpty || availableXcode.version.prereleaseIdentifiers == ["GM"] {
- filteredAvailableXcodes.append(availableXcode)
- } else if availableXcodesWithSameBuildMetadataIdentifiers.count == 1 {
- filteredAvailableXcodes.append(availableXcode)
- }
- }
- return filteredAvailableXcodes
- }
}
diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift
index 4d37634..fec7ac2 100644
--- a/Xcodes/Backend/AppState.swift
+++ b/Xcodes/Backend/AppState.swift
@@ -57,6 +57,12 @@ class AppState: ObservableObject {
private var selectPublisher: AnyCancellable?
private var uninstallPublisher: AnyCancellable?
+ // MARK: -
+
+ var dataSource: DataSource {
+ Current.defaults.string(forKey: "dataSource").flatMap(DataSource.init(rawValue:)) ?? .default
+ }
+
// MARK: - Init
init() {
@@ -422,29 +428,58 @@ class AppState: ObservableObject {
var adjustedAvailableXcodes = availableXcodes
// First, adjust all of the available Xcodes so that available and installed versions line up and the second part of this function works properly.
- for installedXcode in installedXcodes {
- // We can trust that build metadata identifiers are unique for each version of Xcode, so if we have it then it's all we need.
- // If build metadata matches exactly, replace the available version with the installed version.
- // This should handle both Xcode Releases versions which can have different prerelease identifiers and Apple versions which rarely have build metadata identifiers.
- if let index = adjustedAvailableXcodes.map(\.version).firstIndex(where: { $0.buildMetadataIdentifiers == installedXcode.version.buildMetadataIdentifiers }) {
- adjustedAvailableXcodes[index].version = installedXcode.version
- }
- // If an installed version is the same as one that's listed online which doesn't have build metadata, replace it with the installed version
- // Not all prerelease Apple versions available online include build metadata
- else if let index = adjustedAvailableXcodes.firstIndex(where: { availableXcode in
- availableXcode.version.isEquivalent(to: installedXcode.version) &&
- availableXcode.version.buildMetadataIdentifiers.isEmpty
- }) {
- adjustedAvailableXcodes[index].version = installedXcode.version
+ if dataSource == .apple {
+ for installedXcode in installedXcodes {
+ // We can trust that build metadata identifiers are unique for each version of Xcode, so if we have it then it's all we need.
+ // If build metadata matches exactly, replace the available version with the installed version.
+ // This should handle Apple versions from /downloads/more which don't have build metadata identifiers.
+ if let index = adjustedAvailableXcodes.map(\.version).firstIndex(where: { $0.buildMetadataIdentifiers == installedXcode.version.buildMetadataIdentifiers }) {
+ adjustedAvailableXcodes[index].version = installedXcode.version
+ }
+ // If an installed version is the same as one that's listed online which doesn't have build metadata, replace it with the installed version
+ // Not all prerelease Apple versions available online include build metadata
+ else if let index = adjustedAvailableXcodes.firstIndex(where: { availableXcode in
+ availableXcode.version.isEquivalent(to: installedXcode.version) &&
+ availableXcode.version.buildMetadataIdentifiers.isEmpty
+ }) {
+ adjustedAvailableXcodes[index].version = installedXcode.version
+ }
}
}
// Map all of the available versions into Xcode values that join available and installed Xcode data for display.
var newAllXcodes = adjustedAvailableXcodes
+ .filter { availableXcode in
+ // If we don't have the build identifier, don't attempt to filter prerelease versions with identical build identifiers
+ guard !availableXcode.version.buildMetadataIdentifiers.isEmpty else { return true }
+
+ let availableXcodesWithIdenticalBuildIdentifiers = availableXcodes
+ .filter({ $0.version.buildMetadataIdentifiers == availableXcode.version.buildMetadataIdentifiers })
+
+ // Include this version if there's only one with this build identifier
+ return availableXcodesWithIdenticalBuildIdentifiers.count == 1 ||
+ // Or if there's more than one with this build identifier and this is the release version
+ availableXcodesWithIdenticalBuildIdentifiers.count > 1 && availableXcode.version.prereleaseIdentifiers.isEmpty
+ }
.map { availableXcode -> Xcode in
let installedXcode = installedXcodes.first(where: { installedXcode in
availableXcode.version.isEquivalent(to: installedXcode.version)
})
+
+ let identicalBuilds: [Version]
+ let prereleaseAvailableXcodesWithIdenticalBuildIdentifiers = availableXcodes
+ .filter {
+ return $0.version.buildMetadataIdentifiers == availableXcode.version.buildMetadataIdentifiers &&
+ !$0.version.prereleaseIdentifiers.isEmpty &&
+ // If we don't have the build identifier, don't consider this as a potential identical build
+ !$0.version.buildMetadataIdentifiers.isEmpty
+ }
+ // If this is the release version, add the identical builds to it
+ if !prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.isEmpty, availableXcode.version.prereleaseIdentifiers.isEmpty {
+ identicalBuilds = [availableXcode.version] + prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.map(\.version)
+ } else {
+ identicalBuilds = []
+ }
// If the existing install state is "installing", keep it
let existingXcodeInstallState = allXcodes.first { $0.version == availableXcode.version && $0.installState.installing }?.installState
@@ -453,6 +488,7 @@ class AppState: ObservableObject {
return Xcode(
version: availableXcode.version,
+ identicalBuilds: identicalBuilds,
installState: existingXcodeInstallState ?? defaultXcodeInstallState,
selected: installedXcode != nil && selectedXcodePath?.hasPrefix(installedXcode!.path.string) == true,
icon: (installedXcode?.path.string).map(NSWorkspace.shared.icon(forFile:)),
diff --git a/Xcodes/Backend/Version+Xcode.swift b/Xcodes/Backend/Version+Xcode.swift
index 77879a5..5166fba 100644
--- a/Xcodes/Backend/Version+Xcode.swift
+++ b/Xcodes/Backend/Version+Xcode.swift
@@ -50,7 +50,13 @@ public extension Version {
}
if !prereleaseIdentifiers.isEmpty {
base += " " + prereleaseIdentifiers
- .map { $0.replacingOccurrences(of: "-", with: " ").capitalized.replacingOccurrences(of: "Gm", with: "GM") }
+ .map { identifiers in
+ identifiers
+ .replacingOccurrences(of: "-", with: " ")
+ .capitalized
+ .replacingOccurrences(of: "Gm", with: "GM")
+ .replacingOccurrences(of: "Rc", with: "RC")
+ }
.joined(separator: " ")
}
return base
diff --git a/Xcodes/Backend/Version+XcodeReleases.swift b/Xcodes/Backend/Version+XcodeReleases.swift
index 6139d63..4ddd4f3 100644
--- a/Xcodes/Backend/Version+XcodeReleases.swift
+++ b/Xcodes/Backend/Version+XcodeReleases.swift
@@ -25,7 +25,7 @@ extension Version {
versionString += ".\(dp)"
}
case .gm:
- versionString += "-GM"
+ break
case let .gmSeed(gmSeed):
versionString += "-GM.Seed"
if gmSeed > 1 {
diff --git a/Xcodes/Backend/Xcode.swift b/Xcodes/Backend/Xcode.swift
index a5da1bc..ebf1f23 100644
--- a/Xcodes/Backend/Xcode.swift
+++ b/Xcodes/Backend/Xcode.swift
@@ -6,6 +6,8 @@ import struct XCModel.Compilers
struct Xcode: Identifiable, CustomStringConvertible {
let version: Version
+ /// Other Xcode versions that have the same build identifier
+ let identicalBuilds: [Version]
var installState: XcodeInstallState
let selected: Bool
let icon: NSImage?
@@ -17,6 +19,7 @@ struct Xcode: Identifiable, CustomStringConvertible {
init(
version: Version,
+ identicalBuilds: [Version] = [],
installState: XcodeInstallState,
selected: Bool,
icon: NSImage?,
@@ -27,6 +30,7 @@ struct Xcode: Identifiable, CustomStringConvertible {
downloadFileSize: Int64? = nil
) {
self.version = version
+ self.identicalBuilds = identicalBuilds
self.installState = installState
self.selected = selected
self.icon = icon
diff --git a/Xcodes/Frontend/XcodeList/InfoPane.swift b/Xcodes/Frontend/XcodeList/InfoPane.swift
index 382138b..53d5088 100644
--- a/Xcodes/Frontend/XcodeList/InfoPane.swift
+++ b/Xcodes/Frontend/XcodeList/InfoPane.swift
@@ -11,8 +11,8 @@ struct InfoPane: View {
@SwiftUI.Environment(\.openURL) var openURL: OpenURLAction
var body: some View {
- Group {
- if let xcode = appState.allXcodes.first(where: { $0.id == selectedXcodeID }) {
+ if let xcode = appState.allXcodes.first(where: { $0.id == selectedXcodeID }) {
+ ScrollView {
VStack(spacing: 16) {
icon(for: xcode)
@@ -53,6 +53,7 @@ struct InfoPane: View {
Divider()
releaseNotes(for: xcode)
+ identicalBuilds(for: xcode)
compatibility(for: xcode)
sdks(for: xcode)
compilers(for: xcode)
@@ -60,12 +61,13 @@ struct InfoPane: View {
Spacer()
}
- } else {
- empty
+ .padding()
}
+ .frame(minWidth: 200, maxWidth: .infinity)
+ } else {
+ empty
+ .frame(minWidth: 200, maxWidth: .infinity)
}
- .padding()
- .frame(minWidth: 200, maxWidth: .infinity)
}
@ViewBuilder
@@ -80,6 +82,34 @@ struct InfoPane: View {
}
}
+ @ViewBuilder
+ private func identicalBuilds(for xcode: Xcode) -> some View {
+ if !xcode.identicalBuilds.isEmpty {
+ VStack(alignment: .leading) {
+ HStack {
+ Text("Identical Builds")
+ Image(systemName: "square.fill.on.square.fill")
+ .foregroundColor(.secondary)
+ .accessibility(hidden: true)
+ .help("Sometimes a prerelease and release version are the exact same build. Xcodes will automatically display these versions together.")
+ }
+ .font(.headline)
+
+ ForEach(xcode.identicalBuilds, id: \.description) { version in
+ Text("• \(version.appleDescription)")
+ .font(.subheadline)
+ }
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .accessibilityElement()
+ .accessibility(label: Text("Identical Builds"))
+ .accessibility(value: Text(xcode.identicalBuilds.map(\.appleDescription).joined(separator: ", ")))
+ .accessibility(hint: Text("Sometimes a prerelease and release version are the exact same build. Xcodes will automatically display these versions together."))
+ } else {
+ EmptyView()
+ }
+ }
+
@ViewBuilder
private func releaseNotes(for xcode: Xcode) -> some View {
if let releaseNotesURL = xcode.releaseNotesURL {
@@ -183,13 +213,11 @@ struct InfoPane: View {
@ViewBuilder
private var empty: some View {
- VStack {
- Spacer()
- Text("No Xcode Selected")
- .font(.title)
- .foregroundColor(.secondary)
- Spacer()
- }
+ Text("No Xcode Selected")
+ .font(.title)
+ .foregroundColor(.secondary)
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ .padding()
}
}
diff --git a/Xcodes/Frontend/XcodeList/XcodeListView.swift b/Xcodes/Frontend/XcodeList/XcodeListView.swift
index f88d71c..4905e78 100644
--- a/Xcodes/Frontend/XcodeList/XcodeListView.swift
+++ b/Xcodes/Frontend/XcodeList/XcodeListView.swift
@@ -32,7 +32,7 @@ struct XcodeListView: View {
var body: some View {
List(visibleXcodes, selection: $selectedXcodeID) { xcode in
- XcodeListViewRow(xcode: xcode, selected: selectedXcodeID == xcode.id)
+ XcodeListViewRow(xcode: xcode, selected: selectedXcodeID == xcode.id, appState: appState)
}
}
}
@@ -44,6 +44,7 @@ struct XcodeListView_Previews: PreviewProvider {
.environmentObject({ () -> AppState in
let a = AppState()
a.allXcodes = [
+ Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [Version("12.0.0+1234A")!, Version("12.0.0-RC+1234A")!], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil),
Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, icon: nil),
Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, icon: nil),
diff --git a/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift b/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift
index 1d7f099..723d690 100644
--- a/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift
+++ b/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift
@@ -3,17 +3,28 @@ import SwiftUI
import Version
struct XcodeListViewRow: View {
- @EnvironmentObject var appState: AppState
let xcode: Xcode
let selected: Bool
+ let appState: AppState
var body: some View {
HStack {
appIconView(for: xcode)
VStack(alignment: .leading) {
- Text(verbatim: "\(xcode.description) \(xcode.version.buildMetadataIdentifiersDisplay)")
- .font(.body)
+ HStack {
+ Text(verbatim: "\(xcode.description) \(xcode.version.buildMetadataIdentifiersDisplay)")
+ .font(.body)
+
+ if !xcode.identicalBuilds.isEmpty {
+ Image(systemName: "square.fill.on.square.fill")
+ .font(.subheadline)
+ .foregroundColor(.secondary)
+ .accessibility(label: Text("Identical Builds"))
+ .accessibility(value: Text(xcode.identicalBuilds.map(\.appleDescription).joined(separator: ", ")))
+ .help("Sometimes a prerelease and release version are the exact same build. Xcodes will automatically display these versions together.")
+ }
+ }
if case let .installed(path) = xcode.installState {
Text(verbatim: path.string)
@@ -112,29 +123,39 @@ struct XcodeListViewRow_Previews: PreviewProvider {
Group {
XcodeListViewRow(
xcode: Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil),
- selected: false
+ selected: false,
+ appState: AppState()
)
XcodeListViewRow(
xcode: Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, icon: nil),
- selected: false
+ selected: false,
+ appState: AppState()
)
XcodeListViewRow(
xcode: Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, icon: nil),
- selected: false
+ selected: false,
+ appState: AppState()
)
XcodeListViewRow(
xcode: Xcode(version: Version("12.0.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
- selected: false
+ selected: false,
+ appState: AppState()
)
XcodeListViewRow(
xcode: Xcode(version: Version("12.0.0+1234A")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
- selected: false
+ selected: false,
+ appState: AppState()
+ )
+
+ XcodeListViewRow(
+ xcode: Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [Version("12.0.0-RC+1234A")!], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
+ selected: false,
+ appState: AppState()
)
}
- .environmentObject(AppState())
}
}
diff --git a/XcodesTests/AppStateUpdateTests.swift b/XcodesTests/AppStateUpdateTests.swift
index 997108e..39d0d21 100644
--- a/XcodesTests/AppStateUpdateTests.swift
+++ b/XcodesTests/AppStateUpdateTests.swift
@@ -46,6 +46,14 @@ class AppStateUpdateTests: XCTestCase {
}
func testDeterminesIfInstalledByBuildMetadataAlone() throws {
+ Current.defaults.string = { key in
+ if key == "dataSource" {
+ return "apple"
+ } else {
+ return nil
+ }
+ }
+
subject.allXcodes = [
]
@@ -66,6 +74,14 @@ class AppStateUpdateTests: XCTestCase {
}
func testAdjustedVersionsAreUsedToLookupAvailableXcode() throws {
+ Current.defaults.string = { key in
+ if key == "dataSource" {
+ return "apple"
+ } else {
+ return nil
+ }
+ }
+
subject.allXcodes = [
]
@@ -105,13 +121,141 @@ class AppStateUpdateTests: XCTestCase {
XCTAssertEqual(subject.allXcodes.map(\.version), [Version("1.2.3")!, Version("0.0.0+ABC123")!])
}
- func testFilterReleasesThatMatchPrereleases() {
- let result = subject.filterPrereleasesThatMatchReleaseBuildMetadataIdentifiers(
- [
- AvailableXcode(version: Version("12.3.0+12C33")!, url: URL(string: "https://apple.com")!, filename: "Xcode_12.3.xip", releaseDate: nil),
- AvailableXcode(version: Version("12.3.0-RC+12C33")!, url: URL(string: "https://apple.com")!, filename: "Xcode_12.3_RC_1.xip", releaseDate: nil),
- ]
+
+ func testIdenticalBuilds_KeepsReleaseVersion_WithNeitherInstalled() {
+ Current.defaults.string = { key in
+ if key == "dataSource" {
+ return "xcodeReleases"
+ } else {
+ return nil
+ }
+ }
+
+ subject.allXcodes = [
+ ]
+
+ subject.updateAllXcodes(
+ availableXcodes: [
+ AvailableXcode(version: Version("12.4.0+12D4e")!, url: URL(string: "https://apple.com/xcode.xip")!, filename: "mock.xip", releaseDate: nil),
+ AvailableXcode(version: Version("12.4.0-RC+12D4e")!, url: URL(string: "https://apple.com/xcode.xip")!, filename: "mock.xip", releaseDate: nil),
+ ],
+ installedXcodes: [
+ ],
+ selectedXcodePath: nil
)
- XCTAssertEqual(result.map(\.version), [Version("12.3.0+12C33")])
+
+ XCTAssertEqual(subject.allXcodes.map(\.version), [Version("12.4.0+12D4e")!])
+ XCTAssertEqual(subject.allXcodes.map(\.identicalBuilds), [[Version("12.4.0+12D4e")!, Version("12.4.0-RC+12D4e")!]])
+ }
+
+ func testIdenticalBuilds_DoNotMergeReleaseVersions() {
+ Current.defaults.string = { key in
+ if key == "dataSource" {
+ return "xcodeReleases"
+ } else {
+ return nil
+ }
+ }
+
+ subject.allXcodes = [
+ ]
+
+ subject.updateAllXcodes(
+ availableXcodes: [
+ AvailableXcode(version: Version("3.2.3+10M2262")!, url: URL(string: "https://apple.com/xcode.xip")!, filename: "mock.xip", releaseDate: nil),
+ AvailableXcode(version: Version("3.2.3+10M2262")!, url: URL(string: "https://apple.com/xcode.xip")!, filename: "mock.xip", releaseDate: nil),
+ ],
+ installedXcodes: [
+ ],
+ selectedXcodePath: nil
+ )
+
+ XCTAssertEqual(subject.allXcodes.map(\.version), [Version("3.2.3+10M2262")!, Version("3.2.3+10M2262")!])
+ XCTAssertEqual(subject.allXcodes.map(\.identicalBuilds), [[], []])
+ }
+
+ func testIdenticalBuilds_KeepsReleaseVersion_WithPrereleaseInstalled() {
+ Current.defaults.string = { key in
+ if key == "dataSource" {
+ return "xcodeReleases"
+ } else {
+ return nil
+ }
+ }
+
+ subject.allXcodes = [
+ ]
+
+ Current.files.contentsAtPath = { path in
+ if path.contains("Info.plist") {
+ return """
+
+
+
+
+ CFBundleIdentifier
+ com.apple.dt.Xcode
+ CFBundleShortVersionString
+ 12.4.0
+
+
+ """.data(using: .utf8)
+ }
+ else if path.contains("version.plist") {
+ return """
+
+
+
+
+ ProductBuildVersion
+ 12D4e
+
+
+ """.data(using: .utf8)
+ }
+ else {
+ return nil
+ }
+ }
+
+ subject.updateAllXcodes(
+ availableXcodes: [
+ AvailableXcode(version: Version("12.4.0+12D4e")!, url: URL(string: "https://apple.com/xcode.xip")!, filename: "mock.xip", releaseDate: nil),
+ AvailableXcode(version: Version("12.4.0-RC+12D4e")!, url: URL(string: "https://apple.com/xcode.xip")!, filename: "mock.xip", releaseDate: nil),
+ ],
+ installedXcodes: [
+ InstalledXcode(path: Path("/Applications/Xcode-12.4.0-RC.app")!)!
+ ],
+ selectedXcodePath: nil
+ )
+
+ XCTAssertEqual(subject.allXcodes.map(\.version), [Version("12.4.0+12D4e")!])
+ XCTAssertEqual(subject.allXcodes.map(\.identicalBuilds), [[Version("12.4.0+12D4e")!, Version("12.4.0-RC+12D4e")!]])
+ }
+
+ func testIdenticalBuilds_AppleDataSource_DoNotMergeVersionsWithoutBuildIdentifiers() {
+ Current.defaults.string = { key in
+ if key == "dataSource" {
+ return "apple"
+ } else {
+ return nil
+ }
+ }
+
+ subject.allXcodes = [
+ ]
+
+ subject.updateAllXcodes(
+ availableXcodes: [
+ AvailableXcode(version: Version("12.4.0")!, url: URL(string: "https://apple.com/xcode.xip")!, filename: "mock.xip", releaseDate: nil),
+ AvailableXcode(version: Version("12.3.0-RC")!, url: URL(string: "https://apple.com/xcode.xip")!, filename: "mock.xip", releaseDate: nil),
+ ],
+ installedXcodes: [
+ ],
+ selectedXcodePath: nil
+ )
+
+ XCTAssertEqual(subject.allXcodes.map(\.version), [Version("12.4.0")!, Version("12.3.0-RC")!])
+ XCTAssertEqual(subject.allXcodes.map(\.identicalBuilds), [[], []])
}
}