From 3ed161ef557639aead153af683ecb5283acc7ac8 Mon Sep 17 00:00:00 2001 From: Tim Bert <5411131+timbms@users.noreply.github.com> Date: Sat, 7 Sep 2024 12:25:38 +0200 Subject: [PATCH] Swift 5.7 regex (#796) * Migrate to regex introduced in swift 5.7 Migrate swift-tools-version to 5.9 Uplift target for OpenHABCore to iOS 16, watchOS 9 - Consistently upgrade target of watch app to watchOS 9 * Fixing deprecation issue: 'SecTrustGetCertificateAtIndex' was deprecated in watchOS 8.0: renamed to 'SecTrustCopyCertificateChain(_:)' Adding test for regex used for OpenHABStateDescription.numberPattern Adding swift 5.7 regex for OpenHABRootViewController.uiCommandAction() LocalizationsTests migrated to swift 5.7 regex --- NotificationService/NotificationService.swift | 47 ++++++++----------- OpenHABCore/Package.swift | 14 +++--- .../Model/OpenHABStateDescription.swift | 16 +++---- .../OpenHABCore/Model/OpenHABWidget.swift | 8 ++-- .../Util/ServerCertificateManager.swift | 12 ++--- .../OpenHABCoreGeneralTests.swift | 7 ++- openHAB.xcodeproj/project.pbxproj | 6 +-- openHAB/OpenHABRootViewController.swift | 47 ++++++++----------- openHABTestsSwift/LocalizationTests.swift | 26 +++++----- .../Model/ObservableOpenHABWidget.swift | 10 ++-- 10 files changed, 84 insertions(+), 109 deletions(-) diff --git a/NotificationService/NotificationService.swift b/NotificationService/NotificationService.swift index a030ec68..b63d6a0a 100644 --- a/NotificationService/NotificationService.swift +++ b/NotificationService/NotificationService.swift @@ -165,35 +165,28 @@ class NotificationService: UNNotificationServiceExtension { return } if let state = item.state { - do { - // Extract MIME type and base64 string - let pattern = "^data:(.*?);base64,(.*)$" - let regex = try NSRegularExpression(pattern: pattern, options: []) - if let match = regex.firstMatch(in: state, options: [], range: NSRange(location: 0, length: state.utf16.count)) { - let mimeTypeRange = Range(match.range(at: 1), in: state) - let base64Range = Range(match.range(at: 2), in: state) - if let mimeTypeRange, let base64Range { - let mimeType = String(state[mimeTypeRange]) - let base64String = String(state[base64Range]) - if let imageData = Data(base64Encoded: base64String) { - // Create a temporary file URL - let tempDirectory = FileManager.default.temporaryDirectory - let tempFileURL = tempDirectory.appendingPathComponent(UUID().uuidString) - do { - try imageData.write(to: tempFileURL) - os_log("Image saved to temporary file: %{PUBLIC}@", log: .default, type: .info, tempFileURL.absoluteString) - self.attachFile(localURL: tempFileURL, mimeType: mimeType, completion: completion) - return - } catch { - os_log("Failed to write image data to file: %{PUBLIC}@", log: .default, type: .error, error.localizedDescription) - } - } else { - os_log("Failed to decode base64 string to Data", log: .default, type: .error) - } + // Extract MIME type and base64 string + let pattern = /^data:(.*?);base64,(.*)$/ + if let firstMatch = state.firstMatch(of: pattern) { + let mimeType = String(firstMatch.1) + let base64String = String(firstMatch.2) + if let imageData = Data(base64Encoded: base64String) { + // Create a temporary file URL + let tempDirectory = FileManager.default.temporaryDirectory + let tempFileURL = tempDirectory.appendingPathComponent(UUID().uuidString) + do { + try imageData.write(to: tempFileURL) + os_log("Image saved to temporary file: %{PUBLIC}@", log: .default, type: .info, tempFileURL.absoluteString) + self.attachFile(localURL: tempFileURL, mimeType: mimeType, completion: completion) + return + } catch { + os_log("Failed to write image data to file: %{PUBLIC}@", log: .default, type: .error, error.localizedDescription) } + } else { + os_log("Failed to decode base64 string to Data", log: .default, type: .error) } - } catch { - os_log("Failed to parse data: %{PUBLIC}@", log: .default, type: .error, error.localizedDescription) + } else { + os_log("Failed to parse data: %{PUBLIC}@", log: .default, type: .error, error?.localizedDescription ?? "") } } completion(nil) diff --git a/OpenHABCore/Package.swift b/OpenHABCore/Package.swift index 8363eda5..110808da 100644 --- a/OpenHABCore/Package.swift +++ b/OpenHABCore/Package.swift @@ -1,11 +1,11 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "OpenHABCore", - platforms: [.iOS(.v12), .watchOS(.v6)], + platforms: [.iOS(.v16), .watchOS(.v9)], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. .library( @@ -15,8 +15,8 @@ let package = Package( ], dependencies: [ // Dependencies declare other packages that this package depends on. - .package(name: "Alamofire", url: "https://github.com/Alamofire/Alamofire.git", from: "5.0.0"), - .package(name: "Kingfisher", url: "https://github.com/onevcat/Kingfisher.git", from: "7.0.0") + .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.0.0"), + .package(url: "https://github.com/onevcat/Kingfisher.git", from: "7.0.0") ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. @@ -26,14 +26,16 @@ let package = Package( dependencies: [ .product(name: "Alamofire", package: "Alamofire", condition: .when(platforms: [.iOS, .watchOS])), .product(name: "Kingfisher", package: "Kingfisher", condition: .when(platforms: [.iOS, .watchOS])) - ] + ], + swiftSettings: [.enableUpcomingFeature("BareSlashRegexLiterals")] ), .testTarget( name: "OpenHABCoreTests", dependencies: ["OpenHABCore"], resources: [ .process("Resources") - ] + ], + swiftSettings: [.enableUpcomingFeature("BareSlashRegexLiterals")] ) ] ) diff --git a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABStateDescription.swift b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABStateDescription.swift index 03511972..99757aa6 100644 --- a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABStateDescription.swift +++ b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABStateDescription.swift @@ -21,7 +21,7 @@ public class OpenHABStateDescription { public var numberPattern: String? - init(minimum: Double?, maximum: Double?, step: Double?, readOnly: Bool?, options: [OpenHABOptions]?, pattern: String?) { + init(minimum: Double?, maximum: Double?, step: Double?, readOnly: Bool?, options: [OpenHABOptions]?, pattern tobeSearched: String?) { self.minimum = minimum ?? 0.0 self.maximum = maximum ?? 100.0 self.step = step ?? 1.0 @@ -30,16 +30,12 @@ public class OpenHABStateDescription { // Remove transformation instructions (e.g. for 'MAP(foo.map):%s' keep only '%s') - let regexPattern = #"^[A-Z]+(\(.*\))?:(.*)$"# - let regex = try? NSRegularExpression(pattern: regexPattern, options: .caseInsensitive) - if let pattern { - let nsrange = NSRange(pattern.startIndex ..< pattern.endIndex, in: pattern) - if let match = regex?.firstMatch(in: pattern, options: [], range: nsrange) { - if let range = Range(match.range(at: 2), in: pattern) { - numberPattern = String(pattern[range]) - } + let regexPattern = /^[A-Z]+(\(.*\))?:(.*)$/.ignoresCase() + if let tobeSearched { + if let firstMatch = tobeSearched.firstMatch(of: regexPattern) { + numberPattern = String(firstMatch.2) } else { - numberPattern = pattern + numberPattern = tobeSearched } } else { numberPattern = nil diff --git a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABWidget.swift b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABWidget.swift index 44773b73..b881bd56 100644 --- a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABWidget.swift +++ b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABWidget.swift @@ -107,11 +107,9 @@ public class OpenHABWidget: NSObject, MKAnnotation, Identifiable { // Text between square brackets public var labelValue: String? { - // Swift 5 raw strings - let regex = try? NSRegularExpression(pattern: #"\[(.*?)\]"#, options: [.dotMatchesLineSeparators]) - guard let match = regex?.firstMatch(in: label, options: [], range: NSRange(location: 0, length: (label as NSString).length)) else { return nil } - guard let range = Range(match.range(at: 1), in: label) else { return nil } - return String(label[range]) + let pattern = /\[(.*?)\]/.dotMatchesNewlines() + guard let firstMatch = label.firstMatch(of: pattern) else { return nil } + return String(firstMatch.1) } public var coordinate: CLLocationCoordinate2D { diff --git a/OpenHABCore/Sources/OpenHABCore/Util/ServerCertificateManager.swift b/OpenHABCore/Sources/OpenHABCore/Util/ServerCertificateManager.swift index a7a7d15b..f5fd819e 100644 --- a/OpenHABCore/Sources/OpenHABCore/Util/ServerCertificateManager.swift +++ b/OpenHABCore/Sources/OpenHABCore/Util/ServerCertificateManager.swift @@ -224,17 +224,11 @@ public class ServerCertificateManager: ServerTrustManager, ServerTrustEvaluating func getLeafCertificate(trust: SecTrust?) -> SecCertificate? { // Returns the leaf certificate from a SecTrust object (that is always the // certificate at index 0). - var result: SecCertificate? - if let trust { - if SecTrustGetCertificateCount(trust) > 0 { - result = SecTrustGetCertificateAtIndex(trust, 0) - return result - } else { - return nil - } + if let trust, SecTrustGetCertificateCount(trust) > 0, let certificates = SecTrustCopyCertificateChain(trust) as? [SecCertificate] { + certificates[0] } else { - return nil + nil } } diff --git a/OpenHABCore/Tests/OpenHABCoreTests/OpenHABCoreGeneralTests.swift b/OpenHABCore/Tests/OpenHABCoreTests/OpenHABCoreGeneralTests.swift index 773c1443..a57b3830 100644 --- a/OpenHABCore/Tests/OpenHABCoreTests/OpenHABCoreGeneralTests.swift +++ b/OpenHABCore/Tests/OpenHABCoreTests/OpenHABCoreGeneralTests.swift @@ -28,11 +28,16 @@ final class OpenHABCoreGeneralTests: XCTestCase { XCTAssertEqual(urlc, URL(string: "http://192.169.2.1/icon/switch?state=OFF&format=SVG"), "Check endpoint creation") } - func testLabelVale() { + func testLabelValue() { let widget = OpenHABWidget() widget.label = "llldl [llsl]" XCTAssertEqual(widget.labelValue, "llsl") widget.label = "llllsl[kkks] llls" XCTAssertEqual(widget.labelValue, "kkks") } + + func testOpenHABStateDescription() { + let openHABStateDescription = OpenHABStateDescription(minimum: 0.0, maximum: 1.0, step: 0.2, readOnly: true, options: nil, pattern: "MAP(foo.map):%s") + XCTAssertEqual(openHABStateDescription.numberPattern, "%s") + } } diff --git a/openHAB.xcodeproj/project.pbxproj b/openHAB.xcodeproj/project.pbxproj index 6a6b11d7..ebad98d2 100644 --- a/openHAB.xcodeproj/project.pbxproj +++ b/openHAB.xcodeproj/project.pbxproj @@ -1815,7 +1815,7 @@ SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = openHAB; - WATCHOS_DEPLOYMENT_TARGET = 6.0; + WATCHOS_DEPLOYMENT_TARGET = 8.0; }; name = Debug; }; @@ -1903,7 +1903,7 @@ SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; VERSIONING_SYSTEM = "apple-generic"; - WATCHOS_DEPLOYMENT_TARGET = 7.0; + WATCHOS_DEPLOYMENT_TARGET = 9.0; }; name = Debug; }; @@ -1950,7 +1950,7 @@ SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; VERSIONING_SYSTEM = "apple-generic"; - WATCHOS_DEPLOYMENT_TARGET = 7.0; + WATCHOS_DEPLOYMENT_TARGET = 9.0; }; name = Release; }; diff --git a/openHAB/OpenHABRootViewController.swift b/openHAB/OpenHABRootViewController.swift index 48488b27..d41e5a40 100644 --- a/openHAB/OpenHABRootViewController.swift +++ b/openHAB/OpenHABRootViewController.swift @@ -202,34 +202,27 @@ class OpenHABRootViewController: UIViewController { private func uiCommandAction(_ command: String) { os_log("navigateCommandAction: %{PUBLIC}@", log: .notifications, type: .info, command) - let pattern = "^(/basicui/app\\?.*|/.*|.*)$" - - do { - let regex = try NSRegularExpression(pattern: pattern, options: []) - let nsString = command as NSString - let results = regex.matches(in: command, options: [], range: NSRange(location: 0, length: nsString.length)) - - if let match = results.first { - let pathRange = match.range(at: 1) - let path = nsString.substring(with: pathRange) - os_log("navigateCommandAction path: %{PUBLIC}@", log: .notifications, type: .info, path) - if currentView != webViewController { - switchView(target: .webview) - } - if path.starts(with: "/basicui/app?") { - // TODO: this is a sitemap, we should use the native renderer - // temp hack right now to just use a webview - webViewController.loadWebView(force: true, path: path) - } else if path.starts(with: "/") { - // have the webview load this path itself - webViewController.loadWebView(force: true, path: path) - } else { - // have the mainUI handle the navigation - webViewController.navigateCommand(path) - } + let regexPattern = /^(\/basicui\/app\\?.*|\/.*|.*)$/ + if let firstMatch = command.firstMatch(of: regexPattern) { + let path = String(firstMatch.1) + os_log("navigateCommandAction path: %{PUBLIC}@", log: .notifications, type: .info, path) + if currentView != webViewController { + switchView(target: .webview) } - } catch { - os_log("Invalid regex: %{PUBLIC}@", log: .notifications, type: .error, error.localizedDescription) + if path.starts(with: "/basicui/app?") { + // TODO: this is a sitemap, we should use the native renderer + // temp hack right now to just use a webview + webViewController.loadWebView(force: true, path: path) + } else if path.starts(with: "/") { + // have the webview load this path itself + webViewController.loadWebView(force: true, path: path) + } else { + // have the mainUI handle the navigation + webViewController.navigateCommand(path) + } + + } else { + os_log("Invalid regex: %{PUBLIC}@", log: .notifications, type: .error, command) } } diff --git a/openHABTestsSwift/LocalizationTests.swift b/openHABTestsSwift/LocalizationTests.swift index e2f54808..16612b05 100644 --- a/openHABTestsSwift/LocalizationTests.swift +++ b/openHABTestsSwift/LocalizationTests.swift @@ -37,22 +37,18 @@ class LocalizationTests: XCTestCase { for language in LocalizationTests.localizations { print("Testing language: '\(language)'.") for tuple in LocalizationTests.localizedFormatStrings { - do { - guard let translation = tuple.key.localized(for: language)?.replacingOccurrences(of: "%%", with: "") else { - XCTFail("Failed to get translation for key '\(tuple.key)' in language '\(language)'.") - continue - } - - XCTAssertNotEqual(translation, "__MISSING__", "Missing translation for key '\(tuple.key)' in language '\(language)'.") - let formatSpecifiersRegEx = try NSRegularExpression(pattern: "%(?:\\d+\\$)?[+-]?(?:[lh]{0,2})(?:[qLztj])?(?:[ 0]|'.{1})?\\d*(?:\\.\\d?)?[@dDiuUxXoOfeEgGcCsSpaAFn]") - let numberOfMatches = formatSpecifiersRegEx.numberOfMatches(in: translation, options: [], range: NSRange(location: 0, length: translation.utf16.count)) - XCTAssertEqual(numberOfMatches, tuple.arguments.count, "Invalid number of format specifiers for key '\(tuple.key)' in language '\(language)'.") - } catch { - XCTFail("Failed to create regular expression for key '\(tuple.key)' in language '\(language)'.") + guard let translation = tuple.key.localized(for: language)?.replacingOccurrences(of: "%%", with: "") else { + XCTFail("Failed to get translation for key '\(tuple.key)' in language '\(language)'.") + continue } - let translation = tuple.key.localizedWithFormat(for: language, arguments: tuple.arguments) - XCTAssertNotNil(translation, "Failed to get translation for key '\(tuple.key)' in language '\(language)'.") - print("Translation: \(tuple.key) = \(translation ?? "FAILED")") + XCTAssertNotEqual(translation, "__MISSING__", "Missing translation for key '\(tuple.key)' in language '\(language)'.") + let regex = /%(?:\d+\$)?[+-]?(?:[lh]{0,2})(?:[qLztj])?(?:[ 0]|'.{1})?\d*(?:\\.\d?)?[@dDiuUxXoOfeEgGcCsSpaAFn]/ + let numberOfMatches = translation.matches(of: regex).count + XCTAssertEqual(numberOfMatches, tuple.arguments.count, "Invalid number of format specifiers for key '\(tuple.key)' in language '\(language)'.") + + let translationResult = tuple.key.localizedWithFormat(for: language, arguments: tuple.arguments) + XCTAssertNotNil(translationResult, "Failed to get translation for key '\(tuple.key)' in language '\(language)'.") + print("Translation: \(tuple.key) = \(translation)") } } } diff --git a/openHABWatch Extension/openHABWatch Extension/Model/ObservableOpenHABWidget.swift b/openHABWatch Extension/openHABWatch Extension/Model/ObservableOpenHABWidget.swift index 9241caa9..8e0c798b 100644 --- a/openHABWatch Extension/openHABWatch Extension/Model/ObservableOpenHABWidget.swift +++ b/openHABWatch Extension/openHABWatch Extension/Model/ObservableOpenHABWidget.swift @@ -83,12 +83,10 @@ class ObservableOpenHABWidget: NSObject, MKAnnotation, Identifiable, ObservableO } // Text between square brackets - var labelValue: String? { - // Swift 5 raw strings - let regex = try? NSRegularExpression(pattern: #"\[(.*?)\]"#, options: []) - guard let match = regex?.firstMatch(in: label, options: [], range: NSRange(location: 0, length: (label as NSString).length)) else { return nil } - guard let range = Range(match.range(at: 1), in: label) else { return nil } - return String(label[range]) + public var labelValue: String? { + let pattern = /\[(.*?)\]/.dotMatchesNewlines() + guard let firstMatch = label.firstMatch(of: pattern) else { return nil } + return String(firstMatch.1) } var coordinate: CLLocationCoordinate2D {