Merge pull request #239 from stencilproject/feature/swift4.2

Use Swift 4 features
This commit is contained in:
David Jennes
2018-09-21 00:00:13 +02:00
committed by GitHub
16 changed files with 301 additions and 168 deletions

View File

@@ -1,10 +1,15 @@
matrix: matrix:
include: include:
- os: osx - os: osx
osx_image: xcode9.3 osx_image: xcode9.4
env: SWIFT_VERSION=4.1 env: SWIFT_VERSION=4.1
- os: osx
osx_image: xcode10
env: SWIFT_VERSION=4.2
- os: linux - os: linux
env: SWIFT_VERSION=4.1 env: SWIFT_VERSION=4.1
- os: linux
env: SWIFT_VERSION=4.2
language: generic language: generic
sudo: required sudo: required
dist: trusty dist: trusty

View File

@@ -16,11 +16,13 @@
### New Features ### New Features
_None_ _None_
### Internal Changes ### Internal Changes
_None_ - Updated the codebase to use Swift 4 features.
[David Jennes](https://github.com/djbe)
[#239](https://github.com/stencilproject/Stencil/pull/239)
## 0.12.1 ## 0.12.1

View File

@@ -37,11 +37,6 @@ public struct TemplateSyntaxError : Error, Equatable, CustomStringConvertible {
public init(_ description: String) { public init(_ description: String) {
self.init(reason: description) self.init(reason: description)
} }
public static func ==(lhs:TemplateSyntaxError, rhs:TemplateSyntaxError) -> Bool {
return lhs.description == rhs.description && lhs.token == rhs.token && lhs.stackTrace == rhs.stackTrace
}
} }
extension Error { extension Error {
@@ -67,11 +62,16 @@ open class SimpleErrorReporter: ErrorReporter {
func describe(token: Token) -> String { func describe(token: Token) -> String {
let templateName = token.sourceMap.filename ?? "" let templateName = token.sourceMap.filename ?? ""
let location = token.sourceMap.location let location = token.sourceMap.location
let highlight = "\(String(Array(repeating: " ", count: location.lineOffset)))^\(String(Array(repeating: "~", count: max(token.contents.count - 1, 0))))" let highlight = """
\(String(Array(repeating: " ", count: location.lineOffset)))\
^\(String(Array(repeating: "~", count: max(token.contents.count - 1, 0))))
"""
return "\(templateName)\(location.lineNumber):\(location.lineOffset): error: \(templateError.reason)\n" return """
+ "\(location.content)\n" \(templateName)\(location.lineNumber):\(location.lineOffset): error: \(templateError.reason)
+ "\(highlight)\n" \(location.content)
\(highlight)
"""
} }
var descriptions = templateError.stackTrace.reduce([]) { $0 + [describe(token: $1)] } var descriptions = templateError.stackTrace.reduce([]) { $0 + [describe(token: $1)] }

View File

@@ -74,7 +74,9 @@ func indentFilter(value: Any?, arguments: [Any?]) throws -> Any? {
var indentWidth = 4 var indentWidth = 4
if arguments.count > 0 { if arguments.count > 0 {
guard let value = arguments[0] as? Int else { guard let value = arguments[0] as? Int else {
throw TemplateSyntaxError("'indent' filter width argument must be an Integer (\(String(describing: arguments[0])))") throw TemplateSyntaxError("""
'indent' filter width argument must be an Integer (\(String(describing: arguments[0])))
""")
} }
indentWidth = value indentWidth = value
} }
@@ -82,7 +84,9 @@ func indentFilter(value: Any?, arguments: [Any?]) throws -> Any? {
var indentationChar = " " var indentationChar = " "
if arguments.count > 1 { if arguments.count > 1 {
guard let value = arguments[1] as? String else { guard let value = arguments[1] as? String else {
throw TemplateSyntaxError("'indent' filter indentation argument must be a String (\(String(describing: arguments[1]))") throw TemplateSyntaxError("""
'indent' filter indentation argument must be a String (\(String(describing: arguments[1]))
""")
} }
indentationChar = value indentationChar = value
} }

View File

@@ -10,7 +10,11 @@ class IncludeNode : NodeType {
let bits = token.components() let bits = token.components()
guard bits.count == 2 || bits.count == 3 else { guard bits.count == 2 || bits.count == 3 else {
throw TemplateSyntaxError("'include' tag requires one argument, the template file to be included. A second optional argument can be used to specify the context that will be passed to the included file") throw TemplateSyntaxError("""
'include' tag requires one argument, the template file to be included. \
A second optional argument can be used to specify the context that will \
be passed to the included file
""")
} }
return IncludeNode(templateName: Variable(bits[1]), includeContext: bits.count == 3 ? bits[2] : nil, token: token) return IncludeNode(templateName: Variable(bits[1]), includeContext: bits.count == 3 ? bits[2] : nil, token: token)

View File

@@ -98,7 +98,10 @@ public class TokenParser {
if suggestedFilters.isEmpty { if suggestedFilters.isEmpty {
throw TemplateSyntaxError("Unknown filter '\(name)'.") throw TemplateSyntaxError("Unknown filter '\(name)'.")
} else { } else {
throw TemplateSyntaxError("Unknown filter '\(name)'. Found similar filters: \(suggestedFilters.map({ "'\($0)'" }).joined(separator: ", ")).") throw TemplateSyntaxError("""
Unknown filter '\(name)'. \
Found similar filters: \(suggestedFilters.map({ "'\($0)'" }).joined(separator: ", ")).
""")
} }
} }

View File

@@ -114,21 +114,4 @@ public enum Token : Equatable {
return sourceMap return sourceMap
} }
} }
}
public func == (lhs: Token, rhs: Token) -> Bool {
switch (lhs, rhs) {
case let (.text(lhsValue, lhsAt), .text(rhsValue, rhsAt)):
return lhsValue == rhsValue && lhsAt == rhsAt
case let (.variable(lhsValue, lhsAt), .variable(rhsValue, rhsAt)):
return lhsValue == rhsValue && lhsAt == rhsAt
case let (.block(lhsValue, lhsAt), .block(rhsValue, rhsAt)):
return lhsValue == rhsValue && lhsAt == rhsAt
case let (.comment(lhsValue, lhsAt), .comment(rhsValue, rhsAt)):
return lhsValue == rhsValue && lhsAt == rhsAt
default:
return false
}
} }

View File

@@ -128,10 +128,6 @@ public struct Variable : Equatable, Resolvable {
} }
} }
public func ==(lhs: Variable, rhs: Variable) -> Bool {
return lhs.variable == rhs.variable
}
/// A structure used to represet range of two integer values expressed as `from...to`. /// A structure used to represet range of two integer values expressed as `from...to`.
/// Values should be numbers (they will be converted to integers). /// Values should be numbers (they will be converted to integers).
/// Rendering this variable produces array from range `from...to`. /// Rendering this variable produces array from range `from...to`.

