refactor(loader): Throw when template not found
This commit is contained in:
@@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
## Master
|
## Master
|
||||||
|
|
||||||
|
### Breaking
|
||||||
|
|
||||||
|
- `Loader`s will now throw a `TemplateDoesNotExist` error when a template
|
||||||
|
is not found.
|
||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
|
|
||||||
- `FileSystemLoader` will now ensure that template paths are within the base
|
- `FileSystemLoader` will now ensure that template paths are within the base
|
||||||
|
|||||||
14
Sources/Errors.swift
Normal file
14
Sources/Errors.swift
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
public class TemplateDoesNotExist: Error, CustomStringConvertible {
|
||||||
|
let templateNames: [String]
|
||||||
|
let loader: Loader
|
||||||
|
|
||||||
|
public init(templateNames: [String], loader: Loader) {
|
||||||
|
self.templateNames = templateNames
|
||||||
|
self.loader = loader
|
||||||
|
}
|
||||||
|
|
||||||
|
public var description: String {
|
||||||
|
let templates = templateNames.joined(separator: ", ")
|
||||||
|
return "Template named `\(templates)` does not exist in loader \(loader)"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,10 +27,7 @@ class IncludeNode : NodeType {
|
|||||||
throw TemplateSyntaxError("'\(self.templateName)' could not be resolved as a string")
|
throw TemplateSyntaxError("'\(self.templateName)' could not be resolved as a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let template = try loader.loadTemplate(name: templateName) else {
|
let template = try loader.loadTemplate(name: templateName)
|
||||||
throw TemplateSyntaxError("'\(templateName)' template not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
return try template.render(context)
|
return try template.render(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,9 +67,7 @@ class ExtendsNode : NodeType {
|
|||||||
throw TemplateSyntaxError("'\(self.templateName)' could not be resolved as a string")
|
throw TemplateSyntaxError("'\(self.templateName)' could not be resolved as a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let template = try loader.loadTemplate(name: templateName) else {
|
let template = try loader.loadTemplate(name: templateName)
|
||||||
throw TemplateSyntaxError("'\(templateName)' template not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
let blockContext: BlockContext
|
let blockContext: BlockContext
|
||||||
if let context = context[BlockContext.contextKey] as? BlockContext {
|
if let context = context[BlockContext.contextKey] as? BlockContext {
|
||||||
|
|||||||
101
Sources/Loader.swift
Normal file
101
Sources/Loader.swift
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import Foundation
|
||||||
|
import PathKit
|
||||||
|
|
||||||
|
|
||||||
|
public protocol Loader {
|
||||||
|
func loadTemplate(name: String) throws -> Template
|
||||||
|
func loadTemplate(names: [String]) throws -> Template
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension Loader {
|
||||||
|
func loadTemplate(names: [String]) throws -> Template {
|
||||||
|
for name in names {
|
||||||
|
do {
|
||||||
|
return try loadTemplate(name: name)
|
||||||
|
} catch is TemplateDoesNotExist {
|
||||||
|
continue
|
||||||
|
} catch {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw TemplateDoesNotExist(templateNames: names, loader: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A class for loading a template from disk
|
||||||
|
public class FileSystemLoader: Loader, CustomStringConvertible {
|
||||||
|
public let paths: [Path]
|
||||||
|
|
||||||
|
public init(paths: [Path]) {
|
||||||
|
self.paths = paths
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(bundle: [Bundle]) {
|
||||||
|
self.paths = bundle.map {
|
||||||
|
return Path($0.bundlePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var description: String {
|
||||||
|
return "FileSystemLoader(\(paths))"
|
||||||
|
}
|
||||||
|
|
||||||
|
public func loadTemplate(name: String) throws -> Template {
|
||||||
|
for path in paths {
|
||||||
|
let templatePath = try path.safeJoin(path: Path(name))
|
||||||
|
|
||||||
|
if !templatePath.exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return try Template(path: templatePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw TemplateDoesNotExist(templateNames: [name], loader: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func loadTemplate(names: [String]) throws -> Template {
|
||||||
|
for path in paths {
|
||||||
|
for templateName in names {
|
||||||
|
let templatePath = try path.safeJoin(path: Path(templateName))
|
||||||
|
|
||||||
|
if templatePath.exists {
|
||||||
|
return try Template(path: templatePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw TemplateDoesNotExist(templateNames: names, loader: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension Path {
|
||||||
|
func safeJoin(path: Path) throws -> Path {
|
||||||
|
let newPath = self + path
|
||||||
|
|
||||||
|
if !newPath.absolute().description.hasPrefix(absolute().description) {
|
||||||
|
throw SuspiciousFileOperation(basePath: self, path: newPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SuspiciousFileOperation: Error {
|
||||||
|
let basePath: Path
|
||||||
|
let path: Path
|
||||||
|
|
||||||
|
init(basePath: Path, path: Path) {
|
||||||
|
self.basePath = basePath
|
||||||
|
self.path = path
|
||||||
|
}
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
return "Path `\(path)` is located outside of base path `\(basePath)`"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,7 +45,7 @@ func testInclude() {
|
|||||||
do {
|
do {
|
||||||
_ = try node.render(Context(dictionary: ["loader": loader]))
|
_ = try node.render(Context(dictionary: ["loader": loader]))
|
||||||
} catch {
|
} catch {
|
||||||
try expect("\(error)".hasPrefix("'unknown.html' template not found")).to.beTrue()
|
try expect("\(error)".hasPrefix("Template named `unknown.html` does not exist in loader")).to.beTrue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ func testInheritence() {
|
|||||||
$0.it("can inherit from another template") {
|
$0.it("can inherit from another template") {
|
||||||
let context = Context(dictionary: ["loader": loader])
|
let context = Context(dictionary: ["loader": loader])
|
||||||
let template = try loader.loadTemplate(name: "child.html")
|
let template = try loader.loadTemplate(name: "child.html")
|
||||||
try expect(try template?.render(context)) == "Header\nChild"
|
try expect(try template.render(context)) == "Header\nChild"
|
||||||
}
|
}
|
||||||
|
|
||||||
$0.it("can inherit from another template inheriting from another template") {
|
$0.it("can inherit from another template inheriting from another template") {
|
||||||
let context = Context(dictionary: ["loader": loader])
|
let context = Context(dictionary: ["loader": loader])
|
||||||
let template = try loader.loadTemplate(name: "child-child.html")
|
let template = try loader.loadTemplate(name: "child-child.html")
|
||||||
try expect(try template?.render(context)) == "Child Child Header\nChild"
|
try expect(try template.render(context)) == "Child Child Header\nChild"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
31
Tests/StencilTests/LoaderSpec.swift
Normal file
31
Tests/StencilTests/LoaderSpec.swift
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import Spectre
|
||||||
|
import Stencil
|
||||||
|
import PathKit
|
||||||
|
|
||||||
|
|
||||||
|
func testTemplateLoader() {
|
||||||
|
describe("FileSystemLoader") {
|
||||||
|
let path = Path(#file) + ".." + "fixtures"
|
||||||
|
let loader = FileSystemLoader(paths: [path])
|
||||||
|
|
||||||
|
$0.it("errors when a template cannot be found") {
|
||||||
|
try expect(try loader.loadTemplate(name: "unknown.html")).toThrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("errors when an array of templates cannot be found") {
|
||||||
|
try expect(try loader.loadTemplate(names: ["unknown.html", "unknown2.html"])).toThrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("can load a template from a file") {
|
||||||
|
_ = try loader.loadTemplate(name: "test.html")
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("errors when loading absolute file outside of the selected path") {
|
||||||
|
try expect(try loader.loadTemplate(name: "/etc/hosts")).toThrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("errors when loading relative file outside of the selected path") {
|
||||||
|
try expect(try loader.loadTemplate(name: "../LoaderSpec.swift")).toThrow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user