From 3f4622f54fd61ae5704e2d10d32d73d89a64280c Mon Sep 17 00:00:00 2001 From: David Jennes Date: Thu, 20 Sep 2018 05:10:18 +0200 Subject: [PATCH] Fix issues in Sources Sources sources --- Sources/Context.swift | 19 +++-- Sources/Environment.swift | 10 +-- Sources/Errors.swift | 6 +- Sources/Expression.swift | 29 ++----- Sources/Extension.swift | 11 ++- Sources/FilterTag.swift | 5 +- Sources/Filters.swift | 11 ++- Sources/ForTag.swift | 113 ++++++++++++++++----------- Sources/IfTag.swift | 69 ++++++++--------- Sources/Include.swift | 8 +- Sources/Inheritence.swift | 24 +++--- Sources/KeyPath.swift | 12 +-- Sources/Lexer.swift | 9 ++- Sources/Loader.swift | 8 +- Sources/Node.swift | 42 +++++----- Sources/NowTag.swift | 19 +++-- Sources/Parser.swift | 60 +++++++-------- Sources/Template.swift | 13 ++-- Sources/Tokenizer.swift | 69 +++++++++-------- Sources/Variable.swift | 149 +++++++++++++++++++----------------- Sources/_SwiftSupport.swift | 10 +-- 21 files changed, 346 insertions(+), 350 deletions(-) diff --git a/Sources/Context.swift b/Sources/Context.swift index 157f230..007cf68 100644 --- a/Sources/Context.swift +++ b/Sources/Context.swift @@ -3,9 +3,9 @@ public class Context { var dictionaries: [[String: Any?]] public let environment: Environment - - init(dictionary: [String: Any]? = nil, environment: Environment? = nil) { - if let dictionary = dictionary { + + init(dictionary: [String: Any] = [:], environment: Environment? = nil) { + if !dictionary.isEmpty { dictionaries = [dictionary] } else { dictionaries = [] @@ -28,17 +28,16 @@ public class Context { /// Set a variable in the current context, deleting the variable if it's nil set(value) { - if let dictionary = dictionaries.popLast() { - var mutable_dictionary = dictionary - mutable_dictionary[key] = value - dictionaries.append(mutable_dictionary) + if var dictionary = dictionaries.popLast() { + dictionary[key] = value + dictionaries.append(dictionary) } } } /// Push a new level into the Context - fileprivate func push(_ dictionary: [String: Any]? = nil) { - dictionaries.append(dictionary ?? [:]) + fileprivate func push(_ dictionary: [String: Any] = [:]) { + dictionaries.append(dictionary) } /// Pop the last level off of the Context @@ -47,7 +46,7 @@ public class Context { } /// Push a new level onto the context for the duration of the execution of the given closure - public func push(dictionary: [String: Any]? = nil, closure: (() throws -> Result)) rethrows -> Result { + public func push(dictionary: [String: Any] = [:], closure: (() throws -> Result)) rethrows -> Result { push(dictionary) defer { _ = pop() } return try closure() diff --git a/Sources/Environment.swift b/Sources/Environment.swift index 2778a5d..0c2c72e 100644 --- a/Sources/Environment.swift +++ b/Sources/Environment.swift @@ -5,12 +5,12 @@ public struct Environment { public var loader: Loader? public init(loader: Loader? = nil, - extensions: [Extension]? = nil, + extensions: [Extension] = [], templateClass: Template.Type = Template.self) { self.templateClass = templateClass self.loader = loader - self.extensions = (extensions ?? []) + [DefaultExtension()] + self.extensions = extensions + [DefaultExtension()] } public func loadTemplate(name: String) throws -> Template { @@ -29,17 +29,17 @@ public struct Environment { } } - public func renderTemplate(name: String, context: [String: Any]? = nil) throws -> String { + public func renderTemplate(name: String, context: [String: Any] = [:]) throws -> String { let template = try loadTemplate(name: name) return try render(template: template, context: context) } - public func renderTemplate(string: String, context: [String: Any]? = nil) throws -> String { + public func renderTemplate(string: String, context: [String: Any] = [:]) throws -> String { let template = templateClass.init(templateString: string, environment: self) return try render(template: template, context: context) } - func render(template: Template, context: [String: Any]?) throws -> String { + func render(template: Template, context: [String: Any]) throws -> String { // update template environment as it can be created from string literal with default environment template.environment = self return try template.render(context) diff --git a/Sources/Errors.swift b/Sources/Errors.swift index a6191f9..9c1b584 100644 --- a/Sources/Errors.swift +++ b/Sources/Errors.swift @@ -18,14 +18,14 @@ public class TemplateDoesNotExist: Error, CustomStringConvertible { } } -public struct TemplateSyntaxError : Error, Equatable, CustomStringConvertible { +public struct TemplateSyntaxError: Error, Equatable, CustomStringConvertible { public let reason: String public var description: String { return reason } public internal(set) var token: Token? public internal(set) var stackTrace: [Token] public var templateName: String? { return token?.sourceMap.filename } var allTokens: [Token] { - return stackTrace + (token.map({ [$0] }) ?? []) + return stackTrace + (token.map { [$0] } ?? []) } public init(reason: String, token: Token? = nil, stackTrace: [Token] = []) { @@ -50,7 +50,7 @@ extension Error { } } -public protocol ErrorReporter: class { +public protocol ErrorReporter: AnyObject { func renderError(_ error: Error) -> String } diff --git a/Sources/Expression.swift b/Sources/Expression.swift index 572ad46..045b34c 100644 --- a/Sources/Expression.swift +++ b/Sources/Expression.swift @@ -2,17 +2,14 @@ public protocol Expression: CustomStringConvertible { func evaluate(context: Context) throws -> Bool } - protocol InfixOperator: Expression { init(lhs: Expression, rhs: Expression) } - protocol PrefixOperator: Expression { init(expression: Expression) } - final class StaticExpression: Expression, CustomStringConvertible { let value: Bool @@ -29,7 +26,6 @@ final class StaticExpression: Expression, CustomStringConvertible { } } - final class VariableExpression: Expression, CustomStringConvertible { let variable: Resolvable @@ -48,7 +44,7 @@ final class VariableExpression: Expression, CustomStringConvertible { if let result = result as? [Any] { truthy = !result.isEmpty - } else if let result = result as? [String:Any] { + } else if let result = result as? [String: Any] { truthy = !result.isEmpty } else if let result = result as? Bool { truthy = result @@ -68,7 +64,6 @@ final class VariableExpression: Expression, CustomStringConvertible { } } - final class NotExpression: Expression, PrefixOperator, CustomStringConvertible { let expression: Expression @@ -144,7 +139,6 @@ final class OrExpression: Expression, InfixOperator, CustomStringConvertible { } } - final class AndExpression: Expression, InfixOperator, CustomStringConvertible { let lhs: Expression let rhs: Expression @@ -168,7 +162,6 @@ final class AndExpression: Expression, InfixOperator, CustomStringConvertible { } } - class EqualityExpression: Expression, InfixOperator, CustomStringConvertible { let lhs: Expression let rhs: Expression @@ -204,7 +197,6 @@ class EqualityExpression: Expression, InfixOperator, CustomStringConvertible { } } - class NumericExpression: Expression, InfixOperator, CustomStringConvertible { let lhs: Expression let rhs: Expression @@ -215,7 +207,7 @@ class NumericExpression: Expression, InfixOperator, CustomStringConvertible { } var description: String { - return "(\(lhs) \(op) \(rhs))" + return "(\(lhs) \(symbol) \(rhs))" } func evaluate(context: Context) throws -> Bool { @@ -233,7 +225,7 @@ class NumericExpression: Expression, InfixOperator, CustomStringConvertible { return false } - var op: String { + var symbol: String { return "" } @@ -242,9 +234,8 @@ class NumericExpression: Expression, InfixOperator, CustomStringConvertible { } } - class MoreThanExpression: NumericExpression { - override var op: String { + override var symbol: String { return ">" } @@ -253,9 +244,8 @@ class MoreThanExpression: NumericExpression { } } - class MoreThanEqualExpression: NumericExpression { - override var op: String { + override var symbol: String { return ">=" } @@ -264,9 +254,8 @@ class MoreThanEqualExpression: NumericExpression { } } - class LessThanExpression: NumericExpression { - override var op: String { + override var symbol: String { return "<" } @@ -275,9 +264,8 @@ class LessThanExpression: NumericExpression { } } - class LessThanEqualExpression: NumericExpression { - override var op: String { + override var symbol: String { return "<=" } @@ -286,7 +274,6 @@ class LessThanEqualExpression: NumericExpression { } } - class InequalityExpression: EqualityExpression { override var description: String { return "(\(lhs) != \(rhs))" @@ -297,7 +284,7 @@ class InequalityExpression: EqualityExpression { } } - +// swiftlint:disable:next cyclomatic_complexity func toNumber(value: Any) -> Number? { if let value = value as? Float { return Number(value) diff --git a/Sources/Extension.swift b/Sources/Extension.swift index 5af819e..e994e6e 100644 --- a/Sources/Extension.swift +++ b/Sources/Extension.swift @@ -14,12 +14,13 @@ open class Extension { /// Registers a simple template tag with a name and a handler public func registerSimpleTag(_ name: String, handler: @escaping (Context) throws -> String) { - registerTag(name, parser: { parser, token in - return SimpleNode(token: token, handler: handler) - }) + registerTag(name) { _, token in + SimpleNode(token: token, handler: handler) + } } - + /// Registers boolean filter with it's negative counterpart + // swiftlint:disable:next discouraged_optional_boolean public func registerFilter(name: String, negativeFilterName: String, filter: @escaping (Any?) throws -> Bool?) { filters[name] = .simple(filter) filters[negativeFilterName] = .simple { @@ -44,7 +45,6 @@ open class Extension { } } - class DefaultExtension: Extension { override init() { super.init() @@ -77,7 +77,6 @@ class DefaultExtension: Extension { } } - protocol FilterType { func invoke(value: Any?, arguments: [Any?], context: Context) throws -> Any? } diff --git a/Sources/FilterTag.swift b/Sources/FilterTag.swift index 9371b3c..e623b53 100644 --- a/Sources/FilterTag.swift +++ b/Sources/FilterTag.swift @@ -1,4 +1,4 @@ -class FilterNode : NodeType { +class FilterNode: NodeType { let resolvable: Resolvable let nodes: [NodeType] let token: Token? @@ -30,8 +30,7 @@ class FilterNode : NodeType { let value = try renderNodes(nodes, context) return try context.push(dictionary: ["filter_value": value]) { - return try VariableNode(variable: resolvable, token: token).render(context) + try VariableNode(variable: resolvable, token: token).render(context) } } } - diff --git a/Sources/Filters.swift b/Sources/Filters.swift index 693d681..a456299 100644 --- a/Sources/Filters.swift +++ b/Sources/Filters.swift @@ -72,7 +72,7 @@ func indentFilter(value: Any?, arguments: [Any?]) throws -> Any? { } var indentWidth = 4 - if arguments.count > 0 { + if !arguments.isEmpty { guard let value = arguments[0] as? Int else { throw TemplateSyntaxError(""" 'indent' filter width argument must be an Integer (\(String(describing: arguments[0]))) @@ -99,18 +99,17 @@ func indentFilter(value: Any?, arguments: [Any?]) throws -> Any? { indentFirst = value } - let indentation = [String](repeating: indentationChar, count: indentWidth).joined(separator: "") + let indentation = [String](repeating: indentationChar, count: indentWidth).joined() return indent(stringify(value), indentation: indentation, indentFirst: indentFirst) } - func indent(_ content: String, indentation: String, indentFirst: Bool) -> String { guard !indentation.isEmpty else { return content } var lines = content.components(separatedBy: .newlines) let firstLine = (indentFirst ? indentation : "") + lines.removeFirst() - let result = lines.reduce([firstLine]) { (result, line) in - return result + [(line.isEmpty ? "" : "\(indentation)\(line)")] + let result = lines.reduce([firstLine]) { result, line in + result + [(line.isEmpty ? "" : "\(indentation)\(line)")] } return result.joined(separator: "\n") } @@ -120,7 +119,7 @@ func filterFilter(value: Any?, arguments: [Any?], context: Context) throws -> An 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)") diff --git a/Sources/ForTag.swift b/Sources/ForTag.swift index 89a65db..f727324 100644 --- a/Sources/ForTag.swift +++ b/Sources/ForTag.swift @@ -1,14 +1,14 @@ import Foundation -class ForNode : NodeType { +class ForNode: NodeType { let resolvable: Resolvable let loopVariables: [String] - let nodes:[NodeType] + let nodes: [NodeType] let emptyNodes: [NodeType] let `where`: Expression? let token: Token? - class func parse(_ parser:TokenParser, token:Token) throws -> NodeType { + class func parse(_ parser: TokenParser, token: Token) throws -> NodeType { let components = token.components func hasToken(_ token: String, at index: Int) -> Bool { @@ -46,10 +46,24 @@ class ForNode : NodeType { _ = parser.nextToken() } - return ForNode(resolvable: resolvable, loopVariables: loopVariables, nodes: forNodes, emptyNodes: emptyNodes, where: `where`, token: token) + return ForNode( + resolvable: resolvable, + loopVariables: loopVariables, + nodes: forNodes, + emptyNodes: emptyNodes, + where: `where`, + token: token + ) } - init(resolvable: Resolvable, loopVariables: [String], nodes: [NodeType], emptyNodes: [NodeType], where: Expression? = nil, token: Token? = nil) { + init( + resolvable: Resolvable, + loopVariables: [String], + nodes: [NodeType], + emptyNodes: [NodeType], + where: Expression? = nil, + token: Token? = nil + ) { self.resolvable = resolvable self.loopVariables = loopVariables self.nodes = nodes @@ -58,10 +72,48 @@ class ForNode : NodeType { self.token = token } - func push(value: Any, context: Context, closure: () throws -> (Result)) throws -> Result { + func render(_ context: Context) throws -> String { + var values = try resolve(context) + + if let `where` = self.where { + values = try values.filter { item -> Bool in + try push(value: item, context: context) { + try `where`.evaluate(context: context) + } + } + } + + if !values.isEmpty { + let count = values.count + + return try zip(0..., values) + .map { index, item in + let forContext: [String: Any] = [ + "first": index == 0, + "last": index == (count - 1), + "counter": index + 1, + "counter0": index, + "length": count + ] + + return try context.push(dictionary: ["forloop": forContext]) { + try push(value: item, context: context) { + try renderNodes(nodes, context) + } + } + } + .joined() + } + + return try context.push { + try renderNodes(emptyNodes, context) + } + } + + private func push(value: Any, context: Context, closure: () throws -> (Result)) throws -> Result { if loopVariables.isEmpty { - return try context.push() { - return try closure() + return try context.push { + try closure() } } @@ -71,27 +123,26 @@ class ForNode : NodeType { throw TemplateSyntaxError("Tuple '\(value)' has less values than loop variables") } var variablesContext = [String: Any]() - valueMirror.children.prefix(loopVariables.count).enumerated().forEach({ (offset, element) in + valueMirror.children.prefix(loopVariables.count).enumerated().forEach { offset, element in if loopVariables[offset] != "_" { variablesContext[loopVariables[offset]] = element.value } - }) + } return try context.push(dictionary: variablesContext) { - return try closure() + try closure() } } - return try context.push(dictionary: [loopVariables.first!: value]) { - return try closure() + return try context.push(dictionary: [loopVariables.first ?? "": value]) { + try closure() } } - func render(_ context: Context) throws -> String { + private func resolve(_ context: Context) throws -> [Any] { let resolved = try resolvable.resolve(context) var values: [Any] - if let dictionary = resolved as? [String: Any], !dictionary.isEmpty { values = dictionary.sorted { $0.key < $1.key } } else if let array = resolved as? [Any] { @@ -120,36 +171,6 @@ class ForNode : NodeType { values = [] } - if let `where` = self.where { - values = try values.filter({ item -> Bool in - return try push(value: item, context: context) { - try `where`.evaluate(context: context) - } - }) - } - - if !values.isEmpty { - let count = values.count - - return try values.enumerated().map { index, item in - let forContext: [String: Any] = [ - "first": index == 0, - "last": index == (count - 1), - "counter": index + 1, - "counter0": index, - "length": count - ] - - return try context.push(dictionary: ["forloop": forContext]) { - return try push(value: item, context: context) { - try renderNodes(nodes, context) - } - } - }.joined(separator: "") - } - - return try context.push { - try renderNodes(emptyNodes, context) - } + return values } } diff --git a/Sources/IfTag.swift b/Sources/IfTag.swift index e9fe885..061914a 100644 --- a/Sources/IfTag.swift +++ b/Sources/IfTag.swift @@ -12,7 +12,6 @@ enum Operator { } } - let operators: [Operator] = [ .infix("in", 5, InExpression.self), .infix("or", 6, OrExpression.self), @@ -23,21 +22,17 @@ let operators: [Operator] = [ .infix(">", 10, MoreThanExpression.self), .infix(">=", 10, MoreThanEqualExpression.self), .infix("<", 10, LessThanExpression.self), - .infix("<=", 10, LessThanEqualExpression.self), + .infix("<=", 10, LessThanEqualExpression.self) ] - func findOperator(name: String) -> Operator? { - for op in operators { - if op.name == name { - return op - } + for `operator` in operators where `operator`.name == name { + return `operator` } return nil } - indirect enum IfToken { case infix(name: String, bindingPower: Int, operatorType: InfixOperator.Type) case prefix(name: String, bindingPower: Int, operatorType: PrefixOperator.Type) @@ -51,9 +46,9 @@ indirect enum IfToken { return bindingPower case .prefix(_, let bindingPower, _): return bindingPower - case .variable(_): + case .variable: return 0 - case .subExpression(_): + case .subExpression: return 0 case .end: return 0 @@ -64,9 +59,9 @@ indirect enum IfToken { switch self { case .infix(let name, _, _): throw TemplateSyntaxError("'if' expression error: infix operator '\(name)' doesn't have a left hand side") - case .prefix(_, let bindingPower, let op): + case .prefix(_, let bindingPower, let operatorType): let expression = try parser.expression(bindingPower: bindingPower) - return op.init(expression: expression) + return operatorType.init(expression: expression) case .variable(let variable): return VariableExpression(variable: variable) case .subExpression(let expression): @@ -78,14 +73,14 @@ indirect enum IfToken { func leftDenotation(left: Expression, parser: IfExpressionParser) throws -> Expression { switch self { - case .infix(_, let bindingPower, let op): + case .infix(_, let bindingPower, let operatorType): let right = try parser.expression(bindingPower: bindingPower) - return op.init(lhs: left, rhs: right) + return operatorType.init(lhs: left, rhs: right) case .prefix(let name, _, _): throw TemplateSyntaxError("'if' expression error: prefix operator '\(name)' was called with a left hand side") case .variable(let variable): throw TemplateSyntaxError("'if' expression error: variable '\(variable)' was called with a left hand side") - case .subExpression(_): + case .subExpression: throw TemplateSyntaxError("'if' expression error: sub expression was called with a left hand side") case .end: throw TemplateSyntaxError("'if' expression error: end") @@ -102,7 +97,6 @@ indirect enum IfToken { } } - final class IfExpressionParser { let tokens: [IfToken] var position: Int = 0 @@ -110,7 +104,7 @@ final class IfExpressionParser { private init(tokens: [IfToken]) { self.tokens = tokens } - + static func parser(components: [String], environment: Environment, token: Token) throws -> IfExpressionParser { return try IfExpressionParser(components: ArraySlice(components), environment: environment, token: token) } @@ -118,7 +112,7 @@ final class IfExpressionParser { private init(components: ArraySlice, environment: Environment, token: Token) throws { var parsedComponents = Set() var bracketsBalance = 0 - self.tokens = try zip(components.indices, components).compactMap { (index, component) in + self.tokens = try zip(components.indices, components).compactMap { index, component in guard !parsedComponents.contains(index) else { return nil } if component == "(" { @@ -139,8 +133,8 @@ final class IfExpressionParser { return nil } else { parsedComponents.insert(index) - if let op = findOperator(name: component) { - switch op { + if let `operator` = findOperator(name: component) { + switch `operator` { case .infix(let name, let bindingPower, let operatorType): return .infix(name: name, bindingPower: bindingPower, operatorType: operatorType) case .prefix(let name, let bindingPower, let operatorType): @@ -152,17 +146,20 @@ final class IfExpressionParser { } } - private static func subExpression(from components: ArraySlice, environment: Environment, token: Token) throws -> (Expression, Int) { + private static func subExpression( + from components: ArraySlice, + environment: Environment, + token: Token + ) throws -> (Expression, Int) { var bracketsBalance = 1 - let subComponents = components - .prefix(while: { - if $0 == "(" { - bracketsBalance += 1 - } else if $0 == ")" { - bracketsBalance -= 1 - } - return bracketsBalance != 0 - }) + let subComponents = components.prefix { + if $0 == "(" { + bracketsBalance += 1 + } else if $0 == ")" { + bracketsBalance -= 1 + } + return bracketsBalance != 0 + } if bracketsBalance > 0 { throw TemplateSyntaxError("'if' expression error: missing closing bracket") } @@ -171,7 +168,7 @@ final class IfExpressionParser { let expression = try expressionParser.parse() return (expression, subComponents.count) } - + var currentToken: IfToken { if tokens.count > position { return tokens[position] @@ -211,7 +208,6 @@ final class IfExpressionParser { } } - /// Represents an if condition and the associated nodes when the condition /// evaluates final class IfCondition { @@ -225,13 +221,12 @@ final class IfCondition { func render(_ context: Context) throws -> String { return try context.push { - return try renderNodes(nodes, context) + try renderNodes(nodes, context) } } } - -class IfNode : NodeType { +class IfNode: NodeType { let conditions: [IfCondition] let token: Token? @@ -291,8 +286,8 @@ class IfNode : NodeType { return IfNode(conditions: [ IfCondition(expression: expression, nodes: trueNodes), - IfCondition(expression: nil, nodes: falseNodes), - ], token: token) + IfCondition(expression: nil, nodes: falseNodes) + ], token: token) } init(conditions: [IfCondition], token: Token? = nil) { diff --git a/Sources/Include.swift b/Sources/Include.swift index 9c82b68..9d49ed3 100644 --- a/Sources/Include.swift +++ b/Sources/Include.swift @@ -1,7 +1,6 @@ import PathKit - -class IncludeNode : NodeType { +class IncludeNode: NodeType { let templateName: Variable let includeContext: String? let token: Token? @@ -34,9 +33,9 @@ class IncludeNode : NodeType { let template = try context.environment.loadTemplate(name: templateName) do { - let subContext = includeContext.flatMap { context[$0] as? [String: Any] } + let subContext = includeContext.flatMap { context[$0] as? [String: Any] } ?? [:] return try context.push(dictionary: subContext) { - return try template.render(context) + try template.render(context) } } catch { if let error = error as? TemplateSyntaxError { @@ -47,4 +46,3 @@ class IncludeNode : NodeType { } } } - diff --git a/Sources/Inheritence.swift b/Sources/Inheritence.swift index e512bfb..611d28c 100644 --- a/Sources/Inheritence.swift +++ b/Sources/Inheritence.swift @@ -33,7 +33,6 @@ class BlockContext { } } - extension Collection { func any(_ closure: (Iterator.Element) -> Bool) -> Iterator.Element? { for element in self { @@ -46,10 +45,9 @@ extension Collection { } } - -class ExtendsNode : NodeType { +class ExtendsNode: NodeType { let templateName: Variable - let blocks: [String:BlockNode] + let blocks: [String: BlockNode] let token: Token? class func parse(_ parser: TokenParser, token: Token) throws -> NodeType { @@ -66,7 +64,7 @@ class ExtendsNode : NodeType { let blockNodes = parsedNodes.compactMap { $0 as? BlockNode } - let nodes = blockNodes.reduce([String: BlockNode]()) { (accumulator, node) -> [String: BlockNode] in + let nodes = blockNodes.reduce([String: BlockNode]()) { accumulator, node -> [String: BlockNode] in var dict = accumulator dict[node.name] = node return dict @@ -102,7 +100,7 @@ class ExtendsNode : NodeType { // pushes base template and renders it's content // block_context contains all blocks from child templates return try context.push(dictionary: [BlockContext.contextKey: blockContext]) { - return try baseTemplate.render(context) + try baseTemplate.render(context) } } catch { // if error template is already set (see catch in BlockNode) @@ -117,8 +115,7 @@ class ExtendsNode : NodeType { } } - -class BlockNode : NodeType { +class BlockNode: NodeType { let name: String let nodes: [NodeType] let token: Token? @@ -133,7 +130,7 @@ class BlockNode : NodeType { let blockName = bits[1] let nodes = try parser.parse(until(["endblock"])) _ = parser.nextToken() - return BlockNode(name:blockName, nodes:nodes, token: token) + return BlockNode(name: blockName, nodes: nodes, token: token) } init(name: String, nodes: [NodeType], token: Token) { @@ -148,7 +145,7 @@ class BlockNode : NodeType { // render extension node do { return try context.push(dictionary: childContext) { - return try child.render(context) + try child.render(context) } } catch { throw error.withToken(child.token) @@ -163,8 +160,11 @@ class BlockNode : NodeType { var childContext: [String: Any] = [BlockContext.contextKey: blockContext] if let blockSuperNode = child.nodes.first(where: { - if let token = $0.token, case .variable = token.kind, token.contents == "block.super" { return true } - else { return false} + if let token = $0.token, case .variable = token.kind, token.contents == "block.super" { + return true + } else { + return false + } }) { do { // render base node so that its content can be used as part of child node that extends it diff --git a/Sources/KeyPath.swift b/Sources/KeyPath.swift index 7728dcf..98767b7 100644 --- a/Sources/KeyPath.swift +++ b/Sources/KeyPath.swift @@ -24,8 +24,8 @@ final class KeyPath { subscriptLevel = 0 } - for c in variable { - switch c { + for character in variable { + switch character { case "." where subscriptLevel == 0: try foundSeparator() case "[": @@ -33,7 +33,7 @@ final class KeyPath { case "]": try closeBracket() default: - try addCharacter(c) + try addCharacter(character) } } try finish() @@ -90,12 +90,12 @@ final class KeyPath { subscriptLevel -= 1 } - private func addCharacter(_ c: Character) throws { + private func addCharacter(_ character: Character) throws { guard partialComponents.isEmpty || subscriptLevel > 0 else { - throw TemplateSyntaxError("Unexpected character '\(c)' in variable '\(variable)'") + throw TemplateSyntaxError("Unexpected character '\(character)' in variable '\(variable)'") } - current.append(c) + current.append(character) } private func finish() throws { diff --git a/Sources/Lexer.swift b/Sources/Lexer.swift index f6fc426..47465f5 100644 --- a/Sources/Lexer.swift +++ b/Sources/Lexer.swift @@ -24,8 +24,9 @@ struct Lexer { self.templateString = templateString self.lines = templateString.components(separatedBy: .newlines).enumerated().compactMap { - guard !$0.element.isEmpty else { return nil } - return (content: $0.element, number: UInt($0.offset + 1), templateString.range(of: $0.element)!) + guard !$0.element.isEmpty, + let range = templateString.range(of: $0.element) else { return nil } + return (content: $0.element, number: UInt($0.offset + 1), range) } } @@ -43,8 +44,8 @@ struct Lexer { guard string.count > 4 else { return "" } let trimmed = String(string.dropFirst(2).dropLast(2)) .components(separatedBy: "\n") - .filter({ !$0.isEmpty }) - .map({ $0.trim(character: " ") }) + .filter { !$0.isEmpty } + .map { $0.trim(character: " ") } .joined(separator: " ") return trimmed } diff --git a/Sources/Loader.swift b/Sources/Loader.swift index 201dbae..f83d9ae 100644 --- a/Sources/Loader.swift +++ b/Sources/Loader.swift @@ -1,13 +1,11 @@ import Foundation import PathKit - public protocol Loader { func loadTemplate(name: String, environment: Environment) throws -> Template func loadTemplate(names: [String], environment: Environment) throws -> Template } - extension Loader { public func loadTemplate(names: [String], environment: Environment) throws -> Template { for name in names { @@ -24,7 +22,6 @@ extension Loader { } } - // A class for loading a template from disk public class FileSystemLoader: Loader, CustomStringConvertible { public let paths: [Path] @@ -35,7 +32,7 @@ public class FileSystemLoader: Loader, CustomStringConvertible { public init(bundle: [Bundle]) { self.paths = bundle.map { - return Path($0.bundlePath) + Path($0.bundlePath) } } @@ -74,7 +71,6 @@ public class FileSystemLoader: Loader, CustomStringConvertible { } } - public class DictionaryLoader: Loader { public let templates: [String: String] @@ -101,7 +97,6 @@ public class DictionaryLoader: Loader { } } - extension Path { func safeJoin(path: Path) throws -> Path { let newPath = self + path @@ -114,7 +109,6 @@ extension Path { } } - class SuspiciousFileOperation: Error { let basePath: Path let path: Path diff --git a/Sources/Node.swift b/Sources/Node.swift index 2805290..8885ff6 100644 --- a/Sources/Node.swift +++ b/Sources/Node.swift @@ -2,26 +2,27 @@ import Foundation public protocol NodeType { /// Render the node in the given context - func render(_ context:Context) throws -> String + func render(_ context: Context) throws -> String /// Reference to this node's token var token: Token? { get } } - /// Render the collection of nodes in the given context -public func renderNodes(_ nodes:[NodeType], _ context:Context) throws -> String { - return try nodes.map { - do { - return try $0.render(context) - } catch { - throw error.withToken($0.token) +public func renderNodes(_ nodes: [NodeType], _ context: Context) throws -> String { + return try nodes + .map { + do { + return try $0.render(context) + } catch { + throw error.withToken($0.token) + } } - }.joined(separator: "") + .joined() } -public class SimpleNode : NodeType { - public let handler:(Context) throws -> String +public class SimpleNode: NodeType { + public let handler: (Context) throws -> String public let token: Token? public init(token: Token, handler: @escaping (Context) throws -> String) { @@ -34,34 +35,31 @@ public class SimpleNode : NodeType { } } - -public class TextNode : NodeType { - public let text:String +public class TextNode: NodeType { + public let text: String public let token: Token? - public init(text:String) { + public init(text: String) { self.text = text self.token = nil } - public func render(_ context:Context) throws -> String { + public func render(_ context: Context) throws -> String { return self.text } } - public protocol Resolvable { func resolve(_ context: Context) throws -> Any? } - -public class VariableNode : NodeType { +public class VariableNode: NodeType { public let variable: Resolvable public var token: Token? let condition: Expression? let elseExpression: Resolvable? - class func parse(_ parser:TokenParser, token:Token) throws -> NodeType { + class func parse(_ parser: TokenParser, token: Token) throws -> NodeType { var components = token.components func hasToken(_ token: String, at index: Int) -> Bool { @@ -121,7 +119,6 @@ public class VariableNode : NodeType { } } - func stringify(_ result: Any?) -> String { if let result = result as? String { return result @@ -144,7 +141,6 @@ func unwrap(_ array: [Any?]) -> [Any] { } else { return item } - } - else { return item as Any } + } else { return item as Any } } } diff --git a/Sources/NowTag.swift b/Sources/NowTag.swift index ac0ccfb..bad6627 100644 --- a/Sources/NowTag.swift +++ b/Sources/NowTag.swift @@ -1,13 +1,12 @@ #if !os(Linux) import Foundation - -class NowNode : NodeType { - let format:Variable +class NowNode: NodeType { + let format: Variable let token: Token? - class func parse(_ parser:TokenParser, token:Token) throws -> NodeType { - var format:Variable? + class func parse(_ parser: TokenParser, token: Token) throws -> NodeType { + var format: Variable? let components = token.components guard components.count <= 2 else { @@ -17,10 +16,10 @@ class NowNode : NodeType { format = Variable(components[1]) } - return NowNode(format:format, token: token) + return NowNode(format: format, token: token) } - init(format:Variable?, token: Token? = nil) { + init(format: Variable?, token: Token? = nil) { self.format = format ?? Variable("\"yyyy-MM-dd 'at' HH:mm\"") self.token = token } @@ -28,18 +27,18 @@ class NowNode : NodeType { func render(_ context: Context) throws -> String { let date = Date() let format = try self.format.resolve(context) - var formatter:DateFormatter? + var formatter: DateFormatter if let format = format as? DateFormatter { formatter = format } else if let format = format as? String { formatter = DateFormatter() - formatter!.dateFormat = format + formatter.dateFormat = format } else { return "" } - return formatter!.string(from: date) + return formatter.string(from: date) } } #endif diff --git a/Sources/Parser.swift b/Sources/Parser.swift index 41dd7b9..404b8e2 100644 --- a/Sources/Parser.swift +++ b/Sources/Parser.swift @@ -1,10 +1,8 @@ public func until(_ tags: [String]) -> ((TokenParser, Token) -> Bool) { return { parser, token in if let name = token.components.first { - for tag in tags { - if name == tag { - return true - } + for tag in tags where name == tag { + return true } } @@ -12,7 +10,6 @@ public func until(_ tags: [String]) -> ((TokenParser, Token) -> Bool) { } } - /// A class for parsing an array of tokens and converts them into a collection of Node's public class TokenParser { public typealias TagParser = (TokenParser, Token) throws -> NodeType @@ -30,11 +27,11 @@ public class TokenParser { return try parse(nil) } - public func parse(_ parse_until:((_ parser:TokenParser, _ token:Token) -> (Bool))?) throws -> [NodeType] { + public func parse(_ parseUntil: ((_ parser: TokenParser, _ token: Token) -> (Bool))?) throws -> [NodeType] { var nodes = [NodeType]() - while tokens.count > 0 { - let token = nextToken()! + while !tokens.isEmpty { + guard let token = nextToken() else { break } switch token.kind { case .text: @@ -42,7 +39,7 @@ public class TokenParser { case .variable: try nodes.append(VariableNode.parse(self, token: token)) case .block: - if let parse_until = parse_until , parse_until(self, token) { + if let parseUntil = parseUntil, parseUntil(self, token) { prependToken(token) return nodes } @@ -65,14 +62,14 @@ public class TokenParser { } public func nextToken() -> Token? { - if tokens.count > 0 { + if !tokens.isEmpty { return tokens.remove(at: 0) } return nil } - public func prependToken(_ token:Token) { + public func prependToken(_ token: Token) { tokens.insert(token, at: 0) } @@ -94,7 +91,6 @@ public class TokenParser { } extension Environment { - func findTag(name: String) throws -> Extension.TagParser { for ext in extensions { if let filter = ext.tags[name] { @@ -118,23 +114,23 @@ extension Environment { } else { throw TemplateSyntaxError(""" Unknown filter '\(name)'. \ - Found similar filters: \(suggestedFilters.map({ "'\($0)'" }).joined(separator: ", ")). + Found similar filters: \(suggestedFilters.map { "'\($0)'" }.joined(separator: ", ")). """) } } private func suggestedFilters(for name: String) -> [String] { - let allFilters = extensions.flatMap({ $0.filters.keys }) + let allFilters = extensions.flatMap { $0.filters.keys } let filtersWithDistance = allFilters - .map({ (filterName: $0, distance: $0.levenshteinDistance(name)) }) + .map { (filterName: $0, distance: $0.levenshteinDistance(name)) } // do not suggest filters which names are shorter than the distance - .filter({ $0.filterName.count > $0.distance }) + .filter { $0.filterName.count > $0.distance } guard let minDistance = filtersWithDistance.min(by: { $0.distance < $1.distance })?.distance else { return [] } // suggest all filters with the same distance - return filtersWithDistance.filter({ $0.distance == minDistance }).map({ $0.filterName }) + return filtersWithDistance.filter { $0.distance == minDistance }.map { $0.filterName } } /// Create filter expression from a string @@ -153,8 +149,14 @@ extension Environment { // find offset of filter in the containing token so that only filter is highligted, not the whole token if let filterTokenRange = containingToken.contents.range(of: filterToken) { var location = containingToken.sourceMap.location - location.lineOffset += containingToken.contents.distance(from: containingToken.contents.startIndex, to: filterTokenRange.lowerBound) - syntaxError.token = .variable(value: filterToken, at: SourceMap(filename: containingToken.sourceMap.filename, location: location)) + location.lineOffset += containingToken.contents.distance( + from: containingToken.contents.startIndex, + to: filterTokenRange.lowerBound + ) + syntaxError.token = .variable( + value: filterToken, + at: SourceMap(filename: containingToken.sourceMap.filename, location: location) + ) } else { syntaxError.token = containingToken } @@ -183,9 +185,8 @@ extension Environment { // https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows extension String { - - subscript(_ i: Int) -> Character { - return self[self.index(self.startIndex, offsetBy: i)] + subscript(_ index: Int) -> Character { + return self[self.index(self.startIndex, offsetBy: index)] } func levenshteinDistance(_ target: String) -> Int { @@ -198,19 +199,19 @@ extension String { last = [Int](0...target.count) current = [Int](repeating: 0, count: target.count + 1) - for i in 0.. String { - return try render(Context(dictionary: dictionary, environment: environment)) + return try render(Context(dictionary: dictionary ?? [:], environment: environment)) } } diff --git a/Sources/Tokenizer.swift b/Sources/Tokenizer.swift index d86852f..30f3117 100644 --- a/Sources/Tokenizer.swift +++ b/Sources/Tokenizer.swift @@ -1,6 +1,5 @@ import Foundation - extension String { /// Split a string by a separator leaving quoted phrases together func smartSplit(separator: Character = " ") -> [String] { @@ -10,37 +9,18 @@ extension String { var singleQuoteCount = 0 var doubleQuoteCount = 0 - let specialCharacters = ",|:" - func appendWord(_ word: String) { - if components.count > 0 { - if let precedingChar = components.last?.last, specialCharacters.contains(precedingChar) { - components[components.count-1] += word - } else if specialCharacters.contains(word) { - components[components.count-1] += word - } else if word != "(" && word.first == "(" || word != ")" && word.first == ")" { - components.append(String(word.prefix(1))) - appendWord(String(word.dropFirst())) - } else if word != "(" && word.last == "(" || word != ")" && word.last == ")" { - appendWord(String(word.dropLast())) - components.append(String(word.suffix(1))) - } else { - components.append(word) - } - } else { - components.append(word) - } - } - for character in self { - if character == "'" { singleQuoteCount += 1 } - else if character == "\"" { doubleQuoteCount += 1 } + if character == "'" { + singleQuoteCount += 1 + } else if character == "\"" { + doubleQuoteCount += 1 + } if character == separate { - if separate != separator { word.append(separate) } else if (singleQuoteCount % 2 == 0 || doubleQuoteCount % 2 == 0) && !word.isEmpty { - appendWord(word) + appendWord(word, to: &components) word = "" } @@ -54,11 +34,33 @@ extension String { } if !word.isEmpty { - appendWord(word) + appendWord(word, to: &components) } return components } + + private func appendWord(_ word: String, to components: inout [String]) { + let specialCharacters = ",|:" + + if !components.isEmpty { + if let precedingChar = components.last?.last, specialCharacters.contains(precedingChar) { + components[components.count - 1] += word + } else if specialCharacters.contains(word) { + components[components.count - 1] += word + } else if word != "(" && word.first == "(" || word != ")" && word.first == ")" { + components.append(String(word.prefix(1))) + appendWord(String(word.dropFirst()), to: &components) + } else if word != "(" && word.last == "(" || word != ")" && word.last == ")" { + appendWord(String(word.dropLast()), to: &components) + components.append(String(word.suffix(1))) + } else { + components.append(word) + } + } else { + components.append(word) + } + } } public struct SourceMap: Equatable { @@ -72,7 +74,7 @@ public struct SourceMap: Equatable { static let unknown = SourceMap() - public static func ==(lhs: SourceMap, rhs: SourceMap) -> Bool { + public static func == (lhs: SourceMap, rhs: SourceMap) -> Bool { return lhs.filename == rhs.filename && lhs.location == rhs.location } } @@ -88,20 +90,20 @@ public class Token: Equatable { /// A token representing a template block. case block } - + public let contents: String public let kind: Kind public let sourceMap: SourceMap - + /// Returns the underlying value as an array seperated by spaces public private(set) lazy var components: [String] = self.contents.smartSplit() - + init(contents: String, kind: Kind, sourceMap: SourceMap) { self.contents = contents self.kind = kind self.sourceMap = sourceMap } - + /// A token representing a piece of text. public static func text(value: String, at sourceMap: SourceMap) -> Token { return Token(contents: value, kind: .text, sourceMap: sourceMap) @@ -121,9 +123,8 @@ public class Token: Equatable { public static func block(value: String, at sourceMap: SourceMap) -> Token { return Token(contents: value, kind: .block, sourceMap: sourceMap) } - + public static func == (lhs: Token, rhs: Token) -> Bool { return lhs.contents == rhs.contents && lhs.kind == rhs.kind && lhs.sourceMap == rhs.sourceMap } - } diff --git a/Sources/Variable.swift b/Sources/Variable.swift index b2531b9..44569df 100644 --- a/Sources/Variable.swift +++ b/Sources/Variable.swift @@ -1,15 +1,13 @@ import Foundation - typealias Number = Float - -class FilterExpression : Resolvable { +class FilterExpression: Resolvable { let filters: [(FilterType, [Variable])] let variable: Variable init(token: String, environment: Environment) throws { - let bits = token.smartSplit(separator: "|").map({ String($0).trim(character: " ") }) + let bits = token.smartSplit(separator: "|").map { String($0).trim(character: " ") } if bits.isEmpty { throw TemplateSyntaxError("Variable tags must include at least 1 argument") } @@ -32,15 +30,15 @@ class FilterExpression : Resolvable { func resolve(_ context: Context) throws -> Any? { let result = try variable.resolve(context) - 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, context: context) + return try filters.reduce(result) { value, filter in + let arguments = try filter.1.map { try $0.resolve(context) } + return try filter.0.invoke(value: value, arguments: arguments, context: context) } } } /// A structure used to represent a template variable, and to resolve it in a given context. -public struct Variable : Equatable, Resolvable { +public struct Variable: Equatable, Resolvable { public let variable: String /// Create a variable with a string representing the variable @@ -48,16 +46,8 @@ public struct Variable : Equatable, Resolvable { self.variable = variable } - // Split the lookup string and resolve references if possible - fileprivate func lookup(_ context: Context) throws -> [String] { - let keyPath = KeyPath(variable, in: context) - return try keyPath.parse() - } - /// Resolve the variable in the given context public func resolve(_ context: Context) throws -> Any? { - var current: Any? = context - if (variable.hasPrefix("'") && variable.hasSuffix("'")) || (variable.hasPrefix("\"") && variable.hasSuffix("\"")) { // String literal return String(variable[variable.index(after: variable.startIndex) ..< variable.index(before: variable.endIndex)]) @@ -75,35 +65,11 @@ public struct Variable : Equatable, Resolvable { return bool } + var current: Any? = context for bit in try lookup(context) { - current = normalize(current) + current = resolve(bit: bit, context: current) - if let context = current as? Context { - current = context[bit] - } else if let dictionary = current as? [String: Any] { - if bit == "count" { - current = dictionary.count - } else { - current = dictionary[bit] - } - } else if let array = current as? [Any] { - current = resolveCollection(array, bit: bit) - } else if let string = current as? String { - current = resolveCollection(string, bit: bit) - } else if let object = current as? NSObject { // NSKeyValueCoding - #if os(Linux) - return nil - #else - if object.responds(to: Selector(bit)) { - current = object.value(forKey: bit) - } - #endif - } else if let value = current { - current = Mirror(reflecting: value).getValue(for: bit) - if current == nil { - return nil - } - } else { + if current == nil { return nil } } @@ -116,23 +82,66 @@ public struct Variable : Equatable, Resolvable { return normalize(current) } -} -private func resolveCollection(_ collection: T, bit: String) -> Any? { - if let index = Int(bit) { - if index >= 0 && index < collection.count { - return collection[collection.index(collection.startIndex, offsetBy: index)] + // Split the lookup string and resolve references if possible + private func lookup(_ context: Context) throws -> [String] { + let keyPath = KeyPath(variable, in: context) + return try keyPath.parse() + } + + // Try to resolve a partial keypath for the given context + private func resolve(bit: String, context: Any?) -> Any? { + let context = normalize(context) + + if let context = context as? Context { + return context[bit] + } else if let dictionary = context as? [String: Any] { + return resolve(bit: bit, dictionary: dictionary) + } else if let array = context as? [Any] { + return resolve(bit: bit, collection: array) + } else if let string = context as? String { + return resolve(bit: bit, collection: string) + } else if let object = context as? NSObject { // NSKeyValueCoding + #if os(Linux) + return nil + #else + if object.responds(to: Selector(bit)) { + return object.value(forKey: bit) + } + #endif + } else if let value = context { + return Mirror(reflecting: value).getValue(for: bit) + } + + return nil + } + + // Try to resolve a partial keypath for the given dictionary + private func resolve(bit: String, dictionary: [String: Any]) -> Any? { + if bit == "count" { + return dictionary.count + } else { + return dictionary[bit] + } + } + + // Try to resolve a partial keypath for the given collection + private func resolve(bit: String, collection: T) -> Any? { + if let index = Int(bit) { + if index >= 0 && index < collection.count { + return collection[collection.index(collection.startIndex, offsetBy: index)] + } else { + return nil + } + } else if bit == "first" { + return collection.first + } else if bit == "last" { + return collection[collection.index(collection.endIndex, offsetBy: -1)] + } else if bit == "count" { + return collection.count } else { return nil } - } else if bit == "first" { - return collection.first - } else if bit == "last" { - return collection[collection.index(collection.endIndex, offsetBy: -1)] - } else if bit == "count" { - return collection.count - } else { - return nil } } @@ -142,6 +151,7 @@ private func resolveCollection(_ collection: T, bit: String) -> A /// If `from` is more than `to` array will contain values of reversed range. public struct RangeVariable: Resolvable { public let from: Resolvable + // swiftlint:disable:next identifier_name public let to: Resolvable public init?(_ token: String, environment: Environment) throws { @@ -165,24 +175,23 @@ public struct RangeVariable: Resolvable { } public func resolve(_ context: Context) throws -> Any? { - let fromResolved = try from.resolve(context) - let toResolved = try to.resolve(context) + let lowerResolved = try from.resolve(context) + let upperResolved = try to.resolve(context) - guard let from = fromResolved.flatMap(toNumber(value:)).flatMap(Int.init) else { - throw TemplateSyntaxError("'from' value is not an Integer (\(fromResolved ?? "nil"))") + guard let lower = lowerResolved.flatMap(toNumber(value:)).flatMap(Int.init) else { + throw TemplateSyntaxError("'from' value is not an Integer (\(lowerResolved ?? "nil"))") } - guard let to = toResolved.flatMap(toNumber(value:)).flatMap(Int.init) else { - throw TemplateSyntaxError("'to' value is not an Integer (\(toResolved ?? "nil") )") + guard let upper = upperResolved.flatMap(toNumber(value:)).flatMap(Int.init) else { + throw TemplateSyntaxError("'to' value is not an Integer (\(upperResolved ?? "nil") )") } - let range = min(from, to)...max(from, to) - return from > to ? Array(range.reversed()) : Array(range) + let range = min(lower, upper)...max(lower, upper) + return lower > upper ? Array(range.reversed()) : Array(range) } } - func normalize(_ current: Any?) -> Any? { if let current = current as? Normalizable { return current.normalize() @@ -195,19 +204,19 @@ protocol Normalizable { func normalize() -> Any? } -extension Array : Normalizable { +extension Array: Normalizable { func normalize() -> Any? { return map { $0 as Any } } } -extension NSArray : Normalizable { +extension NSArray: Normalizable { func normalize() -> Any? { return map { $0 as Any } } } -extension Dictionary : Normalizable { +extension Dictionary: Normalizable { func normalize() -> Any? { var dictionary: [String: Any] = [:] @@ -235,7 +244,7 @@ func parseFilterComponents(token: String) -> (String, [Variable]) { extension Mirror { func getValue(for key: String) -> Any? { - let result = descendant(key) ?? Int(key).flatMap({ descendant($0) }) + let result = descendant(key) ?? Int(key).flatMap { descendant($0) } if result == nil { // go through inheritance chain to reach superclass properties return superclassMirror?.getValue(for: key) @@ -267,5 +276,3 @@ extension Optional: AnyOptional { } } } - - diff --git a/Sources/_SwiftSupport.swift b/Sources/_SwiftSupport.swift index 5333659..2441350 100644 --- a/Sources/_SwiftSupport.swift +++ b/Sources/_SwiftSupport.swift @@ -10,16 +10,16 @@ import Foundation #if !swift(>=4.1) public extension Collection { - func index(_ i: Self.Index, offsetBy n: Int) -> Self.Index { - let indexDistance = Self.IndexDistance(n) - return index(i, offsetBy: indexDistance) + func index(_ index: Self.Index, offsetBy offset: Int) -> Self.Index { + let indexDistance = Self.IndexDistance(offset) + return self.index(index, offsetBy: indexDistance) } } #endif #if !swift(>=4.1) public extension TemplateSyntaxError { - public static func ==(lhs: TemplateSyntaxError, rhs: TemplateSyntaxError) -> Bool { + public static func == (lhs: TemplateSyntaxError, rhs: TemplateSyntaxError) -> Bool { return lhs.reason == rhs.reason && lhs.description == rhs.description && lhs.token == rhs.token && @@ -31,7 +31,7 @@ public extension TemplateSyntaxError { #if !swift(>=4.1) public extension Variable { - public static func ==(lhs: Variable, rhs: Variable) -> Bool { + public static func == (lhs: Variable, rhs: Variable) -> Bool { return lhs.variable == rhs.variable } }