Skip to content

Commit

Permalink
User info, tag config, and files protocol (#34)
Browse files Browse the repository at this point in the history
* add userInfo + tag config

* fix merge

* add deprecated init

* fix property
  • Loading branch information
tanner0101 authored Mar 6, 2020
1 parent 81253ab commit 1d51559
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 46 deletions.
24 changes: 24 additions & 0 deletions Sources/LeafKit/Deprecated.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
extension LeafRenderer {
@available(*, deprecated, message: "Use files instead of fileio")
public var fileio: NonBlockingFileIO {
guard let nio = self.files as? NIOLeafFiles else {
fatalError("Unexpected non-NIO leaf files.")
}
return nio.fileio
}

@available(*, deprecated, message: "Use files instead of fileio")
public convenience init(
configuration: LeafConfiguration,
cache: LeafCache = DefaultLeafCache(),
fileio: NonBlockingFileIO,
eventLoop: EventLoop
) {
self.init(
configuration: configuration,
cache: cache,
files: NIOLeafFiles(fileio: fileio),
eventLoop: eventLoop
)
}
}
89 changes: 66 additions & 23 deletions Sources/LeafKit/LeafRenderer.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import NIOConcurrencyHelpers

public var defaultTags: [String: LeafTag] = [
"lowercased": Lowercased(),
]

public struct LeafConfiguration {
public var rootDirectory: String

Expand Down Expand Up @@ -53,9 +57,22 @@ public final class DefaultLeafCache: LeafCache {
}

public struct LeafContext {
let params: [ParameterDeclaration]
let data: [String: LeafData]
let body: [Syntax]?
public let parameters: [LeafData]
public let data: [String: LeafData]
public let body: [Syntax]?
public let userInfo: [AnyHashable: Any]

init(
parameters: [LeafData],
data: [String: LeafData],
body: [Syntax]?,
userInfo: [AnyHashable: Any]
) throws {
self.parameters = parameters
self.data = data
self.body = body
self.userInfo = userInfo
}
}

public protocol LeafTag {
Expand All @@ -64,29 +81,61 @@ public protocol LeafTag {

struct Lowercased: LeafTag {
func render(_ ctx: LeafContext) throws -> LeafData {
let resolver = ParameterResolver(params: ctx.params, data: ctx.data)
let resolved = try resolver.resolve()
guard let str = resolved.first?.result.string else { throw "unable to lowercase unexpected data" }
guard let str = ctx.parameters.first?.string else {
throw "unable to lowercase unexpected data"
}
return .init(.string(str.lowercased()))
}
}

public protocol LeafFiles {
func file(path: String, on eventLoop: EventLoop) -> EventLoopFuture<ByteBuffer>
}

public struct NIOLeafFiles: LeafFiles {
let fileio: NonBlockingFileIO

public init(fileio: NonBlockingFileIO) {
self.fileio = fileio
}

public func file(path: String, on eventLoop: EventLoop) -> EventLoopFuture<ByteBuffer> {
let openFile = self.fileio.openFile(path: path, eventLoop: eventLoop)
return openFile.flatMapErrorThrowing { error in
throw "unable to open file \(path)"
}.flatMap { (handle, region) -> EventLoopFuture<ByteBuffer> in
let allocator = ByteBufferAllocator()
let read = self.fileio.read(fileRegion: region, allocator: allocator, eventLoop: eventLoop)
return read.flatMapThrowing { (buffer) in
try handle.close()
return buffer
}
}
}
}

public final class LeafRenderer {
public let configuration: LeafConfiguration
public let tags: [String: LeafTag]
public let cache: LeafCache
public let fileio: NonBlockingFileIO
public let files: LeafFiles
public let eventLoop: EventLoop
public let userInfo: [AnyHashable: Any]

public init(
configuration: LeafConfiguration,
tags: [String: LeafTag] = defaultTags,
cache: LeafCache = DefaultLeafCache(),
fileio: NonBlockingFileIO,
eventLoop: EventLoop
files: LeafFiles,
eventLoop: EventLoop,
userInfo: [AnyHashable: Any] = [:]
) {
self.configuration = configuration
self.tags = tags
self.cache = cache
self.fileio = fileio
self.files = files
self.eventLoop = eventLoop
self.userInfo = userInfo
}

public func render(path: String, context: [String: LeafData]) -> EventLoopFuture<ByteBuffer> {
Expand All @@ -96,7 +145,12 @@ public final class LeafRenderer {
}

func render(_ doc: ResolvedDocument, context: [String: LeafData]) throws -> ByteBuffer {
var serializer = LeafSerializer(ast: doc.ast, context: context)
var serializer = LeafSerializer(
ast: doc.ast,
context: context,
tags: self.tags,
userInfo: self.userInfo
)
return try serializer.serialize()
}

Expand All @@ -122,7 +176,6 @@ public final class LeafRenderer {
}

private func read(file: String) -> EventLoopFuture<ResolvedDocument> {
print("reading \(file)")
let raw = readBytes(file: file)

let syntax = raw.flatMapThrowing { raw -> [Syntax] in
Expand Down Expand Up @@ -160,17 +213,7 @@ public final class LeafRenderer {
}

private func readBytes(file: String) -> EventLoopFuture<ByteBuffer> {
let openFile = self.fileio.openFile(path: file, eventLoop: self.eventLoop)
return openFile.flatMapErrorThrowing { error in
throw "unable to open file \(file)"
}.flatMap { (handle, region) -> EventLoopFuture<ByteBuffer> in
let allocator = ByteBufferAllocator()
let read = self.fileio.read(fileRegion: region, allocator: allocator, eventLoop: self.eventLoop)
return read.flatMapThrowing { (buffer) in
try handle.close()
return buffer
}
}
self.files.file(path: file, on: self.eventLoop)
}
}

Expand Down
60 changes: 43 additions & 17 deletions Sources/LeafKit/LeafSerializer.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
var customTags: [String: LeafTag] = [
"lowercased": Lowercased(),
]

struct LeafSerializer {
private let ast: [Syntax]
private var offset: Int
private var buffer: ByteBuffer
private var data: [String: LeafData]
private let tags: [String: LeafTag]
private let userInfo: [AnyHashable: Any]

init(ast: [Syntax], context: [String: LeafData]) {
init(
ast: [Syntax],
context data: [String: LeafData],
tags: [String: LeafTag] = defaultTags,
userInfo: [AnyHashable: Any] = [:]
) {
self.ast = ast
self.offset = 0
self.buffer = ByteBufferAllocator().buffer(capacity: 0)
self.data = context
self.data = data
self.tags = tags
self.userInfo = userInfo
}

mutating func serialize() throws -> ByteBuffer {
Expand Down Expand Up @@ -44,11 +49,11 @@ struct LeafSerializer {
}

mutating func serialize(expression: [ParameterDeclaration]) throws {
let resolver = ParameterResolver(params: [.expression(expression)], data: data)
let resolved = try resolver.resolve()
guard resolved.count == 1 else { throw "expressions should resolve to single value" }
let data = resolved[0].result
serialize(data)
let resolved = try self.resolve(parameters: [.expression(expression)])
guard resolved.count == 1 else {
throw "expressions should resolve to single value"
}
serialize(resolved[0])
}

mutating func serialize(body: [Syntax]) throws {
Expand All @@ -66,9 +71,10 @@ struct LeafSerializer {
try serialize(body: conditional.body)
return
}

let resolver = ParameterResolver(params: list, data: data)
let satisfied = try resolver.resolve().map { $0.result.bool ?? !$0.result.isNull } .reduce(true) { $0 && $1 }

let satisfied = try self.resolve(parameters: list).map {
$0.bool ?? !$0.isNull
}.reduce(true) { $0 && $1 }
if satisfied {
try serialize(body: conditional.body)
} else if let next = conditional.next {
Expand All @@ -77,8 +83,13 @@ struct LeafSerializer {
}

mutating func serialize(_ tag: Syntax.CustomTagDeclaration) throws {
let sub = LeafContext(params: tag.params, data: data, body: tag.body)
let rendered = try customTags[tag.name]?.render(sub)
let sub = try LeafContext(
parameters: self.resolve(parameters: tag.params),
data: data,
body: tag.body,
userInfo: self.userInfo
)
let rendered = try self.tags[tag.name]?.render(sub)
?? .init(.null)
serialize(rendered)
}
Expand Down Expand Up @@ -130,7 +141,12 @@ struct LeafSerializer {
else if idx == array.count - 1 { innerContext["isLast"] = .bool(true) }
innerContext[loop.item] = item

var serializer = LeafSerializer(ast: loop.body, context: innerContext)
var serializer = LeafSerializer(
ast: loop.body,
context: innerContext,
tags: self.tags,
userInfo: self.userInfo
)
var loopBody = try serializer.serialize()
self.buffer.writeBuffer(&loopBody)
}
Expand All @@ -145,6 +161,16 @@ struct LeafSerializer {
return
}
}

private func resolve(parameters: [ParameterDeclaration]) throws -> [LeafData] {
let resolver = ParameterResolver(
params: parameters,
data: data,
tags: self.tags,
userInfo: userInfo
)
return try resolver.resolve().map { $0.result }
}

func peek() -> Syntax? {
guard self.offset < self.ast.count else {
Expand Down
17 changes: 15 additions & 2 deletions Sources/LeafKit/ParameterResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ extension ParameterDeclaration {
struct ParameterResolver {
let params: [ParameterDeclaration]
let data: [String: LeafData]
let tags: [String: LeafTag]
let userInfo: [AnyHashable: Any]

func resolve() throws -> [ResolvedParameter] {
return try params.map(resolve)
Expand All @@ -45,8 +47,19 @@ struct ParameterResolver {
case .parameter(let p):
result = try resolve(param: p)
case .tag(let t):
let ctx = LeafContext(params: t.params, data: data, body: t.body)
result = try customTags[t.name]?.render(ctx)
let resolver = ParameterResolver(
params: t.params,
data: self.data,
tags: self.tags,
userInfo: self.userInfo
)
let ctx = try LeafContext(
parameters: resolver.resolve().map { $0.result },
data: data,
body: t.body,
userInfo: self.userInfo
)
result = try self.tags[t.name]?.render(ctx)
?? .init(.null)
}
return .init(param: param, result: result)
Expand Down
62 changes: 59 additions & 3 deletions Tests/LeafKitTests/LeafKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -867,7 +867,11 @@ final class LeafKitTests: XCTestCase {
let fileio = NonBlockingFileIO(threadPool: threadPool)
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let config = LeafConfiguration(rootDirectory: templateFolder)
let renderer = LeafRenderer(configuration: config, fileio: fileio, eventLoop: group.next())
let renderer = LeafRenderer(
configuration: config,
files: NIOLeafFiles(fileio: fileio),
eventLoop: group.next()
)

var buffer = try! renderer.render(path: "test", context: [:]).wait()
let string = buffer.readString(length: buffer.readableBytes)!
Expand All @@ -876,11 +880,63 @@ final class LeafKitTests: XCTestCase {
try threadPool.syncShutdownGracefully()
try group.syncShutdownGracefully()
}

func testRendererContext() throws {
var test = TestFiles()
test.files["/foo.leaf"] = """
Hello #custom(name)
"""

struct CustomTag: LeafTag {
func render(_ ctx: LeafContext) throws -> LeafData {
let prefix = ctx.userInfo["prefix"] as? String ?? ""
let param = ctx.parameters.first?.string ?? ""
return .string(prefix + param)
}
}

let renderer = LeafRenderer(
configuration: .init(rootDirectory: "/"),
tags: [
"custom": CustomTag()
],
cache: DefaultLeafCache(),
files: test,
eventLoop: EmbeddedEventLoop(),
userInfo: [
"prefix": "bar"
]
)
let view = try renderer.render(path: "foo", context: [
"name": "vapor"
]).wait()

XCTAssertEqual(view.string, "Hello barvapor")
}
}

struct TestFiles: LeafFiles {
var files: [String: String]

init() {
files = [:]
}


func file(path: String, on eventLoop: EventLoop) -> EventLoopFuture<ByteBuffer> {
if let file = self.files[path] {
var buffer = ByteBufferAllocator().buffer(capacity: 0)
buffer.writeString(file)
return eventLoop.makeSucceededFuture(buffer)
} else {
return eventLoop.makeFailedFuture("no test file: \(path)")
}
}
}

extension ByteBuffer {
var string: String? {
return self.getString(at: self.readerIndex, length: self.readableBytes)
var string: String {
String(decoding: self.readableBytesView, as: UTF8.self)
}
}

Expand Down
Loading

0 comments on commit 1d51559

Please sign in to comment.