Implement Recursive Transforms (#37)
* Implement Recursive Transforms * Correct test names * apply suggestions * format * add comments * move the parse function * refine `parseTransforms()` function * refinements * format * Swift Format again --------- Co-authored-by: Adam Fowler <adamfowler71@gmail.com>
This commit is contained in:
@@ -223,7 +223,7 @@ extension Parser {
|
|||||||
|
|
||||||
/// Read while character at current position is the one supplied
|
/// Read while character at current position is the one supplied
|
||||||
/// - Parameter while: Character to check against
|
/// - Parameter while: Character to check against
|
||||||
/// - Returns: String read from buffer
|
/// - Returns: The count of string read from buffer
|
||||||
@discardableResult mutating func read(while: Character) -> Int {
|
@discardableResult mutating func read(while: Character) -> Int {
|
||||||
var count = 0
|
var count = 0
|
||||||
while !self.reachedEnd(),
|
while !self.reachedEnd(),
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ extension MustacheTemplate {
|
|||||||
case sectionCloseNameIncorrect
|
case sectionCloseNameIncorrect
|
||||||
/// tag was badly formatted
|
/// tag was badly formatted
|
||||||
case unfinishedName
|
case unfinishedName
|
||||||
/// was not expecting a section end
|
/// was expecting a section end
|
||||||
case expectedSectionEnd
|
case expectedSectionEnd
|
||||||
/// set delimiter tag badly formatted
|
/// set delimiter tag badly formatted
|
||||||
case invalidSetDelimiter
|
case invalidSetDelimiter
|
||||||
@@ -43,7 +43,7 @@ extension MustacheTemplate {
|
|||||||
|
|
||||||
struct ParserState {
|
struct ParserState {
|
||||||
var sectionName: String?
|
var sectionName: String?
|
||||||
var sectionTransform: String?
|
var sectionTransforms: [String] = []
|
||||||
var newLine: Bool
|
var newLine: Bool
|
||||||
var startDelimiter: String
|
var startDelimiter: String
|
||||||
var endDelimiter: String
|
var endDelimiter: String
|
||||||
@@ -55,10 +55,10 @@ extension MustacheTemplate {
|
|||||||
self.endDelimiter = "}}"
|
self.endDelimiter = "}}"
|
||||||
}
|
}
|
||||||
|
|
||||||
func withSectionName(_ name: String, transform: String? = nil) -> ParserState {
|
func withSectionName(_ name: String, transforms: [String] = []) -> ParserState {
|
||||||
var newValue = self
|
var newValue = self
|
||||||
newValue.sectionName = name
|
newValue.sectionName = name
|
||||||
newValue.sectionTransform = transform
|
newValue.sectionTransforms = transforms
|
||||||
return newValue
|
return newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,50 +114,50 @@ extension MustacheTemplate {
|
|||||||
case "#":
|
case "#":
|
||||||
// section
|
// section
|
||||||
parser.unsafeAdvance()
|
parser.unsafeAdvance()
|
||||||
let (name, transform) = try parseName(&parser, state: state)
|
let (name, transforms) = try parseName(&parser, state: state)
|
||||||
if self.isStandalone(&parser, state: state) {
|
if self.isStandalone(&parser, state: state) {
|
||||||
setNewLine = true
|
setNewLine = true
|
||||||
} else if whiteSpaceBefore.count > 0 {
|
} else if whiteSpaceBefore.count > 0 {
|
||||||
tokens.append(.text(String(whiteSpaceBefore)))
|
tokens.append(.text(String(whiteSpaceBefore)))
|
||||||
whiteSpaceBefore = ""
|
whiteSpaceBefore = ""
|
||||||
}
|
}
|
||||||
let sectionTokens = try parse(&parser, state: state.withSectionName(name, transform: transform))
|
let sectionTokens = try parse(&parser, state: state.withSectionName(name, transforms: transforms))
|
||||||
tokens.append(.section(name: name, transform: transform, template: MustacheTemplate(sectionTokens)))
|
tokens.append(.section(name: name, transforms: transforms, template: MustacheTemplate(sectionTokens)))
|
||||||
|
|
||||||
case "^":
|
case "^":
|
||||||
// inverted section
|
// inverted section
|
||||||
parser.unsafeAdvance()
|
parser.unsafeAdvance()
|
||||||
let (name, transform) = try parseName(&parser, state: state)
|
let (name, transforms) = try parseName(&parser, state: state)
|
||||||
if self.isStandalone(&parser, state: state) {
|
if self.isStandalone(&parser, state: state) {
|
||||||
setNewLine = true
|
setNewLine = true
|
||||||
} else if whiteSpaceBefore.count > 0 {
|
} else if whiteSpaceBefore.count > 0 {
|
||||||
tokens.append(.text(String(whiteSpaceBefore)))
|
tokens.append(.text(String(whiteSpaceBefore)))
|
||||||
whiteSpaceBefore = ""
|
whiteSpaceBefore = ""
|
||||||
}
|
}
|
||||||
let sectionTokens = try parse(&parser, state: state.withSectionName(name, transform: transform))
|
let sectionTokens = try parse(&parser, state: state.withSectionName(name, transforms: transforms))
|
||||||
tokens.append(.invertedSection(name: name, transform: transform, template: MustacheTemplate(sectionTokens)))
|
tokens.append(.invertedSection(name: name, transforms: transforms, template: MustacheTemplate(sectionTokens)))
|
||||||
|
|
||||||
case "$":
|
case "$":
|
||||||
// inherited section
|
// inherited section
|
||||||
parser.unsafeAdvance()
|
parser.unsafeAdvance()
|
||||||
let (name, transform) = try parseName(&parser, state: state)
|
let (name, transforms) = try parseName(&parser, state: state)
|
||||||
// ERROR: can't have transform applied to inherited sections
|
// ERROR: can't have transform applied to inherited sections
|
||||||
guard transform == nil else { throw Error.transformAppliedToInheritanceSection }
|
guard transforms.isEmpty else { throw Error.transformAppliedToInheritanceSection }
|
||||||
if self.isStandalone(&parser, state: state) {
|
if self.isStandalone(&parser, state: state) {
|
||||||
setNewLine = true
|
setNewLine = true
|
||||||
} else if whiteSpaceBefore.count > 0 {
|
} else if whiteSpaceBefore.count > 0 {
|
||||||
tokens.append(.text(String(whiteSpaceBefore)))
|
tokens.append(.text(String(whiteSpaceBefore)))
|
||||||
whiteSpaceBefore = ""
|
whiteSpaceBefore = ""
|
||||||
}
|
}
|
||||||
let sectionTokens = try parse(&parser, state: state.withSectionName(name, transform: transform))
|
let sectionTokens = try parse(&parser, state: state.withSectionName(name, transforms: transforms))
|
||||||
tokens.append(.inheritedSection(name: name, template: MustacheTemplate(sectionTokens)))
|
tokens.append(.inheritedSection(name: name, template: MustacheTemplate(sectionTokens)))
|
||||||
|
|
||||||
case "/":
|
case "/":
|
||||||
// end of section
|
// end of section
|
||||||
parser.unsafeAdvance()
|
parser.unsafeAdvance()
|
||||||
let position = parser.position
|
let position = parser.position
|
||||||
let (name, transform) = try parseName(&parser, state: state)
|
let (name, transforms) = try parseName(&parser, state: state)
|
||||||
guard name == state.sectionName, transform == state.sectionTransform else {
|
guard name == state.sectionName, transforms == state.sectionTransforms else {
|
||||||
parser.unsafeSetPosition(position)
|
parser.unsafeSetPosition(position)
|
||||||
throw Error.sectionCloseNameIncorrect
|
throw Error.sectionCloseNameIncorrect
|
||||||
}
|
}
|
||||||
@@ -182,9 +182,9 @@ extension MustacheTemplate {
|
|||||||
whiteSpaceBefore = ""
|
whiteSpaceBefore = ""
|
||||||
}
|
}
|
||||||
parser.unsafeAdvance()
|
parser.unsafeAdvance()
|
||||||
let (name, transform) = try parseName(&parser, state: state)
|
let (name, transforms) = try parseName(&parser, state: state)
|
||||||
guard try parser.read("}") else { throw Error.unfinishedName }
|
guard try parser.read("}") else { throw Error.unfinishedName }
|
||||||
tokens.append(.unescapedVariable(name: name, transform: transform))
|
tokens.append(.unescapedVariable(name: name, transforms: transforms))
|
||||||
|
|
||||||
case "&":
|
case "&":
|
||||||
// unescaped variable
|
// unescaped variable
|
||||||
@@ -193,8 +193,8 @@ extension MustacheTemplate {
|
|||||||
whiteSpaceBefore = ""
|
whiteSpaceBefore = ""
|
||||||
}
|
}
|
||||||
parser.unsafeAdvance()
|
parser.unsafeAdvance()
|
||||||
let (name, transform) = try parseName(&parser, state: state)
|
let (name, transforms) = try parseName(&parser, state: state)
|
||||||
tokens.append(.unescapedVariable(name: name, transform: transform))
|
tokens.append(.unescapedVariable(name: name, transforms: transforms))
|
||||||
|
|
||||||
case ">":
|
case ">":
|
||||||
// partial
|
// partial
|
||||||
@@ -258,8 +258,8 @@ extension MustacheTemplate {
|
|||||||
tokens.append(.text(String(whiteSpaceBefore)))
|
tokens.append(.text(String(whiteSpaceBefore)))
|
||||||
whiteSpaceBefore = ""
|
whiteSpaceBefore = ""
|
||||||
}
|
}
|
||||||
let (name, transform) = try parseName(&parser, state: state)
|
let (name, transforms) = try parseName(&parser, state: state)
|
||||||
tokens.append(.variable(name: name, transform: transform))
|
tokens.append(.variable(name: name, transforms: transforms))
|
||||||
}
|
}
|
||||||
state.newLine = setNewLine
|
state.newLine = setNewLine
|
||||||
}
|
}
|
||||||
@@ -296,7 +296,7 @@ extension MustacheTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// parse variable name
|
/// parse variable name
|
||||||
static func parseName(_ parser: inout Parser, state: ParserState) throws -> (String, String?) {
|
static func parseName(_ parser: inout Parser, state: ParserState) throws -> (String, [String]) {
|
||||||
parser.read(while: \.isWhitespace)
|
parser.read(while: \.isWhitespace)
|
||||||
let text = String(parser.read(while: self.sectionNameChars))
|
let text = String(parser.read(while: self.sectionNameChars))
|
||||||
parser.read(while: \.isWhitespace)
|
parser.read(while: \.isWhitespace)
|
||||||
@@ -306,16 +306,39 @@ extension MustacheTemplate {
|
|||||||
var nameParser = Parser(String(text))
|
var nameParser = Parser(String(text))
|
||||||
let string = nameParser.read(while: self.sectionNameCharsWithoutBrackets)
|
let string = nameParser.read(while: self.sectionNameCharsWithoutBrackets)
|
||||||
if nameParser.reachedEnd() {
|
if nameParser.reachedEnd() {
|
||||||
return (text, nil)
|
return (text, [])
|
||||||
} else {
|
} else {
|
||||||
// parse function parameter, as we have just parsed a function name
|
// parse function parameter, as we have just parsed a function name
|
||||||
guard nameParser.current() == "(" else { throw Error.unfinishedName }
|
guard nameParser.current() == "(" else { throw Error.unfinishedName }
|
||||||
nameParser.unsafeAdvance()
|
nameParser.unsafeAdvance()
|
||||||
let string2 = nameParser.read(while: self.sectionNameCharsWithoutBrackets)
|
|
||||||
guard nameParser.current() == ")" else { throw Error.unfinishedName }
|
func parseTransforms(existing: [Substring]) throws -> (Substring, [Substring]) {
|
||||||
nameParser.unsafeAdvance()
|
let name = nameParser.read(while: self.sectionNameCharsWithoutBrackets)
|
||||||
guard nameParser.reachedEnd() else { throw Error.unfinishedName }
|
switch nameParser.current() {
|
||||||
return (String(string2), String(string))
|
case ")":
|
||||||
|
// Transforms are ending
|
||||||
|
nameParser.unsafeAdvance()
|
||||||
|
// We need to have a `)` for each transform that we've parsed
|
||||||
|
guard nameParser.read(while: ")") + 1 == existing.count,
|
||||||
|
nameParser.reachedEnd()
|
||||||
|
else {
|
||||||
|
throw Error.unfinishedName
|
||||||
|
}
|
||||||
|
return (name, existing)
|
||||||
|
case "(":
|
||||||
|
// Parse the next transform
|
||||||
|
nameParser.unsafeAdvance()
|
||||||
|
|
||||||
|
var transforms = existing
|
||||||
|
transforms.append(name)
|
||||||
|
return try parseTransforms(existing: transforms)
|
||||||
|
default:
|
||||||
|
throw Error.unfinishedName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let (parameterName, transforms) = try parseTransforms(existing: [string])
|
||||||
|
|
||||||
|
return (String(parameterName), transforms.map(String.init))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,8 +46,9 @@ extension MustacheTemplate {
|
|||||||
switch token {
|
switch token {
|
||||||
case .text(let text):
|
case .text(let text):
|
||||||
return text
|
return text
|
||||||
case .variable(let variable, let transform):
|
|
||||||
if let child = getChild(named: variable, transform: transform, context: context) {
|
case .variable(let variable, let transforms):
|
||||||
|
if let child = getChild(named: variable, transforms: transforms, context: context) {
|
||||||
if let template = child as? MustacheTemplate {
|
if let template = child as? MustacheTemplate {
|
||||||
return template.render(context: context)
|
return template.render(context: context)
|
||||||
} else if let renderable = child as? MustacheCustomRenderable {
|
} else if let renderable = child as? MustacheCustomRenderable {
|
||||||
@@ -56,20 +57,22 @@ extension MustacheTemplate {
|
|||||||
return context.contentType.escapeText(String(describing: child))
|
return context.contentType.escapeText(String(describing: child))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .unescapedVariable(let variable, let transform):
|
|
||||||
if let child = getChild(named: variable, transform: transform, context: context) {
|
case .unescapedVariable(let variable, let transforms):
|
||||||
|
if let child = getChild(named: variable, transforms: transforms, context: context) {
|
||||||
if let renderable = child as? MustacheCustomRenderable {
|
if let renderable = child as? MustacheCustomRenderable {
|
||||||
return renderable.renderText
|
return renderable.renderText
|
||||||
} else {
|
} else {
|
||||||
return String(describing: child)
|
return String(describing: child)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .section(let variable, let transform, let template):
|
|
||||||
let child = self.getChild(named: variable, transform: transform, context: context)
|
case .section(let variable, let transforms, let template):
|
||||||
|
let child = self.getChild(named: variable, transforms: transforms, context: context)
|
||||||
return self.renderSection(child, with: template, context: context)
|
return self.renderSection(child, with: template, context: context)
|
||||||
|
|
||||||
case .invertedSection(let variable, let transform, let template):
|
case .invertedSection(let variable, let transforms, let template):
|
||||||
let child = self.getChild(named: variable, transform: transform, context: context)
|
let child = self.getChild(named: variable, transforms: transforms, context: context)
|
||||||
return self.renderInvertedSection(child, with: template, context: context)
|
return self.renderInvertedSection(child, with: template, context: context)
|
||||||
|
|
||||||
case .inheritedSection(let name, let template):
|
case .inheritedSection(let name, let template):
|
||||||
@@ -135,7 +138,7 @@ extension MustacheTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get child object from variable name
|
/// Get child object from variable name
|
||||||
func getChild(named name: String, transform: String?, context: MustacheContext) -> Any? {
|
func getChild(named name: String, transforms: [String], context: MustacheContext) -> Any? {
|
||||||
func _getImmediateChild(named name: String, from object: Any) -> Any? {
|
func _getImmediateChild(named name: String, from object: Any) -> Any? {
|
||||||
if let customBox = object as? MustacheParent {
|
if let customBox = object as? MustacheParent {
|
||||||
return customBox.child(named: name)
|
return customBox.child(named: name)
|
||||||
@@ -171,7 +174,7 @@ extension MustacheTemplate {
|
|||||||
let child: Any?
|
let child: Any?
|
||||||
if name == "." {
|
if name == "." {
|
||||||
child = context.stack.last!
|
child = context.stack.last!
|
||||||
} else if name == "", transform != nil {
|
} else if name == "", !transforms.isEmpty {
|
||||||
child = context.sequenceContext
|
child = context.sequenceContext
|
||||||
} else if name.first == "." {
|
} else if name.first == "." {
|
||||||
let nameSplit = name.split(separator: ".").map { String($0) }
|
let nameSplit = name.split(separator: ".").map { String($0) }
|
||||||
@@ -180,14 +183,26 @@ extension MustacheTemplate {
|
|||||||
let nameSplit = name.split(separator: ".").map { String($0) }
|
let nameSplit = name.split(separator: ".").map { String($0) }
|
||||||
child = _getChildInStack(named: nameSplit[...], from: context.stack)
|
child = _getChildInStack(named: nameSplit[...], from: context.stack)
|
||||||
}
|
}
|
||||||
// if we want to run a transform and the current child can have transforms applied to it then
|
|
||||||
// run transform on the current child
|
// skip transforms if child is already nil
|
||||||
if let transform {
|
guard var child else {
|
||||||
if let runnable = child as? MustacheTransformable {
|
|
||||||
return runnable.transform(transform)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if we want to run a transform and the current child can have transforms applied to it then
|
||||||
|
// run transform on the current child
|
||||||
|
for transform in transforms.reversed() {
|
||||||
|
if let runnable = child as? MustacheTransformable,
|
||||||
|
let transformed = runnable.transform(transform)
|
||||||
|
{
|
||||||
|
child = transformed
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// return nil if transform is unsuccessful or has returned nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return child
|
return child
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,10 +34,10 @@ public struct MustacheTemplate: Sendable {
|
|||||||
|
|
||||||
enum Token: Sendable {
|
enum Token: Sendable {
|
||||||
case text(String)
|
case text(String)
|
||||||
case variable(name: String, transform: String? = nil)
|
case variable(name: String, transforms: [String] = [])
|
||||||
case unescapedVariable(name: String, transform: String? = nil)
|
case unescapedVariable(name: String, transforms: [String] = [])
|
||||||
case section(name: String, transform: String? = nil, template: MustacheTemplate)
|
case section(name: String, transforms: [String] = [], template: MustacheTemplate)
|
||||||
case invertedSection(name: String, transform: String? = nil, template: MustacheTemplate)
|
case invertedSection(name: String, transforms: [String] = [], template: MustacheTemplate)
|
||||||
case inheritedSection(name: String, template: MustacheTemplate)
|
case inheritedSection(name: String, template: MustacheTemplate)
|
||||||
case partial(String, indentation: String?, inherits: [String: MustacheTemplate]?)
|
case partial(String, indentation: String?, inherits: [String: MustacheTemplate]?)
|
||||||
case contentType(MustacheContentType)
|
case contentType(MustacheContentType)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public extension StringProtocol {
|
|||||||
case "uppercased":
|
case "uppercased":
|
||||||
return uppercased()
|
return uppercased()
|
||||||
case "reversed":
|
case "reversed":
|
||||||
return reversed()
|
return Substring(self.reversed())
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ private protocol ComparableSequence {
|
|||||||
extension Array: MustacheTransformable {
|
extension Array: MustacheTransformable {
|
||||||
/// Transform Array.
|
/// Transform Array.
|
||||||
///
|
///
|
||||||
/// Transforms available are `first`, `last`, `reversed`, `count` and for arrays
|
/// Transforms available are `first`, `last`, `reversed`, `count`, `empty` and for arrays
|
||||||
/// with comparable elements `sorted`.
|
/// with comparable elements `sorted`.
|
||||||
/// - Parameter name: transform name
|
/// - Parameter name: transform name
|
||||||
/// - Returns: Result
|
/// - Returns: Result
|
||||||
@@ -102,6 +102,78 @@ extension Array: ComparableSequence where Element: Comparable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Set: MustacheTransformable {
|
||||||
|
/// Transform Set.
|
||||||
|
///
|
||||||
|
/// Transforms available are `count`, `empty` and for sets
|
||||||
|
/// with comparable elements `sorted`.
|
||||||
|
/// - Parameter name: transform name
|
||||||
|
/// - Returns: Result
|
||||||
|
public func transform(_ name: String) -> Any? {
|
||||||
|
switch name {
|
||||||
|
case "count":
|
||||||
|
return count
|
||||||
|
case "empty":
|
||||||
|
return isEmpty
|
||||||
|
default:
|
||||||
|
if let comparableSeq = self as? ComparableSequence {
|
||||||
|
return comparableSeq.comparableTransform(name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Set: ComparableSequence where Element: Comparable {
|
||||||
|
func comparableTransform(_ name: String) -> Any? {
|
||||||
|
switch name {
|
||||||
|
case "sorted":
|
||||||
|
return sorted()
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ReversedCollection: MustacheTransformable {
|
||||||
|
/// Transform ReversedCollection.
|
||||||
|
///
|
||||||
|
/// Transforms available are `first`, `last`, `reversed`, `count`, `empty` and for collections
|
||||||
|
/// with comparable elements `sorted`.
|
||||||
|
/// - Parameter name: transform name
|
||||||
|
/// - Returns: Result
|
||||||
|
public func transform(_ name: String) -> Any? {
|
||||||
|
switch name {
|
||||||
|
case "first":
|
||||||
|
return first
|
||||||
|
case "last":
|
||||||
|
return last
|
||||||
|
case "reversed":
|
||||||
|
return reversed()
|
||||||
|
case "count":
|
||||||
|
return count
|
||||||
|
case "empty":
|
||||||
|
return isEmpty
|
||||||
|
default:
|
||||||
|
if let comparableSeq = self as? ComparableSequence {
|
||||||
|
return comparableSeq.comparableTransform(name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ReversedCollection: ComparableSequence where Element: Comparable {
|
||||||
|
func comparableTransform(_ name: String) -> Any? {
|
||||||
|
switch name {
|
||||||
|
case "sorted":
|
||||||
|
return sorted()
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension Dictionary: MustacheTransformable {
|
extension Dictionary: MustacheTransformable {
|
||||||
/// Transform Dictionary
|
/// Transform Dictionary
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -80,6 +80,48 @@ final class TransformTests: XCTestCase {
|
|||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testDoubleSequenceTransformWorks() throws {
|
||||||
|
let template = try MustacheTemplate(string: """
|
||||||
|
{{#repo}}
|
||||||
|
{{count(reversed(numbers))}}
|
||||||
|
{{/repo}}
|
||||||
|
|
||||||
|
""")
|
||||||
|
let object: [String: Any] = ["repo": ["numbers": [1, 2, 3]]]
|
||||||
|
XCTAssertEqual(template.render(object), """
|
||||||
|
3
|
||||||
|
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMultipleTransformWorks() throws {
|
||||||
|
let template = try MustacheTemplate(string: """
|
||||||
|
{{#repo}}
|
||||||
|
{{minusone(plusone(last(reversed(numbers))))}}
|
||||||
|
{{/repo}}
|
||||||
|
|
||||||
|
""")
|
||||||
|
let object: [String: Any] = ["repo": ["numbers": [5, 4, 3]]]
|
||||||
|
XCTAssertEqual(template.render(object), """
|
||||||
|
5
|
||||||
|
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNestedTransformWorks() throws {
|
||||||
|
let template = try MustacheTemplate(string: """
|
||||||
|
{{#repo}}
|
||||||
|
{{#uppercased(string)}}{{reversed(.)}}{{/uppercased(string)}}
|
||||||
|
{{/repo}}
|
||||||
|
|
||||||
|
""")
|
||||||
|
let object: [String: Any] = ["repo": ["string": "a123a"]]
|
||||||
|
XCTAssertEqual(template.render(object), """
|
||||||
|
A321A
|
||||||
|
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
func testEvenOdd() throws {
|
func testEvenOdd() throws {
|
||||||
let template = try MustacheTemplate(string: """
|
let template = try MustacheTemplate(string: """
|
||||||
{{#repo}}
|
{{#repo}}
|
||||||
|
|||||||
Reference in New Issue
Block a user