1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@
|
|||||||
Packages/
|
Packages/
|
||||||
Package.resolved
|
Package.resolved
|
||||||
Package.pins
|
Package.pins
|
||||||
|
*.xcodeproj
|
||||||
|
|||||||
14
.travis.yml
14
.travis.yml
@@ -1,18 +1,10 @@
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: osx
|
- os: osx
|
||||||
osx_image: xcode8.3
|
osx_image: xcode9.3
|
||||||
env: SWIFT_VERSION=3.1.1
|
env: SWIFT_VERSION=4.1
|
||||||
- os: osx
|
|
||||||
osx_image: xcode9
|
|
||||||
env: SWIFT_VERSION=4.0
|
|
||||||
- os: osx
|
|
||||||
osx_image: xcode9.1
|
|
||||||
env: SWIFT_VERSION=4.0
|
|
||||||
- os: linux
|
- os: linux
|
||||||
env: SWIFT_VERSION=3.1.1
|
env: SWIFT_VERSION=4.1
|
||||||
- os: linux
|
|
||||||
env: SWIFT_VERSION=4.0
|
|
||||||
language: generic
|
language: generic
|
||||||
sudo: required
|
sudo: required
|
||||||
dist: trusty
|
dist: trusty
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|
||||||
_None_
|
- Now requires Swift 4.1 or newer.
|
||||||
|
[Yonas Kolb](https://github.com/yonaskolb)
|
||||||
|
[#228](https://github.com/stencilproject/Stencil/pull/228)
|
||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,22 @@
|
|||||||
// swift-tools-version:3.1
|
// swift-tools-version:4.0
|
||||||
import PackageDescription
|
import PackageDescription
|
||||||
|
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "Stencil",
|
name: "Stencil",
|
||||||
|
products: [
|
||||||
|
.library(name: "Stencil", targets: ["Stencil"]),
|
||||||
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.Package(url: "https://github.com/kylef/PathKit.git", majorVersion: 0, minor: 9),
|
.package(url: "https://github.com/kylef/PathKit.git", from: "0.9.0"),
|
||||||
.Package(url: "https://github.com/kylef/Spectre.git", majorVersion: 0, minor: 8),
|
.package(url: "https://github.com/kylef/Spectre.git", from: "0.8.0"),
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
.target(name: "Stencil", dependencies: [
|
||||||
|
"PathKit",
|
||||||
|
], path: "Sources"),
|
||||||
|
.testTarget(name: "StencilTests", dependencies: [
|
||||||
|
"Stencil",
|
||||||
|
"Spectre",
|
||||||
|
])
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
// swift-tools-version:3.1
|
|
||||||
import PackageDescription
|
|
||||||
|
|
||||||
let package = Package(
|
|
||||||
name: "Stencil",
|
|
||||||
dependencies: [
|
|
||||||
.Package(url: "https://github.com/kylef/PathKit.git", majorVersion: 0, minor: 8),
|
|
||||||
.Package(url: "https://github.com/kylef/Spectre.git", majorVersion: 0, minor: 7),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
@@ -67,7 +67,7 @@ open class SimpleErrorReporter: ErrorReporter {
|
|||||||
func describe(token: Token) -> String {
|
func describe(token: Token) -> String {
|
||||||
let templateName = token.sourceMap.filename ?? ""
|
let templateName = token.sourceMap.filename ?? ""
|
||||||
let location = token.sourceMap.location
|
let location = token.sourceMap.location
|
||||||
let highlight = "\(String(Array(repeating: " ", count: location.lineOffset)))^\(String(Array(repeating: "~", count: max(token.contents.characters.count - 1, 0))))"
|
let highlight = "\(String(Array(repeating: " ", count: location.lineOffset)))^\(String(Array(repeating: "~", count: max(token.contents.count - 1, 0))))"
|
||||||
|
|
||||||
return "\(templateName)\(location.lineNumber):\(location.lineOffset): error: \(templateError.reason)\n"
|
return "\(templateName)\(location.lineNumber):\(location.lineOffset): error: \(templateError.reason)\n"
|
||||||
+ "\(location.content)\n"
|
+ "\(location.content)\n"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class ForNode : NodeType {
|
|||||||
throw TemplateSyntaxError("'for' statements should use the syntax: `for <x> in <y> [where <condition>]`.")
|
throw TemplateSyntaxError("'for' statements should use the syntax: `for <x> in <y> [where <condition>]`.")
|
||||||
}
|
}
|
||||||
|
|
||||||
let loopVariables = components[1].characters
|
let loopVariables = components[1]
|
||||||
.split(separator: ",")
|
.split(separator: ",")
|
||||||
.map(String.init)
|
.map(String.init)
|
||||||
.map { $0.trim(character: " ") }
|
.map { $0.trim(character: " ") }
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class ExtendsNode : NodeType {
|
|||||||
throw TemplateSyntaxError("'extends' cannot appear more than once in the same template")
|
throw TemplateSyntaxError("'extends' cannot appear more than once in the same template")
|
||||||
}
|
}
|
||||||
|
|
||||||
let blockNodes = parsedNodes.flatMap { $0 as? BlockNode }
|
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
|
var dict = accumulator
|
||||||
@@ -159,8 +159,8 @@ class BlockNode : NodeType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// child node is a block node from child template that extends this node (has the same name)
|
// child node is a block node from child template that extends this node (has the same name)
|
||||||
func childContext(_ child: BlockNode, blockContext: BlockContext, context: Context) throws -> [String: Any?] {
|
func childContext(_ child: BlockNode, blockContext: BlockContext, context: Context) throws -> [String: Any] {
|
||||||
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 case .variable(let variable, _)? = $0.token, variable == "block.super" { return true }
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ final class KeyPath {
|
|||||||
subscriptLevel = 0
|
subscriptLevel = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
for c in variable.characters {
|
for c in variable {
|
||||||
switch c {
|
switch c {
|
||||||
case "." where subscriptLevel == 0:
|
case "." where subscriptLevel == 0:
|
||||||
try foundSeparator()
|
try foundSeparator()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ struct Lexer {
|
|||||||
self.templateName = templateName
|
self.templateName = templateName
|
||||||
self.templateString = templateString
|
self.templateString = templateString
|
||||||
|
|
||||||
self.lines = templateString.components(separatedBy: .newlines).enumerated().flatMap {
|
self.lines = templateString.components(separatedBy: .newlines).enumerated().compactMap {
|
||||||
guard !$0.element.isEmpty else { return nil }
|
guard !$0.element.isEmpty else { return nil }
|
||||||
return (content: $0.element, number: UInt($0.offset + 1), templateString.range(of: $0.element)!)
|
return (content: $0.element, number: UInt($0.offset + 1), templateString.range(of: $0.element)!)
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ struct Lexer {
|
|||||||
|
|
||||||
func createToken(string: String, at range: Range<String.Index>) -> Token {
|
func createToken(string: String, at range: Range<String.Index>) -> Token {
|
||||||
func strip() -> String {
|
func strip() -> String {
|
||||||
guard string.characters.count > 4 else { return "" }
|
guard string.count > 4 else { return "" }
|
||||||
let start = string.index(string.startIndex, offsetBy: 2)
|
let start = string.index(string.startIndex, offsetBy: 2)
|
||||||
let end = string.index(string.endIndex, offsetBy: -2)
|
let end = string.index(string.endIndex, offsetBy: -2)
|
||||||
let trimmed = String(string[start..<end])
|
let trimmed = String(string[start..<end])
|
||||||
@@ -114,14 +114,14 @@ class Scanner {
|
|||||||
|
|
||||||
range = range.upperBound..<range.upperBound
|
range = range.upperBound..<range.upperBound
|
||||||
while index != content.endIndex {
|
while index != content.endIndex {
|
||||||
let substring = content.substring(from: index)
|
let substring = String(content[index...])
|
||||||
|
|
||||||
if substring.hasPrefix(until) {
|
if substring.hasPrefix(until) {
|
||||||
let result = content.substring(to: index)
|
let result = String(content[..<index])
|
||||||
|
|
||||||
if returnUntil {
|
if returnUntil {
|
||||||
range = range.lowerBound..<originalContent.index(range.upperBound, offsetBy: until.characters.count)
|
range = range.lowerBound..<originalContent.index(range.upperBound, offsetBy: until.count)
|
||||||
content = substring.substring(from: until.endIndex)
|
content = String(substring[until.endIndex...])
|
||||||
return result + until
|
return result + until
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,10 +145,10 @@ class Scanner {
|
|||||||
var index = content.startIndex
|
var index = content.startIndex
|
||||||
range = range.upperBound..<range.upperBound
|
range = range.upperBound..<range.upperBound
|
||||||
while index != content.endIndex {
|
while index != content.endIndex {
|
||||||
let substring = content.substring(from: index)
|
let substring = String(content[index...])
|
||||||
for string in until {
|
for string in until {
|
||||||
if substring.hasPrefix(string) {
|
if substring.hasPrefix(string) {
|
||||||
let result = content.substring(to: index)
|
let result = String(content[..<index])
|
||||||
content = substring
|
content = substring
|
||||||
return (string, result)
|
return (string, result)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ public class TokenParser {
|
|||||||
let filtersWithDistance = allFilters
|
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
|
// do not suggest filters which names are shorter than the distance
|
||||||
.filter({ $0.filterName.characters.count > $0.distance })
|
.filter({ $0.filterName.count > $0.distance })
|
||||||
guard let minDistance = filtersWithDistance.min(by: { $0.distance < $1.distance })?.distance else {
|
guard let minDistance = filtersWithDistance.min(by: { $0.distance < $1.distance })?.distance else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@@ -167,10 +167,10 @@ extension String {
|
|||||||
// initialize v0 (the previous row of distances)
|
// initialize v0 (the previous row of distances)
|
||||||
// this row is A[0][i]: edit distance for an empty s
|
// this row is A[0][i]: edit distance for an empty s
|
||||||
// the distance is just the number of characters to delete from t
|
// the distance is just the number of characters to delete from t
|
||||||
last = [Int](0...target.characters.count)
|
last = [Int](0...target.count)
|
||||||
current = [Int](repeating: 0, count: target.characters.count + 1)
|
current = [Int](repeating: 0, count: target.count + 1)
|
||||||
|
|
||||||
for i in 0..<self.characters.count {
|
for i in 0..<self.count {
|
||||||
// calculate v1 (current row distances) from the previous row v0
|
// calculate v1 (current row distances) from the previous row v0
|
||||||
|
|
||||||
// first element of v1 is A[i+1][0]
|
// first element of v1 is A[i+1][0]
|
||||||
@@ -178,7 +178,7 @@ extension String {
|
|||||||
current[0] = i + 1
|
current[0] = i + 1
|
||||||
|
|
||||||
// use formula to fill in the rest of the row
|
// use formula to fill in the rest of the row
|
||||||
for j in 0..<target.characters.count {
|
for j in 0..<target.count {
|
||||||
current[j+1] = Swift.min(
|
current[j+1] = Swift.min(
|
||||||
last[j+1] + 1,
|
last[j+1] + 1,
|
||||||
current[j] + 1,
|
current[j] + 1,
|
||||||
@@ -190,7 +190,7 @@ extension String {
|
|||||||
last = current
|
last = current
|
||||||
}
|
}
|
||||||
|
|
||||||
return current[target.characters.count]
|
return current[target.count]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ extension String {
|
|||||||
let specialCharacters = ",|:"
|
let specialCharacters = ",|:"
|
||||||
func appendWord(_ word: String) {
|
func appendWord(_ word: String) {
|
||||||
if components.count > 0 {
|
if components.count > 0 {
|
||||||
if let precedingChar = components.last?.characters.last, specialCharacters.characters.contains(precedingChar) {
|
if let precedingChar = components.last?.last, specialCharacters.contains(precedingChar) {
|
||||||
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
|
||||||
@@ -25,7 +25,7 @@ extension String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for character in self.characters {
|
for character in self {
|
||||||
if character == "'" { singleQuoteCount += 1 }
|
if character == "'" { singleQuoteCount += 1 }
|
||||||
else if character == "\"" { doubleQuoteCount += 1 }
|
else if character == "\"" { doubleQuoteCount += 1 }
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class FilterExpression : Resolvable {
|
|||||||
let variable: Variable
|
let variable: Variable
|
||||||
|
|
||||||
init(token: String, parser: TokenParser) throws {
|
init(token: String, parser: TokenParser) throws {
|
||||||
let bits = token.characters.split(separator: "|").map({ String($0).trim(character: " ") })
|
let bits = token.split(separator: "|").map({ String($0).trim(character: " ") })
|
||||||
if bits.isEmpty {
|
if bits.isEmpty {
|
||||||
throw TemplateSyntaxError("Variable tags must include at least 1 argument")
|
throw TemplateSyntaxError("Variable tags must include at least 1 argument")
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ public struct Variable : Equatable, Resolvable {
|
|||||||
|
|
||||||
if (variable.hasPrefix("'") && variable.hasSuffix("'")) || (variable.hasPrefix("\"") && variable.hasSuffix("\"")) {
|
if (variable.hasPrefix("'") && variable.hasSuffix("'")) || (variable.hasPrefix("\"") && variable.hasSuffix("\"")) {
|
||||||
// String literal
|
// String literal
|
||||||
return String(variable[variable.characters.index(after: variable.startIndex) ..< variable.characters.index(before: variable.endIndex)])
|
return String(variable[variable.index(after: variable.startIndex) ..< variable.index(before: variable.endIndex)])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Number literal
|
// Number literal
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
"osx": "10.9",
|
"osx": "10.9",
|
||||||
"tvos": "9.0"
|
"tvos": "9.0"
|
||||||
},
|
},
|
||||||
|
"cocoapods_version": "1.4.0",
|
||||||
|
"swift_version": "4.1",
|
||||||
"requires_arc": true,
|
"requires_arc": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"PathKit": [
|
"PathKit": [
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ func testForNode() {
|
|||||||
let template = Template(templateString: templateString)
|
let template = Template(templateString: templateString)
|
||||||
let result = try template.render(context)
|
let result = try template.render(context)
|
||||||
|
|
||||||
let sortedResult = result.characters.split(separator: ",").map(String.init).sorted(by: <)
|
let sortedResult = result.split(separator: ",").map(String.init).sorted(by: <)
|
||||||
try expect(sortedResult) == ["one: I", "two: II"]
|
try expect(sortedResult) == ["one: I", "two: II"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ func testForNode() {
|
|||||||
let node = ForNode(resolvable: Variable("dict"), loopVariables: ["key"], nodes: nodes, emptyNodes: emptyNodes, where: nil)
|
let node = ForNode(resolvable: Variable("dict"), loopVariables: ["key"], nodes: nodes, emptyNodes: emptyNodes, where: nil)
|
||||||
let result = try node.render(context)
|
let result = try node.render(context)
|
||||||
|
|
||||||
let sortedResult = result.characters.split(separator: ",").map(String.init).sorted(by: <)
|
let sortedResult = result.split(separator: ",").map(String.init).sorted(by: <)
|
||||||
try expect(sortedResult) == ["one", "two"]
|
try expect(sortedResult) == ["one", "two"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ func testForNode() {
|
|||||||
|
|
||||||
let result = try node.render(context)
|
let result = try node.render(context)
|
||||||
|
|
||||||
let sortedResult = result.characters.split(separator: ",").map(String.init).sorted(by: <)
|
let sortedResult = result.split(separator: ",").map(String.init).sorted(by: <)
|
||||||
try expect(sortedResult) == ["one=I", "two=II"]
|
try expect(sortedResult) == ["one=I", "two=II"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user