Skip to content

Commit bc7f62b

Browse files
committed
Initial commit
Init commit with Prism, PrismCase and NoPrism.
1 parent 266e1dd commit bc7f62b

File tree

7 files changed

+2188
-0
lines changed

7 files changed

+2188
-0
lines changed

Package.resolved

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// swift-tools-version: 5.9
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
import CompilerPluginSupport
6+
7+
let package = Package(
8+
name: "SwiftRexMacros",
9+
platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)],
10+
products: [
11+
.library(name: "SwiftRexMacros", targets: ["SwiftRexMacros"]),
12+
.executable(name: "SwiftRexMacrosClient", targets: ["SwiftRexMacrosClient"])
13+
],
14+
dependencies: [
15+
.package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0")
16+
],
17+
targets: [
18+
.macro(
19+
name: "PrismMacro",
20+
dependencies: [
21+
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
22+
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
23+
]
24+
),
25+
.target(name: "SwiftRexMacros", dependencies: ["PrismMacro"]),
26+
.executableTarget(name: "SwiftRexMacrosClient", dependencies: ["SwiftRexMacros"]),
27+
.testTarget(
28+
name: "SwiftRexMacrosTests",
29+
dependencies: [
30+
"PrismMacro",
31+
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
32+
]
33+
)
34+
]
35+
)

