#template #mustache #handlebars #render-template #jinja2 #liquid #jinja

upon

一个简单、强大的模板引擎,具有最少的依赖项和可配置的分隔符

11 个版本 (破坏性更新)

0.8.1 2024年2月23日
0.8.0 2023年10月28日
0.7.1 2023年7月23日
0.6.0 2022年11月20日
0.0.0 2022年3月26日

#8 in 模板引擎

Download history 614/week @ 2024-04-23 713/week @ 2024-04-30 580/week @ 2024-05-07 663/week @ 2024-05-14 561/week @ 2024-05-21 793/week @ 2024-05-28 832/week @ 2024-06-04 827/week @ 2024-06-11 997/week @ 2024-06-18 680/week @ 2024-06-25 470/week @ 2024-07-02 949/week @ 2024-07-09 1012/week @ 2024-07-16 929/week @ 2024-07-23 1111/week @ 2024-07-30 886/week @ 2024-08-06

4,122 每月下载量
用于 24 个包(12 直接使用)

MIT/Apache

310KB
7.5K SLoC

upon

Crates.io Version Docs.rs Latest Build Status

一个简单、强大的模板引擎,具有最少的依赖项和可配置的分隔符。

目录

概述

语法

  • 表达式: {{ user.name }}
  • 条件语句: {% if user.enabled %} ... }
  • 循环: {% for user in users %} ... }
  • 嵌套模板: {% include "nested" %}
  • 可配置的分隔符: <? user.name ?>(( if user.enabled ))
  • 任意用户定义的过滤器: {{ user.name | replace: "\t", " " }}