View File

@@ -7,7 +7,7 @@ func testEnvironment() {
describe("Environment") { describe("Environment") {
var environment: Environment! var environment: Environment!
var template: Template! var template: Template!
$0.before { $0.before {
environment = Environment(loader: ExampleLoader()) environment = Environment(loader: ExampleLoader())
template = nil template = nil
@@ -54,7 +54,7 @@ func testEnvironment() {
func expectError(reason: String, token: String, func expectError(reason: String, token: String,
file: String = #file, line: Int = #line, function: String = #function) throws { file: String = #file, line: Int = #line, function: String = #function) throws {
let expectedError = expectedSyntaxError(token: token, template: template, description: reason) let expectedError = expectedSyntaxError(token: token, template: template, description: reason)
let error = try expect(environment.render(template: template, context: ["names": ["Bob", "Alice"], "name": "Bob"]), let error = try expect(environment.render(template: template, context: ["names": ["Bob", "Alice"], "name": "Bob"]),
file: file, line: line, function: function).toThrow() as TemplateSyntaxError file: file, line: line, function: function).toThrow() as TemplateSyntaxError
let reporter = SimpleErrorReporter() let reporter = SimpleErrorReporter()
@@ -67,58 +67,58 @@ func testEnvironment() {
template = "Hello {% for name in %}{{ name }}, {% endfor %}!" template = "Hello {% for name in %}{{ name }}, {% endfor %}!"
try expectError(reason: "'for' statements should use the syntax: `for <x> in <y> [where <condition>]`.", token: "for name in") try expectError(reason: "'for' statements should use the syntax: `for <x> in <y> [where <condition>]`.", token: "for name in")
} }
$0.it("reports syntax error on missing endfor") { $0.it("reports syntax error on missing endfor") {
template = "{% for name in names %}{{ name }}" template = "{% for name in names %}{{ name }}"
try expectError(reason: "`endfor` was not found.", token: "for name in names") try expectError(reason: "`endfor` was not found.", token: "for name in names")
} }
$0.it("reports syntax error on unknown tag") { $0.it("reports syntax error on unknown tag") {
template = "{% for name in names %}{{ name }}{% end %}" template = "{% for name in names %}{{ name }}{% end %}"
try expectError(reason: "Unknown template tag 'end'", token: "end") try expectError(reason: "Unknown template tag 'end'", token: "end")
} }
} }
$0.context("given unknown filter") { $0.context("given unknown filter") {
$0.it("reports syntax error in for tag") { $0.it("reports syntax error in for tag") {
template = "{% for name in names|unknown %}{{ name }}{% endfor %}" template = "{% for name in names|unknown %}{{ name }}{% endfor %}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "names|unknown") try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "names|unknown")
} }
$0.it("reports syntax error in for-where tag") { $0.it("reports syntax error in for-where tag") {
template = "{% for name in names where name|unknown %}{{ name }}{% endfor %}" template = "{% for name in names where name|unknown %}{{ name }}{% endfor %}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown") try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown")
} }
$0.it("reports syntax error in if tag") { $0.it("reports syntax error in if tag") {
template = "{% if name|unknown %}{{ name }}{% endif %}" template = "{% if name|unknown %}{{ name }}{% endif %}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown") try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown")
} }
$0.it("reports syntax error in elif tag") { $0.it("reports syntax error in elif tag") {
template = "{% if name %}{{ name }}{% elif name|unknown %}{% endif %}" template = "{% if name %}{{ name }}{% elif name|unknown %}{% endif %}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown") try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown")
} }
$0.it("reports syntax error in ifnot tag") { $0.it("reports syntax error in ifnot tag") {
template = "{% ifnot name|unknown %}{{ name }}{% endif %}" template = "{% ifnot name|unknown %}{{ name }}{% endif %}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown") try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown")
} }
$0.it("reports syntax error in filter tag") { $0.it("reports syntax error in filter tag") {
template = "{% filter unknown %}Text{% endfilter %}" template = "{% filter unknown %}Text{% endfilter %}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "filter unknown") try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "filter unknown")
} }
$0.it("reports syntax error in variable tag") { $0.it("reports syntax error in variable tag") {
template = "{{ name|unknown }}" template = "{{ name|unknown }}"
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown") try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", token: "name|unknown")
} }
} }
$0.context("given rendering error") { $0.context("given rendering error") {
$0.it("reports rendering error in variable filter") { $0.it("reports rendering error in variable filter") {
@@ -131,7 +131,7 @@ func testEnvironment() {
template = Template(templateString: "{{ name|throw }}", environment: environment) template = Template(templateString: "{{ name|throw }}", environment: environment)
try expectError(reason: "filter error", token: "name|throw") try expectError(reason: "filter error", token: "name|throw")
} }
$0.it("reports rendering error in filter tag") { $0.it("reports rendering error in filter tag") {
let filterExtension = Extension() let filterExtension = Extension()
filterExtension.registerFilter("throw") { (value: Any?) in filterExtension.registerFilter("throw") { (value: Any?) in
@@ -142,7 +142,7 @@ func testEnvironment() {
template = Template(templateString: "{% filter throw %}Test{% endfilter %}", environment: environment) template = Template(templateString: "{% filter throw %}Test{% endfilter %}", environment: environment)
try expectError(reason: "filter error", token: "filter throw") try expectError(reason: "filter error", token: "filter throw")
} }
$0.it("reports rendering error in simple tag") { $0.it("reports rendering error in simple tag") {
let tagExtension = Extension() let tagExtension = Extension()
tagExtension.registerSimpleTag("simpletag") { context in tagExtension.registerSimpleTag("simpletag") { context in
@@ -153,23 +153,23 @@ func testEnvironment() {
template = Template(templateString: "{% simpletag %}", environment: environment) template = Template(templateString: "{% simpletag %}", environment: environment)
try expectError(reason: "simpletag error", token: "simpletag") try expectError(reason: "simpletag error", token: "simpletag")
} }
$0.it("reporsts passing argument to simple filter") { $0.it("reporsts passing argument to simple filter") {
template = "{{ name|uppercase:5 }}" template = "{{ name|uppercase:5 }}"
try expectError(reason: "cannot invoke filter with an argument", token: "name|uppercase:5") try expectError(reason: "cannot invoke filter with an argument", token: "name|uppercase:5")
} }
$0.it("reports rendering error in custom tag") { $0.it("reports rendering error in custom tag") {
let tagExtension = Extension() let tagExtension = Extension()
tagExtension.registerTag("customtag") { parser, token in tagExtension.registerTag("customtag") { parser, token in
return ErrorNode(token: token) return ErrorNode(token: token)
} }
environment.extensions += [tagExtension] environment.extensions += [tagExtension]
template = Template(templateString: "{% customtag %}", environment: environment) template = Template(templateString: "{% customtag %}", environment: environment)
try expectError(reason: "Custom Error", token: "customtag") try expectError(reason: "Custom Error", token: "customtag")
} }
$0.it("reports rendering error in for body") { $0.it("reports rendering error in for body") {
let tagExtension = Extension() let tagExtension = Extension()
tagExtension.registerTag("customtag") { parser, token in tagExtension.registerTag("customtag") { parser, token in
@@ -180,7 +180,7 @@ func testEnvironment() {
template = Template(templateString: "{% for name in names %}{% customtag %}{% endfor %}", environment: environment) template = Template(templateString: "{% for name in names %}{% customtag %}{% endfor %}", environment: environment)
try expectError(reason: "Custom Error", token: "customtag") try expectError(reason: "Custom Error", token: "customtag")
} }
$0.it("reports rendering error in block") { $0.it("reports rendering error in block") {
let tagExtension = Extension() let tagExtension = Extension()
tagExtension.registerTag("customtag") { parser, token in tagExtension.registerTag("customtag") { parser, token in
@@ -192,48 +192,54 @@ func testEnvironment() {
try expectError(reason: "Custom Error", token: "customtag") try expectError(reason: "Custom Error", token: "customtag")
} }
} }
$0.context("given included template") { $0.context("given included template") {
let path = Path(#file) + ".." + "fixtures" let path = Path(#file) + ".." + "fixtures"
let loader = FileSystemLoader(paths: [path]) let loader = FileSystemLoader(paths: [path])
var environment = Environment(loader: loader) var environment = Environment(loader: loader)
var template: Template! var template: Template!
var includedTemplate: Template! var includedTemplate: Template!
$0.before { $0.before {
environment = Environment(loader: loader) environment = Environment(loader: loader)
template = nil template = nil
includedTemplate = nil includedTemplate = nil
} }
func expectError(reason: String, token: String, includedToken: String, func expectError(reason: String, token: String, includedToken: String,
file: String = #file, line: Int = #line, function: String = #function) throws { file: String = #file, line: Int = #line, function: String = #function) throws {
var expectedError = expectedSyntaxError(token: token, template: template, description: reason) var expectedError = expectedSyntaxError(token: token, template: template, description: reason)
expectedError.stackTrace = [expectedSyntaxError(token: includedToken, template: includedTemplate, description: reason).token!] expectedError.stackTrace = [expectedSyntaxError(token: includedToken, template: includedTemplate, description: reason).token!]
let error = try expect(environment.render(template: template, context: ["target": "World"]), let error = try expect(environment.render(template: template, context: ["target": "World"]),
file: file, line: line, function: function).toThrow() as TemplateSyntaxError file: file, line: line, function: function).toThrow() as TemplateSyntaxError
let reporter = SimpleErrorReporter() let reporter = SimpleErrorReporter()
try expect(reporter.renderError(error), file: file, line: line, function: function) == reporter.renderError(expectedError) try expect(reporter.renderError(error), file: file, line: line, function: function) == reporter.renderError(expectedError)
} }
$0.it("reports syntax error in included template") { $0.it("reports syntax error in included template") {
template = Template(templateString: "{% include \"invalid-include.html\" %}", environment: environment) template = Template(templateString: """
{% include "invalid-include.html" %}
""", environment: environment)
includedTemplate = try environment.loadTemplate(name: "invalid-include.html") includedTemplate = try environment.loadTemplate(name: "invalid-include.html")
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
token: "include \"invalid-include.html\"", token: """
include "invalid-include.html"
""",
includedToken: "target|unknown") includedToken: "target|unknown")
} }
$0.it("reports runtime error in included template") { $0.it("reports runtime error in included template") {
let filterExtension = Extension() let filterExtension = Extension()
filterExtension.registerFilter("unknown", filter: { (_: Any?) in filterExtension.registerFilter("unknown", filter: { (_: Any?) in
throw TemplateSyntaxError("filter error") throw TemplateSyntaxError("filter error")
}) })
environment.extensions += [filterExtension] environment.extensions += [filterExtension]
template = Template(templateString: "{% include \"invalid-include.html\" %}", environment: environment) template = Template(templateString: """
{% include "invalid-include.html" %}
""", environment: environment)
includedTemplate = try environment.loadTemplate(name: "invalid-include.html") includedTemplate = try environment.loadTemplate(name: "invalid-include.html")
try expectError(reason: "filter error", try expectError(reason: "filter error",
@@ -242,7 +248,7 @@ func testEnvironment() {
} }
} }
$0.context("given base and child templates") { $0.context("given base and child templates") {
let path = Path(#file) + ".." + "fixtures" let path = Path(#file) + ".." + "fixtures"
let loader = FileSystemLoader(paths: [path]) let loader = FileSystemLoader(paths: [path])
@@ -255,7 +261,7 @@ func testEnvironment() {
childTemplate = nil childTemplate = nil
baseTemplate = nil baseTemplate = nil
} }
func expectError(reason: String, childToken: String, baseToken: String?, func expectError(reason: String, childToken: String, baseToken: String?,
file: String = #file, line: Int = #line, function: String = #function) throws { file: String = #file, line: Int = #line, function: String = #function) throws {
var expectedError = expectedSyntaxError(token: childToken, template: childTemplate, description: reason) var expectedError = expectedSyntaxError(token: childToken, template: childTemplate, description: reason)
@@ -271,12 +277,12 @@ func testEnvironment() {
$0.it("reports syntax error in base template") { $0.it("reports syntax error in base template") {
childTemplate = try environment.loadTemplate(name: "invalid-child-super.html") childTemplate = try environment.loadTemplate(name: "invalid-child-super.html")
baseTemplate = try environment.loadTemplate(name: "invalid-base.html") baseTemplate = try environment.loadTemplate(name: "invalid-base.html")
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
childToken: "extends \"invalid-base.html\"", childToken: "extends \"invalid-base.html\"",
baseToken: "target|unknown") baseToken: "target|unknown")
} }
$0.it("reports runtime error in base template") { $0.it("reports runtime error in base template") {
let filterExtension = Extension() let filterExtension = Extension()
filterExtension.registerFilter("unknown", filter: { (_: Any?) in filterExtension.registerFilter("unknown", filter: { (_: Any?) in
@@ -291,16 +297,18 @@ func testEnvironment() {
childToken: "block.super", childToken: "block.super",
baseToken: "target|unknown") baseToken: "target|unknown")
} }
$0.it("reports syntax error in child template") { $0.it("reports syntax error in child template") {
childTemplate = Template(templateString: "{% extends \"base.html\" %}\n" + childTemplate = Template(templateString: """
"{% block body %}Child {{ target|unknown }}{% endblock %}", environment: environment, name: nil) {% extends "base.html" %}
{% block body %}Child {{ target|unknown }}{% endblock %}
""", environment: environment, name: nil)
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.", try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
childToken: "target|unknown", childToken: "target|unknown",
baseToken: nil) baseToken: nil)
} }
$0.it("reports runtime error in child template") { $0.it("reports runtime error in child template") {
let filterExtension = Extension() let filterExtension = Extension()
filterExtension.registerFilter("unknown", filter: { (_: Any?) in filterExtension.registerFilter("unknown", filter: { (_: Any?) in
@@ -308,8 +316,10 @@ func testEnvironment() {
}) })
environment.extensions += [filterExtension] environment.extensions += [filterExtension]
childTemplate = Template(templateString: "{% extends \"base.html\" %}\n" + childTemplate = Template(templateString: """
"{% block body %}Child {{ target|unknown }}{% endblock %}", environment: environment, name: nil) {% extends "base.html" %}
{% block body %}Child {{ target|unknown }}{% endblock %}
""", environment: environment, name: nil)
try expectError(reason: "filter error", try expectError(reason: "filter error",
childToken: "target|unknown", childToken: "target|unknown",
@@ -325,13 +335,13 @@ extension Expectation {
@discardableResult @discardableResult
func toThrow<T: Error>() throws -> T { func toThrow<T: Error>() throws -> T {
var thrownError: Error? = nil var thrownError: Error? = nil
do { do {
_ = try expression() _ = try expression()
} catch { } catch {
thrownError = error thrownError = error
} }
if let thrownError = thrownError { if let thrownError = thrownError {
if let thrownError = thrownError as? T { if let thrownError = thrownError as? T {
return thrownError return thrownError

View File

@@ -23,7 +23,9 @@ func testFilter() {
} }
$0.it("allows you to register a custom filter which accepts single argument") { $0.it("allows you to register a custom filter which accepts single argument") {
let template = Template(templateString: "{{ name|repeat:'value1, \"value2\"' }}") let template = Template(templateString: """
{{ name|repeat:'value1, "value2"' }}
""")
let repeatExtension = Extension() let repeatExtension = Extension()
repeatExtension.registerFilter("repeat") { value, arguments in repeatExtension.registerFilter("repeat") { value, arguments in
@@ -35,11 +37,15 @@ func testFilter() {
} }
let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension]))) let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension])))
try expect(result) == "Kyle Kyle with args value1, \"value2\"" try expect(result) == """
Kyle Kyle with args value1, "value2"
"""
} }
$0.it("allows you to register a custom filter which accepts several arguments") { $0.it("allows you to register a custom filter which accepts several arguments") {
let template = Template(templateString: "{{ name|repeat:'value\"1\"',\"value'2'\",'(key, value)' }}") let template = Template(templateString: """
{{ name|repeat:'value"1"',"value'2'",'(key, value)' }}
""")
let repeatExtension = Extension() let repeatExtension = Extension()
repeatExtension.registerFilter("repeat") { value, arguments in repeatExtension.registerFilter("repeat") { value, arguments in
@@ -51,7 +57,9 @@ func testFilter() {
} }
let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension]))) let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension])))
try expect(result) == "Kyle Kyle with args 0: value\"1\", 1: value'2', 2: (key, value)" try expect(result) == """
Kyle Kyle with args 0: value"1", 1: value'2', 2: (key, value)
"""
} }
$0.it("allows you to register a custom which throws") { $0.it("allows you to register a custom which throws") {
@@ -78,7 +86,9 @@ func testFilter() {
} }
$0.it("allows whitespace in expression") { $0.it("allows whitespace in expression") {
let template = Template(templateString: "{{ value | join : \", \" }}") let template = Template(templateString: """
{{ value | join : ", " }}
""")
let result = try template.render(Context(dictionary: ["value": ["One", "Two"]])) let result = try template.render(Context(dictionary: ["value": ["One", "Two"]]))
try expect(result) == "One, Two" try expect(result) == "One, Two"
} }
@@ -114,25 +124,33 @@ func testFilter() {
$0.it("transforms a string to be capitalized") { $0.it("transforms a string to be capitalized") {
let template = Template(templateString: "{{ names|capitalize }}") let template = Template(templateString: "{{ names|capitalize }}")
let result = try template.render(Context(dictionary: ["names": ["kyle", "kyle"]])) let result = try template.render(Context(dictionary: ["names": ["kyle", "kyle"]]))
try expect(result) == "[\"Kyle\", \"Kyle\"]" try expect(result) == """
["Kyle", "Kyle"]
"""
} }
$0.it("transforms a string to be uppercase") { $0.it("transforms a string to be uppercase") {
let template = Template(templateString: "{{ names|uppercase }}") let template = Template(templateString: "{{ names|uppercase }}")
let result = try template.render(Context(dictionary: ["names": ["kyle", "kyle"]])) let result = try template.render(Context(dictionary: ["names": ["kyle", "kyle"]]))
try expect(result) == "[\"KYLE\", \"KYLE\"]" try expect(result) == """
["KYLE", "KYLE"]
"""
} }
$0.it("transforms a string to be lowercase") { $0.it("transforms a string to be lowercase") {
let template = Template(templateString: "{{ names|lowercase }}") let template = Template(templateString: "{{ names|lowercase }}")
let result = try template.render(Context(dictionary: ["names": ["Kyle", "Kyle"]])) let result = try template.render(Context(dictionary: ["names": ["Kyle", "Kyle"]]))
try expect(result) == "[\"kyle\", \"kyle\"]" try expect(result) == """
["kyle", "kyle"]
"""
} }
} }
} }
describe("default filter") { describe("default filter") {
let template = Template(templateString: "Hello {{ name|default:\"World\" }}") let template = Template(templateString: """
Hello {{ name|default:"World" }}
""")
$0.it("shows the variable value") { $0.it("shows the variable value") {
let result = try template.render(Context(dictionary: ["name": "Kyle"])) let result = try template.render(Context(dictionary: ["name": "Kyle"]))
@@ -145,7 +163,9 @@ func testFilter() {
} }
$0.it("supports multiple defaults") { $0.it("supports multiple defaults") {
let template = Template(templateString: "Hello {{ name|default:a,b,c,\"World\" }}") let template = Template(templateString: """
Hello {{ name|default:a,b,c,"World" }}
""")
let result = try template.render(Context(dictionary: [:])) let result = try template.render(Context(dictionary: [:]))
try expect(result) == "Hello World" try expect(result) == "Hello World"
} }
@@ -163,7 +183,9 @@ func testFilter() {
} }
$0.it("checks for underlying nil value correctly") { $0.it("checks for underlying nil value correctly") {
let template = Template(templateString: "Hello {{ user.name|default:\"anonymous\" }}") let template = Template(templateString: """
Hello {{ user.name|default:"anonymous" }}
""")
let nilName: String? = nil let nilName: String? = nil
let user: [String: Any?] = ["name": nilName] let user: [String: Any?] = ["name": nilName]
let result = try template.render(Context(dictionary: ["user": user])) let result = try template.render(Context(dictionary: ["user": user]))
@@ -172,7 +194,9 @@ func testFilter() {
} }
describe("join filter") { describe("join filter") {
let template = Template(templateString: "{{ value|join:\", \" }}") let template = Template(templateString: """
{{ value|join:", " }}
""")
$0.it("joins a collection of strings") { $0.it("joins a collection of strings") {
let result = try template.render(Context(dictionary: ["value": ["One", "Two"]])) let result = try template.render(Context(dictionary: ["value": ["One", "Two"]]))
@@ -185,30 +209,42 @@ func testFilter() {
} }
$0.it("can join by non string") { $0.it("can join by non string") {
let template = Template(templateString: "{{ value|join:separator }}") let template = Template(templateString: """
{{ value|join:separator }}
""")
let result = try template.render(Context(dictionary: ["value": ["One", "Two"], "separator": true])) let result = try template.render(Context(dictionary: ["value": ["One", "Two"], "separator": true]))
try expect(result) == "OnetrueTwo" try expect(result) == "OnetrueTwo"
} }
$0.it("can join without arguments") { $0.it("can join without arguments") {
let template = Template(templateString: "{{ value|join }}") let template = Template(templateString: """
{{ value|join }}
""")
let result = try template.render(Context(dictionary: ["value": ["One", "Two"]])) let result = try template.render(Context(dictionary: ["value": ["One", "Two"]]))
try expect(result) == "OneTwo" try expect(result) == "OneTwo"
} }
} }
describe("split filter") { describe("split filter") {
let template = Template(templateString: "{{ value|split:\", \" }}") let template = Template(templateString: """
{{ value|split:", " }}
""")
$0.it("split a string into array") { $0.it("split a string into array") {
let result = try template.render(Context(dictionary: ["value": "One, Two"])) let result = try template.render(Context(dictionary: ["value": "One, Two"]))
try expect(result) == "[\"One\", \"Two\"]" try expect(result) == """
["One", "Two"]
"""
} }
$0.it("can split without arguments") { $0.it("can split without arguments") {
let template = Template(templateString: "{{ value|split }}") let template = Template(templateString: """
{{ value|split }}
""")
let result = try template.render(Context(dictionary: ["value": "One, Two"])) let result = try template.render(Context(dictionary: ["value": "One, Two"]))
try expect(result) == "[\"One,\", \"Two\"]" try expect(result) == """
["One,", "Two"]
"""
} }
} }
@@ -268,27 +304,67 @@ func testFilter() {
describe("indent filter") { describe("indent filter") {
$0.it("indents content") { $0.it("indents content") {
let template = Template(templateString: "{{ value|indent:2 }}") let template = Template(templateString: """
let result = try template.render(Context(dictionary: ["value": "One\nTwo"])) {{ value|indent:2 }}
try expect(result) == "One\n Two" """)
let result = try template.render(Context(dictionary: ["value": """
One
Two
"""]))
try expect(result) == """
One
Two
"""
} }
$0.it("can indent with arbitrary character") { $0.it("can indent with arbitrary character") {
let template = Template(templateString: "{{ value|indent:2,\"\t\" }}") let template = Template(templateString: """
let result = try template.render(Context(dictionary: ["value": "One\nTwo"])) {{ value|indent:2,"\t" }}
try expect(result) == "One\n\t\tTwo" """)
let result = try template.render(Context(dictionary: ["value": """
One
Two
"""]))
try expect(result) == """
One
\t\tTwo
"""
} }
$0.it("can indent first line") { $0.it("can indent first line") {
let template = Template(templateString: "{{ value|indent:2,\" \",true }}") let template = Template(templateString: """
let result = try template.render(Context(dictionary: ["value": "One\nTwo"])) {{ value|indent:2," ",true }}
try expect(result) == " One\n Two" """)
let result = try template.render(Context(dictionary: ["value": """
One
Two
"""]))
try expect(result) == """
One
Two
"""
} }
$0.it("does not indent empty lines") { $0.it("does not indent empty lines") {
let template = Template(templateString: "{{ value|indent }}") let template = Template(templateString: """
let result = try template.render(Context(dictionary: ["value": "One\n\n\nTwo\n\n"])) {{ value|indent }}
try expect(result) == "One\n\n\n Two\n\n" """)
let result = try template.render(Context(dictionary: ["value": """
One
Two
"""]))
try expect(result) == """
One
Two
"""
} }
} }
} }

View File

@@ -27,7 +27,9 @@ func testFilterTag() {
return ($0 as! String).components(separatedBy: $1[0] as! String) return ($0 as! String).components(separatedBy: $1[0] as! String)
}) })
let env = Environment(extensions: [ext]) let env = Environment(extensions: [ext])
let result = try env.renderTemplate(string: "{% filter split:\",\"|join:\";\" %}{{ items|join:\",\" }}{% endfilter %}", context: ["items": [1, 2]]) let result = try env.renderTemplate(string: """
{% filter split:","|join:";" %}{{ items|join:"," }}{% endfilter %}
""", context: ["items": [1, 2]])
try expect(result) == "1;2" try expect(result) == "1;2"
} }
@@ -38,7 +40,9 @@ func testFilterTag() {
return ($0 as! String).replacingOccurrences(of: $1[0] as! String, with: $1[1] as! String) return ($0 as! String).replacingOccurrences(of: $1[0] as! String, with: $1[1] as! String)
}) })
let env = Environment(extensions: [ext]) let env = Environment(extensions: [ext])
let result = try env.renderTemplate(string: "{% filter replace:'\"',\"\" %}{{ items|join:\",\" }}{% endfilter %}", context: ["items": ["\"1\"", "\"2\""]]) let result = try env.renderTemplate(string: """
{% filter replace:'"',"" %}{{ items|join:"," }}{% endfilter %}
""", context: ["items": ["\"1\"", "\"2\""]])
try expect(result) == "1,2" try expect(result) == "1,2"
} }
} }

View File

@@ -112,9 +112,11 @@ func testForNode() {
} }
$0.it("can render a filter with spaces") { $0.it("can render a filter with spaces") {
let templateString = "{% for article in ars | default: a, b , articles %}" + let templateString = """
"- {{ article.title }} by {{ article.author }}.\n" + {% for article in ars | default: a, b , articles %}\
"{% endfor %}\n" - {{ article.title }} by {{ article.author }}.
{% endfor %}
"""
let context = Context(dictionary: [ let context = Context(dictionary: [
"articles": [ "articles": [
@@ -126,54 +128,70 @@ func testForNode() {
let template = Template(templateString: templateString) let template = Template(templateString: templateString)
let result = try template.render(context) let result = try template.render(context)
let fixture = "" + try expect(result) == """
"- Migrating from OCUnit to XCTest by Kyle Fuller.\n" + - Migrating from OCUnit to XCTest by Kyle Fuller.
"- Memory Management with ARC by Kyle Fuller.\n" + - Memory Management with ARC by Kyle Fuller.
"\n"
try expect(result) == fixture """
} }
$0.context("given array of tuples") { $0.context("given array of tuples") {
$0.it("can iterate over all tuple values") { $0.it("can iterate over all tuple values") {
let templateString = "{% for first,second,third in tuples %}" + let templateString = """
"{{ first }}, {{ second }}, {{ third }}\n" + {% for first,second,third in tuples %}\
"{% endfor %}\n" {{ first }}, {{ second }}, {{ third }}
{% endfor %}
"""
let template = Template(templateString: templateString) let template = Template(templateString: templateString)
let result = try template.render(context) let result = try template.render(context)
let fixture = "1, 2, 3\n4, 5, 6\n\n" try expect(result) == """
try expect(result) == fixture 1, 2, 3
4, 5, 6
"""
} }
$0.it("can iterate with less number of variables") { $0.it("can iterate with less number of variables") {
let templateString = "{% for first,second in tuples %}" + let templateString = """
"{{ first }}, {{ second }}\n" + {% for first,second in tuples %}\
"{% endfor %}\n" {{ first }}, {{ second }}
{% endfor %}
"""
let template = Template(templateString: templateString) let template = Template(templateString: templateString)
let result = try template.render(context) let result = try template.render(context)
let fixture = "1, 2\n4, 5\n\n" try expect(result) == """
try expect(result) == fixture 1, 2
4, 5
"""
} }
$0.it("can use _ to skip variables") { $0.it("can use _ to skip variables") {
let templateString = "{% for first,_,third in tuples %}" + let templateString = """
"{{ first }}, {{ third }}\n" + {% for first,_,third in tuples %}\
"{% endfor %}\n" {{ first }}, {{ third }}
{% endfor %}
"""
let template = Template(templateString: templateString) let template = Template(templateString: templateString)
let result = try template.render(context) let result = try template.render(context)
let fixture = "1, 3\n4, 6\n\n" try expect(result) == """
try expect(result) == fixture 1, 3
4, 6
"""
} }
$0.it("throws when number of variables is more than number of tuple values") { $0.it("throws when number of variables is more than number of tuple values") {
let templateString = "{% for key,value,smth in dict %}" + let templateString = """
"{% endfor %}\n" {% for key,value,smth in dict %}
{% endfor %}
"""
let template = Template(templateString: templateString) let template = Template(templateString: templateString)
try expect(template.render(context)).toThrow() try expect(template.render(context)).toThrow()
@@ -182,9 +200,11 @@ func testForNode() {
} }
$0.it("can iterate over dictionary") { $0.it("can iterate over dictionary") {
let templateString = "{% for key, value in dict %}" + let templateString = """
"{{ key }}: {{ value }}," + {% for key, value in dict %}\
"{% endfor %}" {{ key }}: {{ value }},\
{% endfor %}
"""
let template = Template(templateString: templateString) let template = Template(templateString: templateString)
let result = try template.render(context) let result = try template.render(context)
@@ -248,7 +268,11 @@ func testForNode() {
let node = ForNode(resolvable: Variable("struct"), loopVariables: ["property", "value"], nodes: nodes, emptyNodes: []) let node = ForNode(resolvable: Variable("struct"), loopVariables: ["property", "value"], nodes: nodes, emptyNodes: [])
let result = try node.render(context) let result = try node.render(context)
try expect(result) == "string=abc\nnumber=123\n" try expect(result) == """
string=abc
number=123
"""
} }
$0.it("can iterate tuple items") { $0.it("can iterate tuple items") {
@@ -266,7 +290,11 @@ func testForNode() {
let node = ForNode(resolvable: Variable("tuple"), loopVariables: ["label", "value"], nodes: nodes, emptyNodes: []) let node = ForNode(resolvable: Variable("tuple"), loopVariables: ["label", "value"], nodes: nodes, emptyNodes: [])
let result = try node.render(context) let result = try node.render(context)
try expect(result) == "one=1\ntwo=dva\n" try expect(result) == """
one=1
two=dva
"""
} }
$0.it("can iterate over class properties") { $0.it("can iterate over class properties") {
@@ -297,11 +325,16 @@ func testForNode() {
VariableNode(variable: "value"), VariableNode(variable: "value"),
TextNode(text: "\n"), TextNode(text: "\n"),
] ]
let node = ForNode(resolvable: Variable("class"), loopVariables: ["label", "value"], nodes: nodes, emptyNodes: []) let node = ForNode(resolvable: Variable("class"), loopVariables: ["label", "value"], nodes: nodes, emptyNodes: [])
let result = try node.render(context) let result = try node.render(context)
try expect(result) == "childString=child\nbaseString=base\nbaseInt=1\n" try expect(result) == """
childString=child
baseString=base
baseInt=1
"""
} }
$0.it("can iterate in range of variables") { $0.it("can iterate in range of variables") {
@@ -313,7 +346,6 @@ func testForNode() {
} }
fileprivate struct Article { fileprivate struct Article {
let title: String let title: String
let author: String let author: String

View File

@@ -58,7 +58,9 @@ func testInclude() {
} }
$0.it("successfully passes context") { $0.it("successfully passes context") {
let template = Template(templateString: "{% include \"test.html\" child %}") let template = Template(templateString: """
{% include "test.html" child %}
""")
let context = Context(dictionary: ["child": ["target": "World"]], environment: environment) let context = Context(dictionary: ["child": ["target": "World"]], environment: environment)
let value = try template.render(context) let value = try template.render(context)
try expect(value) == "Hello World!" try expect(value) == "Hello World!"

View File

@@ -11,17 +11,26 @@ func testInheritence() {
$0.it("can inherit from another template") { $0.it("can inherit from another template") {
let template = try environment.loadTemplate(name: "child.html") let template = try environment.loadTemplate(name: "child.html")
try expect(try template.render()) == "Super_Header Child_Header\nChild_Body" try expect(try template.render()) == """
Super_Header Child_Header
Child_Body
"""
} }
$0.it("can inherit from another template inheriting from another template") { $0.it("can inherit from another template inheriting from another template") {
let template = try environment.loadTemplate(name: "child-child.html") let template = try environment.loadTemplate(name: "child-child.html")
try expect(try template.render()) == "Super_Header Child_Header Child_Child_Header\nChild_Body" try expect(try template.render()) == """
Super_Header Child_Header Child_Child_Header
Child_Body
"""
} }
$0.it("can inherit from a template that calls a super block") { $0.it("can inherit from a template that calls a super block") {
let template = try environment.loadTemplate(name: "child-super.html") let template = try environment.loadTemplate(name: "child-super.html")
try expect(try template.render()) == "Header\nChild_Body" try expect(try template.render()) == """
Header
Child_Body
"""
} }
} }
} }

View File

@@ -69,15 +69,16 @@ func testLexer() {
} }
$0.it("can tokenize with new lines") { $0.it("can tokenize with new lines") {
let templateString = let templateString = """
"My name is {%\n" + My name is {%
" if name\n" + if name
" and\n" + and
" name\n" + name
"%}{{\n" + %}{{
"name\n" + name
"}}{%\n" + }}{%
"endif %}." endif %}.
"""
let lexer = Lexer(templateString: templateString) let lexer = Lexer(templateString: templateString)

View File

@@ -32,11 +32,13 @@ func testStencil() {
$0.it("can render the README example") { $0.it("can render the README example") {
let templateString = "There are {{ articles.count }} articles.\n" + let templateString = """
"\n" + There are {{ articles.count }} articles.
"{% for article in articles %}" +
" - {{ article.title }} by {{ article.author }}.\n" + {% for article in articles %}\
"{% endfor %}\n" - {{ article.title }} by {{ article.author }}.
{% endfor %}
"""
let context = [ let context = [
"articles": [ "articles": [
@@ -48,13 +50,13 @@ func testStencil() {
let template = Template(templateString: templateString) let template = Template(templateString: templateString)
let result = try template.render(context) let result = try template.render(context)
let fixture = "There are 2 articles.\n" + try expect(result) == """
"\n" + There are 2 articles.
" - Migrating from OCUnit to XCTest by Kyle Fuller.\n" +
" - Memory Management with ARC by Kyle Fuller.\n" +
"\n"
try expect(result) == fixture - Migrating from OCUnit to XCTest by Kyle Fuller.
- Memory Management with ARC by Kyle Fuller.
"""
} }
$0.it("can render a custom template tag") { $0.it("can render a custom template tag") {