Template inheritance (#9)
* Move all context variables into HBMustacheContext * Add support for reading inherited sections * Render inherited tokens * Test inheritance spec, fix two minor issues * fix warning * swift format
This commit is contained in:
57
Sources/HummingbirdMustache/Context.swift
Normal file
57
Sources/HummingbirdMustache/Context.swift
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
struct HBMustacheContext {
|
||||||
|
let stack: [Any]
|
||||||
|
let sequenceContext: HBMustacheSequenceContext?
|
||||||
|
let indentation: String?
|
||||||
|
let inherited: [String: HBMustacheTemplate]?
|
||||||
|
|
||||||
|
init(_ object: Any) {
|
||||||
|
self.stack = [object]
|
||||||
|
self.sequenceContext = nil
|
||||||
|
self.indentation = nil
|
||||||
|
self.inherited = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private init(
|
||||||
|
stack: [Any],
|
||||||
|
sequenceContext: HBMustacheSequenceContext?,
|
||||||
|
indentation: String?,
|
||||||
|
inherited: [String: HBMustacheTemplate]?
|
||||||
|
) {
|
||||||
|
self.stack = stack
|
||||||
|
self.sequenceContext = sequenceContext
|
||||||
|
self.indentation = indentation
|
||||||
|
self.inherited = inherited
|
||||||
|
}
|
||||||
|
|
||||||
|
func withObject(_ object: Any) -> HBMustacheContext {
|
||||||
|
var stack = self.stack
|
||||||
|
stack.append(object)
|
||||||
|
return .init(stack: stack, sequenceContext: nil, indentation: self.indentation, inherited: self.inherited)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withPartial(indented: String?, inheriting: [String: HBMustacheTemplate]?) -> HBMustacheContext {
|
||||||
|
let indentation: String?
|
||||||
|
if let indented = indented {
|
||||||
|
indentation = (self.indentation ?? "") + indented
|
||||||
|
} else {
|
||||||
|
indentation = self.indentation
|
||||||
|
}
|
||||||
|
let inherits: [String: HBMustacheTemplate]?
|
||||||
|
if let inheriting = inheriting {
|
||||||
|
if let originalInherits = self.inherited {
|
||||||
|
inherits = originalInherits.merging(inheriting) { value, _ in value }
|
||||||
|
} else {
|
||||||
|
inherits = inheriting
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inherits = self.inherited
|
||||||
|
}
|
||||||
|
return .init(stack: self.stack, sequenceContext: nil, indentation: indentation, inherited: inherits)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withSequence(_ object: Any, sequenceContext: HBMustacheSequenceContext) -> HBMustacheContext {
|
||||||
|
var stack = self.stack
|
||||||
|
stack.append(object)
|
||||||
|
return .init(stack: stack, sequenceContext: sequenceContext, indentation: self.indentation, inherited: self.inherited)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,16 @@ public final class HBMustacheLibrary {
|
|||||||
self.templates[name] = template
|
self.templates[name] = template
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register template under name
|
||||||
|
/// - Parameters:
|
||||||
|
/// - mustache: Mustache text
|
||||||
|
/// - name: Name of template
|
||||||
|
public func register(_ mustache: String, named name: String) throws {
|
||||||
|
let template = try HBMustacheTemplate(string: mustache)
|
||||||
|
template.setLibrary(self)
|
||||||
|
self.templates[name] = template
|
||||||
|
}
|
||||||
|
|
||||||
/// Return template registed with name
|
/// Return template registed with name
|
||||||
/// - Parameter name: name to search for
|
/// - Parameter name: name to search for
|
||||||
/// - Returns: Template
|
/// - Returns: Template
|
||||||
|
|||||||
@@ -1,46 +1,39 @@
|
|||||||
|
|
||||||
/// Protocol for objects that can be rendered as a sequence in Mustache
|
/// Protocol for objects that can be rendered as a sequence in Mustache
|
||||||
public protocol HBMustacheSequence {
|
protocol HBMustacheSequence {
|
||||||
/// Render section using template
|
/// Render section using template
|
||||||
func renderSection(with template: HBMustacheTemplate, stack: [Any]) -> String
|
func renderSection(with template: HBMustacheTemplate, context: HBMustacheContext) -> String
|
||||||
/// Render inverted section using template
|
/// Render inverted section using template
|
||||||
func renderInvertedSection(with template: HBMustacheTemplate, stack: [Any]) -> String
|
func renderInvertedSection(with template: HBMustacheTemplate, context: HBMustacheContext) -> String
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension Sequence {
|
extension Sequence {
|
||||||
/// Render section using template
|
/// Render section using template
|
||||||
func renderSection(with template: HBMustacheTemplate, stack: [Any]) -> String {
|
func renderSection(with template: HBMustacheTemplate, context: HBMustacheContext) -> String {
|
||||||
var string = ""
|
var string = ""
|
||||||
var context = HBMustacheSequenceContext(first: true)
|
var sequenceContext = HBMustacheSequenceContext(first: true)
|
||||||
|
|
||||||
var iterator = makeIterator()
|
var iterator = makeIterator()
|
||||||
guard var currentObject = iterator.next() else { return "" }
|
guard var currentObject = iterator.next() else { return "" }
|
||||||
|
|
||||||
while let object = iterator.next() {
|
while let object = iterator.next() {
|
||||||
var stack = stack
|
string += template.render(context: context.withSequence(currentObject, sequenceContext: sequenceContext))
|
||||||
stack.append(currentObject)
|
|
||||||
string += template.render(stack, context: context)
|
|
||||||
currentObject = object
|
currentObject = object
|
||||||
context.first = false
|
sequenceContext.first = false
|
||||||
context.index += 1
|
sequenceContext.index += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
context.last = true
|
sequenceContext.last = true
|
||||||
var stack = stack
|
string += template.render(context: context.withSequence(currentObject, sequenceContext: sequenceContext))
|
||||||
stack.append(currentObject)
|
|
||||||
string += template.render(stack, context: context)
|
|
||||||
|
|
||||||
return string
|
return string
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render inverted section using template
|
/// Render inverted section using template
|
||||||
func renderInvertedSection(with template: HBMustacheTemplate, stack: [Any]) -> String {
|
func renderInvertedSection(with template: HBMustacheTemplate, context: HBMustacheContext) -> String {
|
||||||
var stack = stack
|
|
||||||
stack.append(self)
|
|
||||||
|
|
||||||
var iterator = makeIterator()
|
var iterator = makeIterator()
|
||||||
if iterator.next() == nil {
|
if iterator.next() == nil {
|
||||||
return template.render(stack)
|
return template.render(context: context.withObject(self))
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ extension HBMustacheTemplate {
|
|||||||
case expectedSectionEnd
|
case expectedSectionEnd
|
||||||
/// set delimiter tag badly formatted
|
/// set delimiter tag badly formatted
|
||||||
case invalidSetDelimiter
|
case invalidSetDelimiter
|
||||||
|
/// cannot apply transform to inherited section
|
||||||
|
case transformAppliedToInheritanceSection
|
||||||
|
/// illegal token inside inherit section of partial
|
||||||
|
case illegalTokenInsideInheritSection
|
||||||
|
/// text found inside inherit section of partial
|
||||||
|
case textInsideInheritSection
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ParserState {
|
struct ParserState {
|
||||||
@@ -121,6 +127,21 @@ extension HBMustacheTemplate {
|
|||||||
let sectionTokens = try parse(&parser, state: state.withSectionName(name, method: method))
|
let sectionTokens = try parse(&parser, state: state.withSectionName(name, method: method))
|
||||||
tokens.append(.invertedSection(name: name, method: method, template: HBMustacheTemplate(sectionTokens)))
|
tokens.append(.invertedSection(name: name, method: method, template: HBMustacheTemplate(sectionTokens)))
|
||||||
|
|
||||||
|
case "$":
|
||||||
|
// inherited section
|
||||||
|
parser.unsafeAdvance()
|
||||||
|
let (name, method) = try parseName(&parser, state: state)
|
||||||
|
// ERROR: can't have methods applied to inherited sections
|
||||||
|
guard method == nil else { throw Error.transformAppliedToInheritanceSection }
|
||||||
|
if self.isStandalone(&parser, state: state) {
|
||||||
|
setNewLine = true
|
||||||
|
} else if whiteSpaceBefore.count > 0 {
|
||||||
|
tokens.append(.text(String(whiteSpaceBefore)))
|
||||||
|
whiteSpaceBefore = ""
|
||||||
|
}
|
||||||
|
let sectionTokens = try parse(&parser, state: state.withSectionName(name, method: method))
|
||||||
|
tokens.append(.inheritedSection(name: name, template: HBMustacheTemplate(sectionTokens)))
|
||||||
|
|
||||||
case "/":
|
case "/":
|
||||||
// end of section
|
// end of section
|
||||||
parser.unsafeAdvance()
|
parser.unsafeAdvance()
|
||||||
@@ -174,12 +195,40 @@ extension HBMustacheTemplate {
|
|||||||
}
|
}
|
||||||
if self.isStandalone(&parser, state: state) {
|
if self.isStandalone(&parser, state: state) {
|
||||||
setNewLine = true
|
setNewLine = true
|
||||||
tokens.append(.partial(name, indentation: String(whiteSpaceBefore)))
|
tokens.append(.partial(name, indentation: String(whiteSpaceBefore), inherits: nil))
|
||||||
} else {
|
} else {
|
||||||
tokens.append(.partial(name, indentation: nil))
|
tokens.append(.partial(name, indentation: nil, inherits: nil))
|
||||||
}
|
}
|
||||||
whiteSpaceBefore = ""
|
whiteSpaceBefore = ""
|
||||||
|
|
||||||
|
case "<":
|
||||||
|
// partial with inheritance
|
||||||
|
parser.unsafeAdvance()
|
||||||
|
let (name, method) = try parseName(&parser, state: state)
|
||||||
|
// ERROR: can't have methods applied to inherited sections
|
||||||
|
guard method == nil else { throw Error.transformAppliedToInheritanceSection }
|
||||||
|
var indent: String?
|
||||||
|
if self.isStandalone(&parser, state: state) {
|
||||||
|
setNewLine = true
|
||||||
|
} else if whiteSpaceBefore.count > 0 {
|
||||||
|
indent = String(whiteSpaceBefore)
|
||||||
|
tokens.append(.text(indent!))
|
||||||
|
whiteSpaceBefore = ""
|
||||||
|
}
|
||||||
|
let sectionTokens = try parse(&parser, state: state.withSectionName(name, method: method))
|
||||||
|
var inherit: [String: HBMustacheTemplate] = [:]
|
||||||
|
for token in sectionTokens {
|
||||||
|
switch token {
|
||||||
|
case .inheritedSection(let name, let template):
|
||||||
|
inherit[name] = template
|
||||||
|
case .text:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw Error.illegalTokenInsideInheritSection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens.append(.partial(name, indentation: indent, inherits: inherit))
|
||||||
|
|
||||||
case "=":
|
case "=":
|
||||||
// set delimiter
|
// set delimiter
|
||||||
parser.unsafeAdvance()
|
parser.unsafeAdvance()
|
||||||
|
|||||||
@@ -6,50 +6,57 @@ extension HBMustacheTemplate {
|
|||||||
/// - context: Context that render is occurring in. Contains information about position in sequence
|
/// - context: Context that render is occurring in. Contains information about position in sequence
|
||||||
/// - indentation: indentation of partial
|
/// - indentation: indentation of partial
|
||||||
/// - Returns: Rendered text
|
/// - Returns: Rendered text
|
||||||
func render(_ stack: [Any], context: HBMustacheSequenceContext? = nil, indentation: String? = nil) -> String {
|
func render(context: HBMustacheContext) -> String {
|
||||||
var string = ""
|
var string = ""
|
||||||
if let indentation = indentation, indentation != "" {
|
if let indentation = context.indentation, indentation != "" {
|
||||||
for token in tokens {
|
for token in tokens {
|
||||||
if string.last == "\n" {
|
if string.last == "\n" {
|
||||||
string += indentation
|
string += indentation
|
||||||
}
|
}
|
||||||
string += self.renderToken(token, stack: stack, context: context)
|
string += self.renderToken(token, context: context)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for token in tokens {
|
for token in tokens {
|
||||||
string += self.renderToken(token, stack: stack, context: context)
|
string += self.renderToken(token, context: context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return string
|
return string
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderToken(_ token: Token, stack: [Any], context: HBMustacheSequenceContext? = nil) -> String {
|
func renderToken(_ token: Token, context: HBMustacheContext) -> String {
|
||||||
switch token {
|
switch token {
|
||||||
case .text(let text):
|
case .text(let text):
|
||||||
return text
|
return text
|
||||||
case .variable(let variable, let method):
|
case .variable(let variable, let method):
|
||||||
if let child = getChild(named: variable, from: stack, method: method, context: context) {
|
if let child = getChild(named: variable, method: method, context: context) {
|
||||||
if let template = child as? HBMustacheTemplate {
|
if let template = child as? HBMustacheTemplate {
|
||||||
return template.render(stack)
|
return template.render(context: context)
|
||||||
} else {
|
} else {
|
||||||
return String(describing: child).htmlEscape()
|
return String(describing: child).htmlEscape()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .unescapedVariable(let variable, let method):
|
case .unescapedVariable(let variable, let method):
|
||||||
if let child = getChild(named: variable, from: stack, method: method, context: context) {
|
if let child = getChild(named: variable, method: method, context: context) {
|
||||||
return String(describing: child)
|
return String(describing: child)
|
||||||
}
|
}
|
||||||
case .section(let variable, let method, let template):
|
case .section(let variable, let method, let template):
|
||||||
let child = self.getChild(named: variable, from: stack, method: method, context: context)
|
let child = self.getChild(named: variable, method: method, context: context)
|
||||||
return self.renderSection(child, stack: stack, with: template)
|
return self.renderSection(child, with: template, context: context)
|
||||||
|
|
||||||
case .invertedSection(let variable, let method, let template):
|
case .invertedSection(let variable, let method, let template):
|
||||||
let child = self.getChild(named: variable, from: stack, method: method, context: context)
|
let child = self.getChild(named: variable, method: method, context: context)
|
||||||
return self.renderInvertedSection(child, stack: stack, with: template)
|
return self.renderInvertedSection(child, with: template, context: context)
|
||||||
|
|
||||||
case .partial(let name, let indentation):
|
case .inheritedSection(let name, let template):
|
||||||
|
if let override = context.inherited?[name] {
|
||||||
|
return override.render(context: context)
|
||||||
|
} else {
|
||||||
|
return template.render(context: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
case .partial(let name, let indentation, let overrides):
|
||||||
if let template = library?.getTemplate(named: name) {
|
if let template = library?.getTemplate(named: name) {
|
||||||
return template.render(stack, indentation: indentation)
|
return template.render(context: context.withPartial(indented: indentation, inheriting: overrides))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
@@ -61,16 +68,16 @@ extension HBMustacheTemplate {
|
|||||||
/// - parent: Current object being rendered
|
/// - parent: Current object being rendered
|
||||||
/// - template: Template to render with
|
/// - template: Template to render with
|
||||||
/// - Returns: Rendered text
|
/// - Returns: Rendered text
|
||||||
func renderSection(_ child: Any?, stack: [Any], with template: HBMustacheTemplate) -> String {
|
func renderSection(_ child: Any?, with template: HBMustacheTemplate, context: HBMustacheContext) -> String {
|
||||||
switch child {
|
switch child {
|
||||||
case let array as HBMustacheSequence:
|
case let array as HBMustacheSequence:
|
||||||
return array.renderSection(with: template, stack: stack + [array])
|
return array.renderSection(with: template, context: context)
|
||||||
case let bool as Bool:
|
case let bool as Bool:
|
||||||
return bool ? template.render(stack) : ""
|
return bool ? template.render(context: context) : ""
|
||||||
case let lambda as HBMustacheLambda:
|
case let lambda as HBMustacheLambda:
|
||||||
return lambda.run(stack.last!, template)
|
return lambda.run(context.stack.last!, template)
|
||||||
case .some(let value):
|
case .some(let value):
|
||||||
return template.render(stack + [value])
|
return template.render(context: context.withObject(value))
|
||||||
case .none:
|
case .none:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -82,21 +89,21 @@ extension HBMustacheTemplate {
|
|||||||
/// - parent: Current object being rendered
|
/// - parent: Current object being rendered
|
||||||
/// - template: Template to render with
|
/// - template: Template to render with
|
||||||
/// - Returns: Rendered text
|
/// - Returns: Rendered text
|
||||||
func renderInvertedSection(_ child: Any?, stack: [Any], with template: HBMustacheTemplate) -> String {
|
func renderInvertedSection(_ child: Any?, with template: HBMustacheTemplate, context: HBMustacheContext) -> String {
|
||||||
switch child {
|
switch child {
|
||||||
case let array as HBMustacheSequence:
|
case let array as HBMustacheSequence:
|
||||||
return array.renderInvertedSection(with: template, stack: stack)
|
return array.renderInvertedSection(with: template, context: context)
|
||||||
case let bool as Bool:
|
case let bool as Bool:
|
||||||
return bool ? "" : template.render(stack)
|
return bool ? "" : template.render(context: context)
|
||||||
case .some:
|
case .some:
|
||||||
return ""
|
return ""
|
||||||
case .none:
|
case .none:
|
||||||
return template.render(stack)
|
return template.render(context: context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get child object from variable name
|
/// Get child object from variable name
|
||||||
func getChild(named name: String, from stack: [Any], method: String?, context: HBMustacheSequenceContext?) -> Any? {
|
func getChild(named name: String, method: String?, context: HBMustacheContext) -> Any? {
|
||||||
func _getImmediateChild(named name: String, from object: Any) -> Any? {
|
func _getImmediateChild(named name: String, from object: Any) -> Any? {
|
||||||
if let customBox = object as? HBMustacheParent {
|
if let customBox = object as? HBMustacheParent {
|
||||||
return customBox.child(named: name)
|
return customBox.child(named: name)
|
||||||
@@ -129,12 +136,12 @@ extension HBMustacheTemplate {
|
|||||||
// the name is split by "." and we use mirror to get the correct child object
|
// the name is split by "." and we use mirror to get the correct child object
|
||||||
let child: Any?
|
let child: Any?
|
||||||
if name == "." {
|
if name == "." {
|
||||||
child = stack.last!
|
child = context.stack.last!
|
||||||
} else if name == "", method != nil {
|
} else if name == "", method != nil {
|
||||||
child = context
|
child = context.sequenceContext
|
||||||
} else {
|
} else {
|
||||||
let nameSplit = name.split(separator: ".").map { String($0) }
|
let nameSplit = name.split(separator: ".").map { String($0) }
|
||||||
child = _getChildInStack(named: nameSplit[...], from: stack)
|
child = _getChildInStack(named: nameSplit[...], from: context.stack)
|
||||||
}
|
}
|
||||||
// if we want to run a method and the current child can have methods applied to it then
|
// if we want to run a method and the current child can have methods applied to it then
|
||||||
// run method on the current child
|
// run method on the current child
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public final class HBMustacheTemplate {
|
|||||||
/// - Parameter object: Object to render
|
/// - Parameter object: Object to render
|
||||||
/// - Returns: Rendered text
|
/// - Returns: Rendered text
|
||||||
public func render(_ object: Any) -> String {
|
public func render(_ object: Any) -> String {
|
||||||
self.render([object], context: nil)
|
self.render(context: .init(object))
|
||||||
}
|
}
|
||||||
|
|
||||||
internal init(_ tokens: [Token]) {
|
internal init(_ tokens: [Token]) {
|
||||||
@@ -22,8 +22,10 @@ public final class HBMustacheTemplate {
|
|||||||
self.library = library
|
self.library = library
|
||||||
for token in self.tokens {
|
for token in self.tokens {
|
||||||
switch token {
|
switch token {
|
||||||
case .section(_, _, let template), .invertedSection(_, _, let template):
|
case .section(_, _, let template), .invertedSection(_, _, let template), .inheritedSection(_, let template):
|
||||||
template.setLibrary(library)
|
template.setLibrary(library)
|
||||||
|
case .partial(_, _, let templates):
|
||||||
|
templates?.forEach { $1.setLibrary(library) }
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -36,7 +38,8 @@ public final class HBMustacheTemplate {
|
|||||||
case unescapedVariable(name: String, method: String? = nil)
|
case unescapedVariable(name: String, method: String? = nil)
|
||||||
case section(name: String, method: String? = nil, template: HBMustacheTemplate)
|
case section(name: String, method: String? = nil, template: HBMustacheTemplate)
|
||||||
case invertedSection(name: String, method: String? = nil, template: HBMustacheTemplate)
|
case invertedSection(name: String, method: String? = nil, template: HBMustacheTemplate)
|
||||||
case partial(String, indentation: String?)
|
case inheritedSection(name: String, template: HBMustacheTemplate)
|
||||||
|
case partial(String, indentation: String?, inherits: [String: HBMustacheTemplate]?)
|
||||||
}
|
}
|
||||||
|
|
||||||
let tokens: [Token]
|
let tokens: [Token]
|
||||||
|
|||||||
@@ -51,4 +51,51 @@ final class PartialTests: XCTestCase {
|
|||||||
|
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// test inheritance
|
||||||
|
func testInheritance() throws {
|
||||||
|
let library = HBMustacheLibrary()
|
||||||
|
try library.register(
|
||||||
|
"""
|
||||||
|
<head>
|
||||||
|
<title>{{$title}}Default title{{/title}}</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
""",
|
||||||
|
named: "header"
|
||||||
|
)
|
||||||
|
try library.register(
|
||||||
|
"""
|
||||||
|
<html>
|
||||||
|
{{$header}}{{/header}}
|
||||||
|
{{$content}}{{/content}}
|
||||||
|
</html>
|
||||||
|
|
||||||
|
""",
|
||||||
|
named: "base"
|
||||||
|
)
|
||||||
|
try library.register(
|
||||||
|
"""
|
||||||
|
{{<base}}
|
||||||
|
{{$header}}
|
||||||
|
{{<header}}
|
||||||
|
{{$title}}My page title{{/title}}
|
||||||
|
{{/header}}
|
||||||
|
{{/header}}
|
||||||
|
{{$content}}<h1>Hello world</h1>{{/content}}
|
||||||
|
{{/base}}
|
||||||
|
|
||||||
|
""",
|
||||||
|
named: "mypage"
|
||||||
|
)
|
||||||
|
XCTAssertEqual(library.render({}, withTemplate: "mypage")!, """
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>My page title</title>
|
||||||
|
</head>
|
||||||
|
<h1>Hello world</h1>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
""")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,11 +46,6 @@ public extension AnyDecodable {
|
|||||||
/// Verify implementation against formal standard for Mustache.
|
/// Verify implementation against formal standard for Mustache.
|
||||||
/// https://github.com/mustache/spec
|
/// https://github.com/mustache/spec
|
||||||
final class MustacheSpecTests: XCTestCase {
|
final class MustacheSpecTests: XCTestCase {
|
||||||
func loadSpec(name: String) throws -> Data {
|
|
||||||
let url = URL(string: "https://raw.githubusercontent.com/mustache/spec/master/specs/\(name).json")!
|
|
||||||
return try Data(contentsOf: url)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Spec: Decodable {
|
struct Spec: Decodable {
|
||||||
struct Test: Decodable {
|
struct Test: Decodable {
|
||||||
let name: String
|
let name: String
|
||||||
@@ -91,7 +86,12 @@ final class MustacheSpecTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testSpec(name: String, ignoring: [String] = []) throws {
|
func testSpec(name: String, ignoring: [String] = []) throws {
|
||||||
let data = try loadSpec(name: name)
|
let url = URL(string: "https://raw.githubusercontent.com/mustache/spec/master/specs/\(name).json")!
|
||||||
|
try testSpec(url: url, ignoring: ignoring)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSpec(url: URL, ignoring: [String] = []) throws {
|
||||||
|
let data = try Data(contentsOf: url)
|
||||||
let spec = try JSONDecoder().decode(Spec.self, from: data)
|
let spec = try JSONDecoder().decode(Spec.self, from: data)
|
||||||
|
|
||||||
print(spec.overview)
|
print(spec.overview)
|
||||||
@@ -124,4 +124,11 @@ final class MustacheSpecTests: XCTestCase {
|
|||||||
func testSectionsSpec() throws {
|
func testSectionsSpec() throws {
|
||||||
try self.testSpec(name: "sections", ignoring: ["Variable test"])
|
try self.testSpec(name: "sections", ignoring: ["Variable test"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testInheritanceSpec() throws {
|
||||||
|
let url = URL(
|
||||||
|
string: "https://raw.githubusercontent.com/mustache/spec/ab227509e64961943ca374c09c08b63f59da014a/specs/inheritance.json"
|
||||||
|
)!
|
||||||
|
try self.testSpec(url: url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ extension HBMustacheTemplate.Token: Equatable {
|
|||||||
return lhs1 == rhs1 && lhs2 == rhs2 && lhs3 == rhs3
|
return lhs1 == rhs1 && lhs2 == rhs2 && lhs3 == rhs3
|
||||||
case (.invertedSection(let lhs1, let lhs2, let lhs3), .invertedSection(let rhs1, let rhs2, let rhs3)):
|
case (.invertedSection(let lhs1, let lhs2, let lhs3), .invertedSection(let rhs1, let rhs2, let rhs3)):
|
||||||
return lhs1 == rhs1 && lhs2 == rhs2 && lhs3 == rhs3
|
return lhs1 == rhs1 && lhs2 == rhs2 && lhs3 == rhs3
|
||||||
case (.partial(let name1, let indent1), .partial(let name2, let indent2)):
|
case (.partial(let name1, let indent1, _), .partial(let name2, let indent2, _)):
|
||||||
return name1 == name2 && indent1 == indent2
|
return name1 == name2 && indent1 == indent2
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|||||||
Reference in New Issue
Block a user