added filter to apply dynamic filter
This commit is contained in:
@@ -12,6 +12,8 @@
|
|||||||
[Ilya Puchka](https://github.com/yonaskolb)
|
[Ilya Puchka](https://github.com/yonaskolb)
|
||||||
[#178](https://github.com/stencilproject/Stencil/pull/178)
|
[#178](https://github.com/stencilproject/Stencil/pull/178)
|
||||||
|
|
||||||
|
- Added support for dynamic filter using `filter` filter
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- Fixed using quote as a filter parameter
|
- Fixed using quote as a filter parameter
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
protocol Expression: CustomStringConvertible {
|
public protocol Expression: CustomStringConvertible {
|
||||||
func evaluate(context: Context) throws -> Bool
|
func evaluate(context: Context) throws -> Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ open class Extension {
|
|||||||
|
|
||||||
/// Registers a template filter with the given name
|
/// Registers a template filter with the given name
|
||||||
public func registerFilter(_ name: String, filter: @escaping (Any?, [Any?]) throws -> Any?) {
|
public func registerFilter(_ name: String, filter: @escaping (Any?, [Any?]) throws -> Any?) {
|
||||||
|
filters[name] = .arguments({ value, args, _ in try filter(value, args) })
|
||||||
|
}
|
||||||
|
|
||||||
|
public func registerFilter(_ name: String, filter: @escaping (Any?, [Any?], Context) throws -> Any?) {
|
||||||
filters[name] = .arguments(filter)
|
filters[name] = .arguments(filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,28 +63,28 @@ class DefaultExtension: Extension {
|
|||||||
registerFilter("join", filter: joinFilter)
|
registerFilter("join", filter: joinFilter)
|
||||||
registerFilter("split", filter: splitFilter)
|
registerFilter("split", filter: splitFilter)
|
||||||
registerFilter("indent", filter: indentFilter)
|
registerFilter("indent", filter: indentFilter)
|
||||||
|
registerFilter("filter", filter: filterFilter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protocol FilterType {
|
protocol FilterType {
|
||||||
func invoke(value: Any?, arguments: [Any?]) throws -> Any?
|
func invoke(value: Any?, arguments: [Any?], context: Context) throws -> Any?
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Filter: FilterType {
|
enum Filter: FilterType {
|
||||||
case simple(((Any?) throws -> Any?))
|
case simple(((Any?) throws -> Any?))
|
||||||
case arguments(((Any?, [Any?]) throws -> Any?))
|
case arguments(((Any?, [Any?], Context) throws -> Any?))
|
||||||
|
|
||||||
func invoke(value: Any?, arguments: [Any?]) throws -> Any? {
|
func invoke(value: Any?, arguments: [Any?], context: Context) throws -> Any? {
|
||||||
switch self {
|
switch self {
|
||||||
case let .simple(filter):
|
case let .simple(filter):
|
||||||
if !arguments.isEmpty {
|
if !arguments.isEmpty {
|
||||||
throw TemplateSyntaxError("cannot invoke filter with an argument")
|
throw TemplateSyntaxError("cannot invoke filter with an argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
return try filter(value)
|
return try filter(value)
|
||||||
case let .arguments(filter):
|
case let .arguments(filter):
|
||||||
return try filter(value, arguments)
|
return try filter(value, arguments, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func defaultFilter(value: Any?, arguments: [Any?]) -> Any? {
|
|||||||
|
|
||||||
func joinFilter(value: Any?, arguments: [Any?]) throws -> Any? {
|
func joinFilter(value: Any?, arguments: [Any?]) throws -> Any? {
|
||||||
guard arguments.count < 2 else {
|
guard arguments.count < 2 else {
|
||||||
throw TemplateSyntaxError("'join' filter takes a single argument")
|
throw TemplateSyntaxError("'join' filter takes at most one argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
let separator = stringify(arguments.first ?? "")
|
let separator = stringify(arguments.first ?? "")
|
||||||
@@ -55,7 +55,7 @@ func joinFilter(value: Any?, arguments: [Any?]) throws -> Any? {
|
|||||||
|
|
||||||
func splitFilter(value: Any?, arguments: [Any?]) throws -> Any? {
|
func splitFilter(value: Any?, arguments: [Any?]) throws -> Any? {
|
||||||
guard arguments.count < 2 else {
|
guard arguments.count < 2 else {
|
||||||
throw TemplateSyntaxError("'split' filter takes a single argument")
|
throw TemplateSyntaxError("'split' filter takes at most one argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
let separator = stringify(arguments.first ?? " ")
|
let separator = stringify(arguments.first ?? " ")
|
||||||
@@ -111,3 +111,16 @@ func indent(_ content: String, indentation: String, indentFirst: Bool) -> String
|
|||||||
return result.joined(separator: "\n")
|
return result.joined(separator: "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterFilter(value: Any?, arguments: [Any?], context: Context) throws -> Any? {
|
||||||
|
guard let value = value else { return nil }
|
||||||
|
guard arguments.count == 1 else {
|
||||||
|
throw TemplateSyntaxError("'filter' filter takes one argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
let attribute = stringify(arguments[0])
|
||||||
|
|
||||||
|
let expr = try context.environment.compileFilter("$0|\(attribute)")
|
||||||
|
return try context.push(dictionary: ["$0": value]) {
|
||||||
|
try expr.resolve(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class ForNode : NodeType {
|
|||||||
let resolvable = try parser.compileResolvable(components[3])
|
let resolvable = try parser.compileResolvable(components[3])
|
||||||
|
|
||||||
let `where` = hasToken("where", at: 4)
|
let `where` = hasToken("where", at: 4)
|
||||||
? try parseExpression(components: Array(components.suffix(from: 5)), tokenParser: parser)
|
? try parser.compileExpression(components: Array(components.suffix(from: 5)))
|
||||||
: nil
|
: nil
|
||||||
|
|
||||||
return ForNode(resolvable: resolvable, loopVariables: loopVariables, nodes: forNodes, emptyNodes:emptyNodes, where: `where`)
|
return ForNode(resolvable: resolvable, loopVariables: loopVariables, nodes: forNodes, emptyNodes:emptyNodes, where: `where`)
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ final class IfExpressionParser {
|
|||||||
let tokens: [IfToken]
|
let tokens: [IfToken]
|
||||||
var position: Int = 0
|
var position: Int = 0
|
||||||
|
|
||||||
init(components: [String], tokenParser: TokenParser) throws {
|
init(components: [String], environment: Environment) throws {
|
||||||
self.tokens = try components.map { component in
|
self.tokens = try components.map { component in
|
||||||
if let op = findOperator(name: component) {
|
if let op = findOperator(name: component) {
|
||||||
switch op {
|
switch op {
|
||||||
@@ -111,7 +111,7 @@ final class IfExpressionParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return .variable(try tokenParser.compileResolvable(component))
|
return .variable(try environment.compileResolvable(component))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,12 +155,6 @@ final class IfExpressionParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func parseExpression(components: [String], tokenParser: TokenParser) throws -> Expression {
|
|
||||||
let parser = try IfExpressionParser(components: components, tokenParser: tokenParser)
|
|
||||||
return try parser.parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Represents an if condition and the associated nodes when the condition
|
/// Represents an if condition and the associated nodes when the condition
|
||||||
/// evaluates
|
/// evaluates
|
||||||
final class IfCondition {
|
final class IfCondition {
|
||||||
@@ -187,7 +181,7 @@ class IfNode : NodeType {
|
|||||||
var components = token.components()
|
var components = token.components()
|
||||||
components.removeFirst()
|
components.removeFirst()
|
||||||
|
|
||||||
let expression = try parseExpression(components: components, tokenParser: parser)
|
let expression = try parser.compileExpression(components: components)
|
||||||
let nodes = try parser.parse(until(["endif", "elif", "else"]))
|
let nodes = try parser.parse(until(["endif", "elif", "else"]))
|
||||||
var conditions: [IfCondition] = [
|
var conditions: [IfCondition] = [
|
||||||
IfCondition(expression: expression, nodes: nodes)
|
IfCondition(expression: expression, nodes: nodes)
|
||||||
@@ -197,7 +191,7 @@ class IfNode : NodeType {
|
|||||||
while let current = token, current.contents.hasPrefix("elif") {
|
while let current = token, current.contents.hasPrefix("elif") {
|
||||||
var components = current.components()
|
var components = current.components()
|
||||||
components.removeFirst()
|
components.removeFirst()
|
||||||
let expression = try parseExpression(components: components, tokenParser: parser)
|
let expression = try parser.compileExpression(components: components)
|
||||||
|
|
||||||
let nodes = try parser.parse(until(["endif", "elif", "else"]))
|
let nodes = try parser.parse(until(["endif", "elif", "else"]))
|
||||||
token = parser.nextToken()
|
token = parser.nextToken()
|
||||||
@@ -236,7 +230,7 @@ class IfNode : NodeType {
|
|||||||
_ = parser.nextToken()
|
_ = parser.nextToken()
|
||||||
}
|
}
|
||||||
|
|
||||||
let expression = try parseExpression(components: components, tokenParser: parser)
|
let expression = try parser.compileExpression(components: components)
|
||||||
return IfNode(conditions: [
|
return IfNode(conditions: [
|
||||||
IfCondition(expression: expression, nodes: trueNodes),
|
IfCondition(expression: expression, nodes: trueNodes),
|
||||||
IfCondition(expression: nil, nodes: falseNodes),
|
IfCondition(expression: nil, nodes: falseNodes),
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public class TokenParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let tag = token.components().first {
|
if let tag = token.components().first {
|
||||||
let parser = try findTag(name: tag)
|
let parser = try environment.findTag(name: tag)
|
||||||
nodes.append(try parser(self, token))
|
nodes.append(try parser(self, token))
|
||||||
}
|
}
|
||||||
case .comment:
|
case .comment:
|
||||||
@@ -71,8 +71,24 @@ public class TokenParser {
|
|||||||
tokens.insert(token, at: 0)
|
tokens.insert(token, at: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func compileFilter(_ token: String) throws -> Resolvable {
|
||||||
|
return try environment.compileFilter(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func compileExpression(components: [String]) throws -> Expression {
|
||||||
|
return try environment.compileExpression(components: components)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func compileResolvable(_ token: String) throws -> Resolvable {
|
||||||
|
return try environment.compileResolvable(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Environment {
|
||||||
|
|
||||||
func findTag(name: String) throws -> Extension.TagParser {
|
func findTag(name: String) throws -> Extension.TagParser {
|
||||||
for ext in environment.extensions {
|
for ext in extensions {
|
||||||
if let filter = ext.tags[name] {
|
if let filter = ext.tags[name] {
|
||||||
return filter
|
return filter
|
||||||
}
|
}
|
||||||
@@ -82,7 +98,7 @@ public class TokenParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findFilter(_ name: String) throws -> FilterType {
|
func findFilter(_ name: String) throws -> FilterType {
|
||||||
for ext in environment.extensions {
|
for ext in extensions {
|
||||||
if let filter = ext.filters[name] {
|
if let filter = ext.filters[name] {
|
||||||
return filter
|
return filter
|
||||||
}
|
}
|
||||||
@@ -97,7 +113,7 @@ public class TokenParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func suggestedFilters(for name: String) -> [String] {
|
private func suggestedFilters(for name: String) -> [String] {
|
||||||
let allFilters = environment.extensions.flatMap({ $0.filters.keys })
|
let allFilters = extensions.flatMap({ $0.filters.keys })
|
||||||
|
|
||||||
let filtersWithDistance = allFilters
|
let filtersWithDistance = allFilters
|
||||||
.map({ (filterName: $0, distance: $0.levenshteinDistance(name)) })
|
.map({ (filterName: $0, distance: $0.levenshteinDistance(name)) })
|
||||||
@@ -111,11 +127,15 @@ public class TokenParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func compileFilter(_ token: String) throws -> Resolvable {
|
public func compileFilter(_ token: String) throws -> Resolvable {
|
||||||
return try FilterExpression(token: token, parser: self)
|
return try FilterExpression(token: token, environment: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func compileExpression(components: [String]) throws -> Expression {
|
||||||
|
return try IfExpressionParser(components: components, environment: self).parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func compileResolvable(_ token: String) throws -> Resolvable {
|
public func compileResolvable(_ token: String) throws -> Resolvable {
|
||||||
return try RangeVariable(token, parser: self)
|
return try RangeVariable(token, environment: self)
|
||||||
?? compileFilter(token)
|
?? compileFilter(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ class FilterExpression : Resolvable {
|
|||||||
let filters: [(FilterType, [Variable])]
|
let filters: [(FilterType, [Variable])]
|
||||||
let variable: Variable
|
let variable: Variable
|
||||||
|
|
||||||
init(token: String, parser: TokenParser) throws {
|
init(token: String, environment: Environment) throws {
|
||||||
let bits = token.characters.split(separator: "|").map({ String($0).trim(character: " ") })
|
let bits = token.smartSplit(separator: "|").map({ String($0).trim(character: " ") })
|
||||||
if bits.isEmpty {
|
if bits.isEmpty {
|
||||||
filters = []
|
filters = []
|
||||||
variable = Variable("")
|
variable = Variable("")
|
||||||
@@ -22,7 +22,7 @@ class FilterExpression : Resolvable {
|
|||||||
do {
|
do {
|
||||||
filters = try filterBits.map {
|
filters = try filterBits.map {
|
||||||
let (name, arguments) = parseFilterComponents(token: $0)
|
let (name, arguments) = parseFilterComponents(token: $0)
|
||||||
let filter = try parser.findFilter(name)
|
let filter = try environment.findFilter(name)
|
||||||
return (filter, arguments)
|
return (filter, arguments)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -36,7 +36,7 @@ class FilterExpression : Resolvable {
|
|||||||
|
|
||||||
return try filters.reduce(result) { x, y in
|
return try filters.reduce(result) { x, y in
|
||||||
let arguments = try y.1.map { try $0.resolve(context) }
|
let arguments = try y.1.map { try $0.resolve(context) }
|
||||||
return try y.0.invoke(value: x, arguments: arguments)
|
return try y.0.invoke(value: x, arguments: arguments, context: context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,14 +138,14 @@ public struct RangeVariable: Resolvable {
|
|||||||
public let from: Resolvable
|
public let from: Resolvable
|
||||||
public let to: Resolvable
|
public let to: Resolvable
|
||||||
|
|
||||||
public init?(_ token: String, parser: TokenParser) throws {
|
public init?(_ token: String, environment: Environment) throws {
|
||||||
let components = token.components(separatedBy: "...")
|
let components = token.components(separatedBy: "...")
|
||||||
guard components.count == 2 else {
|
guard components.count == 2 else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
self.from = try parser.compileFilter(components[0])
|
self.from = try environment.compileFilter(components[0])
|
||||||
self.to = try parser.compileFilter(components[1])
|
self.to = try environment.compileFilter(components[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
public func resolve(_ context: Context) throws -> Any? {
|
public func resolve(_ context: Context) throws -> Any? {
|
||||||
|
|||||||
@@ -105,19 +105,19 @@ func testExpressions() {
|
|||||||
|
|
||||||
$0.describe("expression parsing") {
|
$0.describe("expression parsing") {
|
||||||
$0.it("can parse a variable expression") {
|
$0.it("can parse a variable expression") {
|
||||||
let expression = try parseExpression(components: ["value"], tokenParser: parser)
|
let expression = try parser.compileExpression(components: ["value"])
|
||||||
try expect(expression.evaluate(context: Context())).to.beFalse()
|
try expect(expression.evaluate(context: Context())).to.beFalse()
|
||||||
try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beTrue()
|
try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
$0.it("can parse a not expression") {
|
$0.it("can parse a not expression") {
|
||||||
let expression = try parseExpression(components: ["not", "value"], tokenParser: parser)
|
let expression = try parser.compileExpression(components: ["not", "value"])
|
||||||
try expect(expression.evaluate(context: Context())).to.beTrue()
|
try expect(expression.evaluate(context: Context())).to.beTrue()
|
||||||
try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beFalse()
|
try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
$0.describe("and expression") {
|
$0.describe("and expression") {
|
||||||
let expression = try! parseExpression(components: ["lhs", "and", "rhs"], tokenParser: parser)
|
let expression = try! parser.compileExpression(components: ["lhs", "and", "rhs"])
|
||||||
|
|
||||||
$0.it("evaluates to false with lhs false") {
|
$0.it("evaluates to false with lhs false") {
|
||||||
try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": true]))).to.beFalse()
|
try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": true]))).to.beFalse()
|
||||||
@@ -137,7 +137,7 @@ func testExpressions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$0.describe("or expression") {
|
$0.describe("or expression") {
|
||||||
let expression = try! parseExpression(components: ["lhs", "or", "rhs"], tokenParser: parser)
|
let expression = try! parser.compileExpression(components: ["lhs", "or", "rhs"])
|
||||||
|
|
||||||
$0.it("evaluates to true with lhs true") {
|
$0.it("evaluates to true with lhs true") {
|
||||||
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beTrue()
|
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beTrue()
|
||||||
@@ -157,7 +157,7 @@ func testExpressions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$0.describe("equality expression") {
|
$0.describe("equality expression") {
|
||||||
let expression = try! parseExpression(components: ["lhs", "==", "rhs"], tokenParser: parser)
|
let expression = try! parser.compileExpression(components: ["lhs", "==", "rhs"])
|
||||||
|
|
||||||
$0.it("evaluates to true with equal lhs/rhs") {
|
$0.it("evaluates to true with equal lhs/rhs") {
|
||||||
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "a"]))).to.beTrue()
|
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "a"]))).to.beTrue()
|
||||||
@@ -193,7 +193,7 @@ func testExpressions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$0.describe("inequality expression") {
|
$0.describe("inequality expression") {
|
||||||
let expression = try! parseExpression(components: ["lhs", "!=", "rhs"], tokenParser: parser)
|
let expression = try! parser.compileExpression(components: ["lhs", "!=", "rhs"])
|
||||||
|
|
||||||
$0.it("evaluates to true with inequal lhs/rhs") {
|
$0.it("evaluates to true with inequal lhs/rhs") {
|
||||||
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "b"]))).to.beTrue()
|
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "b"]))).to.beTrue()
|
||||||
@@ -205,7 +205,7 @@ func testExpressions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$0.describe("more than expression") {
|
$0.describe("more than expression") {
|
||||||
let expression = try! parseExpression(components: ["lhs", ">", "rhs"], tokenParser: parser)
|
let expression = try! parser.compileExpression(components: ["lhs", ">", "rhs"])
|
||||||
|
|
||||||
$0.it("evaluates to true with lhs > rhs") {
|
$0.it("evaluates to true with lhs > rhs") {
|
||||||
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 4]))).to.beTrue()
|
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 4]))).to.beTrue()
|
||||||
@@ -217,7 +217,7 @@ func testExpressions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$0.describe("more than equal expression") {
|
$0.describe("more than equal expression") {
|
||||||
let expression = try! parseExpression(components: ["lhs", ">=", "rhs"], tokenParser: parser)
|
let expression = try! parser.compileExpression(components: ["lhs", ">=", "rhs"])
|
||||||
|
|
||||||
$0.it("evaluates to true with lhs == rhs") {
|
$0.it("evaluates to true with lhs == rhs") {
|
||||||
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue()
|
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue()
|
||||||
@@ -229,7 +229,7 @@ func testExpressions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$0.describe("less than expression") {
|
$0.describe("less than expression") {
|
||||||
let expression = try! parseExpression(components: ["lhs", "<", "rhs"], tokenParser: parser)
|
let expression = try! parser.compileExpression(components: ["lhs", "<", "rhs"])
|
||||||
|
|
||||||
$0.it("evaluates to true with lhs < rhs") {
|
$0.it("evaluates to true with lhs < rhs") {
|
||||||
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 4, "rhs": 4.5]))).to.beTrue()
|
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 4, "rhs": 4.5]))).to.beTrue()
|
||||||
@@ -241,7 +241,7 @@ func testExpressions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$0.describe("less than equal expression") {
|
$0.describe("less than equal expression") {
|
||||||
let expression = try! parseExpression(components: ["lhs", "<=", "rhs"], tokenParser: parser)
|
let expression = try! parser.compileExpression(components: ["lhs", "<=", "rhs"])
|
||||||
|
|
||||||
$0.it("evaluates to true with lhs == rhs") {
|
$0.it("evaluates to true with lhs == rhs") {
|
||||||
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue()
|
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue()
|
||||||
@@ -253,7 +253,7 @@ func testExpressions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$0.describe("multiple expression") {
|
$0.describe("multiple expression") {
|
||||||
let expression = try! parseExpression(components: ["one", "or", "two", "and", "not", "three"], tokenParser: parser)
|
let expression = try! parser.compileExpression(components: ["one", "or", "two", "and", "not", "three"])
|
||||||
|
|
||||||
$0.it("evaluates to true with one") {
|
$0.it("evaluates to true with one") {
|
||||||
try expect(expression.evaluate(context: Context(dictionary: ["one": true]))).to.beTrue()
|
try expect(expression.evaluate(context: Context(dictionary: ["one": true]))).to.beTrue()
|
||||||
@@ -281,7 +281,7 @@ func testExpressions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$0.describe("in expression") {
|
$0.describe("in expression") {
|
||||||
let expression = try! parseExpression(components: ["lhs", "in", "rhs"], tokenParser: parser)
|
let expression = try! parser.compileExpression(components: ["lhs", "in", "rhs"])
|
||||||
|
|
||||||
$0.it("evaluates to true when rhs contains lhs") {
|
$0.it("evaluates to true when rhs contains lhs") {
|
||||||
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": [1, 2, 3]]))).to.beTrue()
|
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": [1, 2, 3]]))).to.beTrue()
|
||||||
|
|||||||
@@ -3,14 +3,19 @@ import Spectre
|
|||||||
|
|
||||||
|
|
||||||
func testFilter() {
|
func testFilter() {
|
||||||
|
|
||||||
|
func environmentWithFilter(_ name: String, closure: @escaping (Any?) throws -> Any?) -> Environment {
|
||||||
|
let filterExtension = Extension()
|
||||||
|
filterExtension.registerFilter(name, filter: closure)
|
||||||
|
return Environment(extensions: [filterExtension])
|
||||||
|
}
|
||||||
|
|
||||||
describe("template filters") {
|
describe("template filters") {
|
||||||
let context: [String: Any] = ["name": "Kyle"]
|
let context: [String: Any] = ["name": "Kyle"]
|
||||||
|
|
||||||
$0.it("allows you to register a custom filter") {
|
$0.it("allows you to register a custom filter") {
|
||||||
let template = Template(templateString: "{{ name|repeat }}")
|
let template = Template(templateString: "{{ name|repeat }}")
|
||||||
|
let env = environmentWithFilter("repeat") { (value: Any?) in
|
||||||
let repeatExtension = Extension()
|
|
||||||
repeatExtension.registerFilter("repeat") { (value: Any?) in
|
|
||||||
if let value = value as? String {
|
if let value = value as? String {
|
||||||
return "\(value) \(value)"
|
return "\(value) \(value)"
|
||||||
}
|
}
|
||||||
@@ -18,7 +23,7 @@ func testFilter() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension])))
|
let result = try template.render(Context(dictionary: context, environment: env))
|
||||||
try expect(result) == "Kyle Kyle"
|
try expect(result) == "Kyle Kyle"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,13 +72,11 @@ func testFilter() {
|
|||||||
|
|
||||||
$0.it("allows you to override a default filter") {
|
$0.it("allows you to override a default filter") {
|
||||||
let template = Template(templateString: "{{ name|join }}")
|
let template = Template(templateString: "{{ name|join }}")
|
||||||
|
let env = environmentWithFilter("join") { (value: Any?) in
|
||||||
let repeatExtension = Extension()
|
|
||||||
repeatExtension.registerFilter("join") { (value: Any?) in
|
|
||||||
return "joined"
|
return "joined"
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = try template.render(Context(dictionary: context, environment: Environment(extensions: [repeatExtension])))
|
let result = try template.render(Context(dictionary: context, environment: env))
|
||||||
try expect(result) == "joined"
|
try expect(result) == "joined"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +215,6 @@ func testFilter() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
describe("filter suggestion") {
|
describe("filter suggestion") {
|
||||||
|
|
||||||
$0.it("made for unknown filter") {
|
$0.it("made for unknown filter") {
|
||||||
@@ -244,7 +246,6 @@ 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: "{{ value|indent:2 }}")
|
||||||
@@ -270,4 +271,27 @@ func testFilter() {
|
|||||||
try expect(result) == "One\n\n\n Two\n\n"
|
try expect(result) == "One\n\n\n Two\n\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe("dynamic filter") {
|
||||||
|
|
||||||
|
$0.it("can apply dynamic filter") {
|
||||||
|
let template = Template(templateString: "{{ name|filter:somefilter }}")
|
||||||
|
let result = try template.render(Context(dictionary: ["name": "Jhon", "somefilter": "uppercase"]))
|
||||||
|
try expect(result) == "JHON"
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("can apply dynamic filter on array") {
|
||||||
|
let template = Template(templateString: "{{ values|filter:joinfilter }}")
|
||||||
|
let result = try template.render(Context(dictionary: ["values": [1, 2, 3], "joinfilter": "join:\", \""]))
|
||||||
|
try expect(result) == "1, 2, 3"
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("throws on unknown dynamic filter") {
|
||||||
|
let template = Template(templateString: "{{ values|filter:unknown }}")
|
||||||
|
let context = Context(dictionary: ["values": [1, 2, 3], "unknown": "absurd"])
|
||||||
|
try expect(try template.render(context)).toThrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,8 @@ func testForNode() {
|
|||||||
|
|
||||||
$0.it("renders the given nodes while filtering items using where expression") {
|
$0.it("renders the given nodes while filtering items using where expression") {
|
||||||
let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter")]
|
let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter")]
|
||||||
let `where` = try parseExpression(components: ["item", ">", "1"], tokenParser: TokenParser(tokens: [], environment: Environment()))
|
let parser = TokenParser(tokens: [], environment: Environment())
|
||||||
|
let `where` = try parser.compileExpression(components: ["item", ">", "1"])
|
||||||
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [], where: `where`)
|
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [], where: `where`)
|
||||||
try expect(try node.render(context)) == "2132"
|
try expect(try node.render(context)) == "2132"
|
||||||
}
|
}
|
||||||
@@ -106,7 +107,8 @@ func testForNode() {
|
|||||||
$0.it("renders the given empty nodes when all items filtered out with where expression") {
|
$0.it("renders the given empty nodes when all items filtered out with where expression") {
|
||||||
let nodes: [NodeType] = [VariableNode(variable: "item")]
|
let nodes: [NodeType] = [VariableNode(variable: "item")]
|
||||||
let emptyNodes: [NodeType] = [TextNode(text: "empty")]
|
let emptyNodes: [NodeType] = [TextNode(text: "empty")]
|
||||||
let `where` = try parseExpression(components: ["item", "==", "0"], tokenParser: TokenParser(tokens: [], environment: Environment()))
|
let parser = TokenParser(tokens: [], environment: Environment())
|
||||||
|
let `where` = try parser.compileExpression(components: ["item", "==", "0"])
|
||||||
let node = ForNode(resolvable: Variable("emptyItems"), loopVariables: ["item"], nodes: nodes, emptyNodes: emptyNodes, where: `where`)
|
let node = ForNode(resolvable: Variable("emptyItems"), loopVariables: ["item"], nodes: nodes, emptyNodes: emptyNodes, where: `where`)
|
||||||
try expect(try node.render(context)) == "empty"
|
try expect(try node.render(context)) == "empty"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ func testVariable() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
func makeVariable(_ token: String) throws -> RangeVariable? {
|
func makeVariable(_ token: String) throws -> RangeVariable? {
|
||||||
return try RangeVariable(token, parser: TokenParser(tokens: [], environment: context.environment))
|
return try RangeVariable(token, environment: context.environment)
|
||||||
}
|
}
|
||||||
|
|
||||||
$0.it("can resolve closed range as array") {
|
$0.it("can resolve closed range as array") {
|
||||||
|
|||||||
Reference in New Issue
Block a user