CLOVER🍀

That was when it all began.

テンプレートエンジン、Scalateを使ってみる

この前、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を使ったテンプレートエンジンなので、ちょこちょこ使っていってみましょうっと。