はじめに
Tauri のインストーラースクリプト作成に Hnadlebars が使われているのを見かけた。
今後 Rust でのテンプレートエンジンはどれにしようかなとも思っていたのと、Hnadlebars 自体は初めてなので、挙動や書き方をメモろうと思ったのでこのポストを書く。
確認環境
- handlebars 5.1.2
- Windows 11 Pro 23H2
- Rust 1.77.2
導入
cargo add handlebars
使い方
基本的な使い方
基本的には
- テンプレートの作成・取得
- テンプレートの登録
- レンダリングの実行
という手順をとる。
use handlebars::Handlebars;
use serde::Serialize;
#[derive(Serialize)]
struct Data {
world: String,
}
fn main() {
// オブジェクトを作成
let mut handlebars = Handlebars::new();
// 代入するための構造体を作成
let data = Data {
world: "世界!".to_string(),
};
// テンプレートを定義
let template = "hello, {{world}}";
// テンプレートを `t1` という名前で登録
handlebars.register_template_string("t1", template).unwrap();
// レンダリング (値の代入と結果の出力) を実行
println!("{}", handlebars.render("t1", &data).unwrap());
}
実行すると以下の結果を得られる。
hello, 世界!
公式ドキュメントでは、 BTreeMap
のサンプルも記載がある。
let mut data = BTreeMap::new();
data.insert("world".to_string(), "世界!".to_string());
assert_eq!(handlebars.render("t1", &data).unwrap(), "hello 世界!");
これは serde
自体が BTreeMap
のシリアライズを実装しているためそのまま使えるという事。
ドキュメントには serde::Serialize
可能なデータタイプでないとだめとしか書かれていないので混乱するが、つまりそういう事。
配列を処理する
基本は {{#each <key>}} {{/each}}
で囲む。
use handlebars::Handlebars;
use serde::Serialize;
#[derive(Serialize)]
struct Goods {
name: String,
price: u32,
}
#[derive(Serialize)]
struct Basket {
contents: Vec<Goods>,
}
fn main() {
let mut handlebars = Handlebars::new();
let data = Basket {
contents: vec![
Goods {
name: "ほうれん草".to_string(),
price: 99,
},
Goods {
name: "新玉ねぎ".to_string(),
price: 280,
},
Goods {
name: "レタス".to_string(),
price: 158,
},
],
};
let template = "{{#each contents}}\n{{name}} {{price}}円\n{{/each}}\n";
handlebars.register_template_string("t1", template).unwrap();
println!("{}", handlebars.render("t1", &data).unwrap());
}
実行すると次の結果を得られる。
ほうれん草 99円
新玉ねぎ 280円
レタス 158円
テンプレートファイルをレンダリングする
template.txt
今日の買い物リスト {{date}}
バスケット:
{{#each contents}}
- {{name}} {{price}}円
{{/each}}
use handlebars::Handlebars;
use serde::Serialize;
use std::path::PathBuf;
#[derive(Serialize)]
struct Goods {
name: String,
price: u32,
}
#[derive(Serialize)]
struct Basket {
date: String,
contents: Vec<Goods>,
}
fn main() {
let mut handlebars = Handlebars::new();
let data = Basket {
date: chrono::Local::now().to_string(),
contents: vec![
Goods {
name: "ほうれん草".to_string(),
price: 99,
},
Goods {
name: "新玉ねぎ".to_string(),
price: 280,
},
Goods {
name: "レタス".to_string(),
price: 158,
},
],
};
// let template = "{{#each contents}}\n{{name}} {{price}}円\n{{/each}}\n";
handlebars
.register_template_file(
"t1",
PathBuf::from(r"F:\Develop\Rust\_learn\handlebars_\src\template.txt"),
)
.unwrap();
println!("{}", handlebars.render("t1", &data).unwrap());
}
実行すると次の結果が得られる。
今日の買い物リスト 2024-05-18 20:33:49.547171800 +09:00
バスケット:
- ほうれん草 99円
- 新玉ねぎ 280円
- レタス 158円
さいごに
Handlebars 自体は色んな言語環境で使われている様子だし、そんなに書き方も違和感を感じない。
Rust でのどの serde::Serialize タイプを前提にというのもあるので、そこだけ意識しておけばよさそう。