引擎

  • 清晰且文档齐全的API
  • 可定制的值格式化器: {{ user.name | escape_html }}
  • 渲染到 String 或任何 std::io::Write 实现
  • 使用任何 serde 可序列化值进行渲染
  • 方便的宏,用于快速渲染: upon::value!{ name: "John", age: 42 }
  • 使用 {:#} 显示时提供清晰的错误信息
  • 格式无关(默认不转义HTML中的值)
  • 最小依赖和良好的运行时性能

为什么还需要另一个模板引擎?

确实,Rust已经有大量的模板引擎了!

我创建了upon,因为我需要一个具有运行时编译模板、可配置语法分隔符和最小依赖的模板引擎。我也不需要支持模板语法中的任意表达式,但偶尔我需要比输出简单值更灵活的东西(因此有过滤器)。性能也是我的一个关注点,像HandlebarsTera这样的模板引擎功能丰富,但渲染速度可能比TinyTemplate慢五到七倍。

基本上,我想要一个类似TinyTemplate的模板引擎,支持可配置的分隔符和用户定义的过滤器函数。语法灵感来源于像LiquidJinja这样的模板引擎。

MSRV

目前,upon支持的最小Rust版本是1.65。禁用filters功能,可以降低到Rust 1.60。MSRV将在破坏性版本中仅增加。

入门指南

首先,将crate添加到您的Cargo清单中。

cargo add upon

现在构建一个 Engine。该引擎存储语法配置、过滤器函数、格式化器和编译后的模板。通常,您只需要在程序的生命周期内构建一个引擎。

let engine = upon::Engine::new();

接下来,使用 add_template(..) 编译和存储模板到引擎中。

engine.add_template("hello", "Hello {{ user.name }}!")?;

最后,通过调用 template(..)render(..) 并将其渲染为字符串来渲染模板。

let result = engine
    .template("hello")
    .render(upon::value!{ user: { name: "John Smith" }})
    .to_string()?;
assert_eq!(result, "Hello John Smith!");

进一步阅读

  • syntax 模块文档概述了模板语法。
  • filters 模块文档描述了过滤器及其工作原理。
  • fmt 模块文档包含有关值格式化程序的信息。
  • 除了当前文档中的示例外,仓库中的 examples/ 目录还包含一些更具体的代码示例。

功能

以下是一些可用的 crate 功能。

  • filters (默认启用) — 启用模板中对过滤器的支持(参见 Engine::add_filter)。这不会影响值格式化程序(参见 Engine::add_formatter)。禁用此功能将提高编译时间。

  • serde (默认启用) — 启用所有 serde 支持,并拉取 serde crate 作为依赖项。如果禁用,则可以使用 render_from(..) 来渲染模板,并使用 ValueFrom 实现

  • unicode (默认启用) — 启用 unicode 支持,并拉取 unicode-identunicode-width crate。如果禁用,则模板中不再允许使用 unicode 标识符,错误格式化将使用 .chars().count()

要禁用所有功能或使用子集,您需要在您的 Cargo 清单中设置 default-features = false,然后启用您希望启用的功能。例如,要使用 serde 但禁用 filtersunicode,您将执行以下操作。

[dependencies]
upon = { version = "...", default-features = false, features = ["serde"] }

示例

嵌套模板

您可以使用名称通过 {% include .. %} 包含其他模板。

let mut engine = upon::Engine::new();
engine.add_template("hello", "Hello {{ user.name }}!")?;
engine.add_template("goodbye", "Goodbye {{ user.name }}!")?;
engine.add_template("nested", "{% include \"hello\" %}\n{% include \"goodbye\" %}")?;

let result = engine.template("nested")
    .render(upon::value!{ user: { name: "John Smith" }})
    .to_string()?;
assert_eq!(result, "Hello John Smith!\nGoodbye John Smith!");

渲染到写入器

将模板渲染为字符串而不是直接渲染,可以使用 std::io::Write 实现者,通过 to_writer(..) 方法。

use std::io;

let mut engine = upon::Engine::new();
engine.add_template("hello", "Hello {{ user.name }}!")?;

let mut stdout = io::BufWriter::new(io::stdout());
engine
    .template("hello")
    .render(upon::value!{ user: { name: "John Smith" }})
    .to_writer(&mut stdout)?;
// Prints: Hello John Smith!

短期生命周期的借用模板

如果模板源的生命周期短于引擎的生命周期,或者您不需要存储编译后的模板,您还可以使用 compile(..) 函数直接返回模板。

let template = engine.compile("Hello {{ user.name }}!")?;
let result = template
    .render(&engine, upon::value!{ user: { name: "John Smith" }})
    .to_string()?;
assert_eq!(result, "Hello John Smith!");

自定义模板存储和函数

compile(..) 函数还可以与自定义模板存储结合使用,这可以允许更高级的使用场景。例如:相对模板路径或控制模板访问。

let mut store = std::collections::HashMap::<&str, upon::Template>::new();
store.insert("hello", engine.compile("Hello {{ user.name }}!")?);
store.insert("goodbye", engine.compile("Goodbye {{ user.name }}!")?);
store.insert("nested", engine.compile("{% include \"hello\" %}\n{% include \"goodbye\" %}")?);

let result = store.get("nested")
    .unwrap()
    .render(&engine, upon::value!{ user: { name: "John Smith" }})
    .with_template_fn(|name| {
        store
            .get(name)
            .ok_or_else(|| String::from("template not found"))
    })
    .to_string()?;
assert_eq!(result, "Hello John Smith!\nGoodbye John Smith!");

基准测试

在 Rust 生态系统中的几个流行的模板渲染引擎中进行了 upon 的基准测试。显然,每个引擎都具有完全不同的功能集,因此基准测试仅比较它们共享的一些功能的性能。

Violin plot of compile results Violin plot of render results Violin plot of render with filters results

基准测试是在一个安静的云机器上使用 criterion 进行的。

主机

  • Vultr.com
  • 4 CPU
  • 8192 MB RAM
  • Ubuntu 22.04
  • Rust 1.71.0

许可证

许可证:Apache License,版本 2.0(LICENSE-APACHEhttps://apache.ac.cn/licenses/LICENSE-2.0

依赖关系

~110–445KB

~110–445KB