added filter to apply dynamic filter

This commit is contained in:
Ilya Puchka
2018-03-22 23:14:30 +00:00
parent 39ed9aa753
commit 564ccb7af7
12 changed files with 129 additions and 70 deletions

View File

@@ -1,4 +1,4 @@
protocol Expression: CustomStringConvertible {
public protocol Expression: CustomStringConvertible {
func evaluate(context: Context) throws -> Bool
}

View File

@@ -26,6 +26,10 @@ open class Extension {
/// Registers a template filter with the given name
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)
}
}
@@ -59,28 +63,28 @@ class DefaultExtension: Extension {
registerFilter("join", filter: joinFilter)
registerFilter("split", filter: splitFilter)
registerFilter("indent", filter: indentFilter)
registerFilter("filter", filter: filterFilter)
}
}
protocol FilterType {
func invoke(value: Any?, arguments: [Any?]) throws -> Any?
func invoke(value: Any?, arguments: [Any?], context: Context) throws -> Any?
}
enum Filter: FilterType {
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 {
case let .simple(filter):
if !arguments.isEmpty {
throw TemplateSyntaxError("cannot invoke filter with an argument")
}
return try filter(value)
case let .arguments(filter):
return try filter(value, arguments)
return try filter(value, arguments, context)
}
}
}

View File

@@ -39,7 +39,7 @@ func defaultFilter(value: Any?, arguments: [Any?]) -> Any? {
func joinFilter(value: Any?, arguments: [Any?]) throws -> Any? {
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 ?? "")
@@ -55,7 +55,7 @@ func joinFilter(value: Any?, arguments: [Any?]) throws -> Any? {
func splitFilter(value: Any?, arguments: [Any?]) throws -> Any? {
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 ?? " ")
@@ -111,3 +111,16 @@ func indent(_ content: String, indentation: String, indentFirst: Bool) -> String
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)
}
}

View File

@@ -42,7 +42,7 @@ class ForNode : NodeType {
let resolvable = try parser.compileResolvable(components[3])
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
return ForNode(resolvable: resolvable, loopVariables: loopVariables, nodes: forNodes, emptyNodes:emptyNodes, where: `where`)

View File

@@ -100,7 +100,7 @@ final class IfExpressionParser {
let tokens: [IfToken]
var position: Int = 0
init(components: [String], tokenParser: TokenParser) throws {
init(components: [String], environment: Environment) throws {
self.tokens = try components.map { component in
if let op = findOperator(name: component) {
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
/// evaluates
final class IfCondition {
@@ -187,7 +181,7 @@ class IfNode : NodeType {
var components = token.components()
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"]))
var conditions: [IfCondition] = [
IfCondition(expression: expression, nodes: nodes)
@@ -197,7 +191,7 @@ class IfNode : NodeType {
while let current = token, current.contents.hasPrefix("elif") {
var components = current.components()
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"]))
token = parser.nextToken()
@@ -236,7 +230,7 @@ class IfNode : NodeType {
_ = parser.nextToken()
}
let expression = try parseExpression(components: components, tokenParser: parser)
let expression = try parser.compileExpression(components: components)
return IfNode(conditions: [
IfCondition(expression: expression, nodes: trueNodes),
IfCondition(expression: nil, nodes: falseNodes),

View File

@@ -48,7 +48,7 @@ public class TokenParser {
}
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))
}
case .comment:
@@ -71,8 +71,24 @@ public class TokenParser {
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 {
for ext in environment.extensions {
for ext in extensions {
if let filter = ext.tags[name] {
return filter
}
@@ -82,7 +98,7 @@ public class TokenParser {
}
func findFilter(_ name: String) throws -> FilterType {
for ext in environment.extensions {
for ext in extensions {
if let filter = ext.filters[name] {
return filter
}
@@ -97,7 +113,7 @@ public class TokenParser {
}
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
.map({ (filterName: $0, distance: $0.levenshteinDistance(name)) })
@@ -111,11 +127,15 @@ public class TokenParser {
}
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 {
return try RangeVariable(token, parser: self)
return try RangeVariable(token, environment: self)
?? compileFilter(token)
}

View File

@@ -8,8 +8,8 @@ class FilterExpression : Resolvable {
let filters: [(FilterType, [Variable])]
let variable: Variable
init(token: String, parser: TokenParser) throws {
let bits = token.characters.split(separator: "|").map({ String($0).trim(character: " ") })
init(token: String, environment: Environment) throws {
let bits = token.smartSplit(separator: "|").map({ String($0).trim(character: " ") })
if bits.isEmpty {
filters = []
variable = Variable("")
@@ -22,7 +22,7 @@ class FilterExpression : Resolvable {
do {
filters = try filterBits.map {
let (name, arguments) = parseFilterComponents(token: $0)
let filter = try parser.findFilter(name)
let filter = try environment.findFilter(name)
return (filter, arguments)
}
} catch {
@@ -36,7 +36,7 @@ class FilterExpression : Resolvable {
return try filters.reduce(result) { x, y in
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 to: Resolvable
public init?(_ token: String, parser: TokenParser) throws {
public init?(_ token: String, environment: Environment) throws {
let components = token.components(separatedBy: "...")
guard components.count == 2 else {
return nil
}
self.from = try parser.compileFilter(components[0])
self.to = try parser.compileFilter(components[1])
self.from = try environment.compileFilter(components[0])
self.to = try environment.compileFilter(components[1])
}
public func resolve(_ context: Context) throws -> Any? {