commit
06c2bf73b3
|
@ -1,34 +1,19 @@
|
|||
// swift-tools-version:4.0
|
||||
//
|
||||
// Package.swift
|
||||
// PerfectMustache
|
||||
//
|
||||
// Created by Kyle Jessup on 2016-05-02.
|
||||
// Copyright (C) 2016 PerfectlySoft, Inc.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Perfect.org open source project
|
||||
//
|
||||
// Copyright (c) 2015 - 2018 PerfectlySoft Inc. and the Perfect project authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// See http://perfect.org/licensing.html for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
|
||||
// Generated automatically by Perfect Assistant
|
||||
// Date: 2018-05-31 18:31:13 +0000
|
||||
import PackageDescription
|
||||
let package = Package(name: "PerfectMustache",
|
||||
products: [.library(name: "PerfectMustache",targets: ["PerfectMustache"]),],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/PerfectlySoft/Perfect-HTTP.git", from: "3.0.0"),
|
||||
|
||||
let package = Package(
|
||||
name: "PerfectMustache",
|
||||
products: [
|
||||
.library(name: "PerfectMustache", targets: ["PerfectMustache"])
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "PerfectMustache",
|
||||
dependencies: ["PerfectHTTP"]),
|
||||
.testTarget(
|
||||
name: "PerfectMustacheTests",
|
||||
dependencies: ["PerfectMustache"]),
|
||||
])
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/PerfectlySoft/Perfect-Thread.git", from: "3.0.0"),
|
||||
.package(url: "https://github.com/PerfectlySoft/PerfectLib.git", from: "3.0.0"),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "PerfectMustache", dependencies: ["PerfectThread", "PerfectLib"]),
|
||||
.testTarget(name: "PerfectMustacheTests", dependencies: ["PerfectMustache"])
|
||||
]
|
||||
)
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
//
|
||||
// HTTPResponseExtension.swift
|
||||
// PerfectMustache
|
||||
//
|
||||
// Created by Jonathan Guthrie on 2017-08-01.
|
||||
// Copyright (C) 2017 PerfectlySoft, Inc.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Perfect.org open source project
|
||||
//
|
||||
// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// See http://perfect.org/licensing.html for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
|
||||
import PerfectHTTP
|
||||
|
||||
import Foundation
|
||||
extension HTTPResponse {
|
||||
public func renderMustache(template: String, context values: [String: Any] = [String: Any]()) {
|
||||
let context = MustacheEvaluationContext(templatePath: template)
|
||||
let collector = MustacheEvaluationOutputCollector()
|
||||
context.extendValues(with: values)
|
||||
do {
|
||||
let d = try context.formulateResponse(withCollector: collector)
|
||||
self.setBody(string: d)
|
||||
.completed()
|
||||
} catch {
|
||||
self.setBody(string: "\(error)")
|
||||
.completed(status: .internalServerError)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,8 +19,6 @@
|
|||
|
||||
import PerfectThread
|
||||
import PerfectLib
|
||||
import PerfectHTTP
|
||||
|
||||
import Foundation
|
||||
|
||||
let mustacheExtension = "mustache"
|
||||
|
@ -89,57 +87,25 @@ public enum MustacheError : Error {
|
|||
case evaluationError(String)
|
||||
}
|
||||
|
||||
/// A mustache handler, which should be passed to `mustacheRequest`, generates values to fill a mustache template.
|
||||
/// Call `context.extendValues(with: values)` one or more times and then
|
||||
/// `context.requestCompleted(withCollector collector)` to complete the request and output the resulting content to the client.
|
||||
public protocol MustachePageHandler {
|
||||
/// Called by the system when the handler needs to add values for the template.
|
||||
func extendValuesForResponse(context contxt: MustacheWebEvaluationContext, collector: MustacheEvaluationOutputCollector)
|
||||
}
|
||||
|
||||
/** Convenience function to begin a mustache template request
|
||||
|
||||
```swift
|
||||
routes.add(method: .get, uri: "/", handler: {
|
||||
request, response in
|
||||
mustacheRequest(request: request, response: response, handler: UploadHandler(), path: webRoot + "/index.mustache")
|
||||
})
|
||||
```
|
||||
|
||||
*/
|
||||
public func mustacheRequest(request req: HTTPRequest, response: HTTPResponse, handler: MustachePageHandler, templatePath: String) {
|
||||
|
||||
let context = MustacheWebEvaluationContext(webResponse: response)
|
||||
context.templatePath = templatePath
|
||||
let collector = MustacheEvaluationOutputCollector()
|
||||
|
||||
handler.extendValuesForResponse(context: context, collector: collector)
|
||||
}
|
||||
|
||||
/// This class represents an individual scope for mustache template values.
|
||||
/// A mustache template handler will return a `MustacheEvaluationContext.MapType` object as a result from its `PageHandler.valuesForResponse` function.
|
||||
public class MustacheEvaluationContext {
|
||||
var mapValues: MapType
|
||||
|
||||
public typealias MapType = [String:Any]
|
||||
public typealias SequenceType = [MapType]
|
||||
|
||||
/// The parent of this context
|
||||
public var parent: MustacheEvaluationContext? = nil
|
||||
|
||||
/// Complete path to the file being processed
|
||||
public var templatePath: String?
|
||||
|
||||
/// Mustache content for dynamic generation
|
||||
public var templateContent: String?
|
||||
|
||||
/// Returns the name of the current template file.
|
||||
public var templateName: String? {
|
||||
let nam = templatePath?.lastFilePathComponent
|
||||
return nam
|
||||
}
|
||||
|
||||
var mapValues: MapType
|
||||
|
||||
public init(templatePath: String, map: MapType = MapType()) {
|
||||
self.templatePath = templatePath
|
||||
self.mapValues = map
|
||||
|
@ -196,7 +162,6 @@ public class MustacheEvaluationContext {
|
|||
/// - returns: The value, if found, or nil
|
||||
public func getValue(named nam: String) -> MapType.Value? {
|
||||
let values = nam.components(separatedBy: ".")
|
||||
|
||||
var cntxt: MapType.Value? = mapValues
|
||||
for val in values {
|
||||
guard let prev = cntxt as? MapType else {
|
||||
|
@ -204,7 +169,6 @@ public class MustacheEvaluationContext {
|
|||
}
|
||||
cntxt = prev[val]
|
||||
}
|
||||
|
||||
let v = cntxt
|
||||
if v == nil && parent != nil {
|
||||
return parent?.getValue(named: nam)
|
||||
|
@ -231,48 +195,6 @@ public class MustacheEvaluationContext {
|
|||
}
|
||||
}
|
||||
|
||||
/// This class represents an individual scope for mustache template values.
|
||||
/// A mustache template handler will return a `MustacheWebEvaluationContext.MapType` object as a result from its `PageHandler.valuesForResponse` function.
|
||||
public class MustacheWebEvaluationContext: MustacheEvaluationContext {
|
||||
|
||||
/// Provides access to the current HTTPResponse object.
|
||||
public var webResponse: HTTPResponse
|
||||
|
||||
/// Provides access to the current HTTPRequest object.
|
||||
public var webRequest: HTTPRequest {
|
||||
return webResponse.request
|
||||
}
|
||||
|
||||
init(webResponse: HTTPResponse, templatePath: String, map: MapType = MapType()) {
|
||||
self.webResponse = webResponse
|
||||
super.init(templatePath: templatePath, map: map)
|
||||
}
|
||||
|
||||
init(webResponse: HTTPResponse, map: MapType = MapType()) {
|
||||
self.webResponse = webResponse
|
||||
super.init(map: map)
|
||||
}
|
||||
|
||||
override func newChildContext() -> MustacheEvaluationContext {
|
||||
let cc = MustacheWebEvaluationContext(webResponse: webResponse)
|
||||
cc.parent = self
|
||||
return cc
|
||||
}
|
||||
|
||||
override func newChildContext(withMap with: MapType) -> MustacheEvaluationContext {
|
||||
let cc = MustacheWebEvaluationContext(webResponse: webResponse, map: with)
|
||||
cc.parent = self
|
||||
return cc
|
||||
}
|
||||
|
||||
/// All the template values have been completed and resulting content should be
|
||||
/// formulated and returned to the client.
|
||||
public func requestCompleted(withCollector collector: MustacheEvaluationOutputCollector) throws {
|
||||
self.webResponse.appendBody(string: try self.formulateResponse(withCollector: collector))
|
||||
self.webResponse.completed()
|
||||
}
|
||||
}
|
||||
|
||||
/// An instance of this class will collect all output data generated by mustache tags during evaluation.
|
||||
/// Call the `asString()` function to retreive the resulting data.
|
||||
public class MustacheEvaluationOutputCollector {
|
||||
|
@ -368,11 +290,9 @@ public class MustacheTag {
|
|||
/// Reconstitutes the tag into its original source string form.
|
||||
/// - returns: The resulting string, including the original delimiters and tag-type marker.
|
||||
public func description() -> String {
|
||||
|
||||
guard type != .plain else {
|
||||
return tag
|
||||
}
|
||||
|
||||
var s = delimOpen()
|
||||
switch type {
|
||||
case .name:
|
||||
|
@ -406,7 +326,6 @@ public class MustacheTag {
|
|||
|
||||
/// A sub-class of MustacheTag which represents a mustache "partial" tag.
|
||||
public class MustachePartialTag : MustacheTag {
|
||||
|
||||
override func clone() -> MustacheTag {
|
||||
let s = MustachePartialTag()
|
||||
self.populateClone(s)
|
||||
|
@ -415,7 +334,6 @@ public class MustachePartialTag : MustacheTag {
|
|||
|
||||
/// Override for evaluating the partial tag.
|
||||
public override func evaluate(context contxt: MustacheEvaluationContext, collector: MustacheEvaluationOutputCollector) {
|
||||
|
||||
guard let page = contxt.getCurrentFilePath(), !page.isEmpty else {
|
||||
print("Exception while executing partial \(tag): unable to find template root directory")
|
||||
return
|
||||
|
@ -673,21 +591,17 @@ public class MustacheParser {
|
|||
/// - throws: `MustacheError.SyntaxError`
|
||||
/// - returns: A `MustacheTemplate` object which can be evaluated.
|
||||
public func parse(string strng: String) throws -> MustacheTemplate {
|
||||
|
||||
let t = MustacheTemplate()
|
||||
self.activeList = t
|
||||
self.g = strng.unicodeScalars.makeIterator()
|
||||
|
||||
try consumeLoop()
|
||||
|
||||
t.pragmas = pragmas
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func next() -> UnicodeScalar? {
|
||||
offset += 1
|
||||
return g!.next()
|
||||
return g?.next()
|
||||
}
|
||||
|
||||
func consumeLoop() throws {
|
||||
|
@ -712,24 +626,19 @@ public class MustacheParser {
|
|||
func addChild(_ t: MustacheTag) {
|
||||
self.activeList!.children.append(t)
|
||||
t.parent = self.activeList!
|
||||
|
||||
t.openD = openDelimiters
|
||||
t.closeD = closeDelimiters
|
||||
}
|
||||
|
||||
// Read until delimiters are encountered
|
||||
func consumePlain() -> MustacheTagType {
|
||||
|
||||
let currTag = MustacheTag()
|
||||
currTag.type = .plain
|
||||
|
||||
addChild(currTag)
|
||||
|
||||
while true {
|
||||
guard let e = next() else {
|
||||
return .none
|
||||
}
|
||||
|
||||
if e == openDelimiters[0] {
|
||||
testingPutback = String(e)
|
||||
if consumePossibleOpenDelimiter(index: 1) {
|
||||
|
@ -770,12 +679,10 @@ public class MustacheParser {
|
|||
|
||||
// Read until delimiters are encountered
|
||||
func consumeTag() throws -> MustacheTagType {
|
||||
|
||||
if let e = skipWhiteSpace() {
|
||||
// e is first non-white character
|
||||
// # ^ ! >
|
||||
switch e {
|
||||
|
||||
case "%": // pragma
|
||||
let tagName = consumeTagName(firstChar: skipWhiteSpace())
|
||||
let newTag = MustachePragmaTag()
|
||||
|
@ -862,11 +769,9 @@ public class MustacheParser {
|
|||
// reads until closing delimiters
|
||||
// firstChar was read as part of previous step and should be added to the result
|
||||
func consumeTagName(firstChar first: UnicodeScalar?) -> String {
|
||||
|
||||
guard let f = first else {
|
||||
return ""
|
||||
}
|
||||
|
||||
var s = String(f)
|
||||
return consumeTagName(into: &s)
|
||||
}
|
||||
|
@ -970,4 +875,3 @@ public class MustacheParser {
|
|||
closeDelimiters = close
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,51 +1,7 @@
|
|||
import XCTest
|
||||
import PerfectHTTP
|
||||
import PerfectNet
|
||||
import PerfectLib
|
||||
@testable import PerfectMustache
|
||||
|
||||
class ShimHTTPRequest: HTTPRequest {
|
||||
var pathComponents: [String] = []
|
||||
|
||||
var method = HTTPMethod.get
|
||||
var path = "/"
|
||||
var queryParams = [(String, String)]()
|
||||
var protocolVersion = (1, 1)
|
||||
var remoteAddress = (host: "127.0.0.1", port: 8000 as UInt16)
|
||||
var serverAddress = (host: "127.0.0.1", port: 8282 as UInt16)
|
||||
var serverName = "my_server"
|
||||
var documentRoot = "./webroot"
|
||||
var connection = NetTCP()
|
||||
var urlVariables = [String:String]()
|
||||
func header(_ named: HTTPRequestHeader.Name) -> String? { return nil }
|
||||
func addHeader(_ named: HTTPRequestHeader.Name, value: String) {}
|
||||
func setHeader(_ named: HTTPRequestHeader.Name, value: String) {}
|
||||
var headers = AnyIterator<(HTTPRequestHeader.Name, String)> { return nil }
|
||||
var postParams = [(String, String)]()
|
||||
var postBodyBytes: [UInt8]? = nil
|
||||
var postBodyString: String? = nil
|
||||
var postFileUploads: [MimeReader.BodySpec]? = nil
|
||||
var scratchPad = [String:Any]()
|
||||
}
|
||||
|
||||
class ShimHTTPResponse: HTTPResponse {
|
||||
func next() {}
|
||||
var request: HTTPRequest = ShimHTTPRequest()
|
||||
var status: HTTPResponseStatus = .ok
|
||||
var isStreaming = false
|
||||
var bodyBytes = [UInt8]()
|
||||
func header(_ named: HTTPResponseHeader.Name) -> String? { return nil }
|
||||
func addHeader(_ named: HTTPResponseHeader.Name, value: String) -> Self { return self }
|
||||
func setHeader(_ named: HTTPResponseHeader.Name, value: String) -> Self { return self }
|
||||
var headers = AnyIterator<(HTTPResponseHeader.Name, String)> { return nil }
|
||||
func addCookie(_: PerfectHTTP.HTTPCookie) {}
|
||||
func appendBody(bytes: [UInt8]) {}
|
||||
func appendBody(string: String) {}
|
||||
func setBody(json: [String:Any]) throws {}
|
||||
func push(callback: @escaping (Bool) -> ()) {}
|
||||
func completed() {}
|
||||
}
|
||||
|
||||
class PerfectMustacheTests: XCTestCase {
|
||||
|
||||
func testMustacheParser1() {
|
||||
|
@ -54,9 +10,7 @@ class PerfectMustacheTests: XCTestCase {
|
|||
let template = try MustacheParser().parse(string: usingTemplate)
|
||||
let d = ["name":"The name"] as [String:Any]
|
||||
|
||||
let response = ShimHTTPResponse()
|
||||
|
||||
let context = MustacheWebEvaluationContext(webResponse: response, map: d)
|
||||
let context = MustacheEvaluationContext(map: d)
|
||||
let collector = MustacheEvaluationOutputCollector()
|
||||
template.evaluate(context: context, collector: collector)
|
||||
|
||||
|
@ -73,9 +27,7 @@ class PerfectMustacheTests: XCTestCase {
|
|||
let template = try MustacheParser().parse(string: usingTemplate)
|
||||
let d = ["name":{ (tag:String, context:MustacheEvaluationContext) -> String in return nameVal }] as [String:Any]
|
||||
|
||||
let response = ShimHTTPResponse()
|
||||
|
||||
let context = MustacheWebEvaluationContext(webResponse: response, map: d)
|
||||
let context = MustacheEvaluationContext(map: d)
|
||||
let collector = MustacheEvaluationOutputCollector()
|
||||
template.evaluate(context: context, collector: collector)
|
||||
|
||||
|
@ -174,9 +126,7 @@ class PerfectMustacheTests: XCTestCase {
|
|||
let template = try MustacheParser().parse(string: usingTemplate)
|
||||
let d = ["name": ["first": "The", "last": "name"]] as [String:Any]
|
||||
|
||||
let response = ShimHTTPResponse()
|
||||
|
||||
let context = MustacheWebEvaluationContext(webResponse: response, map: d)
|
||||
let context = MustacheEvaluationContext(map: d)
|
||||
let collector = MustacheEvaluationOutputCollector()
|
||||
template.evaluate(context: context, collector: collector)
|
||||
|
||||
|
@ -192,9 +142,7 @@ class PerfectMustacheTests: XCTestCase {
|
|||
let template = try MustacheParser().parse(string: usingTemplate)
|
||||
let d = ["foo": ["data": ["name": ["first": "The", "last": "name"]]]] as [String:Any]
|
||||
|
||||
let response = ShimHTTPResponse()
|
||||
|
||||
let context = MustacheWebEvaluationContext(webResponse: response, map: d)
|
||||
let context = MustacheEvaluationContext(map: d)
|
||||
let collector = MustacheEvaluationOutputCollector()
|
||||
template.evaluate(context: context, collector: collector)
|
||||
|
||||
|
|
Loading…
Reference in New Issue