Skip to content

Commit

Permalink
Merge pull request #95 from vapor/fix-92
Browse files Browse the repository at this point in the history
leaf rc
  • Loading branch information
tanner0101 authored Feb 26, 2018
2 parents bf1ae10 + 935f134 commit 25583fb
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 24 deletions.
8 changes: 4 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ let package = Package(
.library(name: "Leaf", targets: ["Leaf"]),
],
dependencies: [
// Swift Promises, Futures, and Streams.
// Promises and reactive-streams in Swift built for high-performance and scalability.
.package(url: "https://github.com/vapor/async.git", from: "1.0.0-rc"),

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

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

// Easy-to-use foundation for building powerful templating languages in Swift.
// 📄 Easy-to-use foundation for building powerful templating languages in Swift.
.package(url: "https://github.com/vapor/template-kit.git", from: "1.0.0-rc"),
],
targets: [
Expand Down
54 changes: 35 additions & 19 deletions Sources/Leaf/Parser/LeafParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,47 +157,61 @@ extension TemplateByteScanner {
case Data(bytes: [.forwardSlash, .forwardSlash]), Data(bytes: [.forwardSlash, .asterisk]):
break
default:
throw TemplateError.parse(reason: "Invalid tag name", source: makeSource(using: start))
throw TemplateError.parse(reason: "Invalid tag name", template: makeSource(using: start), source: .capture())
}
}

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

