Coder Social home page Coder Social logo

gtmpl-rust's Introduction

gtmpl-rust – Golang Templates for Rust

Latest Version


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 add_me = AddMe { num: 42, plus_one };
    let output = gtmpl::template("The answer is: {{ .plus_one }}", add_me);
    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 gtmpl_dynamic_template in your Cargo.toml:

[dependencies.gtmpl]
version = "0.7"
features = ["gtmpl_dynamic_template"]

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);
    assert_eq!(output.unwrap(), "there is some other template".to_string());
}

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 gtmpl_value's Value as internal data type. gtmpl_derive provides a handy derive macro to generate the From implementation for Value.

See:

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's People

Contributors

fiji-flo avatar flavio avatar pftbest avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

gtmpl-rust's Issues

Enumeration in range

Hi,

Do you know the best way I could get the iteration count to replicate the below which is currently working in helm?

  {{- range $i, $val := $config_col.init }}
  init_{{ printf "%05d" $i }}_{{ $val.name }}: |-
{{ $val.script | indent 4}}
  {{- end }}

Given functions are never called

There exists the possibility to supply a call back in the form of (String, fn(&[Value])->Result<Value,String>). see: here

But from my understanding the Func part is dropped immediately before serialization. see: here and here

Are there any plans to use this feature in the future?

Allow closures to be added as functions.

Happy to write the PR for this, but is there any chance you'd consider allowing adding Closures to the function map.

I used this all the time in Go, and now in rust I'd really like to.

Maybe another function

pub enum MapFunc{
    Closure(Box<Fn(&[Value])->Result<Value,String>),
    Static(fn(&[Value]->Result<Value,String>),
}

//Then add the function
impl Template {
    add_closure(f:Box<Fn(&[Value])->Result<Value,String>{
        ///...
    }
}

Can't assign string to a variable without using printf

This seems to work in go

{{ $suite := "testing" }}

while for gtmpl-rust it seems to be required to use printf, which is not really a problem but breaks compatibility with existing templates.

{{ $suite := printf "testing" }}

I'm sorry in case this is explicitly not supported and I just missed it.

Potentially expose more info about the parsed template in the public API

I wanted to see what the appetite would be for exposing more information about the parsed template publicly would be. My use case is that I want to crawl a list of templates (in my case, a Helm chart) and determine which fields are actually used in them. I have a (very much prototype-level) branch here where I simply exposed the Nodes enum and added a children() method to each node which I use here to walk the tree and build a list of paths that are used.

I'm happy to potentially work on a PR for a better thought out version of the changes to the node modules above. I recognize, though, that this is probably not part of the original goals of the library, and would be a lot of new API surface to support, so I'm happy to keep my branch for my purposes if there's not interest in including something like this.

Support additional delimiters

Golang allows setting the delimiters to use when parsing a template if the template is not using the default delimiters. It would be nice to have the same support here.

Unimplemented syntax =

I found that this implementation is missing a very important syntax = ,
Why is this syntax not implemented, is it because of some limitation or simply forgotten?

These test codes for call func make strange error

These test codes for call func make strange error. Function 'tmpl_test1' can not pass, But function 'tmpl_test2' can pass.
The different between them:
In tmpl_test1, the function is stored in a map;
In tmpl_test2, the function is used straight.

#[cfg(test)]
mod tests {
    use std::collections::HashMap;
    use gtmpl::{gtmpl_fn, template, Value};
    use gtmpl_value::{self, FuncError, Function};

    gtmpl_fn!(
        fn shout(n: i64) -> Result<String, FuncError> {
            let mut v = Vec::new();
            for _ in 0..n {
                v.push("wang".to_string());
            }
            Ok(v.join(","))
        }
    );

    #[test]
    fn tmpl_test1() {
        let mut a: HashMap<String, Value> = HashMap::new();
        a.insert(
            "shout".to_string(),
            Value::Function(Function { f: shout }),
        );
        let equal = template(r#"{{ call .shout 3 }}"#, a);
        if equal.is_ok() {
            assert_eq!(&equal.unwrap(), "wang,wang,wang");
        } else {
            assert_eq!(&equal.unwrap_err().to_string(), "wang,wang,wang");
        }
    }
    #[test]
    fn tmpl_test2() {
        let equal: Result<String, gtmpl::TemplateError> = template(
            r#"{{ call . 3 }}"#,
            Value::Function(Function { f: shout }),
        );
        if equal.is_ok() {
            assert_eq!(&equal.unwrap(), "wang,wang,wang");
        } else {
            assert_eq!(&equal.unwrap_err().to_string(), "wang,wang,wang");
        }
    }
}

Indentation of Dynamic Templates

Hi,

Sorry for all the questions. This lib is just working so well I've pretty much got everything I need to finish what I'm working on so want to try get it done. Last issue which I'm not really sure how to deal with is indentation when inserting dynamic templates.

In this file I'm creating templates https://github.com/sstubbs/postgres-xl-operator/blob/master/templates/_operator_helpers.tpl. This one is creating variables https://github.com/sstubbs/postgres-xl-operator/blob/master/templates/_operator_vars.tpl and then currently there is just one main template loading these at https://github.com/sstubbs/postgres-xl-operator/blob/master/templates/main.tpl

I've been trying various ways and I can't get the inserted dynamic template indented. It respects the whitespace for variables both from the context and others created in the template but not for dynamic templates.

Go has | indent

Can you think of a way I can do this?

Variables in Dynamic Templates

Is this supported?

{{- define "Values"}} {{.values.image}} {{ end -}}

there is {{- template ("Values") -}} template

Or is there another way you suggest I do this?

Sequences

Hi,

Thank you for the work on this great library. Does this work for sequences yet of any type? I'm converting a yaml file with serde_yaml and passing the struct I have created into this. Most of it works well even the nested objects which I'm really happy with. I have a sequence like this in yaml:

  init:
    - name: create_database.sh
      script: |-
        psql -c "CREATE DATABASE tester;"
        export PGDATABASE="tester"

with serde_yaml I'm mapping it like this:

#[derive(Gtmpl, Debug, PartialEq, Serialize, Deserialize)]
pub struct HelmValuesOnLoadInit {
    name: String,
    script: String,
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct HelmValuesOnLoad {
    init: Vec<HelmValuesOnLoadInit>,
}

I have tried various ways but no sequence types seem to be supported.

support .Method

Hi,

Is it possible/planned to support calling method on "current context".

extracted from https://golang.org/pkg/text/template/ :

  • The name of a niladic method of the data, preceded by a period,
    such as
    .Method
    The result is the value of invoking the method with dot as the
    receiver, dot.Method(). Such a method must have one return value (of
    any type) or two return values, the second of which is an error.
    If it has two and the returned error is non-nil, execution terminates
    and an error is returned to the caller as the value of Execute.
    Method invocations may be chained and combined with fields and keys
    to any depth:
    .Field1.Key1.Method1.Field2.Key2.Method2
    Methods can also be evaluated on variables, including chaining:
    $x.Method1.Field

My use case:
being able to call getter / lazy computed value from struct like Path / Uri (path(), filename(), hostname(), lazy value),
because computing those value before, could be costly and useless (if not used in the template).

(Thanks for your work)

Why Any?

Hello

Sorry for asking a stupid question, but I'm curious, why is Arc<Any> used everywhere? Can there be something other than Value?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.