Rust模板引擎gtmpl的使用:轻量高效、支持Go风格模板语法的编译时渲染库
Rust模板引擎gtmpl的使用:轻量高效、支持Go风格模板语法的编译时渲染库
gtmpl-rust为Rust提供了Golang的text/template模板引擎实现,这使得Rust应用能够无缝集成到围绕Kubernetes、Docker等devops工具生态中。
快速开始
在Cargo.toml中添加以下依赖:
[dependencies]
gtmpl = "0.7"
示例代码
基础模板使用
use gtmpl;
fn main() {
// 使用简单字符串作为模板和上下文
let output = gtmpl::template("Finally! Some {{ . }} for Rust", "gtmpl");
assert_eq!(&output.unwrap(), "Finally! Some gtmpl for Rust");
}
添加自定义函数
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");
}
传递结构体作为上下文
use gtmpl_derive::Gtmpl;
// 使用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");
}
调用上下文方法
use gtmpl_derive::Gtmpl;
use gtmpl::{Func, FuncError, Value};
// 定义plus_one方法
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))
}
// 使用Gtmpl派生宏
#[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");
}
当前限制
这是正在进行的工作,目前不支持以下功能:
- 复数
- 以下函数尚未实现:
html
,js
printf
还不完全稳定,但应该支持所有合理的输入
增强功能
虽然不打算扩展Golang text/template的语法,但可能会有一些方便的添加:
动态模板
在Cargo.toml中启用gtmpl_dynamic_template
:
[dependencies.gtmpl]
version = "0.7"
features = ["gtmpl_dynamic_template"]
现在可以为template
动作使用动态模板名称。
示例
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());
}
使用以下语法:
{{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.
上下文
使用gtmpl_value的Value作为内部数据类型。gtmpl_derive提供了一个方便的derive宏来为Value生成From实现。
为什么需要这个?
主要动机是使在Rust中编写devops工具更容易,这些工具感觉更原生。Docker和Helm(Kubernetes)使用golang模板,如果围绕它们的工具使用相同的模板,感觉会更原生。
完整示例Demo
下面是一个结合了多个功能的完整示例:
use gtmpl::{template, Value, FuncError, Func};
use gtmpl_derive::Gtmpl;
use gtmpl_value::Function;
// 自定义结构体
#[derive(Gtmpl)]
struct User {
name: String,
age: u8,
// 可以包含自定义方法
greet: Func,
}
// 自定义方法实现
fn greet_user(args: &[Value]) -> Result<Value, FuncError> {
if let Value::Object(ref o) = &args[0] {
if let Some(Value::String(ref name)) = o.get("name") {
if let Some(Value::Number(ref age)) = o.get("age") {
if let Some(age) = age.as_u64() {
return Ok(Value::String(format!("Hello {}, you are {} years old!", name, age)));
}
}
}
}
Err(FuncError::Custom("Invalid arguments".into()))
}
// 自定义函数
gtmpl_fn!(
fn double(x: i32) -> Result<i32, FuncError> {
Ok(x * 2)
});
fn main() {
// 创建用户实例
let user = User {
name: "Alice".to_string(),
age: 30,
greet: Func { f: greet_user },
};
// 定义模板
let tmpl = r#"
User Information:
Name: {{ .name }}
Age: {{ .age }}
Double Age: {{ call .double .age }}
Greeting: {{ .greet }}
"#;
// 添加自定义函数到上下文
let mut context = Value::from(user);
if let Value::Object(ref mut o) = context {
o.insert("double".to_string(), Value::Function(Function { f: double }));
}
// 渲染模板
let output = template(tmpl, context).unwrap();
println!("{}", output);
}
这个完整示例展示了:
- 使用自定义结构体作为模板上下文
- 在结构体中定义方法并在模板中调用
- 添加独立的自定义函数并在模板中使用
- 复杂的模板渲染场景
输出结果类似:
User Information:
Name: Alice
Age: 30
Double Age: 60
Greeting: Hello Alice, you are 30 years old!
1 回复
Rust模板引擎gtmpl的使用指南
介绍
gtmpl是一个轻量高效的Rust模板引擎,支持Go风格的模板语法,并在编译时完成渲染。它的主要特点包括:
- 完全兼容Go的
text/template
语法 - 编译时模板解析和验证
- 零运行时开销
- 类型安全的模板渲染
- 支持模板继承、部分模板和管道操作
安装
在Cargo.toml中添加依赖:
[dependencies]
gtmpl = "0.7"
gtmpl_derive = "0.7"
基本用法
1. 定义模板
use gtmpl::{Template, Context};
use gtmpl_derive::Gtmpl;
// 定义数据结构并派生Gtmpl trait
#[derive(Gtmpl)]
struct Person {
name: String,
age: u32,
}
// 创建模板实例
let template = Template::new("Hello, {{.name}}! You are {{.age}} years old.");
2. 渲染模板
// 创建数据实例
let person = Person {
name: "Alice".to_string(),
age: 30,
};
// 创建渲染上下文
let ctx = Context::from_serialize(&person).unwrap();
// 渲染模板
let output = template.render(&ctx).unwrap();
println!("{}", output); // 输出: Hello, Alice! You are 30 years old.
高级功能
条件语句
let template = Template::new(r#"
{{if .age >= 18}}
{{.name}} is an adult.
{{else}}
{{.name}} is a minor.
{{end}}
"#);
let person = Person {
name: "Bob".to_string(),
age: 16,
};
let ctx = Context::from_serialize(&person).unwrap();
println!("{}", template.render(&ctx).unwrap());
// 输出: Bob is a minor.
循环
// 定义包含集合的数据结构
#[derive(Gtmpl)]
struct Team {
name: String,
members: Vec<Person>,
}
let team = Team {
name: "Rustaceans".to_string(),
members: vec![
Person { name: "Alice".to_string(), age: 30 },
Person { name: "Bob".to_string(), age: 25 },
],
};
let template = Template::new(r#"
Team {{.name}} members:
{{range .members}}
- {{.name}} ({{.age}})
{{end}}
"#);
let ctx = Context::from_serialize(&team).unwrap();
println!("{}", template.render(&ctx).unwrap());
/* 输出:
Team Rustaceans members:
- Alice (30)
- Bob (25)
*/
模板函数
use gtmpl_value::Func;
let mut template = Template::new("{{greet .name}}");
// 注册自定义模板函数
template.register_func("greet", |name: String| format!("Hello, {}!", name));
let ctx = Context::from_serialize(&Person {
name: "Charlie".to_string(),
age: 40,
}).unwrap();
println!("{}", template.render(&ctx).unwrap());
// 输出: Hello, Charlie!
编译时模板
use gtmpl_derive::template;
// 使用宏在编译时解析模板
#[template(source = "{{.name}} is {{.age}} years old.")]
static PERSON_TEMPLATE: Template;
let ctx = Context::from_serialize(&Person {
name: "Dave".to_string(),
age: 50,
}).unwrap();
println!("{}", PERSON_TEMPLATE.render(&ctx).unwrap());
// 输出: Dave is 50 years old.
完整示例
use gtmpl::{Template, Context};
use gtmpl_derive::{Gtmpl, template};
use gtmpl_value::Func;
// 1. 定义数据结构
#[derive(Gtmpl)]
struct Person {
name: String,
age: u32,
hobbies: Vec<String>,
}
// 2. 定义编译时模板
#[template(source = r#"
{{.name}}的个人信息:
年龄: {{.age}}
{{if gt .age 18}}已成年{{else}}未成年{{end}}
爱好:
{{range .hobbies}}
- {{.}}
{{end}}
"#)]
static PROFILE_TEMPLATE: Template;
fn main() {
// 3. 准备数据
let person = Person {
name: "Eve".to_string(),
age: 22,
hobbies: vec!["编程".to_string(), "阅读".to_string(), "运动".to_string()],
};
// 4. 创建运行时模板并注册函数
let mut runtime_template = Template::new("{{upper .name}}");
runtime_template.register_func("upper", |s: String| s.to_uppercase());
// 5. 渲染编译时模板
let ctx = Context::from_serialize(&person).unwrap();
println!("{}", PROFILE_TEMPLATE.render(&ctx).unwrap());
// 6. 渲染运行时模板
println!("{}", runtime_template.render(&ctx).unwrap());
}
输出结果:
Eve的个人信息:
年龄: 22
已成年
爱好:
- 编程
- 阅读
- 运动
EVE
性能建议
- 对于静态模板,使用
#[template]
宏在编译时解析 - 重用
Template
实例,它们可以安全地跨线程共享 - 对于复杂数据结构,实现
gtmpl::Templateable
trait以获得最佳性能
gtmpl是Rust生态中一个优秀的模板引擎选择,特别适合需要高性能和Go风格模板语法的场景。