Merge branch 'master' into dynamic-filter
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// swift-tools-version:4.1
|
// swift-tools-version:4.0
|
||||||
import PackageDescription
|
import PackageDescription
|
||||||
|
|
||||||
let package = Package(
|
let package = Package(
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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'.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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("""
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
38
Sources/_SwiftSupport.swift
Normal file
38
Sources/_SwiftSupport.swift
Normal 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
|
||||||
@@ -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)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user