Skip to content

Commit db8ae26

Browse files
code/element generator updates and...
- made `HTMLElementType` visibility `public` (was `package`) - added `@inlinable` annotation to some functions
1 parent f3d74de commit db8ae26

File tree

15 files changed

+391
-165
lines changed

15 files changed

+391
-165
lines changed

Package.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ let package = Package(
3939
]
4040
),
4141

42+
.target(
43+
name: "HTMLElements",
44+
dependencies: [
45+
"HTMLKitUtilities",
46+
.product(name: "SwiftDiagnostics", package: "swift-syntax"),
47+
.product(name: "SwiftSyntax", package: "swift-syntax"),
48+
.product(name: "SwiftSyntaxMacros", package: "swift-syntax")
49+
]
50+
),
51+
4252
.macro(
4353
name: "HTMLKitMacros",
4454
dependencies: [

Sources/GenerateElements/main.swift

Lines changed: 124 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ let template:String = """
4545
import SwiftSyntax
4646
4747
/// The `%tagName%`%aliases% HTML element.%elementDocumentation%
48-
public struct %elementName% : HTMLElement {%variables%
48+
public struct %elementName% : HTMLElement {%variables%%render%
4949
}
5050
5151
public extension %elementName% {
@@ -64,9 +64,24 @@ let defaultVariables:[HTMLElementVariable] = [
6464

6565
let indent1:String = "\n "
6666
let indent2:String = indent1 + " "
67+
let indent3:String = indent2 + " "
68+
let indent4:String = indent3 + " "
69+
let indent5:String = indent4 + " "
70+
let indent6:String = indent5 + " "
6771

6872
for (elementType, customAttributes) in attributes().filter({ $0.key == .a }) {
6973
var variablesString:String = ""
74+
var renderString:String = "\n" + indent1 + "@inlinable" + indent1 + "public var description : String {"
75+
var renderAttributesString:String = indent2 + "func attributes() -> String {"
76+
renderAttributesString += indent3 + "let sd:String = encoding.stringDelimiter(forMacro: fromMacro)"
77+
renderAttributesString += indent3
78+
renderAttributesString += """
79+
var items:[String] = self.attributes.compactMap({
80+
guard let v:String = $0.htmlValue(encoding: encoding, forMacro: fromMacro) else { return nil }
81+
let d:String = $0.htmlValueDelimiter(encoding: encoding, forMacro: fromMacro)
82+
return $0.key + ($0.htmlValueIsVoidable && v.isEmpty ? "" : "=" + d + v + d)
83+
})
84+
"""
7085

7186
var variables:[HTMLElementVariable] = defaultVariables
7287
variables.append(get(public: true, mutable: false, name: "tag", valueType: .string, defaultValue: "\"%tagName%\""))
@@ -75,23 +90,99 @@ for (elementType, customAttributes) in attributes().filter({ $0.key == .a }) {
7590
variables.append(attribute)
7691
}
7792

93+
func separator(key: String) -> String {
94+
switch key {
95+
case "accept", "coords", "exportparts", "imagesizes", "imagesrcset", "sizes", "srcset":
96+
return ","
97+
case "allow":
98+
return ";"
99+
default:
100+
return " "
101+
}
102+
}
103+
104+
let excludeRendered:Set<String> = ["attributes", "isVoid", "encoding", "tag", "fromMacro", "trailingSlash", "escaped", "innerHTML"]
78105
for variable in variables.sorted(by: {
79106
guard $0.memoryLayoutAlignment == $1.memoryLayoutAlignment else { return $0.memoryLayoutAlignment > $1.memoryLayoutAlignment }
80107
guard $0.memoryLayoutSize == $1.memoryLayoutSize else { return $0.memoryLayoutSize > $1.memoryLayoutSize }
81108
guard $0.memoryLayoutStride == $1.memoryLayoutStride else { return $0.memoryLayoutStride > $1.memoryLayoutStride }
82109
return $0.name < $1.name
83110
}) {
84111
variablesString += indent1 + variable.description
112+
let variableName:String = variable.name
113+
if !excludeRendered.contains(variableName) {
114+
if variable.valueType.isOptional {
115+
if variable.valueType.isAttribute {
116+
renderAttributesString += indent3 + "if let " + variableName
117+
renderAttributesString += ", let v:String = " + variableName + ".htmlValue(encoding: encoding, forMacro: fromMacro) {"
118+
renderAttributesString += indent4 + "let s:String = " + variableName + ".htmlValueIsVoidable && v.isEmpty ? \"\" : \"=\" + sd + v + sd"
119+
renderAttributesString += indent4 + "items.append(\"" + variableName.lowercased() + "\" + s)"
120+
} else if variable.valueType.isString {
121+
renderAttributesString += indent3 + "if let " + variableName
122+
renderAttributesString += " {"
123+
renderAttributesString += indent4 + "items.append(\"" + variableName.lowercased() + "\" + sd + " + variableName + " + sd)"
124+
} else if variable.valueType.isArray {
125+
let separator:String = separator(key: variableName.lowercased())
126+
renderAttributesString += indent3 + "if !" + variableName + ".isEmpty {"
127+
renderAttributesString += indent4 + "var v:String = sd"
128+
renderAttributesString += indent4
129+
130+
var function:String = indent5
131+
switch variable.valueType {
132+
case .array(.string), .optional(.array(.string)):
133+
function += "v += e + \"\(separator)\""
134+
case .array(.int), .optional(.array(.int)):
135+
function += "v += String(describing: e) + \"\(separator)\""
136+
default:
137+
function += "if let e:String = e.htmlValue(encoding: encoding, forMacro: fromMacro) {" + indent6 + "v += e + \"\(separator)\"" + indent5 + "}"
138+
}
139+
140+
renderAttributesString += """
141+
for e in \(variableName) {\(function)
142+
}
143+
"""
144+
renderAttributesString += indent4 + "v.removeLast()"
145+
renderAttributesString += indent4 + "items.append(\"" + variableName.lowercased() + "=\" + v + sd)"
146+
} else {
147+
renderAttributesString += indent3 + "if let " + variableName
148+
renderAttributesString += " {"
149+
renderAttributesString += indent4 + "// test"
150+
}
151+
renderAttributesString += indent3 + "}"
152+
} else {
153+
renderAttributesString += "\n+ String(describing: " + variableName + ")"
154+
}
155+
}
85156
}
157+
renderAttributesString += indent3 + "return (items.isEmpty ? \"\" : \" \") + items.joined(separator: \" \")"
86158

87159
variables = variables.sorted(by: { $0.name <= $1.name })
88160
var customAttributeCases:String = ""
89161
for variable in variables {
90-
customAttributeCases += indent2 + "case " + variable.name + "(" + variable.valueType.annotation(variableName: variable.name) + " = " + variable.valueType.defaultOptionalValue + ")"
162+
if !excludeRendered.contains(variable.name.lowercased()) {
163+
customAttributeCases += indent2 + "case " + variable.name + "(" + variable.valueType.annotation(variableName: variable.name) + " = " + variable.valueType.defaultOptionalValue + ")"
164+
}
91165
}
166+
167+
renderAttributesString += indent2 + "}"
168+
renderString += renderAttributesString + "\n"
169+
renderString += """
170+
let string:String = innerHTML.map({ String(describing: $0) }).joined()
171+
let l:String, g:String
172+
if escaped {
173+
l = "&lt;"
174+
g = "&gt;"
175+
} else {
176+
l = "<"
177+
g = ">"
178+
}
179+
return l + tag + attributes() + g + string + l + "/" + tag + g
180+
"""
181+
renderString += indent1 + "}"
92182

93183
var code:String = template
94184
code.replace("%variables%", with: variablesString)
185+
code.replace("%render%", with: renderString)
95186
var elementDocumentation:[String] = elementType.documentation
96187
elementDocumentation.append(contentsOf: [" ", "[Read more](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/%tagName%)."])
97188
let elementDocumentationString:String = "\n/// \n" + elementDocumentation.map({ "/// " + $0 }).joined(separator: "\n")
@@ -108,7 +199,8 @@ for (elementType, customAttributes) in attributes().filter({ $0.key == .a }) {
108199
let filePath:String = writeTo + fileName
109200
if FileManager.default.fileExists(atPath: filePath) {
110201
try FileManager.default.removeItem(atPath: filePath)
111-
}*/
202+
}
203+
FileManager.default.createFile(atPath: filePath, contents: code.data(using: .utf8)!)*/
112204
}
113205
extension Array where Element == HTMLElementVariable {
114206
func filterAndSort(_ predicate: (Element) -> Bool) -> [Element] {
@@ -159,152 +251,13 @@ struct HTMLElementVariable : Hashable {
159251
if !string.isEmpty {
160252
string += indent1
161253
}
162-
string += (`public` ? "public" : "private") + " " + (mutable ? "var" : "let") + " " + name + ":" + valueType.annotation(variableName: name) + (defaultValue != nil ? " = " + defaultValue! : "")
254+
string += (`public` ? "public" : "@usableFromInline internal") + " " + (mutable ? "var" : "let") + " " + name + ":" + valueType.annotation(variableName: name) + (defaultValue != nil ? " = " + defaultValue! : "")
163255
return string
164256
}
165257
}
166258

167259
// MARK: HTMLElementType
168-
enum HTMLElementType : String, CaseIterable {
169-
case html
170-
171-
case a
172-
case abbr
173-
case address
174-
case area
175-
case article
176-
case aside
177-
case audio
178-
179-
case b
180-
case base
181-
case bdi
182-
case bdo
183-
case blockquote
184-
case body
185-
case br
186-
case button
187-
188-
case canvas
189-
case caption
190-
case cite
191-
case code
192-
case col
193-
case colgroup
194-
195-
case data
196-
case datalist
197-
case dd
198-
case del
199-
case details
200-
case dfn
201-
case dialog
202-
case div
203-
case dl
204-
case dt
205-
206-
case em
207-
case embed
208-
209-
case fencedframe
210-
case fieldset
211-
case figcaption
212-
case figure
213-
case footer
214-
case form
215-
216-
case h1, h2, h3, h4, h5, h6
217-
case head
218-
case header
219-
case hgroup
220-
case hr
221-
222-
case i
223-
case iframe
224-
case img
225-
case input
226-
case ins
227-
228-
case kbd
229-
230-
case label
231-
case legend
232-
case li
233-
case link
234-
235-
case main
236-
case map
237-
case mark
238-
case menu
239-
case meta
240-
case meter
241-
242-
case nav
243-
case noscript
244-
245-
case object
246-
case ol
247-
case optgroup
248-
case option
249-
case output
250-
251-
case p
252-
case picture
253-
case portal
254-
case pre
255-
case progress
256-
257-
case q
258-
259-
case rp
260-
case rt
261-
case ruby
262-
263-
case s
264-
case samp
265-
case script
266-
case search
267-
case section
268-
case select
269-
case slot
270-
case small
271-
case source
272-
case span
273-
case strong
274-
case style
275-
case sub
276-
case summary
277-
case sup
278-
279-
case table
280-
case tbody
281-
case td
282-
case template
283-
case textarea
284-
case tfoot
285-
case th
286-
case thead
287-
case time
288-
case title
289-
case tr
290-
case track
291-
292-
case u
293-
case ul
294-
295-
case variable // var
296-
case video
297-
298-
case wbr
299-
300-
var isVoid : Bool {
301-
switch self {
302-
case .area, .base, .br, .col, .embed, .hr, .img, .input, .link, .meta, .source, .track, .wbr:
303-
return true
304-
default:
305-
return false
306-
}
307-
}
260+
extension HTMLElementType {
308261

309262
var tagName : String {
310263
switch self {
@@ -330,6 +283,14 @@ enum HTMLElementType : String, CaseIterable {
330283
" ",
331284
"Content within each `<a>` _should_ indicate the link's destination. If the `href` attribute is present, pressing the enter key while focused on the `<a>` element will activate it."
332285
]
286+
case .abbr:
287+
return [
288+
"Represents an abbreviation or acronym.",
289+
"",
290+
"When including an abbreviation or acronym, provide a full expansion of the term in plain text on first use, along with the `<abbr>` to mark up the abbreviation. This informs the user what the abbreviation or acronym means.",
291+
"",
292+
"The optional [`title`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/title) attribute can provide an expansion for the abbreviation or acronym when a full expansion is not present. This provides a hint to user agents on how to announce/display the content while informing all users what the abbreviation means. If present, `title` must contain this full description and nothing else."
293+
]
333294
default: return []
334295
}
335296
}
@@ -389,6 +350,20 @@ enum HTMLElementValueType : Hashable {
389350
default: return false
390351
}
391352
}
353+
354+
var isString : Bool {
355+
switch self {
356+
case .string, .optional(.string): return true
357+
default: return false
358+
}
359+
}
360+
361+
var isOptional : Bool {
362+
switch self {
363+
case .optional(_): return true
364+
default: return false
365+
}
366+
}
392367

393368
var defaultOptionalValue : String {
394369
isArray ? "[]" : isBool ? "false" : "nil"
@@ -398,9 +373,9 @@ enum HTMLElementValueType : Hashable {
398373
// MARK: Get
399374
func get(
400375
_ variableName: String,
401-
_ valueType: HTMLElementValueType,
402-
_ documentation: HTMLElementVariableDocumentation? = nil
376+
_ valueType: HTMLElementValueType
403377
) -> HTMLElementVariable {
378+
let documentation:HTMLElementVariableDocumentation? = HTMLElementVariableDocumentation(rawValue: variableName.lowercased())
404379
return get(public: true, mutable: true, name: variableName, documentation: documentation?.value ?? [], valueType: .optional(valueType))
405380
}
406381
func get(
@@ -465,7 +440,7 @@ func get(
465440
}
466441

467442
// MARK: Attribute Documentation
468-
enum HTMLElementVariableDocumentation {
443+
enum HTMLElementVariableDocumentation : String {
469444
case download
470445

471446
var value : [String] {
@@ -489,7 +464,7 @@ func attributes() -> [HTMLElementType:[HTMLElementVariable]] {
489464
// MARK: A
490465
.a : [
491466
get("attributionsrc", .array(of: .string)),
492-
get("download", .attribute, .download),
467+
get("download", .attribute),
493468
get("href", .string),
494469
get("hrefLang", .string),
495470
get("ping", .array(of: .string)),
@@ -503,7 +478,7 @@ func attributes() -> [HTMLElementType:[HTMLElementVariable]] {
503478
.area : [
504479
get("alt", .string),
505480
get("coords", .array(of: .int)),
506-
get("download", .attribute, .download),
481+
get("download", .attribute),
507482
get("href", .string),
508483
get("shape", .attribute),
509484
get("ping", .array(of: .string)),

0 commit comments

Comments
 (0)