Skip to content

Commit 2c200c4

Browse files
committed
Add a negatable predicate with the operator !
1 parent 6e5bb3c commit 2c200c4

6 files changed

Lines changed: 152 additions & 0 deletions

File tree

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ print(result4) // true
3636
// Check if text contains "fox" AND ("Jumps" OR "swift") case insensitively and without diacritics
3737
let result5 = text.contains(~"fox" && (~"Jumps" || ~"swift"))
3838
print(result5) // true
39+
40+
// Check if text does NOT contain "cat" AND "bird"
41+
let result6 = text.contains(!"cat" && !"bird")
42+
print(result6) // true
43+
44+
// Check if text does NOT contain "brown"
45+
let result7 = text.contains(!"brown")
46+
print(result7) // false
47+
48+
// Check if text does NOT contain "cat" case insensitively and without diacritics
49+
let result8 = text.contains(!~"cat")
50+
print(result8) // true
3951
```
4052

4153
## How to Install
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// NegatablePredicateSearchStrategy.swift
3+
//
4+
//
5+
// Created by Victor C Tavernari on 24/03/2023.
6+
//
7+
8+
import Foundation
9+
10+
/// A search strategy that negates the result of another search strategy.
11+
final class NegatablePredicateSearchStrategy: SearchStrategy {
12+
13+
let searchStrategy: SearchStrategy
14+
15+
init(searchStrategy: SearchStrategy) {
16+
17+
self.searchStrategy = searchStrategy
18+
}
19+
20+
/// Evaluates the given string with the negated search strategy.
21+
///
22+
/// - Parameter string: The string to be evaluated.
23+
/// - Returns: `true` if the given string does not match the original search strategy, `false` otherwise.
24+
func evaluate(string: String) -> Bool {
25+
26+
return !self.searchStrategy.evaluate(string: string)
27+
}
28+
}
29+
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// NegatableValueSearchStrategy.swift
3+
//
4+
//
5+
// Created by Victor C Tavernari on 24/03/2023.
6+
//
7+
8+
import Foundation
9+
10+
/// A search strategy that negates the presence of a given value in a string.
11+
final class NegatableValueSearchStrategy: SearchStrategy {
12+
13+
let value: String
14+
15+
init(value: String) {
16+
17+
self.value = value
18+
}
19+
20+
/// Evaluates the given string with the negated value search strategy.
21+
///
22+
/// - Parameter string: The string to be evaluated.
23+
/// - Returns: `true` if the given string does not contain the value, `false` otherwise.
24+
func evaluate(string: String) -> Bool {
25+
26+
return !string.contains(self.value)
27+
}
28+
}
29+

Sources/StringContainsOperators/SearchStrategyMaker.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ enum SearchStrategyMaker {
4141

4242
case let .regexp(pattern):
4343
return RegexSearchStrategy(pattern: pattern)
44+
45+
case let .negatable(value):
46+
return NegatableValueSearchStrategy(value: value)
47+
48+
case let .negatablePredicate(predicate):
49+
return NegatablePredicateSearchStrategy(searchStrategy: self.make(predicate: predicate))
4450
}
4551
}
4652
}

Sources/StringContainsOperators/StringContainsOperators.swift

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ infix operator || : LogicalDisjunctionPrecedence
1010
infix operator && : LogicalConjunctionPrecedence
1111
prefix operator ~
1212
prefix operator =~
13+
prefix operator !
1314

1415
/// An enum representing a string search predicate.
1516
public indirect enum StringPredicate {
@@ -38,67 +39,131 @@ public indirect enum StringPredicate {
3839
/// Represents a regular expression pattern that can be used to match a string using NSRegularExpression.
3940
/// - Note: The String value should be a valid regular expression pattern.
4041
case regexp(String)
42+
43+
/// Represents a negatable search predicate for a given string.
44+
case negatable(String)
45+
46+
/// Represents a negatable search predicate for a given `StringPredicate`.
47+
case negatablePredicate(StringPredicate)
4148
}
4249

4350
/// Returns a `StringPredicate` that performs a logical OR operation between two strings.
51+
/// - Parameters:
52+
/// - lhs: The first string to be evaluated.
53+
/// - rhs: The second string to be evaluated.
54+
/// - Returns: A `StringPredicate` that performs a logical OR operation between two strings.
4455
public func || (lhs: String, rhs: String) -> StringPredicate {
4556

4657
return .or([lhs, rhs])
4758
}
4859

4960
/// Returns a `StringPredicate` that performs a logical OR operation between a string and a `StringPredicate`.
61+
/// - Parameters:
62+
/// - lhs: The string to be evaluated.
63+
/// - rhs: The `StringPredicate` to be evaluated.
64+
/// - Returns: A `StringPredicate` that performs a logical OR operation between a string and a `StringPredicate`.
5065
public func || (lhs: String, rhs: StringPredicate) -> StringPredicate {
5166

5267
return .orPredicates(lhs, rhs)
5368
}
5469

5570
/// Returns a `StringPredicate` that performs a logical OR operation between a `StringPredicate` and a string.
71+
/// - Parameters:
72+
/// - lhs: The `StringPredicate` to be evaluated.
73+
/// - rhs: The string to be evaluated.
74+
/// - Returns: A `StringPredicate` that performs a logical OR operation between a `StringPredicate` and a string.
5675
public func || (lhs: StringPredicate, rhs: String) -> StringPredicate {
5776

5877
return .orPredicates(rhs, lhs)
5978
}
6079

6180
/// Returns a `StringPredicate` that performs a logical OR operation between two `StringPredicate`s.
81+
/// - Parameters:
82+
/// - lhs: The first `StringPredicate` to be evaluated.
83+
/// - rhs: The second `StringPredicate` to be evaluated.
84+
/// - Returns: A `StringPredicate` that performs a logical OR operation between two `StringPredicate`s.
6285
public func || (lhs: StringPredicate, rhs: StringPredicate) -> StringPredicate {
6386

6487
return .orOnlyPredicates([rhs, lhs])
6588
}
6689

6790
/// Returns a `StringPredicate` that performs a logical AND operation between two strings.
91+
/// - Parameters:
92+
/// - lhs: The first string to be evaluated.
93+
/// - rhs: The second string to be evaluated.
94+
/// - Returns: A `StringPredicate` that performs a logical AND operation between two strings.
6895
public func && (lhs: String, rhs: String) -> StringPredicate {
6996

7097
return .and([lhs, rhs])
7198
}
7299

73100
/// Returns a `StringPredicate` that performs a logical AND operation between a string and a `StringPredicate`.
101+
/// - Parameters:
102+
/// - lhs: The string to be evaluated.
103+
/// - rhs: The `StringPredicate` to be evaluated.
104+
/// - Returns: A `StringPredicate` that performs a logical AND operation between a string and a `StringPredicate`.
74105
public func && (lhs: String, rhs: StringPredicate) -> StringPredicate {
75106

76107
return .andPredicates(lhs, rhs)
77108
}
78109

79110
/// Returns a `StringPredicate` that performs a logical AND operation between a `StringPredicate` and a string.
111+
/// - Parameters:
112+
/// - lhs: The `StringPredicate` to be evaluated.
113+
/// - rhs: The string to be evaluated.
114+
/// - Returns: A `StringPredicate` that performs a logical AND operation between a `StringPredicate` and a string.
80115
public func && (lhs: StringPredicate, rhs: String) -> StringPredicate {
81116

82117
return .andPredicates(rhs, lhs)
83118
}
84119

85120
/// Returns a `StringPredicate` that performs a logical AND operation between two `StringPredicate`s.
121+
/// - Parameters:
122+
/// - lhs: The first `StringPredicate` to be evaluated.
123+
/// - rhs: The second `StringPredicate` to be evaluated.
124+
/// - Returns: A `StringPredicate
86125
public func && (lhs: StringPredicate, rhs: StringPredicate) -> StringPredicate {
87126

88127
return .andOnlyPredicates([rhs, lhs])
89128
}
90129

