Merge branch 'master' into dynamic-filter

This commit is contained in:
Ilya Puchka
2018-10-01 22:45:21 +01:00
committed by GitHub
15 changed files with 116 additions and 57 deletions

View File

@@ -1,11 +1,16 @@
matrix: matrix:
include: include:
- os: osx
osx_image: xcode9.2
env: SWIFT_VERSION=4.0.3
- os: osx - os: osx
osx_image: xcode9.4 osx_image: xcode9.4
env: SWIFT_VERSION=4.1 env: SWIFT_VERSION=4.1
- os: osx - os: osx
osx_image: xcode10 osx_image: xcode10
env: SWIFT_VERSION=4.2 env: SWIFT_VERSION=4.2
- os: linux
env: SWIFT_VERSION=4.0.3
- os: linux - os: linux
env: SWIFT_VERSION=4.1 env: SWIFT_VERSION=4.1
- os: linux - os: linux

View File

@@ -19,11 +19,15 @@ _None_
### Bug Fixes ### Bug Fixes
_None_ - Fixed using parenthesis in boolean expressions, they now can be used without spaces around them.
[Ilya Puchka](https://github.com/ilyapuchka)
[#254](https://github.com/stencilproject/Stencil/pull/254)
### Internal Changes ### Internal Changes
_None_ - `Token` type converted to struct to allow computing token components only once.
[Ilya Puchka](https://github.com/ilyapuchka)
[#256](https://github.com/stencilproject/Stencil/pull/256)
## 0.13.1 ## 0.13.1

View File

@@ -1,4 +1,4 @@
// swift-tools-version:4.1 // swift-tools-version:4.0
import PackageDescription import PackageDescription
let package = Package( let package = Package(

View File

@@ -4,7 +4,7 @@ class FilterNode : NodeType {
let token: Token? let token: Token?
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType { class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
let bits = token.components() let bits = token.components
guard bits.count == 2 else { guard bits.count == 2 else {
throw TemplateSyntaxError("'filter' tag takes one argument, the filter expression") throw TemplateSyntaxError("'filter' tag takes one argument, the filter expression")

View File

@@ -9,7 +9,7 @@ class ForNode : NodeType {
let token: Token? 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() let components = token.components
func hasToken(_ token: String, at index: Int) -> Bool { func hasToken(_ token: String, at index: Int) -> Bool {
return components.count > (index + 1) && components[index] == token return components.count > (index + 1) && components[index] == token

View File

@@ -236,7 +236,7 @@ class IfNode : NodeType {
let token: Token? let token: Token?
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType { class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
var components = token.components() var components = token.components
components.removeFirst() components.removeFirst()
let expression = try parser.compileExpression(components: components, token: token) let expression = try parser.compileExpression(components: components, token: token)
@@ -247,7 +247,7 @@ class IfNode : NodeType {
var nextToken = parser.nextToken() var nextToken = parser.nextToken()
while let current = nextToken, current.contents.hasPrefix("elif") { while let current = nextToken, current.contents.hasPrefix("elif") {
var components = current.components() var components = current.components
components.removeFirst() components.removeFirst()
let expression = try parser.compileExpression(components: components, token: current) let expression = try parser.compileExpression(components: components, token: current)
@@ -269,7 +269,7 @@ class IfNode : NodeType {
} }
class func parse_ifnot(_ parser: TokenParser, token: Token) throws -> NodeType { class func parse_ifnot(_ parser: TokenParser, token: Token) throws -> NodeType {
var components = token.components() var components = token.components
guard components.count == 2 else { guard components.count == 2 else {
throw TemplateSyntaxError("'ifnot' statements should use the following syntax 'ifnot condition'.") throw TemplateSyntaxError("'ifnot' statements should use the following syntax 'ifnot condition'.")
} }

View File

@@ -7,7 +7,7 @@ class IncludeNode : NodeType {
let token: Token? let token: Token?
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType { class func parse(_ parser: TokenParser, token: Token) throws -> 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(""" throw TemplateSyntaxError("""

View File

@@ -53,7 +53,7 @@ class ExtendsNode : NodeType {
let token: Token? let token: Token?
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType { class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
let bits = token.components() let bits = token.components
guard bits.count == 2 else { guard bits.count == 2 else {
throw TemplateSyntaxError("'extends' takes one argument, the template file to be extended") throw TemplateSyntaxError("'extends' takes one argument, the template file to be extended")
@@ -124,7 +124,7 @@ class BlockNode : NodeType {
let token: Token? let token: Token?
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType { class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
let bits = token.components() let bits = token.components
guard bits.count == 2 else { guard bits.count == 2 else {
throw TemplateSyntaxError("'block' tag takes one argument, the block name") throw TemplateSyntaxError("'block' tag takes one argument, the block name")
@@ -163,7 +163,7 @@ class BlockNode : NodeType {
var childContext: [String: Any] = [BlockContext.contextKey: blockContext] var childContext: [String: Any] = [BlockContext.contextKey: blockContext]
if let blockSuperNode = child.nodes.first(where: { if let blockSuperNode = child.nodes.first(where: {
if case .variable(let variable, _)? = $0.token, variable == "block.super" { return true } if let token = $0.token, case .variable = token.kind, token.contents == "block.super" { return true }
else { return false} else { return false}
}) { }) {
do { do {

View File

@@ -62,7 +62,7 @@ public class VariableNode : NodeType {
let elseExpression: Resolvable? 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() var components = token.components
func hasToken(_ token: String, at index: Int) -> Bool { func hasToken(_ token: String, at index: Int) -> Bool {
return components.count > (index + 1) && components[index] == token return components.count > (index + 1) && components[index] == token

View File

@@ -9,7 +9,7 @@ class NowNode : NodeType {
class func parse(_ parser:TokenParser, token:Token) throws -> NodeType { class func parse(_ parser:TokenParser, token:Token) throws -> NodeType {
var format:Variable? var format:Variable?
let components = token.components() let components = token.components
guard components.count <= 2 else { guard components.count <= 2 else {
throw TemplateSyntaxError("'now' tags may only have one argument: the format string.") throw TemplateSyntaxError("'now' tags may only have one argument: the format string.")
} }

View File

@@ -1,6 +1,6 @@
public func until(_ tags: [String]) -> ((TokenParser, Token) -> Bool) { public func until(_ tags: [String]) -> ((TokenParser, Token) -> Bool) {
return { parser, token in return { parser, token in
if let name = token.components().first { if let name = token.components.first {
for tag in tags { for tag in tags {
if name == tag { if name == tag {
return true return true
@@ -36,9 +36,9 @@ public class TokenParser {
while tokens.count > 0 { while tokens.count > 0 {
let token = nextToken()! let token = nextToken()!
switch token { switch token.kind {
case .text(let text, _): case .text:
nodes.append(TextNode(text: text)) nodes.append(TextNode(text: token.contents))
case .variable: case .variable:
try nodes.append(VariableNode.parse(self, token: token)) try nodes.append(VariableNode.parse(self, token: token))
case .block: case .block:
@@ -47,7 +47,7 @@ public class TokenParser {
return nodes return nodes
} }
if let tag = token.components().first { if let tag = token.components.first {
do { do {
let parser = try environment.findTag(name: tag) let parser = try environment.findTag(name: tag)
let node = try parser(self, token) let node = try parser(self, token)

View File

@@ -17,6 +17,12 @@ extension String {
components[components.count-1] += word components[components.count-1] += word
} else if specialCharacters.contains(word) { } else if specialCharacters.contains(word) {
components[components.count-1] += 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 { } else {
components.append(word) components.append(word)
} }
@@ -71,47 +77,53 @@ public struct SourceMap: Equatable {
} }
} }
public enum Token : Equatable { public class Token: Equatable {
public enum Kind: Equatable {
/// A token representing a piece of text.
case text
/// A token representing a variable.
case variable
/// A token representing a comment.
case comment
/// 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. /// A token representing a piece of text.
case text(value: String, at: SourceMap) public static func text(value: String, at sourceMap: SourceMap) -> Token {
return Token(contents: value, kind: .text, sourceMap: sourceMap)
}
/// A token representing a variable. /// A token representing a variable.
case variable(value: String, at: SourceMap) public static func variable(value: String, at sourceMap: SourceMap) -> Token {
return Token(contents: value, kind: .variable, sourceMap: sourceMap)
}
/// A token representing a comment. /// A token representing a comment.
case comment(value: String, at: SourceMap) public static func comment(value: String, at sourceMap: SourceMap) -> Token {
return Token(contents: value, kind: .comment, sourceMap: sourceMap)
}
/// A token representing a template block. /// A token representing a template block.
case block(value: String, at: SourceMap) public static func block(value: String, at sourceMap: SourceMap) -> Token {
return Token(contents: value, kind: .block, sourceMap: sourceMap)
/// Returns the underlying value as an array seperated by spaces }
public func components() -> [String] {
switch self { public static func == (lhs: Token, rhs: Token) -> Bool {
case .block(let value, _), return lhs.contents == rhs.contents && lhs.kind == rhs.kind && lhs.sourceMap == rhs.sourceMap
.variable(let value, _),
.text(let value, _),
.comment(let value, _):
return value.smartSplit()
}
} }
public var contents: String {
switch self {
case .block(let value, _),
.variable(let value, _),
.text(let value, _),
.comment(let value, _):
return value
}
}
public var sourceMap: SourceMap {
switch self {
case .block(_, let sourceMap),
.variable(_, let sourceMap),
.text(_, let sourceMap),
.comment(_, let sourceMap):
return sourceMap
}
}
} }

View File

@@ -0,0 +1,38 @@
import Foundation
#if !swift(>=4.1)
public extension Sequence {
func compactMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] {
return try flatMap(transform)
}
}
#endif
#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)
}
}
#endif
#if !swift(>=4.1)
public extension TemplateSyntaxError {
public static func ==(lhs: TemplateSyntaxError, rhs: TemplateSyntaxError) -> Bool {
return lhs.reason == rhs.reason &&
lhs.description == rhs.description &&
lhs.token == rhs.token &&
lhs.stackTrace == rhs.stackTrace &&
lhs.templateName == rhs.templateName
}
}
#endif
#if !swift(>=4.1)
public extension Variable {
public static func ==(lhs: Variable, rhs: Variable) -> Bool {
return lhs.variable == rhs.variable
}
}
#endif

View File

@@ -145,7 +145,7 @@ class IfNodeTests: XCTestCase {
$0.it("can parse an if with complex expression") { $0.it("can parse an if with complex expression") {
let tokens: [Token] = [ let tokens: [Token] = [
.block(value: "if value == \"test\" and not name", at: .unknown), .block(value: "if value == \"test\" and (not name or not (name and surname) or( some )and other )", at: .unknown),
.text(value: "true", at: .unknown), .text(value: "true", at: .unknown),
.block(value: "endif", at: .unknown) .block(value: "endif", at: .unknown)
] ]

View File

@@ -7,7 +7,7 @@ class TokenTests: XCTestCase {
describe("Token") { describe("Token") {
$0.it("can split the contents into components") { $0.it("can split the contents into components") {
let token = Token.text(value: "hello world", at: .unknown) let token = Token.text(value: "hello world", at: .unknown)
let components = token.components() let components = token.components
try expect(components.count) == 2 try expect(components.count) == 2
try expect(components[0]) == "hello" try expect(components[0]) == "hello"
@@ -16,7 +16,7 @@ class TokenTests: XCTestCase {
$0.it("can split the contents into components with single quoted strings") { $0.it("can split the contents into components with single quoted strings") {
let token = Token.text(value: "hello 'kyle fuller'", at: .unknown) let token = Token.text(value: "hello 'kyle fuller'", at: .unknown)
let components = token.components() let components = token.components
try expect(components.count) == 2 try expect(components.count) == 2
try expect(components[0]) == "hello" try expect(components[0]) == "hello"
@@ -25,7 +25,7 @@ class TokenTests: XCTestCase {
$0.it("can split the contents into components with double quoted strings") { $0.it("can split the contents into components with double quoted strings") {
let token = Token.text(value: "hello \"kyle fuller\"", at: .unknown) let token = Token.text(value: "hello \"kyle fuller\"", at: .unknown)
let components = token.components() let components = token.components
try expect(components.count) == 2 try expect(components.count) == 2
try expect(components[0]) == "hello" try expect(components[0]) == "hello"