Merge pull request #5 from PerfectlySoft/4.0-dev

4.0 dev
This commit is contained in:
Kyle Jessup 2020-05-19 13:43:55 -04:00 committed by GitHub
commit 06c2bf73b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 22 additions and 222 deletions

View File

@ -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"])
]
)

View File

@ -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)
}
}
}

View File

@ -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
}
}

View File

@ -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)