/
EasyPredicate.swift
233 lines (206 loc) · 7.29 KB
/
EasyPredicate.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
//
// Predicate.swift
// DemoUITests
//
// Created by Daniel Yang on 2019/7/2.
// Copyright © 2019 Daniel Yang. All rights reserved.
//
import Foundation
import XCTest
/// infix string of Regular expression
public enum Comparison: RawRepresentable {
// MARK: - Cases
case equals
case notEqual
case beginsWith
case contains
case endsWith
case like
case matches
case other(String)
// MARK: - RawRepresentable
public var rawValue: String {
switch self {
case .equals: return "="
case .notEqual: return "!="
case .beginsWith: return "BEGINSWITH"
case .contains: return "CONTAINS"
case .endsWith: return "ENDSWITH"
case .like: return "LIKE"
case .matches: return "MATCHES"
case .other(let comparisonOperator): return comparisonOperator
}
}
/// default as .other case
///
/// - Parameter rawValue: regular string
public init(rawValue: String) {
switch rawValue {
case "=": self = .equals
case "!=": self = .notEqual
case "BEGINSWITH": self = .beginsWith
case "CONTAINS": self = .contains
case "ENDSWITH": self = .endsWith
case "LIKE": self = .like
case "MATCHES": self = .matches
default: self = .other(rawValue)
}
}
}
/// PredicateKey
public enum PredicateKey {
public enum bool: String { case exists, isEnabled, isHittable, isSelected }
public enum string: String { case identifier, label }
public enum type: String { case elementType }
}
/**
The rawValue of **EasyPredicate**
*/
public enum PredicateRawValue: RawRepresentable {
// MARK: - Cases
case bool(key: PredicateKey.bool, comparison: Comparison, value: Bool)
case string(key: PredicateKey.string, comparison: Comparison, value: String)
case type(value: XCUIElement.ElementType)
case custom(regular: String)
// MARK: - RawRepresentable
/// default as custom case
///
/// - Parameter rawValue: regular string
public init?(rawValue: String) {
self = .custom(regular: rawValue)
}
/// convert to regularString
public var rawValue: String {
switch self {
case .bool(let key, let comparison, let value):
return "\(key.rawValue) \(comparison.rawValue) \(value ? "true" : "false")"
case .string(let key, let comparison, let value):
return "\(key.rawValue) \(comparison.rawValue) '\(value)'"
case .type(let value):
return "\(PredicateKey.type.elementType.rawValue) \(Comparison.equals.rawValue) \(value.rawValue)"
case .custom(let regular):
return regular
}
}
}
/**
MARK: - EasyPredicate
Although `NSPredicate` is powerfull but the developer interface is not good enough,
We can try to convert the hard code style into the object-oriented style as below.
*/
public enum EasyPredicate: RawRepresentable {
// MARK: - Cases
case exists(_ exists: Bool)
case isEnabled(_ isEnabled: Bool)
case isHittable(_ isHittable: Bool)
case isSelected(_ isSelected: Bool)
case label(_ comparison: Comparison, _ value: String)
case identifier(_ identifier: String)
case type(_ type: XCUIElement.ElementType)
case other(_ ragular: String)
// MARK: - RawRepresentable
public init?(rawValue: PredicateRawValue) {
switch rawValue {
case .bool(let key, _, let value):
switch key {
case .exists: self = .exists(value)
case .isEnabled: self = .isEnabled(value)
case .isSelected: self = .isSelected(value)
case .isHittable: self = .isHittable(value)
}
case .type(let value): self = .type(value)
case .string(let key, let comparison, let value):
switch key {
case .label: self = .label(comparison, value)
case .identifier: self = .identifier(value)
}
case .custom(let regular): self = .other(regular)
}
}
public var rawValue: PredicateRawValue {
switch self {
case .exists(let value):
return .bool(key: .exists, comparison: .equals, value: value)
case .isEnabled(let value):
return .bool(key: .isEnabled, comparison: .equals, value: value)
case .isHittable(let value):
return .bool(key: .isHittable, comparison: .equals, value: value)
case .isSelected(let value):
return .bool(key: .isSelected, comparison: .equals, value: value)
case .label(let comparison, let value):
return .string(key: .label, comparison: comparison, value: value)
case .identifier(let value):
return .string(key: .identifier, comparison: .equals, value: value)
case .type(let value):
return .type(value: value)
case .other(let value):
return .custom(regular: value)
}
}
}
extension EasyPredicate: Equatable {
// MARK: - Equatable
/// Equatable prtocol
///
/// - Parameters:
/// - l: left EasyPredicate
/// - r: right EasyPredicate
/// - Returns: is equal result
public static func ==(l: EasyPredicate, r: EasyPredicate) -> Bool {
return l.regularString == r.regularString
}
/// convert to NSPredicate
public var toPredicate: NSPredicate {
return NSPredicate(format: regularString)
}
// MARK: - Extensions
public var regularString: String {
return rawValue.rawValue
}
/// merge two predicate semantics, taking their intersection
///
/// - Parameter p: another easy predicate
/// - Returns: new predicate
public func and(_ p: EasyPredicate) -> EasyPredicate {
return [self, p].merged(withLogic: .and)
}
/// merge two predicate semantics, taking their union
///
/// - Parameter p: another easy predicate
/// - Returns: new predicate
public func or(_ p: EasyPredicate) -> EasyPredicate {
return [self, p].merged(withLogic: .or)
}
/// reverse the semantics of predicates
public var not: EasyPredicate {
return EasyPredicate.other("!(\(regularString))")
}
}
public extension Sequence where Element == EasyPredicate {
/// convert EasyPredicates to NSCompoundPredicate
func toPredicate(_ logic: NSCompoundPredicate.LogicalType) -> NSCompoundPredicate {
return NSCompoundPredicate(type: logic, subpredicates: map { $0.toPredicate })
}
/// merged all EasyPredicate as one
///
/// - Parameter logic: all EasyPredicate relate rule
/// - Returns: new EasyPredicate
func merged(withLogic logic: NSCompoundPredicate.LogicalType = .and) -> EasyPredicate {
let regulars = map { "(\($0.regularString))" }
let _logic = (logic == .not) ? .and : logic
var result = regulars.joined(separator: _logic.regularString)
if logic == .not { result = "!(\(result))" }
return EasyPredicate.other(result)
}
}
extension NSCompoundPredicate.LogicalType {
/// convert LogicalType as regular string
fileprivate var regularString: String {
switch self {
case .and: return " AND "
case .or: return " OR "
case .not: return " NOT "
@unknown default: fatalError()
}
}
}