Rust のテンプレートエンジン askama を使ってみた

2024/09/18に公開

背景、お久しぶりでございます

Rust でテンプレートエンジンを使ってコードの自動生成をしてみることにしました。Rust なのはただの趣味です。意味はない。
で、Rust テンプレートエンジンって調べると、なんだかいっぱいあるっぽい。何を使うのが良いのだろうか。

Rust template engine benchmarks をみると Handlebars, Liquid, Sailfish, Markup というのが早いっぽい。

これを眺めてて気になったのが、 AskamaSailfish 。これらのテンプレートエンジンは、どうやらコンパイル時にテンプレートをプログラムに埋め込むことが出来るっぽい。1つのデータから、複数のテンプレートを使って生成するような場合には向かないけど、今回私は単一のデータから単一のファイルを出力するだけなので、コンパイル時に分かるほうが色々とはかどります。テンプレートファイルを読み込む処理が要らないし、楽じゃん、コレ、というわけで今回はひとまずスターが多い Askama を選んでみました。別に私は速度はそんなに重要でもないしね。

サンプルプロジェクト

サンプルプロジェクトは askama_sample に置いています。

プロジェクトの初期化

cargo init
cargo add askama
mkdir templates
touch askama.toml
  1. Cargo で初期化
  2. askama を入れる
  3. テンプレートを入れるディレクトリを作成
  4. askama の設定ファイルを作成

設定ファイル askama.toml

今回は C++ のヘッダをテンプレートエンジンから作成します。

デフォルトだと拡張子に使えるのが html とか txt だけっぽいので、 hpp を使えるように askama.toml で指定します。私が最大の嵌って調べるのに時間がかかったポイントです。

[[escaper]]
path = "::askama::Text"
extensions = ["hpp"]

::askama::Text は Escaper を実装している構造体名らしい。 Text だと何もエスケープしない。 HTML だと HTML の特殊文字をエスケープ処理してくれるっぽい。

C++ ヘッダなので特にエスケープする必要もないので、 ::askama::Text を選びました。

テンプレートの作成

とりあえず、おもむろにテンプレートファイル templates/foobar.hpp を作成します。文法はしっかりとは理解してませんが、この資料 を適当に眺めながら書いたら、なんかかけました。

{{- のように {{{% などの後に - を書いてやると直前の空白を無効化できます。逆に -}} だと直後の空白を無効化します。これは正直生成された結果を見ながらええ感じに調整したので、どうやるのがベストなのかはよくわかりません。

プログラムの作成

あとは、テンプレートで作ったデータを埋めるようにプログラム側で構造体を用意してやるだけです。

use askama::Template;

#[derive(Template)]
#[template(path = "foobar.hpp")]
struct Foobar<'a> {
    namespace: &'a str,
    structures: Vec<Structure<'a>>,
}

これだけで Foobartemplates/foobar.hpp をテンプレートにした出力を作る render が作られます。

println!("{}", foobar.render().unwrap());

コンパイル時に足りないフィールドやメソッド等も分かるので、実行して確認しなくても安心です。まぁ、もうちょっとわかりやすいエラーメッセージだとありがたいけど。

実行結果

良い感じに C++ のヘッダが出力されましたね。

出力
#pragma once
#include <cstdint>

namespace fooooobaaaaaa {

/// @brief foooooo
struct Foo {
  /// @brief HOGE
  const char* hoge;
  /// @brief PIYO
  uint8_t piyo;
  /// @brief TARO
  uint32_t taro[3][2][10];
};

/// @brief baaaaaa
struct Bar {
  /// @brief HOGE
  uint32_t x;
  /// @brief PIYO
  uint8_t y;
};

} // namespace fooooobaaaaaa

感想

askama.toml の書き方が分かれば非常に簡単に使うことが出来ました。

ファイルの読み込み処理が要らないので使いやすいですね。テンプレートファイルを指定する場合は、自分の実行バイナリの場所からフォルダを見つけるとか、面倒くさい処理しないといけないですからね。

ただ、1つの構造体につき1つのテンプレートになるので、やや使いまわしがしにくいのかな?
トップが Rc を持ってるだけとか一つ階層を挟む必要があるかもしれませんね。

個人的にはとても便利だと思います。

Discussion