@dynamicCallable Part 3: Mustacheable
After
Shell commands as Swift functions
and the
Swift/ObjC Bridge,
Part 3 in our quest to find a useful application for the Swift 5
Dynamic Callable
feature:
Mustache
templates as a function (short: MaaF).
This one may actually make some sense.
This sample for Dynamic Callable is much smaller and easier to follow along than the previous ones. Remember that you need to have Swift 5 via Xcode 10.2.
So what is Mustache? It is a super simple templating language. The ARI is providing an implementation for Swift: mustache. A template looks like this:
Hello {{name}}
You have just won {{& value}} dollars!
{{#in_ca}}
Well, {{{taxed_value}}} dollars, after taxes.
{{/in_ca}}
{{#addresses}}
Has address in: {{city}}
{{/addresses}}
{{^addresses}}
Move to Germany.
{{/addresses}}
The template features value access: {{name}}
,
conditionals: {{#in_ca}}
,
as well as repetitions: {{#addresses}}
.
The ARI Swift version comes w/ a simple Mirror based KVC implementation and you can invoke it like that:
let sampleDict : [ String : Any ] = [
"name" : "Chris",
"value" : 10000,
"taxed_value" : Int(10000 - (10000 * 0.4)),
"in_ca" : true,
"addresses" : [
[ "city" : "Cupertino" ]
]
]
let parser = MustacheParser()
let tree = parser.parse(string: template)
let result = tree.render(object: sampleDict)
You get the idea.
Dynamic Callable
But if you think about it, the template is really a function which takes a set of input parameters and returns a String with the rendered result:
func generateHTMLForWinner(arguments) -> String
let html = generateHTMLForWinner(
name: "Chris", value: 10000,
taxed_value: 6000, in_ca: true,
addresses: [[ "city": "Cupertino" ]]
)
How that function is implemented, doesn’t matter at all. And using Dynamic Callable you can implement Swift functions in arbitrary ways. In this case it is super simple:
@dynamicCallable // <===
struct Mustache {
let template : MustacheNode
init(_ template: String) {
let parser = MustacheParser()
self.template = parser.parse(string: template)
}
func dynamicallyCall(withKeywordArguments
arguments: KeyValuePairs<String, Any>)
-> String
{
let dictArgs = Dictionary(uniqueKeysWithValues:
arguments.map { ( $0.key, $0.value) })
return template.render(object: dictArgs)
}
}
This defines a struct Mustache
which wraps a (parsed) Mustache template
and can be invoked as many times as you wish.
We turn the struct
into callable “function” by implementing
Dynamic Callable.
Declaring a function which is implemented in Mustache, instead of Swift:
let generateHTMLForWinner = Mustache(
"""
Hello {{name}}
You have just won {{& value}} dollars!
{{#in_ca}}
Well, {{{taxed_value}}} dollars, after taxes.
{{/in_ca}}
{{#addresses}}
Has address in: {{city}}
{{/addresses}}
{{^addresses}}
Move to Germany.
{{/addresses}}
""")
And call that function:
let winners = [
generateHTMLForWinner(
name: "Chris", value: 10000,
taxed_value: 6000, in_ca: true,
addresses: [[ "city": "Cupertino" ]]
),
generateHTMLForWinner(
name: "Michael", value: 6000,
taxed_value: 6000, in_ca: false,
addresses: [[ "city": "Austin" ]]
)
]
Pretty slick, isn’t it? To the API consumer it looks like the plain function:
func generateHTMLForWinner(arguments) -> String
It doesn’t matter to the consumer whether that HTML generation is backed by Swift code, by a Mustache template, by a WOTemplate, or by Objective-C.
Of course the approach has some obvious deficiencies. For example it is unclear from the API what kind of arguments are supported. But hey, that’s why it’s called
dynamic
😎
Summary
You can find the finished implementation on GitHub. It even comes with tests! ⛑
The ARI mustache
module can be consumed as a regular SPM module, and it also comes with
an Xcode project.
µExpress supports Mustache
templates. Here is our tutorial on how to enhance
µExpress/SwiftNIO w/ Mustache
template support:
µExpress/NIO - Adding Templates.