Dynamic names support (#49)
* Dynamic names support * Add support for dynamic names in parent tags * Support all dynamic names spec * Swift 5.8 compile fix
This commit is contained in:
@@ -232,29 +232,55 @@ extension MustacheTemplate {
|
||||
case ">":
|
||||
// partial
|
||||
parser.unsafeAdvance()
|
||||
// skip whitespace
|
||||
parser.read(while: \.isWhitespace)
|
||||
var dynamic = false
|
||||
if parser.current() == "*" {
|
||||
parser.unsafeAdvance()
|
||||
dynamic = true
|
||||
}
|
||||
let name = try parsePartialName(&parser, state: state)
|
||||
if whiteSpaceBefore.count > 0 {
|
||||
tokens.append(.text(String(whiteSpaceBefore)))
|
||||
}
|
||||
if self.isStandalone(&parser, state: state) {
|
||||
setNewLine = true
|
||||
tokens.append(.partial(name, indentation: String(whiteSpaceBefore), inherits: nil))
|
||||
if dynamic {
|
||||
tokens.append(.dynamicNamePartial(name, indentation: String(whiteSpaceBefore), inherits: nil))
|
||||
} else {
|
||||
tokens.append(.partial(name, indentation: String(whiteSpaceBefore), inherits: nil))
|
||||
}
|
||||
} else {
|
||||
tokens.append(.partial(name, indentation: nil, inherits: nil))
|
||||
if dynamic {
|
||||
tokens.append(.dynamicNamePartial(name, indentation: nil, inherits: nil))
|
||||
} else {
|
||||
tokens.append(.partial(name, indentation: nil, inherits: nil))
|
||||
}
|
||||
}
|
||||
whiteSpaceBefore = ""
|
||||
|
||||
case "<":
|
||||
// partial with inheritance
|
||||
parser.unsafeAdvance()
|
||||
let name = try parsePartialName(&parser, state: state)
|
||||
// skip whitespace
|
||||
parser.read(while: \.isWhitespace)
|
||||
let sectionName = try parsePartialName(&parser, state: state)
|
||||
let name: String
|
||||
let dynamic: Bool
|
||||
if sectionName.first == "*" {
|
||||
dynamic = true
|
||||
name = String(sectionName.dropFirst())
|
||||
} else {
|
||||
dynamic = false
|
||||
name = sectionName
|
||||
}
|
||||
if whiteSpaceBefore.count > 0 {
|
||||
tokens.append(.text(String(whiteSpaceBefore)))
|
||||
}
|
||||
if self.isStandalone(&parser, state: state) {
|
||||
setNewLine = true
|
||||
}
|
||||
let sectionTokens = try parse(&parser, state: state.withInheritancePartial(name))
|
||||
let sectionTokens = try parse(&parser, state: state.withInheritancePartial(sectionName))
|
||||
var inherit: [String: MustacheTemplate] = [:]
|
||||
// parse tokens in section to extract inherited sections
|
||||
for token in sectionTokens {
|
||||
@@ -267,7 +293,11 @@ extension MustacheTemplate {
|
||||
throw Error.illegalTokenInsideInheritSection
|
||||
}
|
||||
}
|
||||
tokens.append(.partial(name, indentation: String(whiteSpaceBefore), inherits: inherit))
|
||||
if dynamic {
|
||||
tokens.append(.dynamicNamePartial(name, indentation: String(whiteSpaceBefore), inherits: inherit))
|
||||
} else {
|
||||
tokens.append(.partial(name, indentation: String(whiteSpaceBefore), inherits: inherit))
|
||||
}
|
||||
whiteSpaceBefore = ""
|
||||
|
||||
case "$":
|
||||
@@ -478,7 +508,7 @@ extension MustacheTemplate {
|
||||
return state.newLine && self.hasLineFinished(&parser)
|
||||
}
|
||||
|
||||
private static let sectionNameCharsWithoutBrackets = Set<Character>("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_?")
|
||||
private static let sectionNameChars = Set<Character>("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_?()")
|
||||
private static let sectionNameCharsWithoutBrackets = Set<Character>("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_?*")
|
||||
private static let sectionNameChars = Set<Character>("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_?()*")
|
||||
private static let partialNameChars = Set<Character>("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_()")
|
||||
}
|
||||
|
||||
@@ -101,6 +101,28 @@ extension MustacheTemplate {
|
||||
return template.render(context: context.withPartial(indented: indentation, inheriting: overrides))
|
||||
}
|
||||
|
||||
case .dynamicNamePartial(let name, let indentation, let overrides):
|
||||
let child = self.getChild(named: name, transforms: [], context: context)
|
||||
guard let childName = child as? String else {
|
||||
return ""
|
||||
}
|
||||
if var template = context.library?.getTemplate(named: childName) {
|
||||
#if DEBUG
|
||||
if context.reloadPartials {
|
||||
guard let filename = template.filename else {
|
||||
preconditionFailure("Can only use reload if template was generated from a file")
|
||||
}
|
||||
do {
|
||||
guard let partialTemplate = try MustacheTemplate(filename: filename) else { return "Cannot find template at \(filename)" }
|
||||
template = partialTemplate
|
||||
} catch {
|
||||
return "\(error)"
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return template.render(context: context.withPartial(indented: indentation, inheriting: overrides))
|
||||
}
|
||||
|
||||
case .contentType(let contentType):
|
||||
context = context.withContentType(contentType)
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ public struct MustacheTemplate: Sendable {
|
||||
case blockDefinition(name: String, template: MustacheTemplate)
|
||||
case blockExpansion(name: String, default: MustacheTemplate, indentation: String?)
|
||||
case partial(String, indentation: String?, inherits: [String: MustacheTemplate]?)
|
||||
case dynamicNamePartial(String, indentation: String?, inherits: [String: MustacheTemplate]?)
|
||||
case contentType(MustacheContentType)
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ final class MustacheSpecTests: XCTestCase {
|
||||
let expected: String
|
||||
|
||||
func run() throws {
|
||||
// print("Test: \(self.name)")
|
||||
print("Test: \(self.name)")
|
||||
if let partials = self.partials {
|
||||
let template = try MustacheTemplate(string: self.template)
|
||||
var templates: [String: MustacheTemplate] = ["__test__": template]
|
||||
@@ -188,4 +188,8 @@ final class MustacheSpecTests: XCTestCase {
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func testDynamicNamesSpec() async throws {
|
||||
try await self.testSpec(name: "~dynamic-names")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,6 +261,119 @@ final class TemplateRendererTests: XCTestCase {
|
||||
""")
|
||||
}
|
||||
|
||||
/// test dynamic names
|
||||
func testMustacheManualDynamicNames() throws {
|
||||
var library = MustacheLibrary()
|
||||
try library.register(
|
||||
"Hello {{>*dynamic}}",
|
||||
named: "main"
|
||||
)
|
||||
try library.register(
|
||||
"everyone!",
|
||||
named: "world"
|
||||
)
|
||||
let object = ["dynamic": "world"]
|
||||
XCTAssertEqual(library.render(object, withTemplate: "main"), "Hello everyone!")
|
||||
}
|
||||
|
||||
/// test block with defaults
|
||||
func testMustacheManualBlocksWithDefaults() throws {
|
||||
let template = try MustacheTemplate(string: """
|
||||
<h1>{{$title}}The News of Today{{/title}}</h1>
|
||||
{{$body}}
|
||||
<p>Nothing special happened.</p>
|
||||
{{/body}}
|
||||
|
||||
""")
|
||||
XCTAssertEqual(template.render([]), """
|
||||
<h1>The News of Today</h1>
|
||||
<p>Nothing special happened.</p>
|
||||
|
||||
""")
|
||||
}
|
||||
|
||||
func testMustacheManualParents() throws {
|
||||
var library = MustacheLibrary()
|
||||
try library.register(
|
||||
"""
|
||||
{{<article}}
|
||||
Never shown
|
||||
{{$body}}
|
||||
{{#headlines}}
|
||||
<p>{{.}}</p>
|
||||
{{/headlines}}
|
||||
{{/body}}
|
||||
{{/article}}
|
||||
|
||||
{{<article}}
|
||||
{{$title}}Yesterday{{/title}}
|
||||
{{/article}}
|
||||
|
||||
""",
|
||||
named: "main"
|
||||
)
|
||||
try library.register(
|
||||
"""
|
||||
<h1>{{$title}}The News of Today{{/title}}</h1>
|
||||
{{$body}}
|
||||
<p>Nothing special happened.</p>
|
||||
{{/body}}
|
||||
|
||||
""",
|
||||
named: "article"
|
||||
)
|
||||
let object = [
|
||||
"headlines": [
|
||||
"A pug's handler grew mustaches.",
|
||||
"What an exciting day!",
|
||||
],
|
||||
]
|
||||
XCTAssertEqual(
|
||||
library.render(object, withTemplate: "main"),
|
||||
"""
|
||||
<h1>The News of Today</h1>
|
||||
<p>A pug's handler grew mustaches.</p>
|
||||
<p>What an exciting day!</p>
|
||||
|
||||
<h1>Yesterday</h1>
|
||||
<p>Nothing special happened.</p>
|
||||
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
func testMustacheManualDynamicNameParents() throws {
|
||||
var library = MustacheLibrary()
|
||||
try library.register(
|
||||
"""
|
||||
{{<*dynamic}}
|
||||
{{$text}}Hello World!{{/text}}
|
||||
{{/*dynamic}}
|
||||
|
||||
""",
|
||||
named: "dynamic"
|
||||
)
|
||||
try library.register(
|
||||
"""
|
||||
{{$text}}Here goes nothing.{{/text}}
|
||||
""",
|
||||
named: "normal"
|
||||
)
|
||||
try library.register(
|
||||
"""
|
||||
<b>{{$text}}Here also goes nothing but it's bold.{{/text}}</b>
|
||||
""",
|
||||
named: "bold"
|
||||
)
|
||||
let object = ["dynamic": "bold"]
|
||||
XCTAssertEqual(
|
||||
library.render(object, withTemplate: "dynamic"),
|
||||
"""
|
||||
<b>Hello World!</b>
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
/// test MustacheCustomRenderable
|
||||
func testCustomRenderable() throws {
|
||||
let template = try MustacheTemplate(string: "{{.}}")
|
||||
|
||||
Reference in New Issue
Block a user