From d884b8264e70c148a131a8f1506575781eb39f43 Mon Sep 17 00:00:00 2001 From: Thomas Rasch Date: Thu, 5 Sep 2024 11:07:27 +0200 Subject: [PATCH] Regex matching --- .../Extensions/StringExtensions.swift | 24 +++++++++++++++ Sources/MVTTools/QueryParser.swift | 30 +++++++++++++++---- Tests/MVTToolsTests/QueryParserTests.swift | 5 ++++ 3 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 Sources/MVTTools/Extensions/StringExtensions.swift diff --git a/Sources/MVTTools/Extensions/StringExtensions.swift b/Sources/MVTTools/Extensions/StringExtensions.swift new file mode 100644 index 0000000..0199319 --- /dev/null +++ b/Sources/MVTTools/Extensions/StringExtensions.swift @@ -0,0 +1,24 @@ +import Foundation + +extension String { + + public func matches(_ regex: String) -> Bool { + var options: String.CompareOptions = .regularExpression + + var regex = regex + if regex.hasPrefix("/") { + regex.removeFirst() + + if regex.hasSuffix("/i") { + options.insert(.caseInsensitive) + regex.removeLast(2) + } + else if regex.hasSuffix("/") { + regex.removeLast() + } + } + + return self.range(of: regex, options: options) != nil + } + +} diff --git a/Sources/MVTTools/QueryParser.swift b/Sources/MVTTools/QueryParser.swift index 99ed32f..376a9c3 100644 --- a/Sources/MVTTools/QueryParser.swift +++ b/Sources/MVTTools/QueryParser.swift @@ -11,6 +11,7 @@ public struct QueryParser { case greaterThanOrEqual case lessThan case lessThanOrEqual + case regex } // Conditions @@ -69,6 +70,14 @@ public struct QueryParser { else { return false } stack.insert(compare(first: first, second: second, condition: condition), at: 0) + + case .regex: + guard stack.count >= 2, + let regex = stack.removeFirst() as? String, + let value = stack.removeFirst() as? String + else { return false } + + stack.insert(value.matches(regex), at: 0) } case let .condition(condition): @@ -178,12 +187,21 @@ public struct QueryParser { private func compare(left: T, right: T, condition: QueryParser.Expression.Comparison) -> Bool { switch condition { - case .equals: left == right - case .notEquals: left != right - case .greaterThan: left > right - case .greaterThanOrEqual: left >= right - case .lessThan: left < right - case .lessThanOrEqual: left <= right + case .equals: + return left == right + case .notEquals: + return left != right + case .greaterThan: + return left > right + case .greaterThanOrEqual: + return left >= right + case .lessThan: + return left < right + case .lessThanOrEqual: + return left <= right + case .regex: + guard let value = left as? String, let regex = right as? String else { return false } + return value.matches(regex) } } diff --git a/Tests/MVTToolsTests/QueryParserTests.swift b/Tests/MVTToolsTests/QueryParserTests.swift index 624d3c7..af743a3 100644 --- a/Tests/MVTToolsTests/QueryParserTests.swift +++ b/Tests/MVTToolsTests/QueryParserTests.swift @@ -13,6 +13,7 @@ final class QueryParserTests: XCTestCase { "b", ], "value": 1, + "string": "Some name" ] private func result(for pipeline: [QueryParser.Expression]) -> Bool { @@ -40,6 +41,10 @@ final class QueryParserTests: XCTestCase { XCTAssertTrue(result(for: [.valueFor(["value"]), .literal(1), .comparison(.lessThanOrEqual)])) XCTAssertTrue(result(for: [.valueFor(["value"]), .literal(1.5), .comparison(.lessThanOrEqual)])) XCTAssertFalse(result(for: [.valueFor(["x"]), .literal(1), .comparison(.equals)])) + XCTAssertTrue(result(for: [.valueFor(["string"]), .literal("name$"), .comparison(.regex)])) + XCTAssertTrue(result(for: [.valueFor(["string"]), .literal("/[Ss]ome/"), .comparison(.regex)])) + XCTAssertFalse(result(for: [.valueFor(["string"]), .literal("^some"), .comparison(.regex)])) + XCTAssertTrue(result(for: [.valueFor(["string"]), .literal("/^some/i"), .comparison(.regex)])) } func testConditions() throws {