From 3ac2557b855e90930af212f1da6dfbea6869a939 Mon Sep 17 00:00:00 2001 From: udumft Date: Thu, 22 Sep 2022 18:48:47 +0300 Subject: [PATCH 1/7] vk-NAVIOS-374-attribute-options: added AttributeOptions.customOptions and manipulation methods to allow custom options to be processed. --- .../MapboxDirections/AttributeOptions.swift | 111 ++++++++++++++++++ .../AttributeOptionsTests.swift | 103 ++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 Tests/MapboxDirectionsTests/AttributeOptionsTests.swift diff --git a/Sources/MapboxDirections/AttributeOptions.swift b/Sources/MapboxDirections/AttributeOptions.swift index f9096a29f..2d1acfc49 100644 --- a/Sources/MapboxDirections/AttributeOptions.swift +++ b/Sources/MapboxDirections/AttributeOptions.swift @@ -8,6 +8,14 @@ import Foundation public struct AttributeOptions: OptionSet, CustomStringConvertible { public var rawValue: Int + /** + Provides a text value description for user-provided options. + + `AttributeOptions` will recognize a custom option if it's unique `rawValue` flag is set and `customOptions` contains a description for that flag. + Use `update(customOption:)` methid to append a custom option. + */ + public var customOptions: [Int: String] = [:] + public init(rawValue: Int) { self.rawValue = rawValue } @@ -106,6 +114,11 @@ public struct AttributeOptions: OptionSet, CustomStringConvertible { if contains(.numericCongestionLevel) { descriptions.append("congestion_numeric") } + for (key, value) in customOptions { + if rawValue & key != 0 { + descriptions.append(value) + } + } return descriptions.joined(separator: ",") } } @@ -122,3 +135,101 @@ extension AttributeOptions: Codable { self = AttributeOptions(descriptions: descriptions)! } } + +extension AttributeOptions { + + private func conflictingOption(in member: Self.Element, at key: Int) -> Bool { + return customOptions[key] != nil && customOptions[key] != member.customOptions[key] + } + + public func contains(_ member: Self.Element) -> Bool { + let intersection = rawValue & member.rawValue + var containsCustomKeys = true + for offset in 0.. (inserted: Bool, memberAfterInsert: Self.Element) { + let intersection = rawValue & newMember.rawValue + + guard intersection == 0 else { + var result = Self(rawValue: intersection) + result.customOptions = customOptions.filter({ element in intersection & element.key != 0 }) + return (false, result) + } + + rawValue = rawValue | newMember.rawValue + + customOptions.merge(newMember.customOptions) { current, _ in current } + + return (true, newMember) + } + + @discardableResult @inlinable + public mutating func remove(_ member: Self.Element) -> Self.Element? { + let originalRawValue = rawValue + let customKeysToPreserve = customOptions.reduce(0) { partialResult, item in + if member.customOptions[item.key] == nil || + member.customOptions[item.key] == customOptions[item.key] { + return partialResult + } else { + return partialResult + item.key + } + } + rawValue = (rawValue ^ (rawValue & member.rawValue)) | customKeysToPreserve + + let intersectionOptions = customOptions.filter({ element in customKeysToPreserve & element.key != 0 }) + customOptions = customOptions.filter({ element in customKeysToPreserve & element.key == 0 }) + + guard originalRawValue != rawValue else { return nil } + var result = Self(rawValue: originalRawValue ^ rawValue) + result.customOptions = intersectionOptions + return result + } + + @discardableResult @inlinable + public mutating func update(with newMember: Self.Element) -> Self.Element? { + let intersection = rawValue & newMember.rawValue + rawValue = rawValue | newMember.rawValue + + customOptions.merge(newMember.customOptions) { current, _ in current } + + guard intersection != 0 else { return nil } + + var result = Self(rawValue: intersection) + result.customOptions = customOptions.filter({ element in intersection & element.key != 0 }) + return result + } + + /// Inserts the given element into the set unconditionally. + /// + /// If an element equal to `customOption` is already contained in the set, + /// `customOption` replaces the existing element. Otherwise - updates the set contents and fills `customOptions` accordingly. + /// + /// - Parameter customOption: An element to insert into the set. + /// - Returns: For ordinary sets, an element equal to `customOption` if the set + /// already contained such a member; otherwise, `nil`. In some cases, the + /// returned element may be distinguishable from `customOption` by identity + /// comparison or some other means. + /// + /// For sets where the set type and element type are the same, like + /// `OptionSet` types, this method returns any intersection between the + /// set and `[customOption]`, or `nil` if the intersection is empty. + @discardableResult @inlinable + mutating public func update(customOption: (Int, String)) -> Self.Element? { + let result = update(with: .init(rawValue: customOption.0)) + customOptions[customOption.0] = customOption.1 + return result + } + +} diff --git a/Tests/MapboxDirectionsTests/AttributeOptionsTests.swift b/Tests/MapboxDirectionsTests/AttributeOptionsTests.swift new file mode 100644 index 000000000..50bbba42d --- /dev/null +++ b/Tests/MapboxDirectionsTests/AttributeOptionsTests.swift @@ -0,0 +1,103 @@ +import XCTest +import MapboxDirections + +class AttributeOptionsTests: XCTestCase { + func testInsertion() { + var options = AttributeOptions() + var options2merge = AttributeOptions(descriptions: ["speed"])! + var optionsWithCustom = AttributeOptions() + + optionsWithCustom.update(customOption: (1<<7, "Custom7")) + options.update(with: .distance) + options.update(with: optionsWithCustom) + options2merge.update(customOption: (1<<8, "Custom_8")) + + options.update(with: options2merge) + + // Check merged options are collected + XCTAssertEqual(options.rawValue, + AttributeOptions.speed.rawValue + AttributeOptions.distance.rawValue + 1<<7 + 1<<8) + XCTAssertEqual(options.description.split(separator: ",").count, + 4) + XCTAssertEqual(optionsWithCustom, + options.update(customOption: (1<<7, "Custom7"))) + + // insert existing default + XCTAssertFalse(options.insert(.distance).inserted) + // insert existing custom + XCTAssertFalse(options.insert(optionsWithCustom).inserted) + // insert conflicting custom + var optionsWithConflict = AttributeOptions() + optionsWithConflict.update(customOption: (optionsWithCustom.rawValue, "Another custom name")) + XCTAssertFalse(options.insert(optionsWithConflict).inserted) + // insert custom with default raw + optionsWithConflict.rawValue = AttributeOptions.distance.rawValue + XCTAssertFalse(options.insert(optionsWithConflict).inserted) + } + + func testContains() { + var options = AttributeOptions() + options.update(with: .expectedTravelTime) + options.update(customOption: (1<<9, "Custom")) + + XCTAssertTrue(options.contains(.init(rawValue: AttributeOptions.expectedTravelTime.rawValue))) + XCTAssertFalse(options.contains(.congestionLevel)) + + var wrongCustomOption = AttributeOptions() + wrongCustomOption.update(customOption: (1<<9, "Wrong name")) + XCTAssertFalse(options.contains(wrongCustomOption)) + + var correctCustomOption = AttributeOptions() + correctCustomOption.update(customOption: (1<<9, "Custom")) + XCTAssertTrue(options.contains(correctCustomOption)) + + XCTAssertTrue(options.contains(.init(rawValue: 1<<9))) + } + + func testRemove() { + var preservedOption = AttributeOptions() + preservedOption.update(customOption: (1<<12, "Should be preserved")) + var options = AttributeOptions() + options.update(with: .congestionLevel) + options.update(with: .distance) + options.update(customOption: (1<<10, "Custom")) + options.update(with: preservedOption) + + // Removing default item + let distance = options.remove(AttributeOptions(descriptions: ["distance"])!) + + XCTAssertEqual(distance?.rawValue, AttributeOptions.distance.rawValue) + XCTAssertTrue(options.contains(.congestionLevel)) + XCTAssertTrue(options.contains(preservedOption)) + + // Removing not existing item by raw value + XCTAssertNil(options.remove(AttributeOptions(rawValue: 1))) + XCTAssertTrue(options.contains(.congestionLevel)) + XCTAssertTrue(options.contains(preservedOption)) + + // Removing custom option with incorrect name + var wrongCustomOption = AttributeOptions() + wrongCustomOption.update(customOption: (1<<10, "Wrong name")) + + XCTAssertNil(options.remove(wrongCustomOption)) + XCTAssertTrue(options.contains(.congestionLevel)) + XCTAssertTrue(options.contains(preservedOption)) + + // Removing existing custom option + var correctCustomOption = AttributeOptions() + correctCustomOption.update(customOption: (1<<10, "Custom")) + + XCTAssertEqual(options.remove(correctCustomOption), correctCustomOption) + XCTAssertTrue(options.contains(.congestionLevel)) + XCTAssertTrue(options.contains(preservedOption)) + + // Removing custom option with default raw value + var customOptionWithDefaultRaw = AttributeOptions() + customOptionWithDefaultRaw.update(customOption: (AttributeOptions.distance.rawValue, "Not a distance")) + XCTAssertNil(options.remove(customOptionWithDefaultRaw)) + + // Removing custom option by raw value only + options.update(with: correctCustomOption) + XCTAssertEqual(options.remove(.init(rawValue: 1<<10)), correctCustomOption) + } +} From 8643992c6e17bc43f268a037a93fe0728b0ce45f Mon Sep 17 00:00:00 2001 From: udumft Date: Wed, 5 Oct 2022 15:36:53 +0300 Subject: [PATCH 2/7] vk-NAVIOS-374-attribute-options: added CustomValueOptionSet to be a base for AttributeOptions and other similar types to allow custom options to be set. --- MapboxDirections.xcodeproj/project.pbxproj | 30 + .../MapboxDirections/AttributeOptions.swift | 104 +-- .../CustomValueOptionSet.swift | 629 ++++++++++++++++++ .../AttributeOptionsTests.swift | 46 +- .../CustomStringOptionSetTests.swift | 117 ++++ 5 files changed, 812 insertions(+), 114 deletions(-) create mode 100644 Sources/MapboxDirections/CustomValueOptionSet.swift create mode 100644 Tests/MapboxDirectionsTests/CustomStringOptionSetTests.swift diff --git a/MapboxDirections.xcodeproj/project.pbxproj b/MapboxDirections.xcodeproj/project.pbxproj index 60f19bbca..7217e8848 100644 --- a/MapboxDirections.xcodeproj/project.pbxproj +++ b/MapboxDirections.xcodeproj/project.pbxproj @@ -14,6 +14,18 @@ 2B28E22527EDB2AA0029E4C1 /* ForeignMemberContainerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B28E22227EDB2A90029E4C1 /* ForeignMemberContainerTests.swift */; }; 2B39DD40270F034700ED68E4 /* CodingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B39DD3F270F034700ED68E4 /* CodingOperation.swift */; }; 2B4383022549C22700A3E38B /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B4383002549C22700A3E38B /* main.swift */; }; + 2B46024528EDB1670008A624 /* CustomValueOptionSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024428EDB1670008A624 /* CustomValueOptionSet.swift */; }; + 2B46024628EDB1670008A624 /* CustomValueOptionSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024428EDB1670008A624 /* CustomValueOptionSet.swift */; }; + 2B46024728EDB1670008A624 /* CustomValueOptionSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024428EDB1670008A624 /* CustomValueOptionSet.swift */; }; + 2B46024828EDB1670008A624 /* CustomValueOptionSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024428EDB1670008A624 /* CustomValueOptionSet.swift */; }; + 2B46024B28EDB18B0008A624 /* CustomStringOptionSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024928EDB18B0008A624 /* CustomStringOptionSetTests.swift */; }; + 2B46024C28EDB18B0008A624 /* CustomStringOptionSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024928EDB18B0008A624 /* CustomStringOptionSetTests.swift */; }; + 2B46024D28EDB18B0008A624 /* CustomStringOptionSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024928EDB18B0008A624 /* CustomStringOptionSetTests.swift */; }; + 2B46024E28EDB18B0008A624 /* CustomStringOptionSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024928EDB18B0008A624 /* CustomStringOptionSetTests.swift */; }; + 2B46024F28EDB18B0008A624 /* AttributeOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024A28EDB18B0008A624 /* AttributeOptionsTests.swift */; }; + 2B46025028EDB18B0008A624 /* AttributeOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024A28EDB18B0008A624 /* AttributeOptionsTests.swift */; }; + 2B46025128EDB18B0008A624 /* AttributeOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024A28EDB18B0008A624 /* AttributeOptionsTests.swift */; }; + 2B46025228EDB18B0008A624 /* AttributeOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024A28EDB18B0008A624 /* AttributeOptionsTests.swift */; }; 2B46DB0C27EDF8580068C893 /* RouteRefreshResponseWithForeignMembers.json in Resources */ = {isa = PBXBuildFile; fileRef = 2B46DB0B27EDF8580068C893 /* RouteRefreshResponseWithForeignMembers.json */; }; 2B46DB0D27EDF8580068C893 /* RouteRefreshResponseWithForeignMembers.json in Resources */ = {isa = PBXBuildFile; fileRef = 2B46DB0B27EDF8580068C893 /* RouteRefreshResponseWithForeignMembers.json */; }; 2B46DB0E27EDF8580068C893 /* RouteRefreshResponseWithForeignMembers.json in Resources */ = {isa = PBXBuildFile; fileRef = 2B46DB0B27EDF8580068C893 /* RouteRefreshResponseWithForeignMembers.json */; }; @@ -495,6 +507,9 @@ 2B28E22227EDB2A90029E4C1 /* ForeignMemberContainerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForeignMemberContainerTests.swift; sourceTree = ""; }; 2B39DD3F270F034700ED68E4 /* CodingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CodingOperation.swift; path = Sources/MapboxDirectionsCLI/CodingOperation.swift; sourceTree = ""; }; 2B4383002549C22700A3E38B /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = main.swift; path = Sources/MapboxDirectionsCLI/main.swift; sourceTree = ""; }; + 2B46024428EDB1670008A624 /* CustomValueOptionSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomValueOptionSet.swift; sourceTree = ""; }; + 2B46024928EDB18B0008A624 /* CustomStringOptionSetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomStringOptionSetTests.swift; sourceTree = ""; }; + 2B46024A28EDB18B0008A624 /* AttributeOptionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributeOptionsTests.swift; sourceTree = ""; }; 2B46DB0B27EDF8580068C893 /* RouteRefreshResponseWithForeignMembers.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = RouteRefreshResponseWithForeignMembers.json; sourceTree = ""; }; 2B5407EC2451B17E006C820B /* RouteRefreshResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteRefreshResponse.swift; sourceTree = ""; }; 2B5407F12452FA8C006C820B /* RefreshedRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshedRoute.swift; sourceTree = ""; }; @@ -835,6 +850,7 @@ C51538CB1E807FF00093FF3E /* AttributeOptions.swift */, C58EA7A91E9D7EAD008F98CE /* Congestion.swift */, 2B9F387F272AE23A001DBA12 /* Credentials.swift */, + 2B46024428EDB1670008A624 /* CustomValueOptionSet.swift */, DD6254731AE70CB700017857 /* Directions.swift */, 4392557523440EC2006EEE88 /* DirectionsError.swift */, C59094BE203B800300EB2417 /* DirectionsOptions.swift */, @@ -885,7 +901,9 @@ children = ( DA6C9DAD1CAEC93800094FBC /* Fixtures */, C5247D701E818A24004B6154 /* AnnotationTests.swift */, + 2B46024A28EDB18B0008A624 /* AttributeOptionsTests.swift */, 43D992FB2437B8D2008A2D74 /* CredentialsTests.swift */, + 2B46024928EDB18B0008A624 /* CustomStringOptionSetTests.swift */, DAD06E34239F0B19001A917D /* DirectionsErrorTests.swift */, DA1A110A1D01045E009F82FA /* DirectionsTests.swift */, DA6C9DB11CAECA0E00094FBC /* Fixture.swift */, @@ -1445,14 +1463,17 @@ 431E93C0234664A200A71B44 /* DrivingSide.swift in Sources */, C5DAAC9B2019167C001F9261 /* Match.swift in Sources */, 2B5407EE2451B17E006C820B /* RouteRefreshResponse.swift in Sources */, + 2B46024C28EDB18B0008A624 /* CustomStringOptionSetTests.swift in Sources */, 35DBF010217E17A30009D2AE /* HTTPURLResponse.swift in Sources */, 8D43467A219E15C3008B7BF3 /* Double.swift in Sources */, C53A02261E92C26E009837BD /* AttributeOptions.swift in Sources */, DA1A10C91D00F969009F82FA /* RouteLeg.swift in Sources */, C52552BA1FA15D5E00B1545C /* VisualInstructionBanner.swift in Sources */, + 2B46024628EDB1670008A624 /* CustomValueOptionSet.swift in Sources */, 35828C9F217A003F00ED546E /* OfflineDirections.swift in Sources */, 2B5F0E01273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */, DAE7EA95230B5FD10003B211 /* Measurement.swift in Sources */, + 2B46025028EDB18B0008A624 /* AttributeOptionsTests.swift in Sources */, 2E44711827C4C80B0041CB84 /* SilentWaypoint.swift in Sources */, C5DAAC9F20195AAE001F9261 /* Tracepoint.swift in Sources */, 2B9F3882272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */, @@ -1542,14 +1563,17 @@ 431E93C1234664A200A71B44 /* DrivingSide.swift in Sources */, C5DAAC9C2019167D001F9261 /* Match.swift in Sources */, 2B5407EF2451B17E006C820B /* RouteRefreshResponse.swift in Sources */, + 2B46024D28EDB18B0008A624 /* CustomStringOptionSetTests.swift in Sources */, 35DBF011217E17A30009D2AE /* HTTPURLResponse.swift in Sources */, 8D43467B219E15C4008B7BF3 /* Double.swift in Sources */, C53A02271E92C26F009837BD /* AttributeOptions.swift in Sources */, DA1A10EF1D010247009F82FA /* RouteLeg.swift in Sources */, C52552BB1FA15D5F00B1545C /* VisualInstructionBanner.swift in Sources */, + 2B46024728EDB1670008A624 /* CustomValueOptionSet.swift in Sources */, 35828CA0217A003F00ED546E /* OfflineDirections.swift in Sources */, 2B5F0E02273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */, DAE7EA96230B5FD10003B211 /* Measurement.swift in Sources */, + 2B46025128EDB18B0008A624 /* AttributeOptionsTests.swift in Sources */, 2E44711927C4C80B0041CB84 /* SilentWaypoint.swift in Sources */, C5DAACA020195AAF001F9261 /* Tracepoint.swift in Sources */, 2B9F3883272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */, @@ -1639,14 +1663,17 @@ 431E93C2234664A200A71B44 /* DrivingSide.swift in Sources */, C5DAAC9D2019167E001F9261 /* Match.swift in Sources */, 2B5407F02451B17E006C820B /* RouteRefreshResponse.swift in Sources */, + 2B46024E28EDB18B0008A624 /* CustomStringOptionSetTests.swift in Sources */, 35DBF012217E17A30009D2AE /* HTTPURLResponse.swift in Sources */, 8D43467C219E15C6008B7BF3 /* Double.swift in Sources */, C53A02281E92C271009837BD /* AttributeOptions.swift in Sources */, DA1A11061D0103A3009F82FA /* RouteLeg.swift in Sources */, C52552BC1FA15D6000B1545C /* VisualInstructionBanner.swift in Sources */, + 2B46024828EDB1670008A624 /* CustomValueOptionSet.swift in Sources */, 35828CA1217A003F00ED546E /* OfflineDirections.swift in Sources */, 2B5F0E03273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */, DAE7EA97230B5FD10003B211 /* Measurement.swift in Sources */, + 2B46025228EDB18B0008A624 /* AttributeOptionsTests.swift in Sources */, 2E44711A27C4C80B0041CB84 /* SilentWaypoint.swift in Sources */, C5DAACA120195AAF001F9261 /* Tracepoint.swift in Sources */, 2B9F3884272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */, @@ -1701,14 +1728,17 @@ 431E93BF234664A200A71B44 /* DrivingSide.swift in Sources */, C51538CC1E807FF00093FF3E /* AttributeOptions.swift in Sources */, 2B5407ED2451B17E006C820B /* RouteRefreshResponse.swift in Sources */, + 2B46024B28EDB18B0008A624 /* CustomStringOptionSetTests.swift in Sources */, 35DBF00F217E17A30009D2AE /* HTTPURLResponse.swift in Sources */, 8D434679219E1167008B7BF3 /* Double.swift in Sources */, DA2E03EB1CB0E13D00D1269A /* RouteOptions.swift in Sources */, C582BA2E2073ED6300647DAA /* Array.swift in Sources */, C52552B91FA15D5900B1545C /* VisualInstructionBanner.swift in Sources */, + 2B46024528EDB1670008A624 /* CustomValueOptionSet.swift in Sources */, 35828C9E217A003F00ED546E /* OfflineDirections.swift in Sources */, 2B5F0E00273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */, DAE7EA94230B5FD10003B211 /* Measurement.swift in Sources */, + 2B46024F28EDB18B0008A624 /* AttributeOptionsTests.swift in Sources */, 2E44711727C4C80B0041CB84 /* SilentWaypoint.swift in Sources */, C59426071F1EA6C400C8E59C /* RoadClasses.swift in Sources */, 2B9F3881272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */, diff --git a/Sources/MapboxDirections/AttributeOptions.swift b/Sources/MapboxDirections/AttributeOptions.swift index 2d1acfc49..30bf76c16 100644 --- a/Sources/MapboxDirections/AttributeOptions.swift +++ b/Sources/MapboxDirections/AttributeOptions.swift @@ -5,7 +5,7 @@ import Foundation When any of the attributes are specified, the resulting route leg contains one attribute value for each segment in leg, where a segment is the straight line between two coordinates in the route leg’s full geometry. */ -public struct AttributeOptions: OptionSet, CustomStringConvertible { +public struct AttributeOptions: CustomValueOptionSet, CustomStringConvertible { public var rawValue: Int /** @@ -20,6 +20,10 @@ public struct AttributeOptions: OptionSet, CustomStringConvertible { self.rawValue = rawValue } + public init() { + rawValue = 0 + } + /** Distance (in meters) along the segment. @@ -135,101 +139,3 @@ extension AttributeOptions: Codable { self = AttributeOptions(descriptions: descriptions)! } } - -extension AttributeOptions { - - private func conflictingOption(in member: Self.Element, at key: Int) -> Bool { - return customOptions[key] != nil && customOptions[key] != member.customOptions[key] - } - - public func contains(_ member: Self.Element) -> Bool { - let intersection = rawValue & member.rawValue - var containsCustomKeys = true - for offset in 0.. (inserted: Bool, memberAfterInsert: Self.Element) { - let intersection = rawValue & newMember.rawValue - - guard intersection == 0 else { - var result = Self(rawValue: intersection) - result.customOptions = customOptions.filter({ element in intersection & element.key != 0 }) - return (false, result) - } - - rawValue = rawValue | newMember.rawValue - - customOptions.merge(newMember.customOptions) { current, _ in current } - - return (true, newMember) - } - - @discardableResult @inlinable - public mutating func remove(_ member: Self.Element) -> Self.Element? { - let originalRawValue = rawValue - let customKeysToPreserve = customOptions.reduce(0) { partialResult, item in - if member.customOptions[item.key] == nil || - member.customOptions[item.key] == customOptions[item.key] { - return partialResult - } else { - return partialResult + item.key - } - } - rawValue = (rawValue ^ (rawValue & member.rawValue)) | customKeysToPreserve - - let intersectionOptions = customOptions.filter({ element in customKeysToPreserve & element.key != 0 }) - customOptions = customOptions.filter({ element in customKeysToPreserve & element.key == 0 }) - - guard originalRawValue != rawValue else { return nil } - var result = Self(rawValue: originalRawValue ^ rawValue) - result.customOptions = intersectionOptions - return result - } - - @discardableResult @inlinable - public mutating func update(with newMember: Self.Element) -> Self.Element? { - let intersection = rawValue & newMember.rawValue - rawValue = rawValue | newMember.rawValue - - customOptions.merge(newMember.customOptions) { current, _ in current } - - guard intersection != 0 else { return nil } - - var result = Self(rawValue: intersection) - result.customOptions = customOptions.filter({ element in intersection & element.key != 0 }) - return result - } - - /// Inserts the given element into the set unconditionally. - /// - /// If an element equal to `customOption` is already contained in the set, - /// `customOption` replaces the existing element. Otherwise - updates the set contents and fills `customOptions` accordingly. - /// - /// - Parameter customOption: An element to insert into the set. - /// - Returns: For ordinary sets, an element equal to `customOption` if the set - /// already contained such a member; otherwise, `nil`. In some cases, the - /// returned element may be distinguishable from `customOption` by identity - /// comparison or some other means. - /// - /// For sets where the set type and element type are the same, like - /// `OptionSet` types, this method returns any intersection between the - /// set and `[customOption]`, or `nil` if the intersection is empty. - @discardableResult @inlinable - mutating public func update(customOption: (Int, String)) -> Self.Element? { - let result = update(with: .init(rawValue: customOption.0)) - customOptions[customOption.0] = customOption.1 - return result - } - -} diff --git a/Sources/MapboxDirections/CustomValueOptionSet.swift b/Sources/MapboxDirections/CustomValueOptionSet.swift new file mode 100644 index 000000000..b9e305dad --- /dev/null +++ b/Sources/MapboxDirections/CustomValueOptionSet.swift @@ -0,0 +1,629 @@ +import Foundation + +/// Describes how `customOptions` component is compared during logical operations in `CustomValueOptionSet`. +public enum CustomOptionComparisonPolicy { + /// Custom options are equal if `customOptions` key-value pairs are strictly equal + /// + /// Example: + /// [1: "value1"] == [1: "value1"] + /// [1: "value1"] != [1: "value2"] + /// [1: "value1"] != [:] + /// [:] == [:] + case allowEqual + /// Custom options are equal if `customOptions` by the given key is equal or `nil` + /// + /// Example: + /// [1: "value1"] == [1: "value1"] + /// [1: "value1"] != [1: "value2"] + /// [1: "value1"] == [:] + /// [:] == [:] + case allowEqualOrNull + /// Custom options are not compared. Only `rawValue` is taken into account when comparing `CustomStringOptionSet`s. + /// + /// Example: + /// [1: "value1"] == [1: "value1"] + /// [1: "value1"] == [1: "value2"] + /// [1: "value1"] == [:] + /// [:] == [:] + case allowUnequal +} + +/// Option set implementation which allows each option to have custom string value attached. +public protocol CustomValueOptionSet: RawRepresentable, SetAlgebra where RawValue: FixedWidthInteger, Element == Self { + associatedtype Element = Self + associatedtype CustomValue: Equatable + var rawValue: Self.RawValue { get set } + var customOptions: [RawValue: CustomValue] { get set } + + init(rawValue: Self.RawValue) + + /// Returns a Boolean value that indicates whether the given element exists + /// in the set. + /// + /// This example uses the `contains(_:)` method to test whether an integer is + /// a member of a set of prime numbers. + /// + /// let primes: Set = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37] + /// let x = 5 + /// if primes.contains(x) { + /// print("\(x) is prime!") + /// } else { + /// print("\(x). Not prime.") + /// } + /// // Prints "5 is prime!" + /// + /// - Parameter member: An element to look for in the set. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Returns: `true` if `member` exists in the set; otherwise, `false`. + func contains(_ member: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Bool + /// Returns a new set with the elements of both this and the given set. + /// + /// In the following example, the `attendeesAndVisitors` set is made up + /// of the elements of the `attendees` and `visitors` sets: + /// + /// let attendees: Set = ["Alicia", "Bethany", "Diana"] + /// let visitors = ["Marcia", "Nathaniel"] + /// let attendeesAndVisitors = attendees.union(visitors) + /// print(attendeesAndVisitors) + /// // Prints "["Diana", "Nathaniel", "Bethany", "Alicia", "Marcia"]" + /// + /// If the set already contains one or more elements that are also in + /// `other`, the existing members are kept. + /// + /// let initialIndices = Set(0..<5) + /// let expandedIndices = initialIndices.union([2, 3, 6, 7]) + /// print(expandedIndices) + /// // Prints "[2, 4, 6, 7, 0, 1, 3]" + /// + /// - Parameter other: A set of the same type as the current set. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Returns: A new set with the unique elements of this set and `other`. + /// + /// - Note: if this set and `other` contain elements that are equal but + /// distinguishable (e.g. via `===`), which of these elements is present + /// in the result is unspecified. + func union(_ other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Self.Element + /// Adds the elements of the given set to the set. + /// + /// In the following example, the elements of the `visitors` set are added to + /// the `attendees` set: + /// + /// var attendees: Set = ["Alicia", "Bethany", "Diana"] + /// let visitors: Set = ["Diana", "Marcia", "Nathaniel"] + /// attendees.formUnion(visitors) + /// print(attendees) + /// // Prints "["Diana", "Nathaniel", "Bethany", "Alicia", "Marcia"]" + /// + /// If the set already contains one or more elements that are also in + /// `other`, the existing members are kept. + /// + /// var initialIndices = Set(0..<5) + /// initialIndices.formUnion([2, 3, 6, 7]) + /// print(initialIndices) + /// // Prints "[2, 4, 6, 7, 0, 1, 3]" + /// + /// - Parameter other: A set of the same type as the current set. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + mutating func formUnion(_ other: Self, comparisonPolicy: CustomOptionComparisonPolicy) + /// Returns a new set with the elements that are common to both this set and + /// the given set. + /// + /// In the following example, the `bothNeighborsAndEmployees` set is made up + /// of the elements that are in *both* the `employees` and `neighbors` sets. + /// Elements that are in only one or the other are left out of the result of + /// the intersection. + /// + /// let employees: Set = ["Alicia", "Bethany", "Chris", "Diana", "Eric"] + /// let neighbors: Set = ["Bethany", "Eric", "Forlani", "Greta"] + /// let bothNeighborsAndEmployees = employees.intersection(neighbors) + /// print(bothNeighborsAndEmployees) + /// // Prints "["Bethany", "Eric"]" + /// + /// - Parameter other: A set of the same type as the current set. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Returns: A new set. + /// + /// - Note: if this set and `other` contain elements that are equal but + /// distinguishable (e.g. via `===`), which of these elements is present + /// in the result is unspecified. + func intersection(_ other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Self.Element + /// Removes the elements of this set that aren't also in the given set. + /// + /// In the following example, the elements of the `employees` set that are + /// not also members of the `neighbors` set are removed. In particular, the + /// names `"Alicia"`, `"Chris"`, and `"Diana"` are removed. + /// + /// var employees: Set = ["Alicia", "Bethany", "Chris", "Diana", "Eric"] + /// let neighbors: Set = ["Bethany", "Eric", "Forlani", "Greta"] + /// employees.formIntersection(neighbors) + /// print(employees) + /// // Prints "["Bethany", "Eric"]" + /// + /// - Parameter other: A set of the same type as the current set. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + mutating func formIntersection(_ other: Self, comparisonPolicy: CustomOptionComparisonPolicy) + /// Returns a new set with the elements that are either in this set or in the + /// given set, but not in both. + /// + /// In the following example, the `eitherNeighborsOrEmployees` set is made up + /// of the elements of the `employees` and `neighbors` sets that are not in + /// both `employees` *and* `neighbors`. In particular, the names `"Bethany"` + /// and `"Eric"` do not appear in `eitherNeighborsOrEmployees`. + /// + /// let employees: Set = ["Alicia", "Bethany", "Diana", "Eric"] + /// let neighbors: Set = ["Bethany", "Eric", "Forlani"] + /// let eitherNeighborsOrEmployees = employees.symmetricDifference(neighbors) + /// print(eitherNeighborsOrEmployees) + /// // Prints "["Diana", "Forlani", "Alicia"]" + /// + /// - Parameter other: A set of the same type as the current set. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Returns: A new set. + func symmetricDifference(_ other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Self.Element + /// Removes the elements of the set that are also in the given set and adds + /// the members of the given set that are not already in the set. + /// + /// In the following example, the elements of the `employees` set that are + /// also members of `neighbors` are removed from `employees`, while the + /// elements of `neighbors` that are not members of `employees` are added to + /// `employees`. In particular, the names `"Bethany"` and `"Eric"` are + /// removed from `employees` while the name `"Forlani"` is added. + /// + /// var employees: Set = ["Alicia", "Bethany", "Diana", "Eric"] + /// let neighbors: Set = ["Bethany", "Eric", "Forlani"] + /// employees.formSymmetricDifference(neighbors) + /// print(employees) + /// // Prints "["Diana", "Forlani", "Alicia"]" + /// + /// - Parameter other: A set of the same type. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + mutating func formSymmetricDifference(_ other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) + /// Returns a new set containing the elements of this set that do not occur + /// in the given set. + /// + /// In the following example, the `nonNeighbors` set is made up of the + /// elements of the `employees` set that are not elements of `neighbors`: + /// + /// let employees: Set = ["Alicia", "Bethany", "Chris", "Diana", "Eric"] + /// let neighbors: Set = ["Bethany", "Eric", "Forlani", "Greta"] + /// let nonNeighbors = employees.subtracting(neighbors) + /// print(nonNeighbors) + /// // Prints "["Diana", "Chris", "Alicia"]" + /// + /// - Parameter other: A set of the same type as the current set. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Returns: A new set. + func subtracting(_ other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Self.Element + /// Removes the elements of the given set from this set. + /// + /// In the following example, the elements of the `employees` set that are + /// also members of the `neighbors` set are removed. In particular, the + /// names `"Bethany"` and `"Eric"` are removed from `employees`. + /// + /// var employees: Set = ["Alicia", "Bethany", "Chris", "Diana", "Eric"] + /// let neighbors: Set = ["Bethany", "Eric", "Forlani", "Greta"] + /// employees.subtract(neighbors) + /// print(employees) + /// // Prints "["Diana", "Chris", "Alicia"]" + /// + /// - Parameter other: A set of the same type as the current set. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + mutating func subtract(_ other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) + /// Inserts the given element in the set if it is not already present. + /// + /// If an element equal to `newMember` is already contained in the set, this + /// method has no effect. In this example, a new element is inserted into + /// `classDays`, a set of days of the week. When an existing element is + /// inserted, the `classDays` set does not change. + /// + /// enum DayOfTheWeek: Int { + /// case sunday, monday, tuesday, wednesday, thursday, + /// friday, saturday + /// } + /// + /// var classDays: Set = [.wednesday, .friday] + /// print(classDays.insert(.monday)) + /// // Prints "(true, .monday)" + /// print(classDays) + /// // Prints "[.friday, .wednesday, .monday]" + /// + /// print(classDays.insert(.friday)) + /// // Prints "(false, .friday)" + /// print(classDays) + /// // Prints "[.friday, .wednesday, .monday]" + /// + /// - Parameter newMember: An element to insert into the set. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Returns: `(true, newMember)` if `newMember` was not contained in the + /// set. If an element equal to `newMember` was already contained in the + /// set, the method returns `(false, oldMember)`, where `oldMember` is the + /// element that was equal to `newMember`. In some cases, `oldMember` may + /// be distinguishable from `newMember` by identity comparison or some + /// other means. + mutating func insert(_ newMember: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> (inserted: Bool, memberAfterInsert: Self.Element) + /// Removes the given element and any elements subsumed by the given element. + /// + /// - Parameter member: The element of the set to remove. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Returns: For ordinary sets, an element equal to `member` if `member` is + /// contained in the set; otherwise, `nil`. In some cases, a returned + /// element may be distinguishable from `member` by identity comparison + /// or some other means. + /// + /// For sets where the set type and element type are the same, like + /// `OptionSet` types, this method returns any intersection between the set + /// and `[member]`, or `nil` if the intersection is empty. + mutating func remove(_ member: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Self.Element? + /// Inserts the given element into the set unconditionally. + /// + /// If an element equal to `newMember` is already contained in the set, + /// `newMember` replaces the existing element. In this example, an existing + /// element is inserted into `classDays`, a set of days of the week. + /// + /// enum DayOfTheWeek: Int { + /// case sunday, monday, tuesday, wednesday, thursday, + /// friday, saturday + /// } + /// + /// var classDays: Set = [.monday, .wednesday, .friday] + /// print(classDays.update(with: .monday)) + /// // Prints "Optional(.monday)" + /// + /// - Parameter newMember: An element to insert into the set. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Returns: For ordinary sets, an element equal to `newMember` if the set + /// already contained such a member; otherwise, `nil`. In some cases, the + /// returned element may be distinguishable from `newMember` by identity + /// comparison or some other means. + /// + /// For sets where the set type and element type are the same, like + /// `OptionSet` types, this method returns any intersection between the + /// set and `[newMember]`, or `nil` if the intersection is empty. + mutating func update(with newMember: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Self.Element? + /// Inserts the given element into the set unconditionally. + /// + /// If an element equal to `customOption` is already contained in the set, + /// `customOption` replaces the existing element. Otherwise - updates the set contents and fills `customOptions` accordingly. + /// + /// - Parameter customOption: An element to insert into the set. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Returns: For ordinary sets, an element equal to `customOption` if the set + /// already contained such a member; otherwise, `nil`. In some cases, the + /// returned element may be distinguishable from `customOption` by identity + /// comparison or some other means. + /// + /// For sets where the set type and element type are the same, like + /// `OptionSet` types, this method returns any intersection between the + /// set and `[customOption]`, or `nil` if the intersection is empty. + mutating func update(customOption: (RawValue, CustomValue), comparisonPolicy: CustomOptionComparisonPolicy) -> Self.Element? + /// Returns a Boolean value that indicates whether the set is a subset of + /// another set. + /// + /// Set *A* is a subset of another set *B* if every member of *A* is also a + /// member of *B*. + /// + /// let employees: Set = ["Alicia", "Bethany", "Chris", "Diana", "Eric"] + /// let attendees: Set = ["Alicia", "Bethany", "Diana"] + /// print(attendees.isSubset(of: employees)) + /// // Prints "true" + /// + /// - Parameter other: A set of the same type as the current set. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Returns: `true` if the set is a subset of `other`; otherwise, `false`. + func isSubset(of other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Bool + /// Returns a Boolean value that indicates whether the set is a superset of + /// the given set. + /// + /// Set *A* is a superset of another set *B* if every member of *B* is also a + /// member of *A*. + /// + /// let employees: Set = ["Alicia", "Bethany", "Chris", "Diana", "Eric"] + /// let attendees: Set = ["Alicia", "Bethany", "Diana"] + /// print(employees.isSuperset(of: attendees)) + /// // Prints "true" + /// + /// - Parameter other: A set of the same type as the current set. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Returns: `true` if the set is a superset of `possibleSubset`; + /// otherwise, `false`. + func isSuperset(of other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Bool + /// Returns a Boolean value that indicates whether this set is a strict + /// subset of the given set. + /// + /// Set *A* is a strict subset of another set *B* if every member of *A* is + /// also a member of *B* and *B* contains at least one element that is not a + /// member of *A*. + /// + /// let employees: Set = ["Alicia", "Bethany", "Chris", "Diana", "Eric"] + /// let attendees: Set = ["Alicia", "Bethany", "Diana"] + /// print(attendees.isStrictSubset(of: employees)) + /// // Prints "true" + /// + /// // A set is never a strict subset of itself: + /// print(attendees.isStrictSubset(of: attendees)) + /// // Prints "false" + /// + /// - Parameter other: A set of the same type as the current set. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Returns: `true` if the set is a strict subset of `other`; otherwise, + /// `false`. + func isStrictSubset(of other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Bool + /// Returns a Boolean value that indicates whether this set is a strict + /// superset of the given set. + /// + /// Set *A* is a strict superset of another set *B* if every member of *B* is + /// also a member of *A* and *A* contains at least one element that is *not* + /// a member of *B*. + /// + /// let employees: Set = ["Alicia", "Bethany", "Chris", "Diana", "Eric"] + /// let attendees: Set = ["Alicia", "Bethany", "Diana"] + /// print(employees.isStrictSuperset(of: attendees)) + /// // Prints "true" + /// + /// // A set is never a strict superset of itself: + /// print(employees.isStrictSuperset(of: employees)) + /// // Prints "false" + /// + /// - Parameter other: A set of the same type as the current set. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Returns: `true` if the set is a strict superset of `other`; otherwise, + /// `false`. + func isStrictSuperset(of other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Bool + /// Returns a Boolean value that indicates whether the set has no members in + /// common with the given set. + /// + /// In the following example, the `employees` set is disjoint with the + /// `visitors` set because no name appears in both sets. + /// + /// let employees: Set = ["Alicia", "Bethany", "Chris", "Diana", "Eric"] + /// let visitors: Set = ["Marcia", "Nathaniel", "Olivia"] + /// print(employees.isDisjoint(with: visitors)) + /// // Prints "true" + /// + /// - Parameter other: A set of the same type as the current set. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Returns: `true` if the set has no elements in common with `other`; + /// otherwise, `false`. + func isDisjoint(with other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Bool +} + +public extension CustomValueOptionSet where Self == Self.Element { + + // MARK: Implemented methods + + private func customOptionIsEqual(_ lhs: [RawValue: CustomValue], + _ rhs: [RawValue: CustomValue], + key: RawValue, + policy: CustomOptionComparisonPolicy) -> Bool { + switch policy { + case .allowEqual: + return lhs[key] == rhs[key] + case .allowEqualOrNull: + return lhs[key] == rhs[key] || lhs[key] == nil || rhs[key] == nil + case .allowUnequal: + return true + } + } + + @discardableResult + func contains(_ member: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Bool { + let intersection = rawValue & member.rawValue + guard intersection != 0 else { + return false + } + + for offset in 0.. (inserted: Bool, memberAfterInsert: Self.Element) { + + if contains(newMember, comparisonPolicy: comparisonPolicy) { + return (false, intersection(newMember, comparisonPolicy: comparisonPolicy)) + } else { + rawValue = rawValue | newMember.rawValue + customOptions.merge(newMember.customOptions) { current, _ in current } + return (true, newMember) + } + } + + @discardableResult @inlinable + mutating func remove(_ member: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Self.Element? { + + let intersection = intersection(member, comparisonPolicy: comparisonPolicy) + if intersection.rawValue == 0 { + return nil + } else { + rawValue -= intersection.rawValue + customOptions = customOptions.filter { (key, _) in + rawValue & key != 0 + } + return intersection + } + } + + @discardableResult @inlinable + mutating func update(with newMember: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Self.Element? { + let intersection = intersection(newMember, comparisonPolicy: comparisonPolicy) + + if intersection.rawValue == 0 { + // insert + rawValue = rawValue | newMember.rawValue + customOptions.merge(newMember.customOptions) { current, _ in current } + return nil + } else { + // update + rawValue = rawValue | newMember.rawValue + customOptions.merge(intersection.customOptions) { _, new in new } + return intersection + } + } + + mutating func formIntersection(_ other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) { + rawValue = rawValue & other.rawValue + customOptions = customOptions.reduce(into: [:]) { (partialResult, item) in + if customOptionIsEqual(customOptions, other.customOptions, key: item.key, policy: comparisonPolicy) { + partialResult[item.key] = item.value + } else if rawValue & item.key != 0 { + rawValue -= item.key + } + } + } + + mutating func subtract(_ other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) { + rawValue = rawValue ^ (rawValue & other.rawValue) + customOptions = customOptions.reduce(into: [:], { partialResult, item in + if !customOptionIsEqual(customOptions, other.customOptions, key: item.key, policy: comparisonPolicy) { + partialResult[item.key] = item.value + } + }) + } + + // MARK: Deferring methods + + @discardableResult @inlinable + func union(_ other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Self.Element { + var union = self + union.formUnion(other, comparisonPolicy: comparisonPolicy) + return union + } + + @discardableResult @inlinable + func intersection(_ other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Self.Element { + var intersection = self + intersection.formIntersection(other, comparisonPolicy: comparisonPolicy) + return intersection + } + + @discardableResult @inlinable + func symmetricDifference(_ other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Self.Element { + var difference = self + difference.formSymmetricDifference(other, comparisonPolicy: comparisonPolicy) + return difference + } + + @discardableResult @inlinable + mutating func update(customOption: (RawValue, CustomValue), comparisonPolicy: CustomOptionComparisonPolicy) -> Self.Element? { + var newMember = Self(rawValue: customOption.0) + newMember.customOptions[customOption.0] = customOption.1 + return update(with: newMember, comparisonPolicy: comparisonPolicy) + } + + @inlinable + mutating func formUnion(_ other: Self, comparisonPolicy: CustomOptionComparisonPolicy) { + _ = update(with: other, comparisonPolicy: comparisonPolicy) + } + + @inlinable + mutating func formSymmetricDifference(_ other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) { + let intersection = intersection(other, comparisonPolicy: comparisonPolicy) + _ = remove(other, comparisonPolicy: comparisonPolicy) + _ = insert(other.subtracting(intersection, + comparisonPolicy: comparisonPolicy), + comparisonPolicy: comparisonPolicy) + } + + @discardableResult @inlinable + func subtracting(_ other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Self.Element { + var substracted = self + substracted.subtract(other, comparisonPolicy: comparisonPolicy) + return substracted + } + + @discardableResult @inlinable + func isSubset(of other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Bool { + return intersection(other, comparisonPolicy: comparisonPolicy) == self + } + + @discardableResult @inlinable + func isDisjoint(with other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Bool { + return intersection(other, comparisonPolicy: comparisonPolicy).isEmpty + } + + @discardableResult @inlinable + func isSuperset(of other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Bool { + return other.isSubset(of: self, comparisonPolicy: comparisonPolicy) + } + + @discardableResult @inlinable + func isStrictSuperset(of other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Bool { + return isSuperset(of: other, comparisonPolicy: comparisonPolicy) && rawValue > other.rawValue + } + + @discardableResult @inlinable + func isStrictSubset(of other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Bool { + return other.isStrictSuperset(of: self, comparisonPolicy: comparisonPolicy) + } +} + +// MARK: - SetAlgebra implementation +public extension CustomValueOptionSet { + @discardableResult @inlinable + func contains(_ member: Self.Element) -> Bool { + return contains(member, comparisonPolicy: .allowEqual) + } + @discardableResult @inlinable + func union(_ other: Self) -> Self { + return union(other, comparisonPolicy: .allowEqual) + } + @discardableResult @inlinable + func intersection(_ other: Self) -> Self { + return intersection(other, comparisonPolicy: .allowEqual) + } + @discardableResult @inlinable + func symmetricDifference(_ other: Self) -> Self { + return symmetricDifference(other, comparisonPolicy: .allowEqual) + } + @discardableResult @inlinable + mutating func insert(_ newMember: Self.Element) -> (inserted: Bool, memberAfterInsert: Self.Element) { + return insert(newMember, comparisonPolicy: .allowEqual) + } + @discardableResult @inlinable + mutating func remove(_ member: Self.Element) -> Self.Element? { + return remove(member, comparisonPolicy: .allowEqual) + } + @discardableResult @inlinable + mutating func update(with newMember: Self.Element) -> Self.Element? { + return update(with: newMember, comparisonPolicy: .allowEqual) + } + @inlinable + mutating func formUnion(_ other: Self) { + formUnion(other, comparisonPolicy: .allowEqual) + } + @inlinable + mutating func formIntersection(_ other: Self) { + formIntersection(other, comparisonPolicy: .allowEqual) + } + @inlinable + mutating func formSymmetricDifference(_ other: Self) { + formSymmetricDifference(other, comparisonPolicy: .allowEqual) + } + @discardableResult @inlinable + func subtracting(_ other: Self) -> Self { + return subtracting(other, comparisonPolicy: .allowEqual) + } + @discardableResult @inlinable + func isSubset(of other: Self) -> Bool { + return isSubset(of: other, comparisonPolicy: .allowEqual) + } + @discardableResult @inlinable + func isDisjoint(with other: Self) -> Bool { + return isDisjoint(with: other, comparisonPolicy: .allowEqual) + } + @discardableResult @inlinable + func isSuperset(of other: Self) -> Bool { + return isSuperset(of: other, comparisonPolicy: .allowEqual) + } + @inlinable + mutating func subtract(_ other: Self) { + subtract(other, comparisonPolicy: .allowEqual) + } +} diff --git a/Tests/MapboxDirectionsTests/AttributeOptionsTests.swift b/Tests/MapboxDirectionsTests/AttributeOptionsTests.swift index 50bbba42d..a92a0fec2 100644 --- a/Tests/MapboxDirectionsTests/AttributeOptionsTests.swift +++ b/Tests/MapboxDirectionsTests/AttributeOptionsTests.swift @@ -7,10 +7,10 @@ class AttributeOptionsTests: XCTestCase { var options2merge = AttributeOptions(descriptions: ["speed"])! var optionsWithCustom = AttributeOptions() - optionsWithCustom.update(customOption: (1<<7, "Custom7")) + optionsWithCustom.update(customOption: (1<<7, "Custom7"), comparisonPolicy: .allowEqual) options.update(with: .distance) options.update(with: optionsWithCustom) - options2merge.update(customOption: (1<<8, "Custom_8")) + options2merge.update(customOption: (1<<8, "Custom_8"), comparisonPolicy: .allowEqual) options.update(with: options2merge) @@ -20,7 +20,7 @@ class AttributeOptionsTests: XCTestCase { XCTAssertEqual(options.description.split(separator: ",").count, 4) XCTAssertEqual(optionsWithCustom, - options.update(customOption: (1<<7, "Custom7"))) + options.update(customOption: (1<<7, "Custom7"), comparisonPolicy: .allowEqual)) // insert existing default XCTAssertFalse(options.insert(.distance).inserted) @@ -28,8 +28,8 @@ class AttributeOptionsTests: XCTestCase { XCTAssertFalse(options.insert(optionsWithCustom).inserted) // insert conflicting custom var optionsWithConflict = AttributeOptions() - optionsWithConflict.update(customOption: (optionsWithCustom.rawValue, "Another custom name")) - XCTAssertFalse(options.insert(optionsWithConflict).inserted) + optionsWithConflict.update(customOption: (optionsWithCustom.rawValue, "Another custom name"), comparisonPolicy: .allowEqual) + XCTAssertFalse(options.insert(optionsWithConflict, comparisonPolicy: .allowUnequal).inserted) // insert custom with default raw optionsWithConflict.rawValue = AttributeOptions.distance.rawValue XCTAssertFalse(options.insert(optionsWithConflict).inserted) @@ -38,29 +38,29 @@ class AttributeOptionsTests: XCTestCase { func testContains() { var options = AttributeOptions() options.update(with: .expectedTravelTime) - options.update(customOption: (1<<9, "Custom")) + options.update(customOption: (1<<9, "Custom"), comparisonPolicy: .allowEqual) XCTAssertTrue(options.contains(.init(rawValue: AttributeOptions.expectedTravelTime.rawValue))) XCTAssertFalse(options.contains(.congestionLevel)) var wrongCustomOption = AttributeOptions() - wrongCustomOption.update(customOption: (1<<9, "Wrong name")) + wrongCustomOption.update(customOption: (1<<9, "Wrong name"), comparisonPolicy: .allowEqual) XCTAssertFalse(options.contains(wrongCustomOption)) var correctCustomOption = AttributeOptions() - correctCustomOption.update(customOption: (1<<9, "Custom")) + correctCustomOption.update(customOption: (1<<9, "Custom"), comparisonPolicy: .allowEqual) XCTAssertTrue(options.contains(correctCustomOption)) - XCTAssertTrue(options.contains(.init(rawValue: 1<<9))) + XCTAssertTrue(options.contains(.init(rawValue: 1<<9), comparisonPolicy: .allowEqualOrNull)) } func testRemove() { var preservedOption = AttributeOptions() - preservedOption.update(customOption: (1<<12, "Should be preserved")) + preservedOption.update(customOption: (1<<12, "Should be preserved"), comparisonPolicy: .allowEqual) var options = AttributeOptions() options.update(with: .congestionLevel) options.update(with: .distance) - options.update(customOption: (1<<10, "Custom")) + options.update(customOption: (1<<10, "Custom"), comparisonPolicy: .allowEqual) options.update(with: preservedOption) // Removing default item @@ -77,7 +77,7 @@ class AttributeOptionsTests: XCTestCase { // Removing custom option with incorrect name var wrongCustomOption = AttributeOptions() - wrongCustomOption.update(customOption: (1<<10, "Wrong name")) + wrongCustomOption.update(customOption: (1<<10, "Wrong name"), comparisonPolicy: .allowEqual) XCTAssertNil(options.remove(wrongCustomOption)) XCTAssertTrue(options.contains(.congestionLevel)) @@ -85,7 +85,7 @@ class AttributeOptionsTests: XCTestCase { // Removing existing custom option var correctCustomOption = AttributeOptions() - correctCustomOption.update(customOption: (1<<10, "Custom")) + correctCustomOption.update(customOption: (1<<10, "Custom"), comparisonPolicy: .allowEqual) XCTAssertEqual(options.remove(correctCustomOption), correctCustomOption) XCTAssertTrue(options.contains(.congestionLevel)) @@ -93,11 +93,27 @@ class AttributeOptionsTests: XCTestCase { // Removing custom option with default raw value var customOptionWithDefaultRaw = AttributeOptions() - customOptionWithDefaultRaw.update(customOption: (AttributeOptions.distance.rawValue, "Not a distance")) + customOptionWithDefaultRaw.update(customOption: (AttributeOptions.distance.rawValue, "Not a distance"), comparisonPolicy: .allowEqual) XCTAssertNil(options.remove(customOptionWithDefaultRaw)) // Removing custom option by raw value only options.update(with: correctCustomOption) - XCTAssertEqual(options.remove(.init(rawValue: 1<<10)), correctCustomOption) + XCTAssertEqual(options.remove(.init(rawValue: 1<<10), comparisonPolicy: .allowEqualOrNull), correctCustomOption) + } + + func testCustomAttributes() { + let customOption1 = (1, "atmospheric pressure") + let customOption2 = (1<<10, "space radiation") + var attributes = AttributeOptions() + attributes.insert(.congestionLevel) + attributes.insert(.speed) + attributes.update(customOption: customOption1, comparisonPolicy: .allowEqual) + attributes.update(customOption: customOption2, comparisonPolicy: .allowEqual) + + let descriptions = attributes.description.split(separator: ",") + XCTAssertTrue(descriptions.contains { $0 == AttributeOptions.congestionLevel.description }) + XCTAssertTrue(descriptions.contains { $0 == AttributeOptions.speed.description }) + XCTAssertTrue(descriptions.contains { $0 == customOption1.1 }) + XCTAssertTrue(descriptions.contains { $0 == customOption2.1 }) } } diff --git a/Tests/MapboxDirectionsTests/CustomStringOptionSetTests.swift b/Tests/MapboxDirectionsTests/CustomStringOptionSetTests.swift new file mode 100644 index 000000000..0da2561be --- /dev/null +++ b/Tests/MapboxDirectionsTests/CustomStringOptionSetTests.swift @@ -0,0 +1,117 @@ +import Foundation +import XCTest +import MapboxDirections + +struct BareCustomStringOptionSet : CustomValueOptionSet { + init() { + rawValue = 0 + } + + init(rawValue: Int) { + self.rawValue = rawValue + } + + var rawValue: Int + var customOptions: [Int: String] = [:] + var description: String = "" +} + +/// Tests conformance to SetAlgebra [conditions](https://developer.apple.com/documentation/swift/setalgebra#Conforming-to-the-SetAlgebra-Protocol). +class CustomStringOptionSetTests: XCTestCase { + func getEmptySet() -> BareCustomStringOptionSet { + var set = BareCustomStringOptionSet() + set.customOptions = [1: "value"] + return set + } + + func getSet1() -> BareCustomStringOptionSet { + var set = BareCustomStringOptionSet(rawValue: 1+2+4+8) + set.customOptions = [2: "value 2", + 8: "value 8"] + return set + } + + func getSet2() -> BareCustomStringOptionSet { + var set = BareCustomStringOptionSet(rawValue: 4+8+16+32) + set.customOptions = [8: "value 8", + 32: "value 32"] + return set + } + + func getSubset() -> BareCustomStringOptionSet { + var set = BareCustomStringOptionSet(rawValue: 4+8) + set.customOptions = [8: "value 8"] + return set + } + + // S() == [] + func testEmptySet() { + let set = getEmptySet() + + XCTAssertTrue(set.isEmpty) + } + + // x.intersection(x) == x + // x.intersection([]) == [] + func testIntersection() { + let emptySet = getEmptySet() + let set = getSet1() + + XCTAssertEqual(set, set.intersection(set, + comparisonPolicy: .allowEqual)) + XCTAssertEqual(emptySet, set.intersection(emptySet, + comparisonPolicy: .allowEqual)) + } + + // x.union(x) == x + // x.union([]) == x + func testUnion() { + let emptySet = getEmptySet() + let set = getSet1() + + XCTAssertEqual(set, set.union(set, + comparisonPolicy: .allowEqual)) + XCTAssertEqual(set, set.union(emptySet, + comparisonPolicy: .allowEqual)) + } + + // x.contains(e) implies x.union(y).contains(e) + // x.union(y).contains(e) implies x.contains(e) || y.contains(e) + // x.contains(e) && y.contains(e) if and only if x.intersection(y).contains(e) + func testContains() { + let set1 = getSet1() + let set2 = getSet2() + let setE = getSubset() + + XCTAssertTrue(set1.contains(setE, comparisonPolicy: .allowEqual)) + XCTAssertTrue(set1.union(set2, comparisonPolicy: .allowEqual).contains(setE, comparisonPolicy: .allowEqual)) + + XCTAssertTrue(set1.intersection(set2, comparisonPolicy: .allowEqual).contains(setE, comparisonPolicy: .allowEqual)) + } + + // x.isSubset(of: y) implies x.union(y) == y + // x.isSuperset(of: y) implies x.union(y) == x + // x.isSubset(of: y) if and only if y.isSuperset(of: x) + func testSuperset() { + let set1 = getSet1() + let setE = getSubset() + + XCTAssertTrue(setE.isSubset(of: set1, + comparisonPolicy: .allowEqual)) + XCTAssertEqual(setE.union(set1, comparisonPolicy: .allowEqual), set1) + XCTAssertEqual(set1.union(setE, comparisonPolicy: .allowEqual), set1) + XCTAssertTrue(set1.isSuperset(of: setE, + comparisonPolicy: .allowEqual)) + } + + // x.isStrictSuperset(of: y) if and only if x.isSuperset(of: y) && x != y + // x.isStrictSubset(of: y) if and only if x.isSubset(of: y) && x != y + func testStrictSuperset() { + let set1 = getSet1() + let setE = getSubset() + + XCTAssertTrue(set1.isStrictSuperset(of: setE, comparisonPolicy: .allowEqual)) + XCTAssertTrue(setE.isStrictSubset(of: set1, comparisonPolicy: .allowEqual)) + XCTAssertNotEqual(set1, setE) + } +} From e3b852029ba698bda8d7a0d7f735139d82e121f4 Mon Sep 17 00:00:00 2001 From: udumft Date: Wed, 5 Oct 2022 15:56:19 +0300 Subject: [PATCH 3/7] vk-NAVIOS-374-attribute-options: test files groups fixed --- MapboxDirections.xcodeproj/project.pbxproj | 28 ++++++++----------- .../CustomStringOptionSetTests.swift | 1 - 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/MapboxDirections.xcodeproj/project.pbxproj b/MapboxDirections.xcodeproj/project.pbxproj index 7217e8848..afe9bd664 100644 --- a/MapboxDirections.xcodeproj/project.pbxproj +++ b/MapboxDirections.xcodeproj/project.pbxproj @@ -18,14 +18,12 @@ 2B46024628EDB1670008A624 /* CustomValueOptionSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024428EDB1670008A624 /* CustomValueOptionSet.swift */; }; 2B46024728EDB1670008A624 /* CustomValueOptionSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024428EDB1670008A624 /* CustomValueOptionSet.swift */; }; 2B46024828EDB1670008A624 /* CustomValueOptionSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024428EDB1670008A624 /* CustomValueOptionSet.swift */; }; - 2B46024B28EDB18B0008A624 /* CustomStringOptionSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024928EDB18B0008A624 /* CustomStringOptionSetTests.swift */; }; - 2B46024C28EDB18B0008A624 /* CustomStringOptionSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024928EDB18B0008A624 /* CustomStringOptionSetTests.swift */; }; - 2B46024D28EDB18B0008A624 /* CustomStringOptionSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024928EDB18B0008A624 /* CustomStringOptionSetTests.swift */; }; - 2B46024E28EDB18B0008A624 /* CustomStringOptionSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024928EDB18B0008A624 /* CustomStringOptionSetTests.swift */; }; - 2B46024F28EDB18B0008A624 /* AttributeOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024A28EDB18B0008A624 /* AttributeOptionsTests.swift */; }; - 2B46025028EDB18B0008A624 /* AttributeOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024A28EDB18B0008A624 /* AttributeOptionsTests.swift */; }; - 2B46025128EDB18B0008A624 /* AttributeOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024A28EDB18B0008A624 /* AttributeOptionsTests.swift */; }; - 2B46025228EDB18B0008A624 /* AttributeOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024A28EDB18B0008A624 /* AttributeOptionsTests.swift */; }; + 2B46025328EDB62E0008A624 /* AttributeOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024A28EDB18B0008A624 /* AttributeOptionsTests.swift */; }; + 2B46025428EDB62F0008A624 /* AttributeOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024A28EDB18B0008A624 /* AttributeOptionsTests.swift */; }; + 2B46025528EDB6300008A624 /* AttributeOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024A28EDB18B0008A624 /* AttributeOptionsTests.swift */; }; + 2B46025628EDB6390008A624 /* CustomStringOptionSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024928EDB18B0008A624 /* CustomStringOptionSetTests.swift */; }; + 2B46025728EDB63A0008A624 /* CustomStringOptionSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024928EDB18B0008A624 /* CustomStringOptionSetTests.swift */; }; + 2B46025828EDB63B0008A624 /* CustomStringOptionSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024928EDB18B0008A624 /* CustomStringOptionSetTests.swift */; }; 2B46DB0C27EDF8580068C893 /* RouteRefreshResponseWithForeignMembers.json in Resources */ = {isa = PBXBuildFile; fileRef = 2B46DB0B27EDF8580068C893 /* RouteRefreshResponseWithForeignMembers.json */; }; 2B46DB0D27EDF8580068C893 /* RouteRefreshResponseWithForeignMembers.json in Resources */ = {isa = PBXBuildFile; fileRef = 2B46DB0B27EDF8580068C893 /* RouteRefreshResponseWithForeignMembers.json */; }; 2B46DB0E27EDF8580068C893 /* RouteRefreshResponseWithForeignMembers.json in Resources */ = {isa = PBXBuildFile; fileRef = 2B46DB0B27EDF8580068C893 /* RouteRefreshResponseWithForeignMembers.json */; }; @@ -1463,7 +1461,6 @@ 431E93C0234664A200A71B44 /* DrivingSide.swift in Sources */, C5DAAC9B2019167C001F9261 /* Match.swift in Sources */, 2B5407EE2451B17E006C820B /* RouteRefreshResponse.swift in Sources */, - 2B46024C28EDB18B0008A624 /* CustomStringOptionSetTests.swift in Sources */, 35DBF010217E17A30009D2AE /* HTTPURLResponse.swift in Sources */, 8D43467A219E15C3008B7BF3 /* Double.swift in Sources */, C53A02261E92C26E009837BD /* AttributeOptions.swift in Sources */, @@ -1473,7 +1470,6 @@ 35828C9F217A003F00ED546E /* OfflineDirections.swift in Sources */, 2B5F0E01273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */, DAE7EA95230B5FD10003B211 /* Measurement.swift in Sources */, - 2B46025028EDB18B0008A624 /* AttributeOptionsTests.swift in Sources */, 2E44711827C4C80B0041CB84 /* SilentWaypoint.swift in Sources */, C5DAAC9F20195AAE001F9261 /* Tracepoint.swift in Sources */, 2B9F3882272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */, @@ -1538,6 +1534,7 @@ DA8F3A7323B56D3B00B56786 /* RouteLegTests.swift in Sources */, DA1A10CD1D00F972009F82FA /* V5Tests.swift in Sources */, DAE33A1C1F215DF600C06039 /* IntersectionTests.swift in Sources */, + 2B46025428EDB62F0008A624 /* AttributeOptionsTests.swift in Sources */, 2B5407FD245B070A006C820B /* RouteRefreshTests.swift in Sources */, C5DAACB0201AA92B001F9261 /* MatchTests.swift in Sources */, DA1A10CE1D00F972009F82FA /* Fixture.swift in Sources */, @@ -1548,6 +1545,7 @@ DA4F84EE21C08BFB008A0434 /* WaypointTests.swift in Sources */, DAABF78F2395ABA900CEEB61 /* SpokenInstructionTests.swift in Sources */, 35CC310C2285739700EA1966 /* WalkingOptionsTests.swift in Sources */, + 2B46025728EDB63A0008A624 /* CustomStringOptionSetTests.swift in Sources */, DABE6C7F236A37E200D370F4 /* JSONSerialization.swift in Sources */, DAABF7932395AE9800CEEB61 /* GeoJSONTests.swift in Sources */, F4D785F01DDD82C100FF4665 /* RouteStepTests.swift in Sources */, @@ -1563,7 +1561,6 @@ 431E93C1234664A200A71B44 /* DrivingSide.swift in Sources */, C5DAAC9C2019167D001F9261 /* Match.swift in Sources */, 2B5407EF2451B17E006C820B /* RouteRefreshResponse.swift in Sources */, - 2B46024D28EDB18B0008A624 /* CustomStringOptionSetTests.swift in Sources */, 35DBF011217E17A30009D2AE /* HTTPURLResponse.swift in Sources */, 8D43467B219E15C4008B7BF3 /* Double.swift in Sources */, C53A02271E92C26F009837BD /* AttributeOptions.swift in Sources */, @@ -1573,7 +1570,6 @@ 35828CA0217A003F00ED546E /* OfflineDirections.swift in Sources */, 2B5F0E02273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */, DAE7EA96230B5FD10003B211 /* Measurement.swift in Sources */, - 2B46025128EDB18B0008A624 /* AttributeOptionsTests.swift in Sources */, 2E44711927C4C80B0041CB84 /* SilentWaypoint.swift in Sources */, C5DAACA020195AAF001F9261 /* Tracepoint.swift in Sources */, 2B9F3883272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */, @@ -1638,6 +1634,7 @@ DA8F3A7423B56D3B00B56786 /* RouteLegTests.swift in Sources */, DA1A10F41D010251009F82FA /* V5Tests.swift in Sources */, DAE33A1D1F215DF600C06039 /* IntersectionTests.swift in Sources */, + 2B46025528EDB6300008A624 /* AttributeOptionsTests.swift in Sources */, 2B5407FE245B070A006C820B /* RouteRefreshTests.swift in Sources */, C5DAACB1201AA92B001F9261 /* MatchTests.swift in Sources */, DA1A10F51D010251009F82FA /* Fixture.swift in Sources */, @@ -1648,6 +1645,7 @@ DA4F84EF21C08BFB008A0434 /* WaypointTests.swift in Sources */, DAABF7902395ABA900CEEB61 /* SpokenInstructionTests.swift in Sources */, 35CC310D2285739700EA1966 /* WalkingOptionsTests.swift in Sources */, + 2B46025828EDB63B0008A624 /* CustomStringOptionSetTests.swift in Sources */, DABE6C80236A37E200D370F4 /* JSONSerialization.swift in Sources */, DAABF7942395AE9800CEEB61 /* GeoJSONTests.swift in Sources */, F4D785F11DDD82C100FF4665 /* RouteStepTests.swift in Sources */, @@ -1663,7 +1661,6 @@ 431E93C2234664A200A71B44 /* DrivingSide.swift in Sources */, C5DAAC9D2019167E001F9261 /* Match.swift in Sources */, 2B5407F02451B17E006C820B /* RouteRefreshResponse.swift in Sources */, - 2B46024E28EDB18B0008A624 /* CustomStringOptionSetTests.swift in Sources */, 35DBF012217E17A30009D2AE /* HTTPURLResponse.swift in Sources */, 8D43467C219E15C6008B7BF3 /* Double.swift in Sources */, C53A02281E92C271009837BD /* AttributeOptions.swift in Sources */, @@ -1673,7 +1670,6 @@ 35828CA1217A003F00ED546E /* OfflineDirections.swift in Sources */, 2B5F0E03273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */, DAE7EA97230B5FD10003B211 /* Measurement.swift in Sources */, - 2B46025228EDB18B0008A624 /* AttributeOptionsTests.swift in Sources */, 2E44711A27C4C80B0041CB84 /* SilentWaypoint.swift in Sources */, C5DAACA120195AAF001F9261 /* Tracepoint.swift in Sources */, 2B9F3884272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */, @@ -1728,7 +1724,6 @@ 431E93BF234664A200A71B44 /* DrivingSide.swift in Sources */, C51538CC1E807FF00093FF3E /* AttributeOptions.swift in Sources */, 2B5407ED2451B17E006C820B /* RouteRefreshResponse.swift in Sources */, - 2B46024B28EDB18B0008A624 /* CustomStringOptionSetTests.swift in Sources */, 35DBF00F217E17A30009D2AE /* HTTPURLResponse.swift in Sources */, 8D434679219E1167008B7BF3 /* Double.swift in Sources */, DA2E03EB1CB0E13D00D1269A /* RouteOptions.swift in Sources */, @@ -1738,7 +1733,6 @@ 35828C9E217A003F00ED546E /* OfflineDirections.swift in Sources */, 2B5F0E00273BEB3600CC2C1A /* RoadClassExclusionViolation.swift in Sources */, DAE7EA94230B5FD10003B211 /* Measurement.swift in Sources */, - 2B46024F28EDB18B0008A624 /* AttributeOptionsTests.swift in Sources */, 2E44711727C4C80B0041CB84 /* SilentWaypoint.swift in Sources */, C59426071F1EA6C400C8E59C /* RoadClasses.swift in Sources */, 2B9F3881272AE23A001DBA12 /* ProfileIdentifier.swift in Sources */, @@ -1803,6 +1797,7 @@ DA6C9DAC1CAEC72800094FBC /* V5Tests.swift in Sources */, 8A3B4C9B24EB55F60085DA64 /* RouteResponseTests.swift in Sources */, DAE33A1B1F215DF600C06039 /* IntersectionTests.swift in Sources */, + 2B46025328EDB62E0008A624 /* AttributeOptionsTests.swift in Sources */, 2B5407FC245B070A006C820B /* RouteRefreshTests.swift in Sources */, C5DAACAF201AA92B001F9261 /* MatchTests.swift in Sources */, DA6C9DB21CAECA0E00094FBC /* Fixture.swift in Sources */, @@ -1813,6 +1808,7 @@ DA4F84ED21C08BFB008A0434 /* WaypointTests.swift in Sources */, DAABF78E2395ABA900CEEB61 /* SpokenInstructionTests.swift in Sources */, 35CC310B2285739700EA1966 /* WalkingOptionsTests.swift in Sources */, + 2B46025628EDB6390008A624 /* CustomStringOptionSetTests.swift in Sources */, DABE6C7E236A37E200D370F4 /* JSONSerialization.swift in Sources */, DAABF7922395AE9800CEEB61 /* GeoJSONTests.swift in Sources */, F4D785EF1DDD82C100FF4665 /* RouteStepTests.swift in Sources */, diff --git a/Tests/MapboxDirectionsTests/CustomStringOptionSetTests.swift b/Tests/MapboxDirectionsTests/CustomStringOptionSetTests.swift index 0da2561be..2b74642b2 100644 --- a/Tests/MapboxDirectionsTests/CustomStringOptionSetTests.swift +++ b/Tests/MapboxDirectionsTests/CustomStringOptionSetTests.swift @@ -1,4 +1,3 @@ -import Foundation import XCTest import MapboxDirections From dbb9f65b76302b98a6aa0b8b42cb9b969dcf9428 Mon Sep 17 00:00:00 2001 From: udumft Date: Wed, 5 Oct 2022 15:57:05 +0300 Subject: [PATCH 4/7] vk-NAVIOS-374-attribute-options: type corrected --- Sources/MapboxDirections/AttributeOptions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/MapboxDirections/AttributeOptions.swift b/Sources/MapboxDirections/AttributeOptions.swift index 30bf76c16..869f925da 100644 --- a/Sources/MapboxDirections/AttributeOptions.swift +++ b/Sources/MapboxDirections/AttributeOptions.swift @@ -12,7 +12,7 @@ public struct AttributeOptions: CustomValueOptionSet, CustomStringConvertible { Provides a text value description for user-provided options. `AttributeOptions` will recognize a custom option if it's unique `rawValue` flag is set and `customOptions` contains a description for that flag. - Use `update(customOption:)` methid to append a custom option. + Use the `update(customOption:)` method to append a custom option. */ public var customOptions: [Int: String] = [:] From cb2f44253c8016df993988e4e102662b27c7fdcd Mon Sep 17 00:00:00 2001 From: udumft Date: Wed, 5 Oct 2022 16:21:32 +0300 Subject: [PATCH 5/7] vk-NAVIOS-374-attribute-options: restored confromance to OptionSet --- Sources/MapboxDirections/CustomValueOptionSet.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/MapboxDirections/CustomValueOptionSet.swift b/Sources/MapboxDirections/CustomValueOptionSet.swift index b9e305dad..db3646dee 100644 --- a/Sources/MapboxDirections/CustomValueOptionSet.swift +++ b/Sources/MapboxDirections/CustomValueOptionSet.swift @@ -29,7 +29,7 @@ public enum CustomOptionComparisonPolicy { } /// Option set implementation which allows each option to have custom string value attached. -public protocol CustomValueOptionSet: RawRepresentable, SetAlgebra where RawValue: FixedWidthInteger, Element == Self { +public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthInteger, Element == Self { associatedtype Element = Self associatedtype CustomValue: Equatable var rawValue: Self.RawValue { get set } From 335d3d2015c04df065c3ace95978b1c2bb8d6031 Mon Sep 17 00:00:00 2001 From: udumft Date: Wed, 5 Oct 2022 16:34:17 +0300 Subject: [PATCH 6/7] vk-NAVIOS-374-attribure-options: CHANGELOG updated --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 502dde331..3749a58a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Other Changes * Added the `Waypoint.layer` property, which can ensure that the route begins on the correct road if it is above or below another road. ([#745](https://github.com/mapbox/mapbox-directions-swift/pull/745)) +* Expanded `AttributeOptions` to allow user options with custom values. See `CustomValueOptionSet` for reference. ([#748](https://github.com/mapbox/mapbox-directions-swift/pull/748)) * Fixed incorrect shape indicies in `RouteLeg.incidents` after route refresh. ([#752](https://github.com/mapbox/mapbox-directions-swift/pull/752)) ## 2.7.0 From 8a54bd9f0b6dfeb11b0ae5f5b3b211709f6706e7 Mon Sep 17 00:00:00 2001 From: udumft Date: Fri, 7 Oct 2022 14:51:31 +0300 Subject: [PATCH 7/7] vk-NAVIOS-374-attribute-options: member naming improved; CHANGELOG updated; --- CHANGELOG.md | 2 +- .../MapboxDirections/AttributeOptions.swift | 10 +- .../CustomValueOptionSet.swift | 112 +++++++++--------- .../AttributeOptionsTests.swift | 34 +++--- .../CustomStringOptionSetTests.swift | 36 +++--- 5 files changed, 97 insertions(+), 97 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3749a58a9..b71e29656 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ ### Other Changes * Added the `Waypoint.layer` property, which can ensure that the route begins on the correct road if it is above or below another road. ([#745](https://github.com/mapbox/mapbox-directions-swift/pull/745)) -* Expanded `AttributeOptions` to allow user options with custom values. See `CustomValueOptionSet` for reference. ([#748](https://github.com/mapbox/mapbox-directions-swift/pull/748)) +* Expanded `AttributeOptions` to allow user options with custom values. See `AttributeOptions.customOptionsByRawValue` for reference. ([#748](https://github.com/mapbox/mapbox-directions-swift/pull/748)) * Fixed incorrect shape indicies in `RouteLeg.incidents` after route refresh. ([#752](https://github.com/mapbox/mapbox-directions-swift/pull/752)) ## 2.7.0 diff --git a/Sources/MapboxDirections/AttributeOptions.swift b/Sources/MapboxDirections/AttributeOptions.swift index 869f925da..9e89a0b95 100644 --- a/Sources/MapboxDirections/AttributeOptions.swift +++ b/Sources/MapboxDirections/AttributeOptions.swift @@ -8,13 +8,7 @@ import Foundation public struct AttributeOptions: CustomValueOptionSet, CustomStringConvertible { public var rawValue: Int - /** - Provides a text value description for user-provided options. - - `AttributeOptions` will recognize a custom option if it's unique `rawValue` flag is set and `customOptions` contains a description for that flag. - Use the `update(customOption:)` method to append a custom option. - */ - public var customOptions: [Int: String] = [:] + public var customOptionsByRawValue: [Int: String] = [:] public init(rawValue: Int) { self.rawValue = rawValue @@ -118,7 +112,7 @@ public struct AttributeOptions: CustomValueOptionSet, CustomStringConvertible { if contains(.numericCongestionLevel) { descriptions.append("congestion_numeric") } - for (key, value) in customOptions { + for (key, value) in customOptionsByRawValue { if rawValue & key != 0 { descriptions.append(value) } diff --git a/Sources/MapboxDirections/CustomValueOptionSet.swift b/Sources/MapboxDirections/CustomValueOptionSet.swift index db3646dee..60066a90b 100644 --- a/Sources/MapboxDirections/CustomValueOptionSet.swift +++ b/Sources/MapboxDirections/CustomValueOptionSet.swift @@ -1,6 +1,6 @@ import Foundation -/// Describes how `customOptions` component is compared during logical operations in `CustomValueOptionSet`. +/// Describes how `customOptionsByRawValue` component is compared during logical operations in `CustomValueOptionSet`. public enum CustomOptionComparisonPolicy { /// Custom options are equal if `customOptions` key-value pairs are strictly equal /// @@ -9,7 +9,7 @@ public enum CustomOptionComparisonPolicy { /// [1: "value1"] != [1: "value2"] /// [1: "value1"] != [:] /// [:] == [:] - case allowEqual + case equal /// Custom options are equal if `customOptions` by the given key is equal or `nil` /// /// Example: @@ -17,7 +17,7 @@ public enum CustomOptionComparisonPolicy { /// [1: "value1"] != [1: "value2"] /// [1: "value1"] == [:] /// [:] == [:] - case allowEqualOrNull + case equalOrNull /// Custom options are not compared. Only `rawValue` is taken into account when comparing `CustomStringOptionSet`s. /// /// Example: @@ -25,7 +25,7 @@ public enum CustomOptionComparisonPolicy { /// [1: "value1"] == [1: "value2"] /// [1: "value1"] == [:] /// [:] == [:] - case allowUnequal + case rawValueEqual } /// Option set implementation which allows each option to have custom string value attached. @@ -33,7 +33,13 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege associatedtype Element = Self associatedtype CustomValue: Equatable var rawValue: Self.RawValue { get set } - var customOptions: [RawValue: CustomValue] { get set } + + + /// Provides a text value description for user-provided options. + /// + /// The option set will recognize a custom option if it's unique `rawValue` flag is set and `customOptionsByRawValue` contains a description for that flag. + /// Use the `update(customOption:comparisonPolicy:)` method to append a custom option. + var customOptionsByRawValue: [RawValue: CustomValue] { get set } init(rawValue: Self.RawValue) @@ -53,7 +59,7 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege /// // Prints "5 is prime!" /// /// - Parameter member: An element to look for in the set. - /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptionsByRawValue`. /// - Returns: `true` if `member` exists in the set; otherwise, `false`. func contains(_ member: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Bool /// Returns a new set with the elements of both this and the given set. @@ -76,7 +82,7 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege /// // Prints "[2, 4, 6, 7, 0, 1, 3]" /// /// - Parameter other: A set of the same type as the current set. - /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptionsByRawValue`. /// - Returns: A new set with the unique elements of this set and `other`. /// /// - Note: if this set and `other` contain elements that are equal but @@ -103,7 +109,7 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege /// // Prints "[2, 4, 6, 7, 0, 1, 3]" /// /// - Parameter other: A set of the same type as the current set. - /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptionsByRawValue`. mutating func formUnion(_ other: Self, comparisonPolicy: CustomOptionComparisonPolicy) /// Returns a new set with the elements that are common to both this set and /// the given set. @@ -120,7 +126,7 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege /// // Prints "["Bethany", "Eric"]" /// /// - Parameter other: A set of the same type as the current set. - /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptionsByRawValue`. /// - Returns: A new set. /// /// - Note: if this set and `other` contain elements that are equal but @@ -140,7 +146,7 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege /// // Prints "["Bethany", "Eric"]" /// /// - Parameter other: A set of the same type as the current set. - /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptionsByRawValue`. mutating func formIntersection(_ other: Self, comparisonPolicy: CustomOptionComparisonPolicy) /// Returns a new set with the elements that are either in this set or in the /// given set, but not in both. @@ -157,7 +163,7 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege /// // Prints "["Diana", "Forlani", "Alicia"]" /// /// - Parameter other: A set of the same type as the current set. - /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptionsByRawValue`. /// - Returns: A new set. func symmetricDifference(_ other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Self.Element /// Removes the elements of the set that are also in the given set and adds @@ -176,7 +182,7 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege /// // Prints "["Diana", "Forlani", "Alicia"]" /// /// - Parameter other: A set of the same type. - /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptionsByRawValue`. mutating func formSymmetricDifference(_ other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) /// Returns a new set containing the elements of this set that do not occur /// in the given set. @@ -191,7 +197,7 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege /// // Prints "["Diana", "Chris", "Alicia"]" /// /// - Parameter other: A set of the same type as the current set. - /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptionsByRawValue`. /// - Returns: A new set. func subtracting(_ other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Self.Element /// Removes the elements of the given set from this set. @@ -207,7 +213,7 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege /// // Prints "["Diana", "Chris", "Alicia"]" /// /// - Parameter other: A set of the same type as the current set. - /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptionsByRawValue`. mutating func subtract(_ other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) /// Inserts the given element in the set if it is not already present. /// @@ -233,7 +239,7 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege /// // Prints "[.friday, .wednesday, .monday]" /// /// - Parameter newMember: An element to insert into the set. - /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptionsByRawValue`. /// - Returns: `(true, newMember)` if `newMember` was not contained in the /// set. If an element equal to `newMember` was already contained in the /// set, the method returns `(false, oldMember)`, where `oldMember` is the @@ -244,7 +250,7 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege /// Removes the given element and any elements subsumed by the given element. /// /// - Parameter member: The element of the set to remove. - /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptionsByRawValue`. /// - Returns: For ordinary sets, an element equal to `member` if `member` is /// contained in the set; otherwise, `nil`. In some cases, a returned /// element may be distinguishable from `member` by identity comparison @@ -270,7 +276,7 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege /// // Prints "Optional(.monday)" /// /// - Parameter newMember: An element to insert into the set. - /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptionsByRawValue`. /// - Returns: For ordinary sets, an element equal to `newMember` if the set /// already contained such a member; otherwise, `nil`. In some cases, the /// returned element may be distinguishable from `newMember` by identity @@ -283,10 +289,10 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege /// Inserts the given element into the set unconditionally. /// /// If an element equal to `customOption` is already contained in the set, - /// `customOption` replaces the existing element. Otherwise - updates the set contents and fills `customOptions` accordingly. + /// `customOption` replaces the existing element. Otherwise - updates the set contents and fills `customOptionsByRawValue` accordingly. /// /// - Parameter customOption: An element to insert into the set. - /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptionsByRawValue`. /// - Returns: For ordinary sets, an element equal to `customOption` if the set /// already contained such a member; otherwise, `nil`. In some cases, the /// returned element may be distinguishable from `customOption` by identity @@ -308,7 +314,7 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege /// // Prints "true" /// /// - Parameter other: A set of the same type as the current set. - /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptionsByRawValue`. /// - Returns: `true` if the set is a subset of `other`; otherwise, `false`. func isSubset(of other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Bool /// Returns a Boolean value that indicates whether the set is a superset of @@ -323,7 +329,7 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege /// // Prints "true" /// /// - Parameter other: A set of the same type as the current set. - /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptionsByRawValue`. /// - Returns: `true` if the set is a superset of `possibleSubset`; /// otherwise, `false`. func isSuperset(of other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Bool @@ -344,7 +350,7 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege /// // Prints "false" /// /// - Parameter other: A set of the same type as the current set. - /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptionsByRawValue`. /// - Returns: `true` if the set is a strict subset of `other`; otherwise, /// `false`. func isStrictSubset(of other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Bool @@ -365,7 +371,7 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege /// // Prints "false" /// /// - Parameter other: A set of the same type as the current set. - /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptionsByRawValue`. /// - Returns: `true` if the set is a strict superset of `other`; otherwise, /// `false`. func isStrictSuperset(of other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Bool @@ -381,7 +387,7 @@ public protocol CustomValueOptionSet: OptionSet where RawValue: FixedWidthIntege /// // Prints "true" /// /// - Parameter other: A set of the same type as the current set. - /// - Parameter comparisonPolicy: comparison method to be used for `customOptions`. + /// - Parameter comparisonPolicy: comparison method to be used for `customOptionsByRawValue`. /// - Returns: `true` if the set has no elements in common with `other`; /// otherwise, `false`. func isDisjoint(with other: Self.Element, comparisonPolicy: CustomOptionComparisonPolicy) -> Bool @@ -396,11 +402,11 @@ public extension CustomValueOptionSet where Self == Self.Element { key: RawValue, policy: CustomOptionComparisonPolicy) -> Bool { switch policy { - case .allowEqual: + case .equal: return lhs[key] == rhs[key] - case .allowEqualOrNull: + case .equalOrNull: return lhs[key] == rhs[key] || lhs[key] == nil || rhs[key] == nil - case .allowUnequal: + case .rawValueEqual: return true } } @@ -413,8 +419,8 @@ public extension CustomValueOptionSet where Self == Self.Element { } for offset in 0.. Self.Element? { var newMember = Self(rawValue: customOption.0) - newMember.customOptions[customOption.0] = customOption.1 + newMember.customOptionsByRawValue[customOption.0] = customOption.1 return update(with: newMember, comparisonPolicy: comparisonPolicy) } @@ -568,62 +574,62 @@ public extension CustomValueOptionSet where Self == Self.Element { public extension CustomValueOptionSet { @discardableResult @inlinable func contains(_ member: Self.Element) -> Bool { - return contains(member, comparisonPolicy: .allowEqual) + return contains(member, comparisonPolicy: .equal) } @discardableResult @inlinable func union(_ other: Self) -> Self { - return union(other, comparisonPolicy: .allowEqual) + return union(other, comparisonPolicy: .equal) } @discardableResult @inlinable func intersection(_ other: Self) -> Self { - return intersection(other, comparisonPolicy: .allowEqual) + return intersection(other, comparisonPolicy: .equal) } @discardableResult @inlinable func symmetricDifference(_ other: Self) -> Self { - return symmetricDifference(other, comparisonPolicy: .allowEqual) + return symmetricDifference(other, comparisonPolicy: .equal) } @discardableResult @inlinable mutating func insert(_ newMember: Self.Element) -> (inserted: Bool, memberAfterInsert: Self.Element) { - return insert(newMember, comparisonPolicy: .allowEqual) + return insert(newMember, comparisonPolicy: .equal) } @discardableResult @inlinable mutating func remove(_ member: Self.Element) -> Self.Element? { - return remove(member, comparisonPolicy: .allowEqual) + return remove(member, comparisonPolicy: .equal) } @discardableResult @inlinable mutating func update(with newMember: Self.Element) -> Self.Element? { - return update(with: newMember, comparisonPolicy: .allowEqual) + return update(with: newMember, comparisonPolicy: .equal) } @inlinable mutating func formUnion(_ other: Self) { - formUnion(other, comparisonPolicy: .allowEqual) + formUnion(other, comparisonPolicy: .equal) } @inlinable mutating func formIntersection(_ other: Self) { - formIntersection(other, comparisonPolicy: .allowEqual) + formIntersection(other, comparisonPolicy: .equal) } @inlinable mutating func formSymmetricDifference(_ other: Self) { - formSymmetricDifference(other, comparisonPolicy: .allowEqual) + formSymmetricDifference(other, comparisonPolicy: .equal) } @discardableResult @inlinable func subtracting(_ other: Self) -> Self { - return subtracting(other, comparisonPolicy: .allowEqual) + return subtracting(other, comparisonPolicy: .equal) } @discardableResult @inlinable func isSubset(of other: Self) -> Bool { - return isSubset(of: other, comparisonPolicy: .allowEqual) + return isSubset(of: other, comparisonPolicy: .equal) } @discardableResult @inlinable func isDisjoint(with other: Self) -> Bool { - return isDisjoint(with: other, comparisonPolicy: .allowEqual) + return isDisjoint(with: other, comparisonPolicy: .equal) } @discardableResult @inlinable func isSuperset(of other: Self) -> Bool { - return isSuperset(of: other, comparisonPolicy: .allowEqual) + return isSuperset(of: other, comparisonPolicy: .equal) } @inlinable mutating func subtract(_ other: Self) { - subtract(other, comparisonPolicy: .allowEqual) + subtract(other, comparisonPolicy: .equal) } } diff --git a/Tests/MapboxDirectionsTests/AttributeOptionsTests.swift b/Tests/MapboxDirectionsTests/AttributeOptionsTests.swift index a92a0fec2..6813459ee 100644 --- a/Tests/MapboxDirectionsTests/AttributeOptionsTests.swift +++ b/Tests/MapboxDirectionsTests/AttributeOptionsTests.swift @@ -7,10 +7,10 @@ class AttributeOptionsTests: XCTestCase { var options2merge = AttributeOptions(descriptions: ["speed"])! var optionsWithCustom = AttributeOptions() - optionsWithCustom.update(customOption: (1<<7, "Custom7"), comparisonPolicy: .allowEqual) + optionsWithCustom.update(customOption: (1<<7, "Custom7"), comparisonPolicy: .equal) options.update(with: .distance) options.update(with: optionsWithCustom) - options2merge.update(customOption: (1<<8, "Custom_8"), comparisonPolicy: .allowEqual) + options2merge.update(customOption: (1<<8, "Custom_8"), comparisonPolicy: .equal) options.update(with: options2merge) @@ -20,7 +20,7 @@ class AttributeOptionsTests: XCTestCase { XCTAssertEqual(options.description.split(separator: ",").count, 4) XCTAssertEqual(optionsWithCustom, - options.update(customOption: (1<<7, "Custom7"), comparisonPolicy: .allowEqual)) + options.update(customOption: (1<<7, "Custom7"), comparisonPolicy: .equal)) // insert existing default XCTAssertFalse(options.insert(.distance).inserted) @@ -28,8 +28,8 @@ class AttributeOptionsTests: XCTestCase { XCTAssertFalse(options.insert(optionsWithCustom).inserted) // insert conflicting custom var optionsWithConflict = AttributeOptions() - optionsWithConflict.update(customOption: (optionsWithCustom.rawValue, "Another custom name"), comparisonPolicy: .allowEqual) - XCTAssertFalse(options.insert(optionsWithConflict, comparisonPolicy: .allowUnequal).inserted) + optionsWithConflict.update(customOption: (optionsWithCustom.rawValue, "Another custom name"), comparisonPolicy: .equal) + XCTAssertFalse(options.insert(optionsWithConflict, comparisonPolicy: .rawValueEqual).inserted) // insert custom with default raw optionsWithConflict.rawValue = AttributeOptions.distance.rawValue XCTAssertFalse(options.insert(optionsWithConflict).inserted) @@ -38,29 +38,29 @@ class AttributeOptionsTests: XCTestCase { func testContains() { var options = AttributeOptions() options.update(with: .expectedTravelTime) - options.update(customOption: (1<<9, "Custom"), comparisonPolicy: .allowEqual) + options.update(customOption: (1<<9, "Custom"), comparisonPolicy: .equal) XCTAssertTrue(options.contains(.init(rawValue: AttributeOptions.expectedTravelTime.rawValue))) XCTAssertFalse(options.contains(.congestionLevel)) var wrongCustomOption = AttributeOptions() - wrongCustomOption.update(customOption: (1<<9, "Wrong name"), comparisonPolicy: .allowEqual) + wrongCustomOption.update(customOption: (1<<9, "Wrong name"), comparisonPolicy: .equal) XCTAssertFalse(options.contains(wrongCustomOption)) var correctCustomOption = AttributeOptions() - correctCustomOption.update(customOption: (1<<9, "Custom"), comparisonPolicy: .allowEqual) + correctCustomOption.update(customOption: (1<<9, "Custom"), comparisonPolicy: .equal) XCTAssertTrue(options.contains(correctCustomOption)) - XCTAssertTrue(options.contains(.init(rawValue: 1<<9), comparisonPolicy: .allowEqualOrNull)) + XCTAssertTrue(options.contains(.init(rawValue: 1<<9), comparisonPolicy: .equalOrNull)) } func testRemove() { var preservedOption = AttributeOptions() - preservedOption.update(customOption: (1<<12, "Should be preserved"), comparisonPolicy: .allowEqual) + preservedOption.update(customOption: (1<<12, "Should be preserved"), comparisonPolicy: .equal) var options = AttributeOptions() options.update(with: .congestionLevel) options.update(with: .distance) - options.update(customOption: (1<<10, "Custom"), comparisonPolicy: .allowEqual) + options.update(customOption: (1<<10, "Custom"), comparisonPolicy: .equal) options.update(with: preservedOption) // Removing default item @@ -77,7 +77,7 @@ class AttributeOptionsTests: XCTestCase { // Removing custom option with incorrect name var wrongCustomOption = AttributeOptions() - wrongCustomOption.update(customOption: (1<<10, "Wrong name"), comparisonPolicy: .allowEqual) + wrongCustomOption.update(customOption: (1<<10, "Wrong name"), comparisonPolicy: .equal) XCTAssertNil(options.remove(wrongCustomOption)) XCTAssertTrue(options.contains(.congestionLevel)) @@ -85,7 +85,7 @@ class AttributeOptionsTests: XCTestCase { // Removing existing custom option var correctCustomOption = AttributeOptions() - correctCustomOption.update(customOption: (1<<10, "Custom"), comparisonPolicy: .allowEqual) + correctCustomOption.update(customOption: (1<<10, "Custom"), comparisonPolicy: .equal) XCTAssertEqual(options.remove(correctCustomOption), correctCustomOption) XCTAssertTrue(options.contains(.congestionLevel)) @@ -93,12 +93,12 @@ class AttributeOptionsTests: XCTestCase { // Removing custom option with default raw value var customOptionWithDefaultRaw = AttributeOptions() - customOptionWithDefaultRaw.update(customOption: (AttributeOptions.distance.rawValue, "Not a distance"), comparisonPolicy: .allowEqual) + customOptionWithDefaultRaw.update(customOption: (AttributeOptions.distance.rawValue, "Not a distance"), comparisonPolicy: .equal) XCTAssertNil(options.remove(customOptionWithDefaultRaw)) // Removing custom option by raw value only options.update(with: correctCustomOption) - XCTAssertEqual(options.remove(.init(rawValue: 1<<10), comparisonPolicy: .allowEqualOrNull), correctCustomOption) + XCTAssertEqual(options.remove(.init(rawValue: 1<<10), comparisonPolicy: .equalOrNull), correctCustomOption) } func testCustomAttributes() { @@ -107,8 +107,8 @@ class AttributeOptionsTests: XCTestCase { var attributes = AttributeOptions() attributes.insert(.congestionLevel) attributes.insert(.speed) - attributes.update(customOption: customOption1, comparisonPolicy: .allowEqual) - attributes.update(customOption: customOption2, comparisonPolicy: .allowEqual) + attributes.update(customOption: customOption1, comparisonPolicy: .equal) + attributes.update(customOption: customOption2, comparisonPolicy: .equal) let descriptions = attributes.description.split(separator: ",") XCTAssertTrue(descriptions.contains { $0 == AttributeOptions.congestionLevel.description }) diff --git a/Tests/MapboxDirectionsTests/CustomStringOptionSetTests.swift b/Tests/MapboxDirectionsTests/CustomStringOptionSetTests.swift index 2b74642b2..6ffcf089d 100644 --- a/Tests/MapboxDirectionsTests/CustomStringOptionSetTests.swift +++ b/Tests/MapboxDirectionsTests/CustomStringOptionSetTests.swift @@ -11,7 +11,7 @@ struct BareCustomStringOptionSet : CustomValueOptionSet { } var rawValue: Int - var customOptions: [Int: String] = [:] + var customOptionsByRawValue: [Int: String] = [:] var description: String = "" } @@ -19,27 +19,27 @@ struct BareCustomStringOptionSet : CustomValueOptionSet { class CustomStringOptionSetTests: XCTestCase { func getEmptySet() -> BareCustomStringOptionSet { var set = BareCustomStringOptionSet() - set.customOptions = [1: "value"] + set.customOptionsByRawValue = [1: "value"] return set } func getSet1() -> BareCustomStringOptionSet { var set = BareCustomStringOptionSet(rawValue: 1+2+4+8) - set.customOptions = [2: "value 2", + set.customOptionsByRawValue = [2: "value 2", 8: "value 8"] return set } func getSet2() -> BareCustomStringOptionSet { var set = BareCustomStringOptionSet(rawValue: 4+8+16+32) - set.customOptions = [8: "value 8", + set.customOptionsByRawValue = [8: "value 8", 32: "value 32"] return set } func getSubset() -> BareCustomStringOptionSet { var set = BareCustomStringOptionSet(rawValue: 4+8) - set.customOptions = [8: "value 8"] + set.customOptionsByRawValue = [8: "value 8"] return set } @@ -57,9 +57,9 @@ class CustomStringOptionSetTests: XCTestCase { let set = getSet1() XCTAssertEqual(set, set.intersection(set, - comparisonPolicy: .allowEqual)) + comparisonPolicy: .equal)) XCTAssertEqual(emptySet, set.intersection(emptySet, - comparisonPolicy: .allowEqual)) + comparisonPolicy: .equal)) } // x.union(x) == x @@ -69,9 +69,9 @@ class CustomStringOptionSetTests: XCTestCase { let set = getSet1() XCTAssertEqual(set, set.union(set, - comparisonPolicy: .allowEqual)) + comparisonPolicy: .equal)) XCTAssertEqual(set, set.union(emptySet, - comparisonPolicy: .allowEqual)) + comparisonPolicy: .equal)) } // x.contains(e) implies x.union(y).contains(e) @@ -82,10 +82,10 @@ class CustomStringOptionSetTests: XCTestCase { let set2 = getSet2() let setE = getSubset() - XCTAssertTrue(set1.contains(setE, comparisonPolicy: .allowEqual)) - XCTAssertTrue(set1.union(set2, comparisonPolicy: .allowEqual).contains(setE, comparisonPolicy: .allowEqual)) + XCTAssertTrue(set1.contains(setE, comparisonPolicy: .equal)) + XCTAssertTrue(set1.union(set2, comparisonPolicy: .equal).contains(setE, comparisonPolicy: .equal)) - XCTAssertTrue(set1.intersection(set2, comparisonPolicy: .allowEqual).contains(setE, comparisonPolicy: .allowEqual)) + XCTAssertTrue(set1.intersection(set2, comparisonPolicy: .equal).contains(setE, comparisonPolicy: .equal)) } // x.isSubset(of: y) implies x.union(y) == y @@ -96,11 +96,11 @@ class CustomStringOptionSetTests: XCTestCase { let setE = getSubset() XCTAssertTrue(setE.isSubset(of: set1, - comparisonPolicy: .allowEqual)) - XCTAssertEqual(setE.union(set1, comparisonPolicy: .allowEqual), set1) - XCTAssertEqual(set1.union(setE, comparisonPolicy: .allowEqual), set1) + comparisonPolicy: .equal)) + XCTAssertEqual(setE.union(set1, comparisonPolicy: .equal), set1) + XCTAssertEqual(set1.union(setE, comparisonPolicy: .equal), set1) XCTAssertTrue(set1.isSuperset(of: setE, - comparisonPolicy: .allowEqual)) + comparisonPolicy: .equal)) } // x.isStrictSuperset(of: y) if and only if x.isSuperset(of: y) && x != y @@ -109,8 +109,8 @@ class CustomStringOptionSetTests: XCTestCase { let set1 = getSet1() let setE = getSubset() - XCTAssertTrue(set1.isStrictSuperset(of: setE, comparisonPolicy: .allowEqual)) - XCTAssertTrue(setE.isStrictSubset(of: set1, comparisonPolicy: .allowEqual)) + XCTAssertTrue(set1.isStrictSuperset(of: setE, comparisonPolicy: .equal)) + XCTAssertTrue(setE.isStrictSubset(of: set1, comparisonPolicy: .equal)) XCTAssertNotEqual(set1, setE) } }