Skip to content

Commit bf8c415

Browse files
committed
Support for collection expressions
1 parent bf4ce32 commit bf8c415

21 files changed

Lines changed: 482 additions & 140 deletions

Resources/MacDemo.png

20.8 KB
Loading

Sources/PredicateView/Attributes/ExpressionAttribute.swift

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,27 @@
77

88
import Foundation
99

10-
public struct ExpressionAttribute<Expr>: OperatorContainer, Hashable where Expr: SimpleExpression {
10+
public protocol ExpressionAttribute<Expr>: OperatorContainer, Hashable where Expr: SimpleExpression {
11+
var `operator`: Expr.Operator { get set }
12+
var value: Expr.AttributeValue { get set }
13+
}
14+
15+
public struct StandardAttribute<Expr>: ExpressionAttribute where Expr: SimpleExpression {
16+
public var `operator`: Expr.Operator
17+
public var value: Expr.AttributeValue
18+
}
19+
20+
public struct MetadataAttribute<Expr, Metadata>: ExpressionAttribute where Expr: SimpleExpression {
1121
public var `operator`: Expr.Operator
12-
public var value: Expr.Value
22+
public var value: Expr.AttributeValue
23+
public var metadata: Metadata
24+
25+
public static func == (lhs: MetadataAttribute<Expr, Metadata>, rhs: MetadataAttribute<Expr, Metadata>) -> Bool {
26+
lhs.operator == rhs.operator && lhs.value == rhs.value
27+
}
28+
29+
public func hash(into hasher: inout Hasher) {
30+
hasher.combine(self.operator)
31+
hasher.combine(self.value)
32+
}
1333
}

Sources/PredicateView/Expressions/Abstract/AnyExpression.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ import Foundation
1010
public typealias ExpressionCompatible = Codable & Hashable
1111

1212
public struct AnyExpression<Root>: Identifiable {
13-
public var wrappedValue: any ValueExpression<Root>
13+
public var wrappedValue: any TitledExpression<Root>
14+
public var title: String { wrappedValue.title }
1415
public var id: UUID { wrappedValue.id }
16+
17+
@inlinable
18+
public init(wrappedValue: any TitledExpression<Root>) {
19+
self.wrappedValue = wrappedValue
20+
}
21+
22+
public func copy() -> Self {
23+
var result = wrappedValue
24+
result.id = .init()
25+
return AnyExpression(wrappedValue: wrappedValue)
26+
}
1527
}

Sources/PredicateView/Expressions/Abstract/ContentExpression.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ public protocol ContentExpression<Root>: SimpleExpression {
1414
associatedtype Result: View
1515

1616
@ViewBuilder
17-
static func makeContentView(_ value: Binding<Value>) -> Result
17+
static func makeContentView(_ value: Binding<AttributeValue>) -> Result
1818
}
1919

20-
// MARK: - StandardValueExpressionView
20+
// MARK: - ContentExpressionView
2121

2222
public struct ContentExpressionView<Root, Expr: ContentExpression<Root>>: ExpressionView {
2323
public var expression: Binding<Expr>
@@ -34,7 +34,7 @@ public struct ContentExpressionView<Root, Expr: ContentExpression<Root>>: Expres
3434
}, content: {
3535
Expr.makeContentView($expression.attribute.value)
3636
}, menu: {
37-
Expr.operatorPickerView(using: $expression.attribute.operator)
37+
Expr.makeOperatorMenu(using: $expression.attribute.operator)
3838
})
3939
}
4040
}

Sources/PredicateView/Expressions/Abstract/Expression+View.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@ import SwiftUI
99

