Tests Badge Code Coverage Latest/Minimum Swift Version

EnumeratorMacro

一个实用工具,可以使用 Mustache 模板引擎为你的 Swift 枚举创建逐个案例的代码。
EnumeratorMacro 使用 swift-mustache 的风格。

该宏会解析你的枚举代码,并将枚举的不同信息传递给 Mustache 模板渲染器。
然后你可以在模板中访问每个 case 名称、case 参数等,并在此基础上创建代码。

Mustache 模板是如何工作的?

它非常简单。

通用行为

点击展开

EnumeratorMacro 将会

例子

注意

当你滚动时,这些例子会变得更加高级。

派生 Case 名称

@Enumerator("""
var caseName: String {
 switch self {
 {{#cases}}
 case .{{name}}: "{{snakeCased(name)}}"
 {{/cases}}
 }
}
""")
enum TestEnum {
    case a(val1: String, val2: Int)
    case b
    case testCase(testValue: String)
}

扩展为

enum TestEnum {
    case a(val1: String, val2: Int)
    case b
    case testCase(testValue: String)

+    var caseName: String {
+        switch self {
+        case .a: "a"
+        case .b: "b"
+        case .testCase: "test_case"
+        }
+    }
}

创建一个子类型枚举

点击展开
@Enumerator("""
enum Subtype: String {
 {{#cases}}
 case {{name}}
 {{/cases}}
}
""",
"""
var subtype: Subtype {
 switch self {
 {{#cases}}
 case .{{name}}:
 .{{name}}
 {{/cases}}
 }
}
""")
enum TestEnum {
    case a(val1: String, val2: Int)
    case b
    case testCase(testValue: String)
}

扩展为

enum TestEnum {
    case a(val1: String, val2: Int)
    case b
    case testCase(testValue: String)

+    enum Subtype: String {
+        case a
+        case b
+        case testCase
+    }

+    var subtype: Subtype {
+        switch self {
+        case .a:
+            .a
+        case .b:
+            .b
+        case .testCase:
+            .testCase
+        }
+    }
}

创建 Is-Case 属性

点击展开
@Enumerator("""
{{#cases}}
var is{{capitalized(name)}}: Bool {
 switch self {
 case .{{name}}: return true
 default: return false
 }
}
{{/cases}}
""")
enum TestEnum {
    case a(val1: String, val2: Int)
    case b
    case testCase(testValue: String)
}

扩展为

enum TestEnum {
    case a(val1: String, val2: Int)
    case b
    case testCase(testValue: String)

+    var isA: Bool {
+        switch self {
+        case .a: return true
+        default: return false
+        }
+    }

+    var isB: Bool {
+        switch self {
+        case .b: return true
+        default: return false
+        }
+    }

+    var isTestCase: Bool {
+        switch self {
+        case .testCase: return true
+        default: return false
+        }
+    }
}

创建 Get-Case-Value 函数

点击展开
@Enumerator("""
{{#cases}}
{{^isEmpty(parameters)}}
func get{{capitalized(name)}}() -> ({{joined(tupleValue(parameters))}})? {
 switch self {
 case let .{{name}}{{withParens(joined(names(parameters)))}}:
 return {{withParens(joined(names(parameters)))}}
 default:
 return nil
 }
}
{{/isEmpty(parameters)}}
{{/cases}}
""")
enum TestEnum {
    case a(val1: String, Int)
    case b
    case testCase(testValue: String)
}

扩展为

enum TestEnum {
    case a(val1: String, val2: Int)
    case b
    case testCase(testValue: String)


+    func getA() -> (val1: String, param2: Int)? {
+        switch self {
+        case let .a(val1, param2):
+            return (val1, param2)
+        default:
+            return nil
+        }
+    }

+    func getTestCase() -> (String)? {
+        switch self {
+        case let .testCase(testValue):
+            return (testValue)
+        default:
+            return nil
+        }
+    }
}

为每个 Case 创建函数

点击展开
@Enumerator("""
{{#cases}}
{{^isEmpty(parameters)}}
func get{{capitalized(name)}}() -> ({{joined(tupleValue(parameters))}})? {
 switch self {
 case let .{{name}}{{withParens(joined(names(parameters)))}}:
 return {{withParens(joined(names(parameters)))}}
 default:
 return nil
 }
}
{{/isEmpty(parameters)}}
{{/cases}}
""")
enum TestEnum {
    case a(val1: String, Int)
    case b
    case testCase(testValue: String)
}

扩展为

enum TestEnum {
    case a(val1: String, Int)
    case b
    case testCase(testValue: String)

+    func getA() -> (val1: String, param2: Int)? {
+        switch self {
+        case let .a(val1, param2):
+            return (val1, param2)
+        default:
+            return nil
+        }
+    }

+    func getTestCase() -> (String)? {
+        switch self {
+        case let .testCase(testValue):
+            return (testValue)
+        default:
+            return nil
+        }
+    }
}

使用注释进行代码生成

注意

你可以在每个 case 前面使用注释,作为 EnumeratorMacro 处理的值。
使用 ; 分隔注释,并使用 : 分隔 key 和可能的 value
例如:myKey1; myKey2: value; myKey3

提示

你应该声明一个 allowedComments 参数来强制只使用某些注释,以避免拼写错误导致的 bug。

