Skip to content

Commit 7d0a8fc

Browse files
finished logic to support closures for elements; minor performance improvements
1 parent 2a21bf8 commit 7d0a8fc

File tree

3 files changed

+92
-56
lines changed

3 files changed

+92
-56
lines changed

Sources/HTMLKitParse/extensions/HTMLElementValueType.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ extension HTMLElementValueType {
3232
return nil
3333
}
3434
var c = context
35+
c.expansion.trailingClosure = function.trailingClosure
3536
c.arguments = function.arguments
3637
switch key {
3738
case "a": return get(c, a.self)

Sources/HTMLKitUtilityMacros/HTMLElements.swift

Lines changed: 77 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,13 @@ import SwiftSyntax
1010
import SwiftSyntaxMacros
1111

1212
enum HTMLElements : DeclarationMacro {
13+
// MARK: expansion
1314
static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] {
1415
let dictionary:DictionaryElementListSyntax = node.arguments.children(viewMode: .all).first!.as(LabeledExprSyntax.self)!.expression.as(DictionaryExprSyntax.self)!.content.as(DictionaryElementListSyntax.self)!
1516

1617
var items:[DeclSyntax] = []
1718
items.reserveCapacity(dictionary.count)
1819

19-
func separator(key: String) -> String {
20-
switch key {
21-
case "accept", "coords", "exportparts", "imagesizes", "imagesrcset", "sizes", "srcset":
22-
return ","
23-
case "allow":
24-
return ";"
25-
default:
26-
return " "
27-
}
28-
}
29-
3020
let void_elements:Set<String> = [
3121
"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr"
3222
]
@@ -92,22 +82,21 @@ enum HTMLElements : DeclarationMacro {
9282
}
9383
string += attribute_declarations
9484

95-
initializers += "\npublic init(\n"
96-
initializers += "attributes: [HTMLAttribute] = [],\n"
97-
for (key, value_type, default_value) in attributes {
98-
initializers += key + ": " + value_type + default_value + ",\n"
99-
}
100-
initializers += "_ innerHTML: CustomStringConvertible & Sendable...\n) {\n"
101-
initializers += "self.attributes = attributes\n"
102-
for (key, _, _) in attributes {
103-
var keyLiteral = key
104-
if keyLiteral.first == "`" {
105-
keyLiteral.removeFirst()
106-
keyLiteral.removeLast()
107-
}
108-
initializers += "self.\(keyLiteral) = \(key)\n"
109-
}
110-
initializers += "self.innerHTML = innerHTML\n}\n"
85+
initializers += "\n" + defaultInitializer(
86+
attributes: attributes,
87+
innerHTMLValueType: "[CustomStringConvertible & Sendable] = []",
88+
assignInnerHTML: "innerHTML"
89+
)
90+
initializers += "\n" + defaultInitializer(
91+
attributes: attributes,
92+
innerHTMLValueType: "CustomStringConvertible & Sendable...",
93+
assignInnerHTML: "innerHTML"
94+
)
95+
initializers += "\n" + defaultInitializer(
96+
attributes: attributes,
97+
innerHTMLValueType: "() -> CustomStringConvertible & Sendable...",
98+
assignInnerHTML: "innerHTML.map { $0() }"
99+
)
111100

112101
initializers += "public init(_ encoding: HTMLEncoding, _ data: HTMLKitUtilities.ElementData) {\n"
113102
initializers += "self.encoding = encoding\n"
@@ -116,14 +105,14 @@ enum HTMLElements : DeclarationMacro {
116105
initializers += "self.trailingSlash = data.trailingSlash\n"
117106
}
118107
initializers += "self.attributes = data.globalAttributes\n"
119-
for (key, value_type, _) in attributes {
108+
for (key, valueType, _) in attributes {
120109
var keyLiteral = key
121110
if keyLiteral.first == "`" {
122111
keyLiteral.removeFirst()
123112
keyLiteral.removeLast()
124113
}
125-
var value = "as? \(value_type)"
126-
switch value_type {
114+
var value = "as? \(valueType)"
115+
switch valueType {
127116
case "Bool":
128117
value += " ?? false"
129118
default:
@@ -142,24 +131,22 @@ enum HTMLElements : DeclarationMacro {
142131
attributes_func += "let sd = encoding.stringDelimiter(forMacro: fromMacro)\n"
143132
itemsArray += "var items:[String] = []\n"
144133
}
145-
for (key, value_type, _) in attributes {
134+
for (key, valueType, _) in attributes {
146135
var keyLiteral = key
147136
if keyLiteral.first == "`" {
148137
keyLiteral.removeFirst()
149138
keyLiteral.removeLast()
150139
}
151-
let variable_name = keyLiteral
152-
if keyLiteral == "httpEquiv" {
153-
keyLiteral = "http-equiv"
154-
} else if keyLiteral == "acceptCharset" {
155-
keyLiteral = "accept-charset"
140+
let variableName = keyLiteral
141+
switch keyLiteral {
142+
case "httpEquiv": keyLiteral = "http-equiv"
143+
case "acceptCharset": keyLiteral = "accept-charset"
144+
default: break
156145
}
157-
if value_type == "Bool" {
158-
itemsArray += "if \(key) { items.append(\"\(keyLiteral)\") }\n"
159-
} else if value_type.first == "[" {
160-
itemsArray += "if let _\(variable_name):String = "
146+
if valueType.first == "[" {
147+
itemsArray += "if let _\(variableName):String = "
161148
let separator = separator(key: key)
162-
switch value_type {
149+
switch valueType {
163150
case "[String]":
164151
itemsArray += "\(key)?"
165152
case "[Int]", "[Float]":
@@ -168,18 +155,23 @@ enum HTMLElements : DeclarationMacro {
168155
itemsArray += "\(key)?.compactMap({ return $0.htmlValue(encoding: encoding, forMacro: fromMacro) })"
169156
}
170157
itemsArray += ".joined(separator: \"\(separator)\") {\n"
171-
itemsArray += #"let k:String = _\#(variable_name).isEmpty ? "" : "=" + sd + _\#(variable_name) + sd"#
158+
itemsArray += #"let k:String = _\#(variableName).isEmpty ? "" : "=" + sd + _\#(variableName) + sd"#
172159
itemsArray += "\nitems.append(\"\(keyLiteral)\" + k)"
173160
itemsArray += "\n}\n"
174-
} else if value_type == "String" || value_type == "Int" || value_type == "Float" || value_type == "Double" {
175-
let value = value_type == "String" ? key : "String(describing: \(key))"
176-
itemsArray += #"if let \#(key) { items.append("\#(keyLiteral)=" + sd + \#(value) + sd) }"#
177-
itemsArray += "\n"
178161
} else {
179-
itemsArray += "if let \(key), let v = \(key).htmlValue(encoding: encoding, forMacro: fromMacro) {\n"
180-
itemsArray += #"let s = \#(key).htmlValueIsVoidable && v.isEmpty ? "" : "=" + sd + v + sd"#
181-
itemsArray += "\nitems.append(\"\(keyLiteral)\" + s)"
182-
itemsArray += "\n}\n"
162+
switch valueType {
163+
case "Bool":
164+
itemsArray += "if \(key) { items.append(\"\(keyLiteral)\") }\n"
165+
case "String", "Int", "Float", "Double":
166+
let value = valueType == "String" ? key : "String(describing: \(key))"
167+
itemsArray += #"if let \#(key) { items.append("\#(keyLiteral)=" + sd + \#(value) + sd) }"#
168+
itemsArray += "\n"
169+
default:
170+
itemsArray += "if let \(key), let v = \(key).htmlValue(encoding: encoding, forMacro: fromMacro) {\n"
171+
itemsArray += #"let s = \#(key).htmlValueIsVoidable && v.isEmpty ? "" : "=" + sd + v + sd"#
172+
itemsArray += "\nitems.append(\"\(keyLiteral)\" + s)"
173+
itemsArray += "\n}\n"
174+
}
183175
}
184176
}
185177
render += attributes_func + itemsArray
@@ -199,6 +191,41 @@ enum HTMLElements : DeclarationMacro {
199191
}
200192
return items
201193
}
194+
// MARK: separator
195+
static func separator(key: String) -> String {
196+
switch key {
197+
case "accept", "coords", "exportparts", "imagesizes", "imagesrcset", "sizes", "srcset":
198+
return ","
199+
case "allow":
200+
return ";"
201+
default:
202+
return " "
203+
}
204+
}
205+
// MARK: default initializer
206+
static func defaultInitializer(
207+
attributes: [(String, String, String)],
208+
innerHTMLValueType: String,
209+
assignInnerHTML: String
210+
) -> String {
211+
var initializers = "public init(\n"
212+
initializers += "attributes: [HTMLAttribute] = [],\n"
213+
for (key, valueType, defaultValue) in attributes {
214+
initializers += key + ": " + valueType + defaultValue + ",\n"
215+
}
216+
initializers += "_ innerHTML: \(innerHTMLValueType)\n) {\n"
217+
initializers += "self.attributes = attributes\n"
218+
for (key, _, _) in attributes {
219+
var keyLiteral = key
220+
if keyLiteral.first == "`" {
221+
keyLiteral.removeFirst()
222+
keyLiteral.removeLast()
223+
}
224+
initializers += "self.\(keyLiteral) = \(key)\n"
225+
}
226+
initializers += "self.innerHTML = \(assignInnerHTML)\n}\n"
227+
return initializers
228+
}
202229
// MARK: parse value type
203230
static func parse_value_type(isArray: inout Bool, key: String, _ expr: ExprSyntax) -> (value_type: String, default_value: String, value_type_literal: HTMLElementValueType) {
204231
let value_type_key:String

Tests/HTMLKitTests/ElementTests.swift

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ extension ElementTests {
5151
string = #html(a(href: "test", "Test"))
5252
#expect(string == "<a href=\"test\">Test</a>")
5353

54+
string = #html(a(href: "test") {
55+
"Test"
56+
})
57+
#expect(string == "<a href=\"test\">Test</a>")
58+
5459
string = #html(a(href: "", "Test"))
5560
#expect(string == "<a href=\"\">Test</a>")
5661

@@ -430,12 +435,15 @@ extension ElementTests {
430435
}*/
431436

432437
@Test func multilineInnerHTMLValue() {
433-
let string:StaticString = #html(p("""
434-
bro
435-
dude
436-
hermano
437-
"""
438-
))
438+
let string:StaticString = #html(
439+
p {
440+
"""
441+
bro
442+
dude
443+
hermano
444+
"""
445+
}
446+
)
439447
#expect(string == "<p>bro\n dude\nhermano</p>")
440448
}
441449

0 commit comments

Comments
 (0)