Skip to content

Commit 25583fb

Browse files
authored
Merge pull request #95 from vapor/fix-92
leaf rc
2 parents bf1ae10 + 935f134 commit 25583fb

File tree

6 files changed

+90
-24
lines changed

6 files changed

+90
-24
lines changed

Package.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ let package = Package(
77
.library(name: "Leaf", targets: ["Leaf"]),
88
],
99
dependencies: [
10-
// Swift Promises, Futures, and Streams.
10+
// Promises and reactive-streams in Swift built for high-performance and scalability.
1111
.package(url: "https://github.com/vapor/async.git", from: "1.0.0-rc"),
1212

13-
// Core extensions, type-aliases, and functions that facilitate common tasks.
13+
// 🌎 Utility package containing tools for byte manipulation, Codable, OS APIs, and debugging.
1414
.package(url: "https://github.com/vapor/core.git", from: "3.0.0-rc"),
1515

16-
// Service container and configuration system.
16+
// 📦 Dependency injection / inversion of control framework.
1717
.package(url: "https://github.com/vapor/service.git", from: "1.0.0-rc"),
1818

19-
// Easy-to-use foundation for building powerful templating languages in Swift.
19+
// 📄 Easy-to-use foundation for building powerful templating languages in Swift.
2020
.package(url: "https://github.com/vapor/template-kit.git", from: "1.0.0-rc"),
2121
],
2222
targets: [

Sources/Leaf/Parser/LeafParser.swift

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -157,47 +157,61 @@ extension TemplateByteScanner {
157157
case Data(bytes: [.forwardSlash, .forwardSlash]), Data(bytes: [.forwardSlash, .asterisk]):
158158
break
159159
default:
160-
throw TemplateError.parse(reason: "Invalid tag name", source: makeSource(using: start))
160+
throw TemplateError.parse(reason: "Invalid tag name", template: makeSource(using: start), source: .capture())
161161
}
162162
}
163163

164164
// Extract the tag params.
165165
let params: [TemplateSyntax]
166166
guard let name = String(data: id, encoding: .utf8) else {
167-
throw TemplateError.parse(reason: "Invalid UTF-8 string", source: makeSource(using: start))
167+
throw TemplateError.parse(reason: "Invalid UTF-8 string", template: makeSource(using: start), source: .capture())
168168
}
169169

