golang text/template for rust
gtmpl-rust β Golang Templates for Rust
[![Latest Version]][crates.io]
[Latest Version]: https://img.shields.io/crates/v/gtmpl.svg [crates.io]: https://crates.io/crates/gtmpl
[gtmpl-rust] provides the [Golang text/template] engine for Rust. This enables seamless integration of Rust application into the world of devops tools around [kubernetes], [docker] and whatnot.
Getting Started
Add the following dependency to your Cargo manifestβ¦
[dependencies] gtmpl = "0.7"
and look at the docs:
It's not perfect, yet. Help and feedback is more than welcome.
Some Examples
Basic template:
use gtmpl;
fn main() { let output = gtmpl::template("Finally! Some {{ . }} for Rust", "gtmpl"); assert_eq!(&output.unwrap(), "Finally! Some gtmpl for Rust"); }
Adding custom functions:
use gtmpl_value::Function; use gtmpl::{FuncError, gtmpl_fn, template, Value};
fn main() { gtmpl_fn!( fn add(a: u64, b: u64) -> Result<u64, FuncError> { Ok(a + b) }); let equal = template(r#"{{ call . 1 2 }}"#, Value::Function(Function { f: add })); assert_eq!(&equal.unwrap(), "3"); }
Passing a struct as context:
use gtmpl_derive::Gtmpl;
#[derive(Gtmpl)] struct Foo { bar: u8 }
fn main() { let foo = Foo { bar: 42 }; let output = gtmpl::template("The answer is: {{ .bar }}", foo); assert_eq!(&output.unwrap(), "The answer is: 42"); }
Invoking a method on a context:
use gtmpl_derive::Gtmpl; use gtmpl::{Func, FuncError, Value};
fn plus_one(args: &[Value]) -> Result<Value, FuncError> { if let Value::Object(ref o) = &args[0] { if let Some(Value::Number(ref n)) = o.get("num") { if let Some(i) = n.as_i64() { return Ok((i +1).into()) } } } Err(anyhow!("integer required, got: {:?}", args)) }
#[derive(Gtmpl)] struct AddMe { num: u8, plus_one: Func }
fn main() { let addme = AddMe { num: 42, plusone }; let output = gtmpl::template("The answer is: {{ .plusone }}", addme); assert_eq!(&output.unwrap(), "The answer is: 43"); }
Current Limitations
This is work in progress. Currently the following features are not supported:
- complex numbers
- the following functions have not been implemented:
html, js
printf is not yet fully stable, but should support all sane* input
Enhancements
Even though it was never intended to extend the syntax of Golang text/template there might be some convenient additions:
Dynamic Template
Enable gtmpldynamictemplate in your Cargo.toml:
[dependencies.gtmpl] version = "0.7" features = ["gtmpldynamictemplate"]
Now you can have dynamic template names for the template action.
Example
use gtmpl::{Context, Template};
fn main() { let mut template = Template::default(); template .parse( r#" {{- define "tmpl1"}} some {{ end -}} {{- define "tmpl2"}} some other {{ end -}} there is {{- template (.) -}} template "#, ) .unwrap();
let context = Context::from("tmpl2");
let output = template.render(&context); asserteq!(output.unwrap(), "there is some other template".tostring()); }
The following syntax is used:
{{template (pipeline)}} The template with the name evaluated from the pipeline (parenthesized) is executed with nil data.
{{template (pipeline) pipeline}} The template with the name evaluated from the first pipeline (parenthesized) is executed with dot set to the value of the second pipeline.
Context
We use [gtmplvalue]'s Value as internal data type. [gtmplderive] provides a handy derive macro to generate the From implementation for Value.
See:
- gtmpl_value at crates.io
- gtmpl_value documentation
- gtmpl_derive at crates.io
- gtmpl_derive documentation
Why do we need this?
Why? Dear god, why? I can already imagine the question coming up why anyone would ever do this. I wasn't a big fan of Golang templates when i first had to write some custom formatting strings for docker. Learning a new template language usually isn't something one is looking forward to. Most people avoid it completely. However, it's really useful for automation if you're looking for something more lightweight than a full blown DSL.
The main motivation for this is to make it easier to write devops tools in Rust that feel native. [docker] and [helm] ([kubernetes]) use golang templates and it feels more native if tooling around them uses the same.
[gtmpl-rust]: https://github.com/fiji-flo/gtmpl-rust [Golang text/template]: https://golang.org/pkg/text/template/ [kubernetes]: https://kubernetes.io [helm]: https://github.com/kubernetes/helm/blob/master/docs/chartbestpractices/templates.md [docker]: https://docker.com [gtmplvalue]: https://github.com/fiji-flo/gtmplvalue [gtmplderive]: https://github.com/fiji-flo/gtmplderive