ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

dbt-jinja 中的 eval_to_state 实战:用 minijinja State 渲染单块、调用宏与读取模板导出

dbt-jinja 中的 eval_to_state 实战:用 minijinja State 渲染单块、调用宏与读取模板导出 dbt-jinja 中的 eval_to_state 实战用 minijinja State 渲染单块、调用宏与读取模板导出【免费下载链接】dbtdbt enables data analysts and engineers to transform their data using the same practices that software engineers use to build applications.项目地址: https://gitcode.com/GitHub_Trending/db/dbt本篇指南以仓库内 crates/dbt-jinja/examples/eval-to-state 示例为主线系统讲解 minijinja 模板引擎中eval_to_state与State对象这对 API 的用法。读完本文你将掌握如何在不产生最终渲染字符串的前提下完成模板内省——单独渲染某个 block、按名调用宏、读取顶层导出变量以及从外部直接调用内置函数这些能力在静态站点生成、宏单元测试与模板调试等场景中非常实用。示例背景minijinja 在 dbt-core 仓库中的位置dbt-core 仓库的 crates/dbt-jinja 目录内嵌了 minijinja 模板引擎的完整源码版本 2.5.0见 crates/dbt-jinja/minijinja/Cargo.tomlexamples/目录下则提供了大量可运行示例eval-to-state就是其中之一。该示例的定位非常明确原文档写道An example that shows how to useeval_to_stateand the resultingStateobject. Together these APIs can be used to render single blocks, invoke macros, access exports of a template and more.即eval_to_state与State组合起来可以渲染单个 block、调用宏、访问模板的导出项等。整个示例的目录结构如下crates/dbt-jinja/examples/eval-to-state/ ├── Cargo.toml ├── README.md └── src/ ├── main.rs └── templates/ ├── index.html └── layout.html运行方式也很简单在示例目录下执行$ cargo runeval_to_state 的本质评估模板丢弃输出保留状态理解eval_to_state之前先看它的源码定义。在 crates/dbt-jinja/minijinja/src/template.rs 中它的文档注释说得很清楚Evaluates the template into aState. This evaluates the template, discards the output and returns the finalStatefor introspection. From there global variables or blocks can be accessed. What this does is quite similar to how the engine internally works with templates that are extended or imported from.也就是说它与普通render的最大区别在于模板仍然会被完整执行包括extends、import、set等顶层语句但最终拼接出的输出字符串被丢弃取而代之的是返回一个可内省的State对象。其实现路径template.rs#L227-L245大致是将上下文ctx序列化为根Value创建Vm虚拟机用编译后的指令、块集合与初始自动转义配置执行模板从执行结果中取出State并返回。这正是引擎在处理extends/import时内部使用的方式——评估父模板或被导入模板时也需要先拿到其状态再继续组合。因此eval_to_state并不是一个取巧的 hack而是把引擎内部的既有机制开放给了使用者。一个值得注意的细节是仓库内 minijinja 源码中的eval_to_state签名还带有一个listeners参数见 template.rs#L218-L224 与文档示例而eval-to-state示例中的调用只传了上下文这与当前源码的完整签名略有出入。以仓库内该示例的源码为准即可实际接入时请以你所依赖版本的 API 签名为准。模板文件extends、block、macro 与顶层 set示例的模板分两个文件index.html继承layout.html结构如下。index.html{% extends layout.html %} {% macro utility() %}Global var is {{ global_variable}}{% endmacro %} {% block title %}Index{% endblock %} {% block body %} Hello from index.html {{ utility() }} {% endblock %}layout.html!doctype html title{% block title %}{% endblock %} | My Site/title nav ul lia href{{ site_url }}/index.htmlIndex/a/li lia href{{ site_url }}/about.htmlAbout/a/li /ul /nav {%- set global_variable 42 %} div classcontent {% block body %}{% endblock %} /div这两个模板覆盖了eval_to_state演示所需的全部要素extends继承index.html通过{% extends layout.html %}继承布局layout.html中定义了title、body两个可覆盖的 blockmacro宏定义index.html定义了utility宏内部引用了global_variable顶层setlayout.html在顶层执行{%- set global_variable 42 %}由于eval_to_state会完整执行模板这个变量会在评估后被写入导出集合上下文变量layout.html中的site_url来自调用方传入的上下文。主程序逐段拆解 eval_to_state 的全部用法main.rs 是示例的核心。先看环境搭建部分use minijinja::{args, context, Environment}; fn main() { let mut env Environment::new(); env.add_template(layout.html, include_str!(templates/layout.html)) .unwrap(); env.add_template(index.html, include_str!(templates/index.html)) .unwrap(); let template env.get_template(index.html).unwrap(); let mut state template .eval_to_state(context! { site_url http://example.com, }) .unwrap();关键点有三处模板源码通过include_str!在编译期嵌入运行时直接注册到Environmentcontext!宏构造上下文这里向模板注入了site_url变量供layout.html中的导航链接使用eval_to_state返回State注意这里的state被声明为mut——因为后续的render_block是一个有状态操作需要可变引用。渲染单个 blockrender_blockprintln!(Block title: {:?}, state.render_block(title).unwrap()); println!(Block body: {:?}, state.render_block(body).unwrap());render_block的源码位于 crates/dbt-jinja/minijinja/src/vm/state.rs#L364-L371其内部通过Vm::call_block执行指定名字的块。它的文档注释强调了两个重要约束Note that rendering a block is a stateful operation. If an error is returned the module has to be re-created as the internal state can end up corrupted.也就是说渲染 block 是有状态的一旦出错内部状态可能损坏需要重新创建模板/状态同时它要求mut self因此在过滤器等回调内部无法使用。此外该方法受multi_templatefeature 门控从 state.rs#L362-L363 的#[cfg(feature multi_template)]可以看到。对于本示例render_block(title)会渲染index.html覆盖后的title块输出Indexrender_block(body)会渲染body块其中还调用了utility()宏输出大致为Hello from index.html\nGlobal var is 42。这演示了跳过整页渲染、只取其中某一块的能力——静态站点生成器可以借此为每个页面单独渲染头部、正文、侧栏等局部内容。按名调用宏call_macroprintln!( Macro utility: {:?}, state.call_macro(utility, args!()).unwrap() );call_macro的实现位于 state.rs#L307-L314它先像lookup一样按名字查找全局宏再以传入参数调用并转换为字符串底层call_macro_rawstate.rs#L322-L333则返回原始Value。若找不到对应宏会返回ErrorKind::UnknownFunction错误。该方法受macrosfeature 门控state.rs#L305-L306。这里调用utility宏时未传参数args!()为空参数列表宏内部引用global_variable输出Global var is 42——注意这个值来自布局模板顶层的set说明宏在调用时能正确捕获评估后的变量闭包。读取变量lookupprintln!( Variable global_variable: {:?}, state.lookup(global_variable) );lookup用于按名在上下文中查找变量签名位于 state.rs#L274-L299。它的文档注释特别提醒了闭包语义Macros and call blocks analyze which variables are referenced and create closures for them. This means that unless a variable is defined as a global in the environment or it was referenced by a macro, this method wont be able to find it.即宏与 call block 会做变量引用分析并创建闭包因此lookup只能找到环境级 global、以及被宏引用过的变量。在本例中global_variable恰好被utility宏引用所以可以正常查得Some(42)。另外lookup还会走宏命名空间解析macro_namespace_template_resolver尝试把名字解析为package.macro形式的宏派发对象。读取导出exportsprintln!(Exports: {:?}, state.exports());exports返回模板评估后所有顶层变量的名字列表实现于 state.rs#L374-L376pub fn exports(self) - Vecstr { self.ctx.exports().keys().copied().collect() }它读取上下文导出表Locals的全部键名。对于本示例由于layout.html顶层执行了set global_variable 42且模板评估完整执行了继承链因此可以推断导出列表主要包含global_variable等顶层变量。这正是模板即模块思想的体现State相当于模板执行后的模块对象exports()就是它的__all__。元信息与内置函数name、undefined_behavior、rangeprintln!(Template name: {:?}, state.name()); println!(Undefined behavior: {:?}, state.undefined_behavior()); println!( Range function resolved: {:?}, state.lookup(range).unwrap() ); println!( Range function invoked: {:?}, state .lookup(range) .unwrap() .call(state, args!(5)) .unwrap() );name()state.rs#L243-L245返回当前模板名即index.htmlundefined_behavior()state.rs#L255-L257直接透传环境上配置的未定义变量行为UndefinedBehavior默认情况下访问未定义变量会得到Undefined而非报错最后一段展示了从外部解析并调用内置函数先用lookup(range)取出函数对象再通过Value::call以args!(5)调用它。minijinja 内置的range会生成从 0 开始的序列因此可以推断最终输出为[0, 1, 2, 3, 4]。这证明了State不仅能读数据还能作为入口执行模板环境中的任何可调用对象。运行示例与预期输出在 crates/dbt-jinja/examples/eval-to-state 目录下执行$ cargo run该示例的 Cargo.toml 声明了对仓库内 minijinja 的路径依赖[dependencies] minijinja { version 2.5.0, path ../../minijinja }即直接复用仓库内的 crates/dbt-jinja/minijinja 源码编译无需联网拉取依赖。结合上文分析程序的输出可归纳为title块渲染出Index、body块渲染出包含宏调用结果的正文、utility宏输出Global var is 42、lookup(global_variable)返回Some(42)、导出列表包含顶层变量名、模板名为index.html、未定义行为为默认值以及range函数解析成功并被调用生成序列。典型应用场景综合原文档与源码实现eval_to_stateState的典型价值体现在局部渲染 / 静态站点生成一次评估拿到State后反复调用render_block输出页面不同区块避免对同一模板重复评估extends继承链上的块也能被精确定位渲染宏的单元测试用call_macro在隔离环境中直接调用模板宏并断言输出无需先渲染完整页面模板调试与内省exports()查看模板导出了哪些变量、lookup检查某变量在当前状态下的值、name/undefined_behavior获取模板元信息方便排查为什么这个变量渲染不出来类问题外部驱动模板逻辑通过lookup拿到内置函数或其他可调用对象后从 Rust 侧直接调用把模板环境当作一个可编程的运行时来使用。需要注意的是render_block的有状态性与出错后的不可恢复性意味着它适合一次性评估、多次顺序渲染的模型若要在并发或过滤器回调中使用应结合Template::new_state见 template.rs#L300-L308等机制另行设计。总体而言这份示例是理解 minijinja 模板即状态机 理念的最佳入门读物而eval_to_state正是把引擎内部求值机制开放给上层应用的钥匙。【免费下载链接】dbtdbt enables data analysts and engineers to transform their data using the same practices that software engineers use to build applications.项目地址: https://gitcode.com/GitHub_Trending/db/dbt创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表