Skip to content

Commit 9fc7b1c

Browse files
committed
only use custom types to have full control over transforms
1 parent deeb432 commit 9fc7b1c

17 files changed

+305
-391
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ enum TestEnum {
129129
```swift
130130
@Enumerator("""
131131
{{#cases}}
132-
var is{{firstCapitalized(name)}}: Bool {
132+
var is{{capitalized(name)}}: Bool {
133133
switch self {
134134
case .{{name}}: return true
135135
default: return false
@@ -219,7 +219,7 @@ enum TestEnum {
219219
@Enumerator("""
220220
{{#cases}}
221221
{{^empty(parameters)}}
222-
func get{{firstCapitalized(name)}}() -> ({{joined(tupleValue(parameters))}})? {
222+
func get{{capitalized(name)}}() -> ({{joined(tupleValue(parameters))}})? {
223223
switch self {
224224
case let .{{name}}{{withParens(joined(names(parameters)))}}:
225225
return {{withParens(joined(names(parameters)))}}
@@ -290,7 +290,7 @@ Although not visible when writing templates, each underlying value that is passe
290290
In addition to [`swift-mustache`'s own "functions"/"transforms"](https://docs.hummingbird.codes/2.0/documentation/hummingbird/transforms/), `EnumeratorMacro` supports these transformations for each type:
291291

292292
* `String`:
293-
* `firstCapitalized`: Capitalizes the first letter.
293+
* `capitalized`: Capitalizes the first letter.
294294
* `snakeCased`: Converts the string from camelCase to snake_case.
295295
* `camelCased`: Converts the string from snake_case to camelCase.
296296
* `withParens`: If the string is not empty, surrounds it in parenthesis.

Sources/EnumeratorMacroImpl/RenderingContext.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@ final class RenderingContext: @unchecked Sendable {
44

55
var diagnostic: MacroError?
66

7-
func cleanDiagnostic() {
8-
self.diagnostic = nil
9-
}
10-
117
func addOrReplaceDiagnostic(_ error: MacroError) {
128
self.diagnostic = error
139
}
Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Mustache
22
import Foundation
33

4-
struct EArray<Element> {
4+
struct EArray<Element: Comparable> {
55
let underlying: [Element]
66

77
init(underlying: [Element]) {
@@ -38,51 +38,58 @@ extension EArray: CustomReflectable {
3838
}
3939
}
4040

41-
extension EArray: MustacheTransformable {
41+
extension EArray: EMustacheTransformable {
4242
func transform(_ name: String) -> Any? {
43-
if let defaultTransformed = self.underlying.transform(name) {
44-
return convertToCustomTypesIfPossible(defaultTransformed)
45-
} else {
46-
RenderingContext.current.cleanDiagnostic()
47-
switch name {
48-
case "joined":
49-
let joined = self.underlying
50-
.map { String(describing: $0) }
51-
.joined(separator: ", ")
52-
let string = EString(joined)
53-
return string
54-
case "keyValues":
55-
let split: [EKeyValue] = self.underlying
56-
.map { String(describing: $0) }
57-
.compactMap { string -> EKeyValue? in
58-
let split = string.split(
59-
separator: ":",
60-
maxSplits: 1
61-
).map {
62-
$0.trimmingCharacters(in: .whitespacesAndNewlines)
63-
}
64-
guard split.count > 0 else {
65-
return nil
66-
}
67-
return EKeyValue(
68-
key: EString(split[0]),
69-
value: EString(split.count > 1 ? split[1] : "")
70-
)
43+
switch name {
44+
case "first":
45+
return self.underlying.first
46+
case "last":
47+
return self.underlying.last
48+
case "reversed":
49+
return EOptionalsArray(underlying: self.reversed().map { $0 })
50+
case "count":
51+
return self.underlying.count
52+
case "empty":
53+
return self.underlying.isEmpty
54+
case "sorted":
55+
return EArray(underlying: self.underlying.sorted())
56+
case "joined":
57+
let joined = self.underlying
58+
.map { String(describing: $0) }
59+
.joined(separator: ", ")
60+
let string = EString(joined)
61+
return string
62+
case "keyValues":
63+
let split: [EKeyValue] = self.underlying
64+
.map { String(describing: $0) }
65+
.compactMap { string -> EKeyValue? in
66+
let split = string.split(
67+
separator: ":",
68+
maxSplits: 1
69+
).map {
70+
$0.trimmingCharacters(in: .whitespacesAndNewlines)
7171
}
72-
return EArray<EKeyValue>(underlying: split)
73-
default:
74-
if let keyValues = self as? EArray<EKeyValue> {
75-
let value = keyValues.first(where: { $0.key == name })?.value
76-
return EOptional(value)
77-
}
78-
RenderingContext.current.addOrReplaceDiagnostic(
79-
.invalidTransform(
80-
transform: name,
81-
normalizedTypeName: Self.normalizedTypeName
72+
guard split.count > 0 else {
73+
return nil
74+
}
75+
return EKeyValue(
76+
key: EString(split[0]),
77+
value: EString(split.count > 1 ? split[1] : "")
8278
)
83-
)
84-
return nil
79+
}
80+
return EArray<EKeyValue>(underlying: split)
81+
default:
82+
if let keyValues = self as? EArray<EKeyValue> {
83+
let value = keyValues.first(where: { $0.key == name })?.value
84+
return EOptional(value)
8585
}
86+
RenderingContext.current.addOrReplaceDiagnostic(
87+
.invalidTransform(
88+
transform: name,
89+
normalizedTypeName: Self.normalizedTypeName
90+
)
91+
)
92+
return nil
8693
}
8794
}
8895
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import Mustache
2+
3+
struct EBool {
4+
var underlying: Bool
5+
6+
init(_ underlying: Bool) {
7+
self.underlying = underlying
8+
}
9+
}
10+
11+
extension EBool: CustomStringConvertible {
12+
var description: String {
13+
self.underlying.description
14+
}
15+
}
16+
17+
extension EBool: WithNormalizedTypeName {
18+
static var normalizedTypeName: String {
19+
"Bool"
20+
}
21+
}
22+
23+
extension EBool: CustomReflectable {
24+
var customMirror: Mirror {
25+
self.underlying.customMirror
26+
}
27+
}
28+
29+
extension EBool: Comparable {
30+
static func < (lhs: EBool, rhs: EBool) -> Bool {
31+
!lhs.underlying && rhs.underlying
32+
}
33+
}

Sources/EnumeratorMacroImpl/Types/ECase.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,13 @@ extension ECase: WithNormalizedTypeName {
3030
"Case"
3131
}
3232
}
33+
34+
extension ECase: Comparable {
35+
static func < (lhs: ECase, rhs: ECase) -> Bool {
36+
lhs.name < rhs.name
37+
}
38+
39+
static func == (lhs: ECase, rhs: ECase) -> Bool {
40+
lhs.name == rhs.name
41+
}
42+
}

Sources/EnumeratorMacroImpl/Types/ECases.swift

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ extension ECases: CustomStringConvertible {
1818
self.underlying.description
1919
}
2020
}
21+
2122
extension ECases: WithNormalizedTypeName {
2223
static var normalizedTypeName: String {
2324
"[Case]"
@@ -36,26 +37,24 @@ extension ECases: CustomReflectable {
3637
}
3738
}
3839

39-
extension ECases: MustacheTransformable {
40+
extension ECases: EMustacheTransformable {
4041
func transform(_ name: String) -> Any? {
41-
if let defaultTransformed = self.underlying.transform(name) {
42-
return convertToCustomTypesIfPossible(defaultTransformed)
43-
} else {
44-
RenderingContext.current.cleanDiagnostic()
45-
switch name {
46-
case "filterNoParams":
47-
return self.filter(\.parameters.underlying.underlying.isEmpty)
48-
case "filterWithParams":
49-
return self.filter({ !$0.parameters.underlying.underlying.isEmpty })
50-
default:
51-
RenderingContext.current.addOrReplaceDiagnostic(
52-
.invalidTransform(
53-
transform: name,
54-
normalizedTypeName: Self.normalizedTypeName
55-
)
56-
)
57-
return nil
42+
switch name {
43+
case "filterNoParams":
44+
return self.filter(\.parameters.underlying.underlying.isEmpty)
45+
case "filterWithParams":
46+
return self.filter({ !$0.parameters.underlying.underlying.isEmpty })
47+
default:
48+
if let transformed = self.underlying.transform(name) {
49+
return transformed
5850
}
51+
RenderingContext.current.addOrReplaceDiagnostic(
52+
.invalidTransform(
53+
transform: name,
54+
normalizedTypeName: Self.normalizedTypeName
55+
)
56+
)
57+
return nil
5958
}
6059
}
6160
}

Sources/EnumeratorMacroImpl/Types/EKeyValue.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,17 @@ extension EKeyValue: WithNormalizedTypeName {
2222
}
2323
}
2424

25-
extension EKeyValue: MustacheTransformable {
25+
extension EKeyValue: Comparable {
26+
static func < (lhs: EKeyValue, rhs: EKeyValue) -> Bool {
27+
lhs.key < rhs.key
28+
}
29+
30+
static func == (lhs: EKeyValue, rhs: EKeyValue) -> Bool {
31+
lhs.key == rhs.key
32+
}
33+
}
34+
35+
extension EKeyValue: EMustacheTransformable {
2636
func transform(_ name: String) -> Any? {
2737
switch name {
2838
case "key":
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import Mustache
2+
3+
/// The same as `MustacheTransformable` but only for this library's types.
4+
protocol EMustacheTransformable: MustacheTransformable {}

Sources/EnumeratorMacroImpl/Types/EOptional.swift

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Mustache
22

3-
enum EOptional<Wrapped> {
3+
enum EOptional<Wrapped: Comparable> {
44
case none
55
case some(Wrapped)
66

@@ -75,11 +75,28 @@ extension EOptional: WithNormalizedTypeName {
7575
}
7676
}
7777

78-
extension EOptional: MustacheTransformable {
78+
extension EOptional: Comparable {
79+
static func < (lhs: EOptional<Wrapped>, rhs: EOptional<Wrapped>) -> Bool {
80+
switch (lhs, rhs) {
81+
case let (.some(lhs), .some(rhs)):
82+
return lhs < rhs
83+
case (.some, .none):
84+
return false
85+
case (.none, .some):
86+
return true
87+
case (.none, .none):
88+
return false
89+
}
90+
}
91+
}
92+
93+
extension EOptional: EMustacheTransformable {
7994
func transform(_ name: String) -> Any? {
8095
switch self {
8196
case .none:
8297
switch name {
98+
case "empty":
99+
return true
83100
case "bool":
84101
return false
85102
case "exists":
@@ -98,18 +115,9 @@ extension EOptional: MustacheTransformable {
98115
case "exists":
99116
return true
100117
default:
101-
if let value = value as? MustacheTransformable {
102-
if let transformed = value.transform(name) {
103-
return transformed
104-
} else {
105-
RenderingContext.current.addOrReplaceDiagnostic(
106-
.invalidTransform(
107-
transform: name,
108-
normalizedTypeName: bestEffortTypeName(Wrapped.self)
109-
)
110-
)
111-
return nil
112-
}
118+
if let value = value as? EMustacheTransformable {
119+
/// The underlying type is in charge of adding a diagnostic, if needed.
120+
return value.transform(name)
113121
} else {
114122
RenderingContext.current.addOrReplaceDiagnostic(
115123
.invalidTransform(

0 commit comments

Comments
 (0)