1010
extension Expression {
1111
@ViewBuilder
12-
public static func operatorPickerView<T: Hashable>(
12+
public static func makeOperatorMenu<T: Hashable>(
1313
using operation: Binding<T>
1414
) -> some View {
15-
operatorPickerView(using: operation) { option in
15+
makeOperatorMenu(using: operation) { option in
1616
Text(option.rawValue)
1717
.tag(option as! T)
1818
}
1919
}
2020

2121
@ViewBuilder
22-
public static func operatorPickerView<T: Hashable>(
22+
public static func makeOperatorMenu<T: Hashable>(
2323
using operation: Binding<T>,
2424
@ViewBuilder itemProvider: @escaping (Operator) -> some View
2525
) -> some View {
@@ -30,6 +30,7 @@ extension Expression {
3030
}
3131
}
3232
.pickerStyle(.inline)
33+
.labelsHidden()
3334
}
3435

3536
public static func makeView(for expression: Binding<Self>) -> some ExpressionView {

Sources/PredicateView/Expressions/Abstract/ValueExpression.swift

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,68 +7,75 @@
77

88
import Foundation
99

10+
// MARK: - TitledExpression
11+
12+
public protocol TitledExpression<Root>: Expression {
13+
var title: String { get }
14+
}
15+
1016
// MARK: - ValueExpression
1117

12-
public protocol ValueExpression<Root>: Expression {
18+
public protocol ValueExpression<Root>: TitledExpression {
1319
associatedtype Root
1420
associatedtype Value: Hashable
1521

1622
var keyPath: KeyPath<Root, Value> { get }
17-
var title: String { get }
1823
}
1924

2025
// MARK: - SimpleExpression
2126

2227
public protocol SimpleExpression<Root>: ValueExpression {
23-
static var defaultAttribute: ExpressionAttribute<Self> { get }
24-
var attribute: ExpressionAttribute<Self> { get set }
28+
associatedtype Attribute: ExpressionAttribute<Self>
29+
associatedtype AttributeValue: Hashable
2530

26-
static func buildPredicate<V>(
31+
static var defaultAttribute: Attribute { get }
32+
var attribute: Attribute { get set }
33+
34+
static func buildPredicate<V: StandardPredicateExpression<Value>>(
2735
for variable: V,
28-
using value: Value,
29-
operation: Operator
30-
) -> (any StandardPredicateExpression<Bool>)? where V: StandardPredicateExpression<Value>
36+
using attribute: Attribute
37+
) -> (any StandardPredicateExpression<Bool>)?
3138
}
3239

3340
extension SimpleExpression {
34-
public var currentValue: AnyHashable { attribute }
35-
41+
public var currentValue: AnyHashable {
42+
attribute
43+
}
44+
3645
public func buildPredicate(
3746
using input: PredicateExpressions.Variable<Root>
3847
) -> (any StandardPredicateExpression<Bool>)? {
3948
let keyPath = PredicateExpressions.KeyPath(root: input, keyPath: keyPath)
40-
return Self.buildPredicate(for: keyPath, using: attribute.value, operation: attribute.operator)
49+
return Self.buildPredicate(for: keyPath, using: attribute)
4150
}
4251
}
4352

4453
// MARK: - ExpressionWrapper
4554

4655
public protocol ExpressionWrapper<Root, WrappedExpression>: ValueExpression {
4756
associatedtype WrappedExpression: SimpleExpression<Root>
57+
associatedtype Attribute: ExpressionAttribute<WrappedExpression>
4858

49-
var attribute: ExpressionAttribute<WrappedExpression>? { get set }
59+
var attribute: Attribute? { get set }
5060
var `operator`: Operator { get }
5161

5262
static func buildPredicate<V>(
5363
for variable: V,
54-
using value: WrappedExpression.Value?,
55-
wrappedOperation: WrappedExpression.Operator?,
56-
operation: Operator
64+
using wrappedExpression: Attribute?,
65+
operator: Operator
5766
) -> (any StandardPredicateExpression<Bool>)? where V: StandardPredicateExpression<Value>
5867
}
5968

6069
extension ExpressionWrapper {
6170
public var currentValue: AnyHashable {
6271
ExpressionWrapperValue(op: self.operator, attribute: self.attribute)
6372
}
64-
73+
6574
public func buildPredicate(
6675
using input: PredicateExpressions.Variable<Root>
6776
) -> (any StandardPredicateExpression<Bool>)? {
6877
let keyPath = PredicateExpressions.KeyPath(root: input, keyPath: keyPath)
69-
let wrappedValue = attribute?.value
70-
let op = attribute?.operator
71-
return Self.buildPredicate(for: keyPath, using: wrappedValue, wrappedOperation: op, operation: `operator`)
78+
return Self.buildPredicate(for: keyPath, using: attribute, operator: `operator`)
7279
}
7380
}
7481

Sources/PredicateView/Expressions/BoolExpression.swift

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@ import SwiftUI
99

1010
extension AnyExpression {
1111
public init(keyPath: KeyPath<Root, Bool>, title: String) {
12-
self.wrappedValue = BoolExpression(keyPath: keyPath, title: title)
12+
self.init(wrappedValue: BoolExpression(keyPath: keyPath, title: title))
1313
}
1414

1515
public init(keyPath: KeyPath<Root, Bool?>, title: String) {
16-
self.wrappedValue = OptionalExpression<Root, BoolExpression>(keyPath: keyPath, title: title)
16+
self.init(wrappedValue: OptionalExpression<Root, BoolExpression>(keyPath: keyPath, title: title))
1717
}
1818
}
1919

2020
struct BoolExpression<Root>: ContentExpression {
21+
typealias AttributeValue = Bool
22+
2123
enum Operator: String, CaseIterable {
2224
case `is` = "is"
2325
case isNot = "is not"
@@ -37,28 +39,27 @@ struct BoolExpression<Root>: ContentExpression {
3739
}
3840
}
3941

40-
static var defaultAttribute: ExpressionAttribute<Self> { .init(operator: .is, value: true) }
42+
static var defaultAttribute: StandardAttribute<Self> { .init(operator: .is, value: true) }
4143

4244
var id = UUID()
4345
let keyPath: KeyPath<Root, Bool>
4446
let title: String
45-
var attribute: ExpressionAttribute<Self> = Self.defaultAttribute
47+
var attribute: StandardAttribute<Self> = Self.defaultAttribute
4648

4749
static func buildPredicate<V>(
4850
for variable: V,
49-
using value: Value,
50-
operation: Operator
51+
using attribute: StandardAttribute<Self>
5152
) -> (any StandardPredicateExpression<Bool>)? where V: StandardPredicateExpression<Value> {
52-
switch operation {
53+
switch attribute.operator {
5354
case .is:
5455
PredicateExpressions.Equal(
5556
lhs: variable,
56-
rhs: PredicateExpressions.Value(value)
57+
rhs: PredicateExpressions.Value(attribute.value)
5758
)
5859
case .isNot:
5960
PredicateExpressions.NotEqual(
6061
lhs: variable,
61-
rhs: PredicateExpressions.Value(value)
62+
rhs: PredicateExpressions.Value(attribute.value)
6263
)
6364
}
6465
}

0 commit comments

Comments
 (0)