一个实用工具,可以使用 Mustache 模板引擎为你的 Swift 枚举创建逐个案例的代码。
EnumeratorMacro
使用 swift-mustache 的风格。
该宏会解析你的枚举代码,并将枚举的不同信息传递给 Mustache 模板渲染器。
然后你可以在模板中访问每个 case 名称、case 参数等,并在此基础上创建代码。
它非常简单。
{{variableName}}
语法注入变量。{{#array}} {{/array}}
语法循环遍历数组。{{#boolean}} {{/boolean}}
语法应用 if 条件。{{^boolean}} {{/boolean}}
语法应用反向 if 条件。snakedCased(variable)
。{{! comment here }}
语法在模板中编写注释。EnumeratorMacro
将会
FixIt
s,如果 FixIt
s 不会导致已知问题。注意
当你滚动时,这些例子会变得更加高级。
@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
+ }
+ }
}
@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
+ }
+ }
}
@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
+ }
+ }
}
@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
支持这些类型的转换
字符串
:
capitalized() -> String
:将首字母大写。dropFirst() -> String
:相当于 Swift 的 .dropFirst()
。dropLast() -> String
:相当于 Swift 的 .dropLast()
。hash() -> String
:使用 CRC32
算法的字符串哈希值。sha() -> String
:使用 SHA256
算法的字符串 SHA256 哈希值。snakeCased() -> String
:将字符串从 camelCase 转换为 snake_case。camelCased() -> String
:将字符串从 snake_case 转换为 camelCase。withParens() -> String
:如果字符串不为空,则用括号将其括起来。整数
:
plusOne() -> Int
:将整数加一。minusOne() -> Int
:从整数中减一。equalsZero() -> Bool
:返回整数是否等于零。isOdd() -> Bool
:返回整数是奇数还是偶数。isEven() -> Bool
:返回整数是偶数还是奇数。hash() -> String
:使用 CRC32
算法的整数字符串表示形式的哈希值。sha() -> String
:使用 SHA256
算法的整数字符串表示形式的哈希值。数组
:
first() -> Element?
:返回数组的第一个元素。last() -> Element?
:返回数组的最后一个元素。count() -> Int
:返回数组中元素的数量。isEmpty() -> Bool
:返回数组是否为空。reversed() -> Self
:返回一个反向数组。sorted() -> Self
:如果数组的元素是可比较的,则对元素进行排序。joined() -> String
:相当于 Swift 的 .joined(separator: ", ")
。keyValues() -> [KeyValue]
:将数组的元素解析为由“:”分隔的键值对。可选值
:
exists() -> Bool
:返回此可选值是否包含任何内容。isEmpty() -> Bool
:返回此可选值是否包含任何内容。Optional<String>.some("")
将为 isEmpty
返回 true
,因为即使可选值存在,字符串值也是空的。Optional
就像 Swift 中一样,是一个透明的值。当您确定该值存在时,可以使用任何适用于包装类型的功能。KeyValue
:
key() -> String
:返回 key。你也可以使用 Mustache 原生的 {{key}} 语法。value() -> String
:返回 value。你也可以使用 Mustache 原生的 {{value}} 语法。[KeyValue]
(comments
)Array
函数也适用于 [KeyValue]
。[KeyValue]
想象成一个 Dictionary<String, String>
。business_error(myKeyValues)
将在 myKeyValues
中找到一个 key
== business_error
的元素,并将 value
作为 String?
返回。[Parameter]
(parameters
)names() -> [String]
:返回参数的名称。names(parameters)
-> [param1, param2, param3]
。types() -> [String]
:返回参数的类型。joined
一起使用:joined(types(parameters))
-> (String, Int, Double)
。namesAndTypes() -> [String]
:返回一个数组,其中每个元素等效于 "\(name): \(type)"
。joined
一起使用:joined(namesAndTypes(parameters))
-> (key: String)
或 (key: String, value: Int)
。tupleValue() -> String
:适合用于从参数创建元组。withParens
一起使用:withParens(tupleValue(parameters))
-> (String)
或 (key: String, value: Int)
。如果您认为某个函数可以解决问题,请随时提出建议。
如果在扩展的宏代码中,或代码生成的任何其他步骤中出现错误,EnumeratorMacro
将尝试发出指向代码行的诊断信息,该行是问题的根源。
例如,EnumeratorMacro
会正确地将来自模板引擎的模板渲染错误转发到你的源代码。 在下面的例子中,我错误地写了 {{cases}
而不是 {{cases}}
或者在这里,扩展的 Swift 代码会导致代码出现语法错误,并且该宏预先报告了错误。 在这里,我应该忘记在 caseName
和 String
之间写 :
。
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
添加到你的源代码中。