@Enumerator(
    allowedComments: ["business_error", "l8n_params"],
    """
 package var isBusinessError: Bool {
 switch self {
 case
 {{#cases}}{{#bool(business_error(comments))}}
 .{{name}},
 {{/bool(business_error(comments))}}{{/cases}}
 :
 return true
 default:
 return false
 }
 }
 """
)
public enum ErrorMessage {
    case case1 // business_error
    case case2 // business_error: true
    case case3 // business_error: false
    case case4 // business_error: adfasdfdsff
    case somethingSomething(value: String)
    case otherCase(error: Error, isViolation: Bool) // business_error; l8n_params:
}

扩展为

public enum ErrorMessage {
    case case1 // business_error
    case case2 // business_error: true
    case case3 // business_error: false
    case case4 // business_error: adfasdfdsff
    case somethingSomething(value: String)
    case otherCase(error: Error, isViolation: Bool) // business_error; l8n_params:

+    package var isBusinessError: Bool {
+        switch self {
+        case
+        .case1,
+        .case2,
+        .otherCase
+        :
+            return true
+        default:
+            return false
+        }
+    }
}

高级注释用法代码生成

点击展开
@Enumerator(
    allowedComments: ["business_error", "l8n_params"],
    """
 private var localizationParameters: [Any] {
 switch self {
 {{#cases}}
 {{! Only create a case for enum cases that have any parameters at all: }}
 {{^isEmpty(parameters)}}
 
 {{! Create a case for those who have non-empty 'l8n_params' comment: }}
 {{^isEmpty(l8n_params(comments))}}
 case let .{{name}}{{withParens(joined(names(parameters)))}}:
 [{{l8n_params(comments)}}]
 {{/isEmpty(l8n_params(comments))}}
 
 {{! Create a case for those who don't have 'l8n_params' comment at all: }}
 {{^exists(l8n_params(comments))}}
 case let .{{name}}{{withParens(joined(names(parameters)))}}:
 [
 {{#parameters}}
 {{name}}{{#isOptional}} as Any{{/isOptional}},
 {{/parameters}}
 ]
 {{/exists(l8n_params(comments))}}
 
 {{/isEmpty(parameters)}}
 {{/cases}}
 default:
 []
 }
 }
 """
)
public enum ErrorMessage {
    case case1 // business_error
    case case2 // business_error: true
    case case3 // business_error: false
    case case4 // business_error: adfasdfdsff
    case somethingSomething(value1: String, Int) // l8n_params: value
    case otherCase(error: Error, isViolation: Bool) // business_error; l8n_params:
}

扩展为

public enum ErrorMessage {
    case case1 // business_error
    case case2 // business_error: true
    case case3 // business_error: false
    case case4 // business_error: adfasdfdsff
    case somethingSomething(value1: String, Int) // l8n_params: value
    case otherCase(error: Error, isViolation: Bool) // business_error; l8n_params:

    private var localizationParameters: [Any] {
        switch self {
        case .somethingSomething:
            [value]
        default:
            []
        }
    }
}

可用的上下文值

这是一个示例上下文对象

{
    "cases": [
        {
            "name": "somethingWentWrong",
            "parameters": [
                {
                    "name": "error",
                    "type": "Error?",
                    "isOptional": true,
                    "index": 0,
                    "isFirst": true,
                    "isLast": false
                },
                {
                    "name": "statusCode",
                    "type": "String",
                    "isOptional": false,
                    "index": 1,
                    "isFirst": false,
                    "isLast": true
                }
            ],
            "comments": [
                {
                    "key": "business_error",
                    "value": ""
                },
                {
                    "key": "l8n_params",
                    "value": "error as Any, statusCode"
                }
            ],
            "index": 0,
            "isFirst": true,
            "isLast": true
        }
    ]
}

可用函数

虽然在编写模板时不可见,但传递给模板引擎的每个底层值都有一个实际类型。

EnumeratorMacro 支持这些类型的转换

如果您认为某个函数可以解决问题,请随时提出建议。

错误处理

点击展开

如果在扩展的宏代码中,或代码生成的任何其他步骤中出现错误,EnumeratorMacro 将尝试发出指向代码行的诊断信息,该行是问题的根源。

例如,EnumeratorMacro 会正确地将来自模板引擎的模板渲染错误转发到你的源代码。 在下面的例子中,我错误地写了 {{cases} 而不是 {{cases}}

Screenshot 2024-07-13 at 12 09 16 AM

或者在这里,扩展的 Swift 代码会导致代码出现语法错误,并且该宏预先报告了错误。 在这里,我应该忘记在 caseNameString 之间写 :

Screenshot 2024-07-13 at 12 10 19 AM

EnumeratorMacro 还可以捕获正在使用的无效函数

Screenshot 2024-07-17 at 6 44 41 PM

如何将 EnumeratorMacro 添加到你的项目

要在 SwiftPM 项目中使用 EnumeratorMacro 库,请将以下行添加到 Package.swift 文件中的依赖项中

.package(url: "https://github.com/MahdiBM/enumerator-macro", branch: "main"),

EnumeratorMacro 作为你目标的依赖项包含在内

.target(name: "<target>", dependencies: [
    .product(name: "EnumeratorMacro", package: "enumerator-macro"),
]),

最后,将 import EnumeratorMacro 添加到你的源代码中。