テンプレートエンジンnなGo!

まれにあるごーこんごー。

「Goはコンパイル型であり、どうやら原則全部スタティックリンクしちゃう乱暴者らしいので、作ったバイナリはなんなら単独での持ち運びには便利ぞ」あたりのとこまでの理解は出来ていた、とする。というかあなたが、じゃのーて、ワタシがな。どうも直接 Go をネタにしたのはこれまで3回だけらしい:

このうちの最初のは「直接」というよりは、話の流れでついでに出しただけ。そして、ふたつめのが今回のネタに動機を等しくする。つまり「おぉもよもとよ EXE でないとはなさけない」と脅迫されてしまっている場合の、Goを「ひとつの救世主」とみなした場合の、「じゃあこいつの「テンプレートエンジン」る手間とか実力ってばどーなのよ」てことね。

ここ数日来ネタにしてきた MSYS なんぞもそうなんだけれど、「なんでもできるリッチな Python とかってすてきーきゃー」だけでは済まないことってのも結構あって、「小さくて持ち運びしやすい」ものでないと困るって状況ね、そういう状況にある場合に「Go で簡単なテンプレートエンジンが見繕えたらそれってすっげー助かるであろうよ?」って話。

テンプレートエンジン関係の話って、結構してきたつもりになってたんだけれど、独立したネタとしてはあんまり書いてこなかったかしら? 書いたような記憶だけはあるんだがなぁ、見返すとあまり書いてない。使ってるネタは結構あるんだけどね、たとえばこれとかこれ。どっかでは言ったと思うんだけど、「Python ユーザとして Ruby が羨ましい」の二つあるうちの一つが YAML で、もう一つが ERB なのよね。そう、結構な高機能テンプレートエンジンを組み込みにする決断をしたのね(たぶん1.9とかそのあたりで)。

その「うらやましい」の意味に注意。「高機能」+「標準添付」だけだよ。サードパーティ製も含めたならばどちらによりステキなエンジンがあるか、の比較でうらやましいのではない。Python も標準添付に簡単なテンプレートエンジンはある。その簡単なテンプレートエンジンには何があって何がないかって、「単純な substitution はあって、control structure はない」てこと、簡単に言えば。だから繰り返しだの条件分岐だの、あるいはネストだのは「テンプレートの外」で実現するしかなく、だからすぐに Jinja2 や Mako のようなサードパーティ製モジュールに白羽の矢が立つわけである。

さて、では Go ではどうか、ということになるんだけれど、ちょっと Python や Ruby で考える優先度とは違っててもいいよな、とは思う。Recall: 「Goはコンパイル型であり、どうやら原則全部スタティックリンクしちゃう乱暴者らしいので、作ったバイナリはなんなら単独での持ち運びには便利ぞ」。そう、「標準でないモジュール」も、結局ビルド時にスタティックに取り込んでしまうわけだから、実行時にはもう「標準でないモジュール」であったことは関係ないんだよね。(無論特殊な外部依存があれば別だけど。)ので、きっと実行時基準で考えれば「別に標準ライブラリでなくてもいい」かなって思う。

と思って調べ始めたんだけどさ、まずはバッドニュースとしては「わっかりにくーい、読みにくいし」てことなんだけれど、頑張って読めば、「あらま、data evaluations or control structures だってさ」。テンプレートの書き方はまぁ別に独特ではないね、すぐに馴染めるだろう。ともあれ標準ライブラリのこれでまずは満足出来る気がするよな、て感触から始まる。たとえばこういう標準でないものを使う必要があるかもなぁと思ってたんで、ちょっとビックリ。ステキだこれは。

てわけで、まずはほとんど何もしないと言うか「左から右へ」に近い版:

tmplexpr0.go
 1 package main
 2 
 3 import (
 4     "os"
 5     "fmt"
 6     "text/template"
 7 )
 8 
 9 func main() {
10     tmpl, err := template.New("test").Parse(os.Args[1])
11     if err != nil { panic(err) }
12     tmpl.Execute(os.Stdout, os.Args[2])
13     fmt.Printf("\n")
14 }

go build して tmplexpr0.exe が出来上がったとして:

1 [me@host: tmplexpr0]$ ./tmplexpr0.exe 'Hello {{$}}!' hoge
2 Hello hoge!

うん、まぁこれでは何かの役に立つ見込みもないが、とりあえず意図通りだ。

当然やりたいのは「template + data で望みのもの」の、この「data」が柔軟なやつ。json にしてみようと。スクリプト言語みたいなダイナミックバインディングとは違うんでちょっと趣は違うんだけれど、違ってもちゃんと出来るのはステキ、「interface{}」で。ちゃんと書いてある。ちぅわけで:

tmplexpr1.go
 1 package main
 2 
 3 import (
 4     "os"
 5     "fmt"
 6     "encoding/json"
 7     "text/template"
 8 )
 9 
10 func main() {
11     tmpl, err := template.New("test").Parse(os.Args[1])
12     if err != nil { panic(err) }
13     b := []byte(os.Args[2])
14     var i interface{}
15     json.Unmarshal(b, &i)
16     tmpl.Execute(os.Stdout, i)
17     fmt.Printf("\n")
18 }
1 [me@host: tmplexpr1]$ ./tmplexpr1.exe 'Hey {{.name}}, {{.greet}}!' '{"name": "chiemi", "greet": "have a nice weekend"}'
2 Hey chiemi, have a nice weekend!

ちえみって誰やねん。うん、田中だ。いや、なんでもない。そうね、これさ、もう「コマンドライン引数から取ってるのがダサい」だけで、これをちゃんとファイルから取るようにしたらさ、もうかなり満足できる気がせん? このたった一個の EXE をだよ、たとえば USB メモリとかにぶっ込んで…みたいなことよ、それって Python + Mako みたいなアプローチでは結構大変でしょ、出来なくはないんだけどね、出来るにしてもかなり鬱陶しいんだよ、その比較から考えたら、この気軽さってば驚異的と思わん?

「コマンドライン引数から取ってるのがダサい」なんだけど、まだまだワタシ Go 初心者の極み、でなぁ、さっと書けないのよ。マニュアルを開きっぱなしにしてずっと読みながらでないと書けないんで、あとで。