Skip to content

Commit

Permalink
only use custom types to have full control over transforms
Browse files Browse the repository at this point in the history
  • Loading branch information
MahdiBM committed Jul 17, 2024
1 parent deeb432 commit 9fc7b1c
Show file tree
Hide file tree
Showing 17 changed files with 305 additions and 391 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ enum TestEnum {
```swift
@Enumerator("""
{{#cases}}
var is{{firstCapitalized(name)}}: Bool {
var is{{capitalized(name)}}: Bool {
switch self {
case .{{name}}: return true
default: return false
Expand Down Expand Up @@ -219,7 +219,7 @@ enum TestEnum {
@Enumerator("""
{{#cases}}
{{^empty(parameters)}}
func get{{firstCapitalized(name)}}() -> ({{joined(tupleValue(parameters))}})? {
func get{{capitalized(name)}}() -> ({{joined(tupleValue(parameters))}})? {
switch self {
case let .{{name}}{{withParens(joined(names(parameters)))}}:
return {{withParens(joined(names(parameters)))}}
Expand Down Expand Up @@ -290,7 +290,7 @@ Although not visible when writing templates, each underlying value that is passe
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:

* `String`:
* `firstCapitalized`: Capitalizes the first letter.
* `capitalized`: Capitalizes the first letter.
* `snakeCased`: Converts the string from camelCase to snake_case.
* `camelCased`: Converts the string from snake_case to camelCase.
* `withParens`: If the string is not empty, surrounds it in parenthesis.
Expand Down
4 changes: 0 additions & 4 deletions Sources/EnumeratorMacroImpl/RenderingContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ final class RenderingContext: @unchecked Sendable {

var diagnostic: MacroError?

func cleanDiagnostic() {
self.diagnostic = nil
}

func addOrReplaceDiagnostic(_ error: MacroError) {
self.diagnostic = error
}
Expand Down
91 changes: 49 additions & 42 deletions Sources/EnumeratorMacroImpl/Types/EArray.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Mustache
import Foundation

struct EArray<Element> {
struct EArray<Element: Comparable> {
let underlying: [Element]

init(underlying: [Element]) {
Expand Down Expand Up @@ -38,51 +38,58 @@ extension EArray: CustomReflectable {
}
}

extension EArray: MustacheTransformable {
extension EArray: EMustacheTransformable {
func transform(_ name: String) -> Any? {
if let defaultTransformed = self.underlying.transform(name) {
return convertToCustomTypesIfPossible(defaultTransformed)
} else {
RenderingContext.current.cleanDiagnostic()
switch name {
case "joined":
let joined = self.underlying
.map { String(describing: $0) }
.joined(separator: ", ")
let string = EString(joined)
return string
case "keyValues":
let split: [EKeyValue] = self.underlying
.map { String(describing: $0) }
.compactMap { string -> EKeyValue? in
let split = string.split(
separator: ":",
maxSplits: 1
).map {
$0.trimmingCharacters(in: .whitespacesAndNewlines)
}
guard split.count > 0 else {
return nil
}
return EKeyValue(
key: EString(split[0]),
value: EString(split.count > 1 ? split[1] : "")
)
switch name {
case "first":
return self.underlying.first
case "last":
return self.underlying.last
case "reversed":
return EOptionalsArray(underlying: self.reversed().map { $0 })
case "count":
return self.underlying.count
case "empty":
return self.underlying.isEmpty
case "sorted":
return EArray(underlying: self.underlying.sorted())
case "joined":
let joined = self.underlying
.map { String(describing: $0) }
.joined(separator: ", ")
let string = EString(joined)
return string
case "keyValues":
let split: [EKeyValue] = self.underlying
.map { String(describing: $0) }
.compactMap { string -> EKeyValue? in
let split = string.split(
separator: ":",
maxSplits: 1
).map {
$0.trimmingCharacters(in: .whitespacesAndNewlines)
}
return EArray<EKeyValue>(underlying: split)
default:
if let keyValues = self as? EArray<EKeyValue> {
let value = keyValues.first(where: { $0.key == name })?.value
return EOptional(value)
}
RenderingContext.current.addOrReplaceDiagnostic(
.invalidTransform(
transform: name,
normalizedTypeName: Self.normalizedTypeName
guard split.count > 0 else {
return nil
}
return EKeyValue(
key: EString(split[0]),
value: EString(split.count > 1 ? split[1] : "")
)
)
return nil
}
return EArray<EKeyValue>(underlying: split)
default:
if let keyValues = self as? EArray<EKeyValue> {
let value = keyValues.first(where: { $0.key == name })?.value
return EOptional(value)
}
RenderingContext.current.addOrReplaceDiagnostic(
.invalidTransform(
transform: name,
normalizedTypeName: Self.normalizedTypeName
)
)
return nil
}
}
}
33 changes: 33 additions & 0 deletions Sources/EnumeratorMacroImpl/Types/EBool.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Mustache

struct EBool {
var underlying: Bool

init(_ underlying: Bool) {
self.underlying = underlying
}
}

extension EBool: CustomStringConvertible {
var description: String {
self.underlying.description
}
}

extension EBool: WithNormalizedTypeName {
static var normalizedTypeName: String {
"Bool"
}
}

extension EBool: CustomReflectable {
var customMirror: Mirror {
self.underlying.customMirror
}
}

extension EBool: Comparable {
static func < (lhs: EBool, rhs: EBool) -> Bool {
!lhs.underlying && rhs.underlying
}
}
10 changes: 10 additions & 0 deletions Sources/EnumeratorMacroImpl/Types/ECase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,13 @@ extension ECase: WithNormalizedTypeName {
"Case"
}
}

extension ECase: Comparable {
static func < (lhs: ECase, rhs: ECase) -> Bool {
lhs.name < rhs.name
}

static func == (lhs: ECase, rhs: ECase) -> Bool {
lhs.name == rhs.name
}
}
35 changes: 17 additions & 18 deletions Sources/EnumeratorMacroImpl/Types/ECases.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ extension ECases: CustomStringConvertible {
self.underlying.description
}
}

extension ECases: WithNormalizedTypeName {
static var normalizedTypeName: String {
"[Case]"
Expand All @@ -36,26 +37,24 @@ extension ECases: CustomReflectable {
}
}

extension ECases: MustacheTransformable {
extension ECases: EMustacheTransformable {
func transform(_ name: String) -> Any? {
if let defaultTransformed = self.underlying.transform(name) {
return convertToCustomTypesIfPossible(defaultTransformed)
} else {
RenderingContext.current.cleanDiagnostic()
switch name {
case "filterNoParams":
return self.filter(\.parameters.underlying.underlying.isEmpty)
case "filterWithParams":
return self.filter({ !$0.parameters.underlying.underlying.isEmpty })
default:
RenderingContext.current.addOrReplaceDiagnostic(
.invalidTransform(
transform: name,
normalizedTypeName: Self.normalizedTypeName
)
)
return nil
switch name {
case "filterNoParams":
return self.filter(\.parameters.underlying.underlying.isEmpty)
case "filterWithParams":
return self.filter({ !$0.parameters.underlying.underlying.isEmpty })
default:
if let transformed = self.underlying.transform(name) {
return transformed
}
RenderingContext.current.addOrReplaceDiagnostic(
.invalidTransform(
transform: name,
normalizedTypeName: Self.normalizedTypeName
)
)
return nil
}
}
}
12 changes: 11 additions & 1 deletion Sources/EnumeratorMacroImpl/Types/EKeyValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,17 @@ extension EKeyValue: WithNormalizedTypeName {
}
}

extension EKeyValue: MustacheTransformable {
extension EKeyValue: Comparable {
static func < (lhs: EKeyValue, rhs: EKeyValue) -> Bool {
lhs.key < rhs.key
}

static func == (lhs: EKeyValue, rhs: EKeyValue) -> Bool {
lhs.key == rhs.key
}
}

extension EKeyValue: EMustacheTransformable {
func transform(_ name: String) -> Any? {
switch name {
case "key":
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Mustache

/// The same as `MustacheTransformable` but only for this library's types.
protocol EMustacheTransformable: MustacheTransformable {}
36 changes: 22 additions & 14 deletions Sources/EnumeratorMacroImpl/Types/EOptional.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Mustache

enum EOptional<Wrapped> {
enum EOptional<Wrapped: Comparable> {
case none
case some(Wrapped)

Expand Down Expand Up @@ -75,11 +75,28 @@ extension EOptional: WithNormalizedTypeName {
}
}

extension EOptional: MustacheTransformable {
extension EOptional: Comparable {
static func < (lhs: EOptional<Wrapped>, rhs: EOptional<Wrapped>) -> Bool {
switch (lhs, rhs) {
case let (.some(lhs), .some(rhs)):
return lhs < rhs
case (.some, .none):
return false
case (.none, .some):
return true
case (.none, .none):
return false
}
}
}

extension EOptional: EMustacheTransformable {
func transform(_ name: String) -> Any? {
switch self {
case .none:
switch name {
case "empty":
return true
case "bool":
return false
case "exists":
Expand All @@ -98,18 +115,9 @@ extension EOptional: MustacheTransformable {
case "exists":
return true
default:
if let value = value as? MustacheTransformable {
if let transformed = value.transform(name) {
return transformed
} else {
RenderingContext.current.addOrReplaceDiagnostic(
.invalidTransform(
transform: name,
normalizedTypeName: bestEffortTypeName(Wrapped.self)
)
)
return nil
}
if let value = value as? EMustacheTransformable {
/// The underlying type is in charge of adding a diagnostic, if needed.
return value.transform(name)
} else {
RenderingContext.current.addOrReplaceDiagnostic(
.invalidTransform(
Expand Down
Loading

0 comments on commit 9fc7b1c

Please sign in to comment.