README.md

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,230 @@
11
# SwiftRexMacros
22
Macros to help automating SwiftRex boilerplate
3+
4+
## Prism
5+
A macro that produces predicates and prisms for all cases of an Enum.
6+
Predicates will be Bool properties in the format `isCaseA` that returns `true` whenever that instance points to the `caseA` case of the enum.
7+
Prism is a property with the same name as the case, but for the instance of the enum. If the instance points to that case, the variable will return a tuple of all associated values of that case, or instance of Void `()` for case without associated values. However, if the instance points to another case, it will return `nil`. This is extremely useful for using KeyPaths.
8+
9+
### Example of predicates:
10+
```swift
11+
@Prism
12+
enum Color {
13+
case red, green, blue
14+
}
15+
```
16+
produces:
17+
```swift
18+
extension Color {
19+
var isRed: Bool {
20+
if case .red = self { true } else { false }
21+
}
22+
var isGreen: Bool {
23+
if case .green = self { true } else { false }
24+
}
25+
var isBlue: Bool {
26+
if case .blue = self { true } else { false }
27+
}
28+
}
29+
```
30+
usage:
31+
```swift
32+
let color1 = Color.red
33+
color1.isRed // true
34+
color1.isGreen // false
35+
color1.isBlue // false
36+
```
37+
38+
### Example os prism:
39+
```swift
40+
@Prism
41+
enum Contact {
42+
case email(address: String)
43+
case phone(countryCode: String, number: String)
44+
case letter(street: String, house: String, postalCode: String, city: String, state: String, country: String)
45+
case noContact
46+
}
47+
```
48+
produces:
49+
```swift
50+
extension Contact {
51+
var email: String? {
52+
guard case let .email(address) = self else { return nil }
53+
return address
54+
}
55+
var phone: (countryCode: String, number: String)? {
56+
guard case let .phone(countryCode, number) = self else { return nil }
57+
return (countryCode: countryCode, number: number)
58+
}
59+
var letter: (street: String, house: String, postalCode: String, city: String, state: String, country: String)? {
60+
guard case let .letter(street, house, postalCode, city, state, country) = self else { return nil }
61+
return (street: street, house: house, postalCode: postalCode, city: city, state: state, country: country)
62+
}
63+
var noContact: Void? {
64+
guard case .noContact = self else { return nil }
65+
return ()
66+
}
67+
}
68+
```
69+
70+
Please notice that the `Void` case is important not only for consistency, but for more advanced cases of composition.
71+
Logically, a case with no associated values "holds" a Void associated value (singleton type), or not (nil) if the instance has another case.
72+
73+
usage:
74+
```swift
75+
let contact = Contact.phone(countryCode: "44", number: "078906789")
76+
let phone = contact.phone.map { $0.countryCode + " " + $0.number } ?? "<No Phone>" // "44 078906789"
77+
78+
let resolveEmail: KeyPath<Contact, String?> = \Contact.email // passing contact will resolve to `nil`,
79+
// but passing something with email will resolve to the addrees
80+
```
81+
82+
Setter
83+
Prisms also produce setters. In that case, if the enum case has an associated value and you want to change the values in the tuple, that is possible as long as the instance points to the same case, otherwise it will be ignored. For example:
84+
```swift
85+
var contact = Contact.phone(countryCode: "44", number: "078906789")
86+
contact.phone = (countryCode: "44", number: "99999999") // ✅ this change happens with success
87+
contact.email = "my@email.com" // 🚫 this change is ignored, because the enum instance points to phone, not email
88+
```
89+
90+
The setter can be really useful if you have a long tree of enums and want to change the leaf. It's also useful for `WritableKeyPath` situations.
91+
92+
Extra:
93+
- Use `Prism` in the enum if you want to generate code for every case
94+
- Use `PrismCase` in a case if you want a different visibility only for that case generated code.
95+
- Use only `PrismCase` without `Prism` in the enum if you want code generated only for that case.
96+
- Use `NoPrism` in a case if you don't want code generated for that case.
97+
98+
## PrismCase
99+
A macro that produces predicates and prisms for a single case of an Enum.
100+
101+
### Example of predicates:
102+
```swift
103+
enum Color {
104+
case red, black
105+
@PrismCase
106+
case green, blue
107+
case yellow, white
108+
}
109+
```
110+
produces:
111+
```swift
112+
extension Color {
113+
var isGreen: Bool {
114+
if case .green = self { true } else { false }
115+
}
116+
var isBlue: Bool {
117+
if case .blue = self { true } else { false }
118+
}
119+
}
120+
```
121+
usage:
122+
```swift
123+
let color1 = Color.green
124+
color1.isGreen // true
125+
color1.isBlue // false
126+
color1.isRed 🚫 // Compiler error, not generated
127+
```
128+
129+
### Example os prism:
130+
```swift
131+
enum Contact {
132+
case email(address: String)
133+
@PrismCase
134+
case phone(countryCode: String, number: String)
135+
case letter(street: String, house: String, postalCode: String, city: String, state: String, country: String)
136+
case noContact
137+
}
138+
```
139+
produces:
140+
```swift
141+
extension Contact {
142+
var phone: (countryCode: String, number: String)? {
143+
guard case let .phone(countryCode, number) = self else { return nil }
144+
return (countryCode: countryCode, number: number)
145+
}
146+
}
147+
```
148+
Please notice that the `Void` case is important not only for consistency, but for more advanced cases of composition.
149+
Logically, a case with no associated values "holds" a Void associated value (singleton type), or not (nil) if the instance has another case.
150+
151+
usage:
152+
```swift
153+
let contact = Contact.phone(countryCode: "44", number: "078906789")
154+
let phone = contact.phone.map { $0.countryCode + " " + $0.number } ?? "<No Phone>" // "44 078906789"
155+
156+
let resolveEmail: KeyPath<Contact, String?> = \Contact.phone?.number // passing contact will resolve to `"078906789"`,
157+
// but passing something with email will resolve to nil
158+
```
159+
160+
## NoPrism
161+
A macro that prevents the code generatio of predicates and prisms for a specific case, in an Enum marked with `Prism`
162+
163+
### Example of predicates:
164+
```swift
165+
@Prism
166+
enum Color {
167+
case red, green, blue
168+
@NoPrism
169+
case white, black
170+
}
171+
```
172+
produces:
173+
```swift
174+
extension Color {
175+
var isRed: Bool {
176+
if case .red = self { true } else { false }
177+
}
178+
var isGreen: Bool {
179+
if case .green = self { true } else { false }
180+
}
181+
var isBlue: Bool {
182+
if case .blue = self { true } else { false }
183+
}
184+
}
185+
```
186+
usage:
187+
```swift
188+
let color1 = Color.red
189+
color1.isRed // true
190+
color1.isGreen // false
191+
color1.isBlue // false
192+
color1.isWhite 🚫 // Compiler error, not generated
193+
```
194+
195+
### Example os prism:
196+
```swift
197+
@Prism
198+
enum Contact {
199+
case email(address: String)
200+
case phone(countryCode: String, number: String)
201+
@NoPrism
202+
case letter(street: String, house: String, postalCode: String, city: String, state: String, country: String)
203+
@NoPrism
204+
case noContact
205+
}
206+
```
207+
produces:
208+
```swift
209+
extension Contact {
210+
var email: String? {
211+
guard case let .email(address) = self else { return nil }
212+
return address
213+
}
214+
var phone: (countryCode: String, number: String)? {
215+
guard case let .phone(countryCode, number) = self else { return nil }
216+
return (countryCode: countryCode, number: number)
217+
}
218+
}
219+
```
220+
Please notice that the `Void` case is important not only for consistency, but for more advanced cases of composition.
221+
Logically, a case with no associated values "holds" a Void associated value (singleton type), or not (nil) if the instance has another case.
222+
223+
usage:
224+
```swift
225+
let contact = Contact.phone(countryCode: "44", number: "078906789")
226+
let phone = contact.phone.map { $0.countryCode + " " + $0.number } ?? "<No Phone>" // "44 078906789"
227+
228+
let resolveEmail: KeyPath<Contact, String?> = \Contact.email // passing contact will resolve to `nil`,
229+
// but passing something with email will resolve to the addrees
230+
```

0 commit comments

Comments
 (0)