91130
/// Returns a `StringPredicate` that performs a case-insensitive and diacritic-insensitive search for a given string.
131+
///
132+
/// - Parameter value: The value to be evaluated.
133+
/// - Returns: A `StringPredicate` that performs a case-insensitive and diacritic-insensitive search for a given string.
92134
public prefix func ~ (value: String) -> StringPredicate {
93135

94136
return .diacriticAndCaseInsensitive(value)
95137
}
96138

139+
/// Returns a `StringPredicate` that performs a regular expression pattern that can be used to match a string using NSRegularExpression.
140+
///
141+
/// - Parameter value: The regular expression pattern as a string value.
142+
/// - Returns: A `StringPredicate` that can be used to perform regular expression pattern matching.
97143
public prefix func =~ (value: String) -> StringPredicate {
98144

99145
return .regexp(value)
100146
}
101147

148+
149+
/// Returns a `StringPredicate` that negates another `StringPredicate`.
150+
///
151+
/// - Parameter predicate: The predicate to be negated.
152+
/// - Returns: A `StringPredicate` that represents the negation of the given predicate.
153+
public prefix func ! (predicate: StringPredicate) -> StringPredicate {
154+
155+
return .negatablePredicate(predicate)
156+
}
157+
158+
/// Returns a `StringPredicate` that negates a given value.
159+
///
160+
/// - Parameter value: The value to be negated.
161+
/// - Returns: A `StringPredicate` that represents the negation of the given value.
162+
public prefix func ! (value: String) -> StringPredicate {
163+
164+
return .negatable(value)
165+
}
166+
102167
public extension String {
103168

104169
/// Returns a Boolean value indicating whether the string contains the given `StringPredicate`.

Tests/StringContainsOperatorsTests/StringContainsOperatorsTests.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,15 @@ final class StringContainsOperatorsTests: XCTestCase {
121121

122122
XCTAssertFalse(string.contains(=~"^*$(dis.a"))
123123
}
124+
125+
func testNegatablePredicate() {
126+
let text = "Hello my little friend"
127+
128+
XCTAssertTrue(text.contains(!"fiance"))
129+
XCTAssertFalse(text.contains(!"my"))
130+
XCTAssertTrue(text.contains(!("enemy" && "little")))
131+
XCTAssertFalse(text.contains(!("friend" && "little")))
132+
XCTAssertTrue(text.contains(!("enemy" || "big")))
133+
XCTAssertFalse(text.contains(!("friend" || "big")))
134+
}
124135
}

0 commit comments

Comments
 (0)