Skip to content

Commit dd5cb84

Browse files
committed
Update code to match Stencil changes
- Add `token` attribute for better errors support
- Switches to resolvable where possible
- Had to make the `CallableBlock` a non-`NodeType` to avoid rendering during resolvable resolution t
1 parent 5a6ff4e commit dd5cb84

File tree

7 files changed

+120
-98
lines changed

7 files changed

+120
-98
lines changed

Sources/CallMacroNodes.swift

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@
66

77
import Stencil
88

9-
struct CallableBlock: NodeType {
9+
struct CallableBlock {
1010
let parameters: [String]
1111
let nodes: [NodeType]
1212

13+
init(parameters: [String], nodes: [NodeType], token: Token? = nil) {
14+
self.parameters = parameters
15+
self.nodes = nodes
16+
}
17+
1318
func context(_ context: Context, arguments: [Resolvable]) throws -> [String: Any] {
1419
var result = [String: Any]()
1520

@@ -19,16 +24,13 @@ struct CallableBlock: NodeType {
1924

2025
return result
2126
}
22-
23-
func render(_ context: Context) throws -> String {
24-
throw TemplateSyntaxError("A callable block must be called using the 'call' tag.")
25-
}
2627
}
2728

2829
class MacroNode: NodeType {
2930
let variableName: String
3031
let parameters: [String]
3132
let nodes: [NodeType]
33+
let token: Token?
3234

3335
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
3436
let components = token.components()
@@ -43,49 +45,55 @@ class MacroNode: NodeType {
4345
throw TemplateSyntaxError("`endmacro` was not found.")
4446
}
4547

46-
return MacroNode(variableName: variable, parameters: parameters, nodes: setNodes)
48+
return MacroNode(variableName: variable, parameters: parameters, nodes: setNodes, token: token)
4749
}
4850

49-
init(variableName: String, parameters: [String], nodes: [NodeType]) {
51+
init(variableName: String, parameters: [String], nodes: [NodeType], token: Token? = nil) {
5052
self.variableName = variableName
5153
self.parameters = parameters
5254
self.nodes = nodes
55+
self.token = token
5356
}
5457

5558
func render(_ context: Context) throws -> String {
56-
let result = CallableBlock(parameters: parameters, nodes: nodes)
59+
let result = CallableBlock(parameters: parameters, nodes: nodes, token: token)
5760
context[variableName] = result
5861
return ""
5962
}
6063
}
6164

6265
class CallNode: NodeType {
63-
let variableName: String
66+
let variable: Variable
6467
let arguments: [Resolvable]
68+
let token: Token?
6569

6670
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
6771
let components = token.components()
6872
guard components.count >= 2 else {
6973
throw TemplateSyntaxError("'call' tag takes at least one argument, the name of the block to call.")
7074
}
71-
let variable = components[1]
72-
let arguments = try Array(components.dropFirst(2)).map(parser.compileFilter)
7375

74-
return CallNode(variableName: variable, arguments: arguments)
76+
let variable = Variable(components[1])
77+
let arguments = try Array(components.dropFirst(2)).map {
78+
try parser.compileResolvable($0, containedIn: token)
79+
}
80+
81+
return CallNode(variable: variable, arguments: arguments, token: token)
7582
}
7683

77-
init(variableName: String, arguments: [Resolvable]) {
78-
self.variableName = variableName
84+
init(variable: Variable, arguments: [Resolvable], token: Token? = nil) {
85+
self.variable = variable
7986
self.arguments = arguments
87+
self.token = token
8088
}
8189

8290
func render(_ context: Context) throws -> String {
83-
guard let block = context[variableName] as? CallableBlock else {
84-
throw TemplateSyntaxError("Call to undefined block '\(variableName)'.")
91+
guard let block = try variable.resolve(context) as? CallableBlock else {
92+
throw TemplateSyntaxError("Call to undefined block '\(variable.variable)'.")
8593
}
8694
guard block.parameters.count == arguments.count else {
8795
throw TemplateSyntaxError("""
88-
Block '\(variableName)' accepts \(block.parameters.count) parameters, \
96+
Block '\(variable.variable)' accepts \(block.parameters.count) parameters, \
8997
\(arguments.count) given.
9098
""")
9199
}

Sources/MapNode.swift

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,33 @@
88
import Stencil
99

1010
class MapNode: NodeType {
11-
let variable: Variable
11+
let resolvable: Resolvable
1212
let resultName: String
1313
let mapVariable: String?
1414
let nodes: [NodeType]
15+
let token: Token?
1516

1617
class func parse(parser: TokenParser, token: Token) throws -> NodeType {
1718
let components = token.components()
1819

19-
guard components.count == 4 && components[2] == "into" ||
20-
components.count == 6 && components[2] == "into" && components[4] == "using" else {
20+
func hasToken(_ token: String, at index: Int) -> Bool {
21+
return components.count > (index + 1) && components[index] == token
22+
}
23+
24+
func endsOrHasToken(_ token: String, at index: Int) -> Bool {
25+
return components.count == index || hasToken(token, at: index)
26+
}
27+
28+
guard hasToken("into", at: 2) && endsOrHasToken("using", at: 4) else {
2129
throw TemplateSyntaxError("""
2230
'map' statements should use the following 'map {array} into \
23-
{varname} [using {element}]' `\(token.contents)`.
31+
{varname} [using {element}]'.
2432
""")
2533
}
2634

27-
let variable = components[1]
35+
let resolvable = try parser.compileResolvable(components[1], containedIn: token)
2836
let resultName = components[3]
29-
var mapVariable: String? = nil
30-
if components.count > 4 {
31-
mapVariable = components[5]
32-
}
37+
let mapVariable = hasToken("using", at: 4) ? components[5] : nil
3338

3439
let mapNodes = try parser.parse(until(["endmap", "empty"]))
3540

@@ -41,18 +46,25 @@ class MapNode: NodeType {
4146
_ = parser.nextToken()
4247
}
4348

44-
return MapNode(variable: variable, resultName: resultName, mapVariable: mapVariable, nodes: mapNodes)
49+
return MapNode(
50+
resolvable: resolvable,
51+
resultName: resultName,
52+
mapVariable: mapVariable,
53+
nodes: mapNodes,
54+
token: token
55+
)
4556
}
4657

47-
init(variable: String, resultName: String, mapVariable: String?, nodes: [NodeType]) {
48-
self.variable = Variable(variable)
58+
init(resolvable: Resolvable, resultName: String, mapVariable: String?, nodes: [NodeType], token: Token? = nil) {
59+
self.resolvable = resolvable
4960
self.resultName = resultName
5061
self.mapVariable = mapVariable
5162
self.nodes = nodes
63+
self.token = token
5264
}
5365

5466
func render(_ context: Context) throws -> String {
55-
let values = try variable.resolve(context)
67+
let values = try resolvable.resolve(context)
5668

5769
if let values = values as? [Any], !values.isEmpty {
5870
let mappedValues: [String] = try values.enumerated().map { index, item in

Sources/SetNode.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class SetNode: NodeType {
1414

1515
let variableName: String
1616
let content: Content
17+
let token: Token?
1718

1819
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
1920
let components = token.components()
@@ -27,8 +28,8 @@ class SetNode: NodeType {
2728
let variable = components[1]
2829
if components.count == 3 {
2930
// we have a value expression, no nodes
30-
let value = try parser.compileFilter(components[2])
31-
return SetNode(variableName: variable, content: .reference(to: value))
31+
let resolvable = try parser.compileResolvable(components[1], containedIn: token)
32+
return SetNode(variableName: variable, content: .reference(to: resolvable))
3233
} else {
3334
// no value expression, parse until an `endset` node
3435
let setNodes = try parser.parse(until(["endset"]))
@@ -37,13 +38,14 @@ class SetNode: NodeType {
3738
throw TemplateSyntaxError("`endset` was not found.")
3839
}
3940

40-
return SetNode(variableName: variable, content: .nodes(setNodes))
41+
return SetNode(variableName: variable, content: .nodes(setNodes), token: token)
4142
}
4243
}
4344

44-
init(variableName: String, content: Content) {
45+
init(variableName: String, content: Content, token: Token? = nil) {
4546
self.variableName = variableName
4647
self.content = content
48+
self.token = token
4749
}
4850

4951
func render(_ context: Context) throws -> String {

Tests/StencilSwiftKitTests/CallNodeTests.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import XCTest
1111
class CallNodeTests: XCTestCase {
1212
func testParser() {
1313
let tokens: [Token] = [
14-
.block(value: "call myFunc")
14+
.block(value: "call myFunc", at: .unknown)
1515
]
1616

1717
let parser = TokenParser(tokens: tokens, environment: stencilSwiftEnvironment())
@@ -21,13 +21,13 @@ class CallNodeTests: XCTestCase {
2121
return
2222
}
2323

24-
XCTAssertEqual(node.variableName, "myFunc")
24+
XCTAssertEqual(node.variable.variable, "myFunc")
2525
XCTAssertEqual(node.arguments.count, 0)
2626
}
2727

2828
func testParserWithArguments() {
2929
let tokens: [Token] = [
30-
.block(value: "call myFunc a b c")
30+
.block(value: "call myFunc a b c", at: .unknown)
3131
]
3232

3333
let parser = TokenParser(tokens: tokens, environment: stencilSwiftEnvironment())
@@ -37,15 +37,15 @@ class CallNodeTests: XCTestCase {
3737
return
3838
}
3939

40-
XCTAssertEqual(node.variableName, "myFunc")
40+
XCTAssertEqual(node.variable.variable, "myFunc")
4141
let variables = node.arguments.compactMap { $0 as? FilterExpression }.compactMap { $0.variable }
4242
XCTAssertEqual(variables, [Variable("a"), Variable("b"), Variable("c")])
4343
}
4444

4545
func testParserFail() {
4646
do {
4747
let tokens: [Token] = [
48-
.block(value: "call")
48+
.block(value: "call", at: .unknown)
4949
]
5050

5151
let parser = TokenParser(tokens: tokens, environment: stencilSwiftEnvironment())
@@ -56,15 +56,15 @@ class CallNodeTests: XCTestCase {
5656
func testRender() throws {
5757
let block = CallableBlock(parameters: [], nodes: [TextNode(text: "hello")])
5858
let context = Context(dictionary: ["myFunc": block])
59-
let node = CallNode(variableName: "myFunc", arguments: [])
59+
let node = CallNode(variable: Variable("myFunc"), arguments: [])
6060
let output = try node.render(context)
6161

6262
XCTAssertEqual(output, "hello")
6363
}
6464

6565
func testRenderFail() {
6666
let context = Context(dictionary: [:])
67-
let node = CallNode(variableName: "myFunc", arguments: [])
67+
let node = CallNode(variable: Variable("myFunc"), arguments: [])
6868

6969
XCTAssertThrowsError(try node.render(context))
7070
}
@@ -77,7 +77,7 @@ class CallNodeTests: XCTestCase {
7777
VariableNode(variable: "c")
7878
])
7979
let context = Context(dictionary: ["myFunc": block])
80-
let node = CallNode(variableName: "myFunc", arguments: [
80+
let node = CallNode(variable: Variable("myFunc"), arguments: [
8181
Variable("\"hello\""),
8282
Variable("\"world\""),
8383
Variable("\"test\"")
@@ -98,21 +98,21 @@ class CallNodeTests: XCTestCase {
9898

9999
// must pass arguments
100100
do {
101-
let node = CallNode(variableName: "myFunc", arguments: [])
101+
let node = CallNode(variable: Variable("myFunc"), arguments: [])
102102
XCTAssertThrowsError(try node.render(context))
103103
}
104104

105105
// not enough arguments
106106
do {
107-
let node = CallNode(variableName: "myFunc", arguments: [
107+
let node = CallNode(variable: Variable("myFunc"), arguments: [
108108
Variable("\"hello\"")
109109
])
110110
XCTAssertThrowsError(try node.render(context))
111111
}
112112

113113
// too many arguments
114114
do {
115-
let node = CallNode(variableName: "myFunc", arguments: [
115+
let node = CallNode(variable: Variable("myFunc"), arguments: [
116116
Variable("\"hello\""),
117117
Variable("\"world\""),
118118
Variable("\"test\""),

Tests/StencilSwiftKitTests/MacroNodeTests.swift

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import XCTest
1111
class MacroNodeTests: XCTestCase {
1212
func testParser() {
1313
let tokens: [Token] = [
14-
.block(value: "macro myFunc"),
15-
.text(value: "hello"),
16-
.block(value: "endmacro")
14+
.block(value: "macro myFunc", at: .unknown),
15+
.text(value: "hello", at: .unknown),
16+
.block(value: "endmacro", at: .unknown)
1717
]
1818

1919
let parser = TokenParser(tokens: tokens, environment: stencilSwiftEnvironment())
@@ -31,9 +31,9 @@ class MacroNodeTests: XCTestCase {
3131

3232
func testParserWithParameters() {
3333
let tokens: [Token] = [
34-
.block(value: "macro myFunc a b c"),
35-
.text(value: "hello"),
36-
.block(value: "endmacro")
34+
.block(value: "macro myFunc a b c", at: .unknown),
35+
.text(value: "hello", at: .unknown),
36+
.block(value: "endmacro", at: .unknown)
3737
]
3838

3939
let parser = TokenParser(tokens: tokens, environment: stencilSwiftEnvironment())
@@ -52,8 +52,8 @@ class MacroNodeTests: XCTestCase {
5252
func testParserFail() {
5353
do {
5454
let tokens: [Token] = [
55-
.block(value: "macro myFunc"),
56-
.text(value: "hello")
55+
.block(value: "macro myFunc", at: .unknown),
56+
.text(value: "hello", at: .unknown)
5757
]
5858

5959
let parser = TokenParser(tokens: tokens, environment: stencilSwiftEnvironment())
@@ -62,9 +62,9 @@ class MacroNodeTests: XCTestCase {
6262

6363
do {
6464
let tokens: [Token] = [
65-
.block(value: "macro"),
66-
.text(value: "hello"),
67-
.block(value: "endmacro")
65+
.block(value: "macro", at: .unknown),
66+
.text(value: "hello", at: .unknown),
67+
.block(value: "endmacro", at: .unknown)
6868
]
6969

7070
let parser = TokenParser(tokens: tokens, environment: stencilSwiftEnvironment())
@@ -116,13 +116,6 @@ class MacroNodeTests: XCTestCase {
116116
XCTAssert(block.nodes.first is TextNode)
117117
}
118118

119-
func testCallableBlockRender() {
120-
let block = CallableBlock(parameters: [], nodes: [TextNode(text: "hello")])
121-
let context = Context(dictionary: [:])
122-
123-
XCTAssertThrowsError(try block.render(context))
124-
}
125-
126119
func testCallableBlockContext() throws {
127120
let block = CallableBlock(parameters: ["p1", "p2", "p3"], nodes: [TextNode(text: "hello")])
128121
let arguments = [Variable("a"), Variable("b"), Variable("\"hello\"")]

0 commit comments

Comments
 (0)