Skip to content

Commit b9d885f

Browse files
committed
Initial support for decoding predicates
1 parent 2822ca8 commit b9d885f

22 files changed

Lines changed: 434 additions & 124 deletions

Example/PredicateViewExample/SwiftDataDemoView.swift

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ struct SwiftDataDemoView: View {
4141
}
4242

4343
@Environment(\.modelContext) private var modelContext
44-
@State var predicate: Predicate<Item> = .true
44+
@State var predicate: Predicate<Item> = #Predicate<Item> {
45+
$0.title.localizedStandardContains("Item")
46+
}
47+
48+
@State private var savedPredicates: [Predicate<Item>] = []
4549

4650
var body: some View {
4751
VStack(alignment: .leading) {
@@ -52,6 +56,21 @@ struct SwiftDataDemoView: View {
5256
])
5357
}
5458

59+
GroupBox("Cloned Predicates") {
60+
ForEach(Array($savedPredicates.enumerated()), id: \.offset) { index, $predicate in
61+
ScrollView(.horizontal) {
62+
PredicateView(predicate: $predicate, rowTemplates: [
63+
.init(keyPath: \.title, title: "Title"),
64+
.init(StatusExpressionView.self),
65+
])
66+
}
67+
}
68+
69+
Button("Clone") {
70+
savedPredicates.append(predicate)
71+
}
72+
}
73+
5574
Table(items) {
5675
TableColumn("Title", value: \.title)
5776
TableColumn("Status", value: \.status.rawValue)

Sources/PredicateView/Attributes/ExpressionAttribute.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,17 @@
77

88
import Foundation
99

10-
public protocol ExpressionAttribute<Expr>: OperatorContainer, Hashable where Expr: SimpleExpression {
10+
public protocol ExpressionAttribute<Expr>: OperatorContainer, Hashable where Expr: PredicateExpressionConvertible {
1111
var `operator`: Expr.Operator { get set }
1212
var value: Expr.AttributeValue { get set }
1313
}
1414

15-
public struct StandardAttribute<Expr>: ExpressionAttribute where Expr: SimpleExpression {
15+
public struct StandardAttribute<Expr>: ExpressionAttribute where Expr: PredicateExpressionConvertible {
1616
public var `operator`: Expr.Operator
1717
public var value: Expr.AttributeValue
1818
}
1919

20-
public struct MetadataAttribute<Expr, Metadata>: ExpressionAttribute where Expr: SimpleExpression {
20+
public struct MetadataAttribute<Expr, Metadata>: ExpressionAttribute where Expr: PredicateExpressionConvertible {
2121
public var `operator`: Expr.Operator
2222
public var value: Expr.AttributeValue
2323
public var metadata: Metadata

Sources/PredicateView/Expressions/Abstract/ContentExpression.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import SwiftUI
99

1010
// MARK: - ContentExpression
1111

12-
public protocol ContentExpression<Root>: SimpleExpression {
12+
public protocol ContentExpression<Root>: PredicateExpressionConvertible & TitledExpression {
1313
associatedtype ExprView = ContentExpressionView<Root, Self>
1414
associatedtype Result: View
1515

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//
2+
// ExpressionWrapper.swift
3+
// PredicateView
4+
//
5+
// Created by Phil Zakharchenko on 9/22/24.
6+
//
7+
8+
import Foundation
9+
10+
public protocol ExpressionWrapper<Root, WrappedExpression>: KeyPathExpression {
11+
associatedtype WrappedExpression: PredicateExpressionConvertible<Root>
12+
associatedtype Attribute: ExpressionAttribute<WrappedExpression>
13+
14+
var attribute: Attribute? { get set }
15+
var `operator`: Operator { get }
16+
17+
static func buildPredicate<V>(
18+
for variable: V,
19+
using wrappedExpression: Attribute?,
20+
operator: Operator
21+
) -> (any StandardPredicateExpression<Bool>)? where V: StandardPredicateExpression<Value>
22+
}
23+
24+
extension ExpressionWrapper {
25+
public var currentValue: AnyHashable {
26+
ExpressionWrapperValue(op: self.operator, attribute: self.attribute)
27+
}
28+
29+
public func buildPredicate(
30+
using input: PredicateExpressions.Variable<Root>
31+
) -> (any StandardPredicateExpression<Bool>)? {
32+
let keyPath = PredicateExpressions.KeyPath(root: input, keyPath: keyPath)
33+
return Self.buildPredicate(for: keyPath, using: attribute, operator: `operator`)
34+
}
35+
}
36+
37+
private struct ExpressionWrapperValue: Hashable {
38+
var op: AnyHashable
39+
var attribute: AnyHashable?
40+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// ValueExpression.swift
3+
//
4+
//
5+
// Created by Phil Zakharchenko on 4/20/24.
6+
//
7+
8+
import Foundation
9+
10+
// MARK: - KeyPathExpression
11+
12+
public protocol KeyPathExpression<Root>: TitledExpression {
13+
associatedtype Root
14+
associatedtype Value: Hashable
15+
16+
var keyPath: KeyPath<Root, Value> { get }
17+
}
18+
19+
extension KeyPathExpression {
20+
typealias KeyPathPredicateExpression = PredicateExpressions.KeyPath<PredicateExpressions.Variable<Root>, Value>
21+
typealias ValuePredicateExpression = PredicateExpressions.Value<Value>
22+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// PredicateExpressionConvertible.swift
3+
// PredicateView
4+
//
5+
// Created by Phil Zakharchenko on 9/22/24.
6+
//
7+
8+
import Foundation
9+
10+
/// Describes an ``Expression`` capable of both building a ``StandardPredicateExpression`` from
11+
/// itself and decoding a provided ``PredicateExpression``.
12+
public protocol PredicateExpressionConvertible<Root>: LeafPredicateExpressionDecoding {
13+
associatedtype Attribute: ExpressionAttribute<Self>
14+
associatedtype AttributeValue: Hashable
15+
16+
static var defaultAttribute: Attribute { get }
17+
var attribute: Attribute { get set }
18+
19+
func buildPredicate(
20+
using input: PredicateExpressions.Variable<Root>
21+
) -> (any StandardPredicateExpression<Bool>)?
22+
}
23+
24+
extension PredicateExpressionConvertible {
25+
public var currentValue: AnyHashable {
26+
attribute
27+
}
28+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// PredicateExpressionDecoding.swift
3+
// PredicateView
4+
//
5+
// Created by Phil Zakharchenko on 9/22/24.
6+
//
7+
8+
import Foundation
9+
10+
/// Describes an ``Expression`` that is able to decode from a provided ``PredicateExpression``.
11+
///
12+
/// Typically, a given type should only be concerned with decoding predicates that map to itself.
13+
/// As such, the return type in most cases should be `Self`.
14+
///
15+
/// However, in some cases, such as expressions containing other subexpressions, notably
16+
/// ``LogicalExpression``, the returned ``Expression`` may be of a type different than `Self`.
17+
public protocol PredicateExpressionDecoding<Root>: Expression {
18+
func decode<PredicateExpressionType: PredicateExpression<Bool>>(
19+
_ expression: PredicateExpressionType,
20+
using decoders: [any PredicateExpressionDecoding<Root>]
21+
) -> (any Expression<Root>)?
22+
}
23+
24+
public protocol LeafPredicateExpressionDecoding<Root>: PredicateExpressionDecoding {
25+
func decode<PredicateExpressionType: PredicateExpression<Bool>>(
26+
_ expression: PredicateExpressionType
27+
) -> (any Expression<Root>)?
28+
}
29+
30+
extension LeafPredicateExpressionDecoding {
31+
public func decode<PredicateExpressionType: PredicateExpression<Bool>>(
32+
_ expression: PredicateExpressionType,
33+
using decoders: [any PredicateExpressionDecoding<Root>]
34+
) -> (any Expression<Root>)? {
35+
self.decode(expression)
36+
}
37+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// TitledExpression.swift
3+
// PredicateView
4+
//
5+
// Created by Phil Zakharchenko on 9/22/24.
6+
//
7+
8+
import Foundation
9+
10+
/// A specialized expression that has a read-only title shown to the user.
11+
public protocol TitledExpression<Root>: Expression {
12+
var title: String { get }
13+
}

Sources/PredicateView/Expressions/Abstract/ValueExpression.swift

Lines changed: 0 additions & 95 deletions
This file was deleted.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// WrappablePredicateExpression.swift
3+
// PredicateView
4+
//
5+
// Created by Phil Zakharchenko on 9/22/24.
6+
//
7+
8+
import Foundation
9+
10+
/// Describes an ``Expression`` that can be wrapped by an ``ExpressionWrapper``, such as ``OptionalExpression``.
11+
public protocol WrappablePredicateExpression: PredicateExpressionConvertible & KeyPathExpression {
12+
static func buildPredicate<V: StandardPredicateExpression<Value>>(
13+
for variable: V,
14+
using attribute: Attribute
15+
) -> (any StandardPredicateExpression<Bool>)?
16+
}
17+
18+
extension PredicateExpressionConvertible where Self: WrappablePredicateExpression {
19+
public func buildPredicate(
20+
using input: PredicateExpressions.Variable<Root>
21+
) -> (any StandardPredicateExpression<Bool>)? {
22+
let keyPath = PredicateExpressions.KeyPath(root: input, keyPath: keyPath)
23+
return Self.buildPredicate(for: keyPath, using: attribute)
24+
}
25+
}

0 commit comments

Comments
 (0)