switch name {
case "for":
try expect(.leftParenthesis)
if peek() == .space {
throw TemplateError.parse(
reason: "Whitespace not allowed before key in 'for' tag.",
template: makeSource(using: start),
source: .capture()
)
}
let key = try extractIdentifier()
try expect(.space)
try expect(.i)
try expect(.n)
try expect(.space)
guard let val = try extractParameter() else {
throw TemplateError.parse(reason: "Parameter required after `in` in for-loop", source: makeSource(using: start))
throw TemplateError.parse(reason: "Parameter required after `in` in for-loop", template: makeSource(using: start), source: .capture())
}

switch val.type {
case .identifier, .tag:
break
default:
throw TemplateError.parse(reason: "Identifier or tag required", source: makeSource(using: start))
throw TemplateError.parse(reason: "Identifier or tag required", template: makeSource(using: start), source: .capture())
}

if peek(by: -1) == .space {
throw TemplateError.parse(
reason: "Whitespace not allowed after value in 'for' tag.",
template: makeSource(using: start),
source: .capture()
)
}
try expect(.rightParenthesis)

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

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

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

let raw = TemplateSyntax(
Expand Down Expand Up @@ -272,7 +286,7 @@ extension TemplateByteScanner {
switch name {
case "if":
guard params.count == 1 else {
throw TemplateError.parse(reason: "One parameter required for if tag.", source: makeSource(using: start))
throw TemplateError.parse(reason: "One parameter required for if tag.", template: makeSource(using: start), source: .capture())
}
let cond = try TemplateConditional(
Expand All @@ -283,13 +297,13 @@ extension TemplateByteScanner {
type = .conditional(cond)
case "embed":
guard params.count == 1 else {
throw TemplateError.parse(reason: "One parameter required for embed tag.", source: makeSource(using: start))
throw TemplateError.parse(reason: "One parameter required for embed tag.", template: makeSource(using: start), source: .capture())
}
let embed = TemplateEmbed(path: params[0])
type = .embed(embed)
case "for":
guard params.count == 2 else {
throw TemplateError.parse(reason: "Two parameters required for for-loop.", source: makeSource(using: start))
throw TemplateError.parse(reason: "Two parameters required for for-loop.", template: makeSource(using: start), source: .capture())
}
let iterator = TemplateIterator(key: params[1], data: params[0], body: body ?? [])
type = .iterator(iterator)
Expand Down Expand Up @@ -392,7 +406,7 @@ extension TemplateByteScanner {
try extractSpaces()

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

return try TemplateConditional(
Expand Down Expand Up @@ -568,16 +582,16 @@ extension TemplateByteScanner {

let bytes = data[start.offset..<offset]
guard let string = String(data: bytes, encoding: .utf8) else {
throw TemplateError.parse(reason: "Invalid UTF8 string", source: makeSource(using: start))
throw TemplateError.parse(reason: "Invalid UTF8 string", template: makeSource(using: start), source: .capture())
}
if bytes.contains(.period) {
guard let double = Double(string) else {
throw TemplateError.parse(reason: "Invalid double", source: makeSource(using: start))
throw TemplateError.parse(reason: "Invalid double", template: makeSource(using: start), source: .capture())
}
return .double(double)
} else {
guard let int = Int(string) else {
throw TemplateError.parse(reason: "Invalid integer", source: makeSource(using: start))
throw TemplateError.parse(reason: "Invalid integer", template: makeSource(using: start), source: .capture())
}
return .int(int)
}
Expand All @@ -590,7 +604,7 @@ extension TemplateByteScanner {
let start = makeSourceStart()

guard let byte = peek() else {
throw TemplateError.parse(reason: "Unexpected EOF", source: makeSource(using: start))
throw TemplateError.parse(reason: "Unexpected EOF", template: makeSource(using: start), source: .capture())
}

let kind: TemplateSyntaxType
Expand All @@ -609,7 +623,7 @@ extension TemplateByteScanner {
case .exclamation:
try expect(.exclamation)
guard let param = try extractParameter() else {
throw TemplateError.parse(reason: "Parameter required after not `!`", source: makeSource(using: start))
throw TemplateError.parse(reason: "Parameter required after not `!`", template: makeSource(using: start), source: .capture())
}
kind = .expression(.prefix(operator: .not, right: param))
default:
Expand Down Expand Up @@ -687,7 +701,7 @@ extension TemplateByteScanner {
}

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

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

guard let byte = peek() else {
throw TemplateError.parse(reason: "Unexpected EOF", source: makeSource(using: start))
throw TemplateError.parse(reason: "Unexpected EOF", template: makeSource(using: start), source: .capture())
}

guard byte == expect else {
throw TemplateError.parse(reason: "Expected \(expect) got \(byte)", source: makeSource(using: start))
let expectedChar = Character(Unicode.Scalar.init(expect))
let char = Character(Unicode.Scalar.init(byte))
throw TemplateError.parse(reason: "Expected '\(expectedChar)' got '\(char)'", template: makeSource(using: start), source: .capture())
}

try requirePop()
Expand Down
48 changes: 47 additions & 1 deletion Tests/LeafTests/LeafTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,50 @@ class LeafTests: XCTestCase {
try XCTAssertEqual(renderer.testRender(template, data), expected)
}

func testInvalidForSyntax() throws {
let data = try TemplateDataEncoder().encode(["names": ["foo"]])
do {
_ = try renderer.testRender("#for( name in names) {}", data)
XCTFail("Whitespace not allowed here")
} catch {
XCTAssert("\(error)".contains("space not allowed"))
}

do {
_ = try renderer.testRender("#for(name in names ) {}", data)
XCTFail("Whitespace not allowed here")
} catch {
XCTAssert("\(error)".contains("space not allowed"))
}

do {
_ = try renderer.testRender("#for( name in names ) {}", data)
XCTFail("Whitespace not allowed here")
} catch {
XCTAssert("\(error)".contains("space not allowed"))
}

do {
_ = try renderer.testRender("#for(name in names) {}", data)
} catch {
XCTFail("\(error)")
}
}

func testTemplating() throws {
let home = """
#set("title", "Home")
#set("body") {<p>#(foo)</p>}
#embed("base")
"""
let expected = """
<title>Home</title>
<body><p>bar</p></title>
"""
let data = try TemplateDataEncoder().encode(["foo": "bar"])
try XCTAssertEqual(renderer.testRender(home, data), expected)
}

static var allTests = [
("testPrint", testPrint),
("testConstant", testConstant),
Expand All @@ -408,7 +452,9 @@ class LeafTests: XCTestCase {
("testDateFormat", testDateFormat),
("testStringIf", testStringIf),
("testEmptyForLoop", testEmptyForLoop),
("testKeyEqual", testKeyEqual)
("testKeyEqual", testKeyEqual),
("testInvalidForSyntax", testInvalidForSyntax),
("testTemplating", testTemplating),
]
}

Expand Down
1 change: 1 addition & 0 deletions Views/bar.leaf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
You have loaded bar.leaf!
2 changes: 2 additions & 0 deletions Views/base.leaf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<title>#get(title)</title>
<body>#get(body)</title>
1 change: 1 addition & 0 deletions Views/hello.leaf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello, world!

0 comments on commit 25583fb

Please sign in to comment.