On this article I will present you methods to create some helpful customized tags for the Leaf template engine, written in Swift.
The way to lengthen Leaf?
With the rebirth of Leaf we will really lengthen the template engine and customized tags are only a factor of the previous. You already know in earlier variations of Leaf every thing was known as a tag and there was no differentiation between these little bastards beginning with the # image. Now issues have modified. There are a lot of completely different entities in Leaf Tau.
- Blocks (e.g. #for, #whereas, #if, #elseif, #else)
- Features (e.g. #Date, #Timestamp, and so on.)
- Strategies (e.g. .rely(), .isEmpty, and so on.)
This can be a great point and on high of this you’ll be able to create your very personal features, strategies and even blocks. This brings us to a completely extensible template engine that may render every thing in a non-blocking asynchronous means. How cool is that? 😎
Did I point out that Leaf you’ll be able to lengthen the context with customized LeafDataGenerators? Sure, that is a factor now, previously you can use the userInfo object to set a “international” accessible variable in Leaf, that was properly accessible in each single template file.
Now there are some particular variables accessible which you can lengthen:
The present context in fact is what you go to your template utilizing the render methodology written in Swift. It’s price to say that self is simply an alias to the present $context, so it would not issues which one you employ. The $app and $req scopes are empty by design, however you’ll be able to lengthen them. You’ll be able to even register your individual scope for instance
$api and set every thing you want globally beneath that variable. I will present you ways to do that in a while.
As you’ll be able to see there are many choices accessible to increase Leaf. It’s a must to suppose twice which path you are taking, but it surely’s nice that we have now this many alternatives. Now we’ll stroll by of every of these items and I will present you methods to write customized extensions for Leaf Tau. 🥳
The way to lengthen Leaf contexts?
One of the simple means of extending Leaf is to offer customized context variables. We will simply write an extension for the
Software and the
Request object and return
LeafDataGenerator values with particular keys and in a while we will register these as further context variables.
import Vapor import Leaf extension Software var customLeafVars: [String: LeafDataGenerator] [ "isDebug": .lazy(LeafData.bool(!self.environment.isRelease && self.environment != .production)) ] extension Request var customLeafVars: [String: LeafDataGenerator] [ "url": .lazy([ "isSecure": LeafData.bool(self.url.scheme?.contains("https")), "host": LeafData.string(self.url.host), "port": LeafData.int(self.url.port), "path": LeafData.string(self.url.path), "query": LeafData.string(self.url.query) ]), ]
A LeafDataGenerator object may be lazy or speedy. Rapid values will probably be saved immediately, alternatively lazy values will produce generator blocks which might be going to be known as solely when the renderer wants them. Nothing particular, this works just like the lazy key phrase in Swift.
struct ScopeExtensionMiddleware: Middleware func reply(to req: Request, chainingTo subsequent: Responder) -> EventLoopFuture<Response> do attempt req.leaf.context.register(turbines: req.utility.customLeafVars, toScope: "app") attempt req.leaf.context.register(turbines: req.customLeafVars, toScope: "req") catch return req.eventLoop.future(error: error) return subsequent.reply(to: req)
We want an extension middleware that registers our generator variables to the given scope.
public func configure(_ app: Software) throws app.middleware.use(ScopeExtensionMiddleware())
Attempt to print these values in a template file, you’ll be able to entry child-values utilizing the dot notation.
#(self) #($context) #($app) #($app.isDebug) #($req) #($req.url) #($req.url.host) #($req.url.isSecure) #($req.url.path) #($req.url.port) #($req.url.question)
Now we’re going to create a customized context to get some details about the host machine.
last class ServerLeafContextGenerator: LeafContextPublisher var osName: String #if os(macOS) return "macOS" #elseif os(Linux) return "Linux" #elseif os(Home windows) return "Home windows" #else return "Unknown" #endif lazy var leafVariables: [String: LeafDataGenerator] = [ "os": .lazy([ "name": LeafData.string(self.osName), "version": LeafData.string(ProcessInfo.processInfo.operatingSystemVersionString), ]), "cpu-cores": .speedy(ProcessInfo.processInfo.processorCount), "reminiscence": .speedy(ProcessInfo.processInfo.physicalMemory), ]
We will merely put this line subsequent to the opposite two within the scope extension middleware.
attempt req.leaf.context.register(turbines: ServerLeafContextGenerator().leafVariables, toScope: "server")
This fashion we will get some more information in regards to the server in our Leaf templates by utilizing the
$server scope. One other means is to increase a scope regionally with a generator.
app.get("server-info") req -> EventLoopFuture<View> in var context: LeafRenderer.Context = [ "title": "Server info", ] attempt context.register(object: ServerLeafContextGenerator(), toScope: "server") return req.leaf.render(template: "server-info", context: context)
The distinction is that within the second case the server scope is simply accessible for a single endpoint, but when we register it by the middleware then it may be reached globally in each single Leaf file.
I feel scopes are very helpful, particularly Request associated ones. Previously we needed to create a customized Leaf tag to get the trail, however now we will use a scope extension and this data will probably be accessible in every single place. With the lazy load we additionally get some free efficiency enhancements.
Customized Leaf features and strategies
You’ll be able to create customized features and strategies for Leaf, I might say that this new API is the replacemenet of the outdated tag system. There are some variations and at first sight you may suppose that it is tougher to create a perform with the brand new instruments, however in time you will get used to it.
public struct Hey: LeafFunction, StringReturn, Invariant public static var callSignature: [LeafCallParameter] [.string] public func consider(_ params: LeafCallValues) -> LeafData guard let identify = params.string else return .error("`Hey` have to be known as with a string parameter.") return .string("Hey (identify)!")
This can be a very fundamental perform. Each single perform has a name signature, which is only a record of type-safe arguments. Features can have return sorts, thankfully there are pre-made protocols for these, so you do not have to implement the required stuff, however you’ll be able to say that this features is e.g. a StringReturn perform. Invariant signifies that the perform will at all times return the identical output for a similar enter. That is what you need more often than not, it additionally lets you keep away from side-effects.
Within the consider perform you may get entry to all of the enter parameters and you need to return with a LeafData kind. If a parameter is lacking or it might probably’t be casted to the correct kind you’ll be able to at all times return with an error. Consider is wish to the outdated render methodology, but it surely’s far more superior.
LeafConfiguration.entities.use(Hey(), asFunction: "Hey")
You additionally must register this newly created perform beneath a give identify.
Oh by the way in which strategies are simply particular features so you’ll be able to construct them the identical means and register them by way of the
asMethod: property. If you wish to see extra examples, it’s best to check out my different submit about what’s new in Leaf Tau or scroll all the way down to the final part of this text.
The way to construct customized Leaf blocks?
This can be a very fascinating and sophisticated matter. Blocks are particular type of LeafFunctions, similar to strategies, however issues are just a bit bit extra sophisticated on this case. Instance time:
import Vapor import Leaf struct MaybeBlock: LeafBlock, VoidReturn, Invariant static var parseSignatures: ParseSignatures? = nil static var evaluable: Bool = false var scopeVariables: [String]? = nil static var callSignature: [LeafCallParameter] [.double(labeled: "chance")] static func instantiate(_ signature: String?, _ params: [String]) throws -> MaybeBlock .init() mutating func evaluateScope(_ params: LeafCallValues, _ variables: inout [String: LeafData]) -> EvalCount params.double! > Double.random(in: 0..<1) ? .as soon as : .discard mutating func reEvaluateScope(_ variables: inout [String : LeafData]) -> EvalCount fatalError("Error: `Possibly` blocks cannot be re-evaluated.")
This block has a name signature with a labeled argument known as likelihood. It has an instantiate methodology which is utilized by the Leaf engine to create this block. It will not have any parseSignatures or scope variables, we’ll depart that for the for block (go and examine the supply in LeafKit in case you are curious & courageous sufficient). We set evaluable to false since we do not wish to make it callable by way of the #consider perform. Now let’s discuss scope analysis actual fast.
The evaluateScope methodology will probably be known as first when the block inside your template will get evaluated. It’s a must to return an EvalCount on this methodology, which is able to determine what number of instances ought to we print out the contents in between your block (#[name]:THIS PART#finish[name]).
Principally when a LeafBlock is evaluated the primary time, it is by way of evaluateScope. If that returns a consequence moderately than nil, any additional calls will use reEvaluateScope as an alternative. – tdotclare
If EvalCount is about to discard then the contents will probably be discarded, in any other case it will be evaluated as many instances as you come back. If the rely is .as soon as meaning the top of the story, but when it get’s evaluated a number of instances and you do not want further params for additional analysis, then the reEvaluateScope will probably be known as for all the opposite cycles.
LeafConfiguration.entities.use(MaybeBlock.self, asBlock: "perhaps")
Do not forget that we have now to register this block with a given identify earlier than we may use it.
#perhaps(likelihood: 0.5): <p>Is that this going to occur? 50-50.</p> #endmaybe
That is it, we have simply prolonged Leaf with a fundamental block, you’ll be able to attempt to construct your individual A/B testing Chained block if you wish to dig deeper, however that is fairly a complicated matter and there are not any docs accessible simply but so you’ve gotten to try the LeafKit supply recordsdata in many of the instances.
Helpful Leaf extensions.
I’ve made a bunch of helpful Leaf extensions accessible beneath the LeafFoundation repository. It is a work-in-progress mission, however hopefully it’ll comprise lot extra fascinating extensions by the point Leaf 4 will probably be formally launched. PR’s are welcomed. 😬
Leave a Reply