この前、Mustacheについて調べた時に、このテンプレートのScala実装があることがわかりました。
で、その実装がScalate。あ…なんか、聞いたことあります。なんですが、使ったことがなかったのでちょっと試してみることにしました。
Scalate
http://scalate.fusesource.org/
このテンプレートエンジン、いろんなテンプレートの形式をサポートしていて、ドキュメントによると以下が使えるそうです。
http://scalate.fusesource.org/documentation/index.html
- Mustache
- Scaml
- Jade
- SSP
- Scuery
とはいえ、いきなりこんなに触れないし、ちょっと馴染みのない形式のものも多いので、今回はSSPとMustacheを使ってみました。
準備
ビルドには、sbtを使用することにします。というわけで、こんなbuild.sbtを用意。
name := "scalate-example" version := "0.0.1" organization := "littlewings" scalaVersion := "2.9.2" libraryDependencies += "org.fusesource.scalate" % "scalate-core" % "1.5.3"
ライブラリの依存関係はこれで解決できるので、まずはスタンドアロンで使ってみます。ドキュメントは、どちらかというとWeb寄りなのですが。
Scalate User Guide
http://scalate.fusesource.org/documentation/user-guide.html
普通に使う場合は、以下のドキュメントを参照します。
Scalate Embedding Guide
http://scalate.fusesource.org/documentation/scalate-embedding-guide.html
が、これが意外とハマったんですよね…。
TemplateEngineの雛形コード
用意したScalaのコードは、こんな感じです。
ScalateExample.scala
import java.io.File import org.fusesource.scalate.TemplateEngine import org.fusesource.scalate.support.ScalaCompiler object ScalateExample { def main(args: Array[String]): Unit = { val engine = new TemplateEngine try { engine.workingDirectory = new File("./tmp") val bindings = Map( "name" -> "Scalate", "languages" -> List("Java", "Scala", "Clojure", "Groovy") ) val output = engine.layout("テンプレートへのパス", bindings) println(output) } finally { engine.compiler.asInstanceOf[ScalaCompiler].compiler.askShutdown() } } }
テンプレートを使用するには、まずTemplateEngineのインスタンスを生成します。
val engine = new TemplateEngine
続いて、サンプルではワークディレクトリの設定をしています。
engine.workingDirectory = new File("./tmp")
これ何だろう?って話ですけど、ScalateはテンプレートをコンパイルしてScalaコードを生成して、さらにそれをコンパイルします。ここで指定したパスが、その時に出力するScalaコード及びClassファイルの出力先になります。
何も指定しなかった場合は、システムの一時領域(Linuxなら/tmp/scalate-xxxxx-workdir)に出力されます。そして、実行するごとに増えていきます…。
val output = engine.layout("テンプレートへのパス", bindings)
あとは、TemplateEngine#layoutを呼び出すことで、テンプレートを評価してくれます。テンプレートにバインドするオブジェクトは、第2引数に指定してください。
また、テンプレートの拡張子によって、テンプレートを評価するParserが変わるようです。
そして、最後のこの1行。
engine.compiler.asInstanceOf[ScalaCompiler].compiler.askShutdown()
これ、割と重要でこれがないとアプリが終了しません。Scalateは内部でScalaコンパイラを使っているようなのですが、それがず〜っと待ち続けてしまうみたいなんですよね。
しかも、シャットダウンの方法がドキュメントに載っていないという…。
*まだSNAPSHOTの段階ですが、Scalate 1.6.0では状況が変わってそうです
ドキュメントにはScaladocのことは書いてないっぽいので、APIリファレンスが見たければ以下へ。
scalate-core 1.5.3 API
http://scalate.fusesource.org/maven/1.5.3/scalate-core/scaladocs/#package
SSP(Scala Server Pages)
まずは、ScalaコードとVelocityライクな記法が使えるというSSPから。JSP、VelocityのScala版って位置付けみたいです。
SSPのリファレンスはこちらです。
http://scalate.fusesource.org/documentation/ssp-reference.html
では、先ほどのScalaコードのうち、TemplateEngine#layoutの箇所を
val bindings = Map( "name" -> "Scalate", "languages" -> List("Java", "Scala", "Clojure", "Groovy") ) val output = engine.layout("template/template.ssp", bindings)
のように、拡張子「.ssp」で終わるようにして指定します。
テンプレートはこちらです。
template/template.ssp
<%@ var name: String %> <%@ var languages: List[String] %> Hello ${name}!! by Ssp Template Programming Languages: #for (l <- languages) ${l} #end
テンプレートにバインドする変数は、テンプレート側でも変数宣言する必要があります。しないと、テンプレートのコンパイルに失敗します…。
一応、Bindingを使用すると変数宣言しなくてもよくなるようですが、これはこれで微妙だったりします…。
http://scalate.fusesource.org/documentation/scalate-embedding-guide.html#Implicitly_Bound_Variables
では、実行。
> run [info] Compiling 1 Scala source to /xxxxx/scalate-example/target/scala-2.9.2/classes... [info] Running ScalateExample SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. Hello Scalate!! by Ssp Template Programming Languages: Java Scala Clojure Groovy [success] Total time: 2 s, completed 2012/12/16 0:02:38
SLF4Jが怒っていますが、とりあえず今は気にしない方向で…。
テンプレートの変換後のコードとコンパイル結果は、こんな感じで生成されています。
tmp/src/template/template.ssp.scala tmp/classes/template/$_scalate_$template_ssp$$anonfun$$_scalate_$render$1.class tmp/classes/template/$_scalate_$template_ssp.class tmp/classes/template/$_scalate_$template_ssp$.class
Mustache
続いて、Mustache。テンプレートの基本的な記法は以前紹介した感じですね。
Mustache Reference
http://scalate.fusesource.org/documentation/mustache.html
では、先ほどのScalaコードのうち、TemplateEngine#layoutの箇所を
val bindings = Map( "name" -> "Scalate", "languages" -> List("Java", "Scala", "Clojure", "Groovy") ) val output = engine.layout("template/template.mustache", bindings)
のように、拡張子「.mustache」で終わるようにして指定します。
テンプレートはこちらです。
template/template.mustache
Hello {{name}} by Mustache Template Programming Languages: {{#languages}} {{.}} {{/languages}}
Mustacheテンプレートの場合は、SSPと異なり変数の宣言は不要です。
では、実行。
> run [info] Compiling 1 Scala source to /xxxxx/scalate-example/target/scala-2.9.2/classes... [info] Running ScalateExample SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. Hello Scalate by Mustache Template Programming Languages: Java Scala Clojure Groovy [success] Total time: 2 s, completed 2012/12/16 0:16:02
生成されたコード等は、こんな感じです。
tmp/src/template/template.mustache.scala tmp/classes/template/$_scalate_$template_mustache$.class tmp/classes/template/$_scalate_$template_mustache$$anonfun$$_scalate_$render$1.class tmp/classes/template/$_scalate_$template_mustache.class
なんか癖もある感じですが、せっかくScalaを使ったテンプレートエンジンなので、ちょこちょこ使っていってみましょうっと。