170170
switch name {
171171
case "for":
172172
try expect(.leftParenthesis)
173+
if peek() == .space {
174+
throw TemplateError.parse(
175+
reason: "Whitespace not allowed before key in 'for' tag.",
176+
template: makeSource(using: start),
177+
source: .capture()
178+
)
179+
}
173180
let key = try extractIdentifier()
174181
try expect(.space)
175182
try expect(.i)
176183
try expect(.n)
177184
try expect(.space)
178185
guard let val = try extractParameter() else {
179-
throw TemplateError.parse(reason: "Parameter required after `in` in for-loop", source: makeSource(using: start))
186+
throw TemplateError.parse(reason: "Parameter required after `in` in for-loop", template: makeSource(using: start), source: .capture())
180187
}
181188

182189
switch val.type {
183190
case .identifier, .tag:
184191
break
185192
default:
186-
throw TemplateError.parse(reason: "Identifier or tag required", source: makeSource(using: start))
193+
throw TemplateError.parse(reason: "Identifier or tag required", template: makeSource(using: start), source: .capture())
187194
}
188195

196+
if peek(by: -1) == .space {
197+
throw TemplateError.parse(
198+
reason: "Whitespace not allowed after value in 'for' tag.",
199+
template: makeSource(using: start),
200+
source: .capture()
201+
)
202+
}
189203
try expect(.rightParenthesis)
190204

191205
guard case .identifier(let name) = key.type else {
192-
throw TemplateError.parse(reason: "Invalid key type in for-loop", source: makeSource(using: start))
206+
throw TemplateError.parse(reason: "Invalid key type in for-loop", template: makeSource(using: start), source: .capture())
193207
}
194208

195209
guard name.path.count == 1 else {
196-
throw TemplateError.parse(reason: "One key required in for-loop", source: makeSource(using: start))
210+
throw TemplateError.parse(reason: "One key required in for-loop", template: makeSource(using: start), source: .capture())
197211
}
198212

199213
guard let data = name.path[0].stringValue.data(using: .utf8) else {
200-
throw TemplateError.parse(reason: "Invalid UTF-8 string", source: makeSource(using: start))
214+
throw TemplateError.parse(reason: "Invalid UTF-8 string", template: makeSource(using: start), source: .capture())
201215
}
202216

203217
let raw = TemplateSyntax(
@@ -272,7 +286,7 @@ extension TemplateByteScanner {
272286
switch name {
273287
case "if":
274288
guard params.count == 1 else {
275-
throw TemplateError.parse(reason: "One parameter required for if tag.", source: makeSource(using: start))
289+
throw TemplateError.parse(reason: "One parameter required for if tag.", template: makeSource(using: start), source: .capture())
276290
}
277291
278292
let cond = try TemplateConditional(
@@ -283,13 +297,13 @@ extension TemplateByteScanner {
283297
type = .conditional(cond)
284298
case "embed":
285299
guard params.count == 1 else {
286-
throw TemplateError.parse(reason: "One parameter required for embed tag.", source: makeSource(using: start))
300+
throw TemplateError.parse(reason: "One parameter required for embed tag.", template: makeSource(using: start), source: .capture())
287301
}
288302
let embed = TemplateEmbed(path: params[0])
289303
type = .embed(embed)
290304
case "for":
291305
guard params.count == 2 else {
292-
throw TemplateError.parse(reason: "Two parameters required for for-loop.", source: makeSource(using: start))
306+
throw TemplateError.parse(reason: "Two parameters required for for-loop.", template: makeSource(using: start), source: .capture())
293307
}
294308
let iterator = TemplateIterator(key: params[1], data: params[0], body: body ?? [])
295309
type = .iterator(iterator)
@@ -392,7 +406,7 @@ extension TemplateByteScanner {
392406
try extractSpaces()
393407

394408
guard params.count == 1 else {
395-
throw TemplateError.parse(reason: "One parameter required for else tag.", source: makeSource(using: start))
409+
throw TemplateError.parse(reason: "One parameter required for else tag.", template: makeSource(using: start), source: .capture())
396410
}
397411

398412
return try TemplateConditional(
@@ -568,16 +582,16 @@ extension TemplateByteScanner {
568582

569583
let bytes = data[start.offset..<offset]
570584
guard let string = String(data: bytes, encoding: .utf8) else {
571-
throw TemplateError.parse(reason: "Invalid UTF8 string", source: makeSource(using: start))
585+
throw TemplateError.parse(reason: "Invalid UTF8 string", template: makeSource(using: start), source: .capture())
572586
}
573587
if bytes.contains(.period) {
574588
guard let double = Double(string) else {
575-
throw TemplateError.parse(reason: "Invalid double", source: makeSource(using: start))
589+
throw TemplateError.parse(reason: "Invalid double", template: makeSource(using: start), source: .capture())
576590
}
577591
return .double(double)
578592
} else {
579593
guard let int = Int(string) else {
580-
throw TemplateError.parse(reason: "Invalid integer", source: makeSource(using: start))
594+
throw TemplateError.parse(reason: "Invalid integer", template: makeSource(using: start), source: .capture())
581595
}
582596
return .int(int)
583597
}
@@ -590,7 +604,7 @@ extension TemplateByteScanner {
590604
let start = makeSourceStart()
591605

592606
guard let byte = peek() else {
593-
throw TemplateError.parse(reason: "Unexpected EOF", source: makeSource(using: start))
607+
throw TemplateError.parse(reason: "Unexpected EOF", template: makeSource(using: start), source: .capture())
594608
}
595609

596610
let kind: TemplateSyntaxType
@@ -609,7 +623,7 @@ extension TemplateByteScanner {
609623
case .exclamation:
610624
try expect(.exclamation)
611625
guard let param = try extractParameter() else {
612-
throw TemplateError.parse(reason: "Parameter required after not `!`", source: makeSource(using: start))
626+
throw TemplateError.parse(reason: "Parameter required after not `!`", template: makeSource(using: start), source: .capture())
613627
}
614628
kind = .expression(.prefix(operator: .not, right: param))
615629
default:
@@ -687,7 +701,7 @@ extension TemplateByteScanner {
687701
}
688702

689703
guard let right = try extractParameter() else {
690-
throw TemplateError.parse(reason: "Parameter required after infix operator", source: makeSource(using: start))
704+
throw TemplateError.parse(reason: "Parameter required after infix operator", template: makeSource(using: start), source: .capture())
691705
}
692706

693707
// FIXME: allow for () grouping and proper PEMDAS
@@ -716,11 +730,13 @@ extension TemplateByteScanner {
716730
let start = makeSourceStart()
717731

718732
guard let byte = peek() else {
719-
throw TemplateError.parse(reason: "Unexpected EOF", source: makeSource(using: start))
733+
throw TemplateError.parse(reason: "Unexpected EOF", template: makeSource(using: start), source: .capture())
720734
}
721735

722736
guard byte == expect else {
723-
throw TemplateError.parse(reason: "Expected \(expect) got \(byte)", source: makeSource(using: start))
737+
let expectedChar = Character(Unicode.Scalar.init(expect))
738+
let char = Character(Unicode.Scalar.init(byte))
739+
throw TemplateError.parse(reason: "Expected '\(expectedChar)' got '\(char)'", template: makeSource(using: start), source: .capture())
724740
}
725741

726742
try requirePop()

Tests/LeafTests/LeafTests.swift

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,50 @@ class LeafTests: XCTestCase {
383383
try XCTAssertEqual(renderer.testRender(template, data), expected)
384384
}
385385

386+
func testInvalidForSyntax() throws {
387+
let data = try TemplateDataEncoder().encode(["names": ["foo"]])
388+
do {
389+
_ = try renderer.testRender("#for( name in names) {}", data)
390+
XCTFail("Whitespace not allowed here")
391+
} catch {
392+
XCTAssert("\(error)".contains("space not allowed"))
393+
}
394+
395+
do {
396+
_ = try renderer.testRender("#for(name in names ) {}", data)
397+
XCTFail("Whitespace not allowed here")
398+
} catch {
399+
XCTAssert("\(error)".contains("space not allowed"))
400+
}
401+
402+
do {
403+
_ = try renderer.testRender("#for( name in names ) {}", data)
404+
XCTFail("Whitespace not allowed here")
405+
} catch {
406+
XCTAssert("\(error)".contains("space not allowed"))
407+
}
408+
409+
do {
410+
_ = try renderer.testRender("#for(name in names) {}", data)
411+
} catch {
412+
XCTFail("\(error)")
413+
}
414+
}
415+
416+
func testTemplating() throws {
417+
let home = """
418+
#set("title", "Home")
419+
#set("body") {<p>#(foo)</p>}
420+
#embed("base")
421+
"""
422+
let expected = """
423+
<title>Home</title>
424+
<body><p>bar</p></title>
425+
"""
426+
let data = try TemplateDataEncoder().encode(["foo": "bar"])
427+
try XCTAssertEqual(renderer.testRender(home, data), expected)
428+
}
429+
386430
static var allTests = [
387431
("testPrint", testPrint),
388432
("testConstant", testConstant),
@@ -408,7 +452,9 @@ class LeafTests: XCTestCase {
408452
("testDateFormat", testDateFormat),
409453
("testStringIf", testStringIf),
410454
("testEmptyForLoop", testEmptyForLoop),
411-
("testKeyEqual", testKeyEqual)
455+
("testKeyEqual", testKeyEqual),
456+
("testInvalidForSyntax", testInvalidForSyntax),
457+
("testTemplating", testTemplating),
412458
]
413459
}
414460

Views/bar.leaf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
You have loaded bar.leaf!

Views/base.leaf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<title>#get(title)</title>
2+
<body>#get(body)</title>

Views/hello.leaf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello, world!

0 commit comments

Comments
 (0)