CLOVER🍀

That was when it all began.

テンプレートエンジンTwirlを試してみる

もう少しScalaのテンプレートエンジンを、と思い、今度はTwirlを触ってみました。

Twirl
https://github.com/playframework/twirl

Play Frameworkで使われているテンプレートエンジンだそうですね、Play自体使ったことないですけど…。

以前はPlayから完全に独立したテンプレートエンジンとして存在していたみたいですが、Playの一部に戻ったみたいですね。
※この経緯については、xuweiさんからコメントで補足をいただきました。ありがとうございます

旧リポジトリはこちら。
Twirl
https://github.com/spray/twirl

Playに戻っても、単独で使えることには変わりはなさそうです。

ドキュメントは、Playのものを見ることになりそうです。

The template engine
https://www.playframework.com/documentation/2.0/ScalaTemplates

タイプセーフなテンプレートエンジン、ということだそうな。ということは、コンパイルするんですな。

準備

Twirlを使用するには、sbtプラグインの設定を行うようにすればよいみたいです。
project/plugins.sbt

addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.2")

あと、build.sbtには以下の1行を加えます。

lazy val root = (project in file(".")).enablePlugins(SbtTwirl)

Twirlを使うだけなら、依存関係の追加自体は不要です。

サンプルは?

新しいリポジトリにはそれっぽいディレクトリがなかったので、旧リポジトリをちょっと漁ってみました。

テンプレートのサンプル
https://github.com/spray/twirl/blob/master/example/src/main/twirl/org/example/hello.scala.txt
https://github.com/spray/twirl/blob/master/example/src/main/twirl/org/example/hello.scala.html
https://github.com/spray/twirl/blob/master/example/src/main/twirl/org/example/hello.scala.xml

テンプレートファイルは、「src/main/twirl」というディレクトリの中に配置するようです。
ファイル名は、[テンプレート名].scala.[拡張子]となり、拡張子で挙動が変わるっぽい?

Scalaコードのサンプル
https://github.com/spray/twirl/blob/master/example/src/main/scala/org/example/Main.scala

予備知識が全然なかったので、テンプレートの評価ってどうやるんだろう?と思って上記のコードを見て驚きました。ただのメソッド呼び出しですね。ということは、ホントにScalaコードとしてコンパイルされるんですね。

コンパイル、かぁ…。

ファイルが「src/main/twirl/org/example/hello.scala.txt」だったら、呼び出しは「org.example.txt」までがパッケージ名な感じで、「hello」で呼べますよっと。サンプルコードはFQCNで書いていますが、同じパッケージにいるなら普通に省略可能でしたね。

println("A rendered Twirl TXT template:")
println(org.example.txt.hello("Alice", 16))

あと、「src/main/twirl」以下のディレクトリ名は、そのままパッケージ名の一部になる感じですね。

ちょっと試してみる

では、Twirlを自分でも試してみましょう。こんなコードを用意。
src/main/scala/org/littlewings/twirl/TwirlExample.scala

package org.littlewings.twirl

object TwirlExample {
  def main(args: Array[String]): Unit = {
    // ここにコードを書く
  }
}

case class Person(name: String, age: Int)

何気に、Casee Classも。

では、テンプレートを書いてみます。
src/main/twirl/hello.scala.txt

@(name: String)
Hello @name!!

まずは、「src/main/twirl」ディレクトリ直下に作成。

呼び出し側のコード

    println(txt.hello("<Twirl>"))

と実行結果。

Hello <Twirl>!!

動きましたね!けっこう簡単。

実行は、普通に「run」すればOKです。

> run

テンプレートは一緒にコンパイルされます。

コンパイルされるので、こういう風に変数名を間違ったりすると

@(name: String)
Hello @nam!!

コンパイルエラーになります。

[info] Compiling 1 Scala source to /xxxxx/target/scala-2.11/classes...
[error] /xxxxx/src/main/twirl/hello.scala.txt:2: not found: value nam
[error] Hello @nam!!
[error]        ^
[error] one error found

ちなみに、今回テンプレートの配置ディレクトリを呼び出し元と同じパッケージにしていないのですが、上記コードは、テンプレート側にパッケージ名と同じディレクトリを作成してテンプレートを配置してしまうと、そのままだと呼び出せなくなります。
その場合は、「_root_」付ければなんとかなりますが…。合わせるか無視するか、どちらかにしましょうねということでいいのかな。

今度は、拡張子を.htmlに。中身はHTMLではないですけれど。
src/main/twirl/hello.scala.html

@(name: String)
Hello @name!!
Raw: @Html(name)

呼び出し側。

    println(html.hello("<Twirl>"))

実行結果。

Hello &lt;Twirl&gt;!!
Raw: <Twirl>

拡張子を.htmllにした場合は、デフォルトでエスケープされるみたいですね。解除するには、@Htmlを使用する、と。

繰り返しの例。
src/main/twirl/people.scala.txt

@import org.littlewings.twirl.Person
@(people: List[Person])

ひとびと
@for(p <- people) {
  名前:@p.name
  年齢:@p.age
}

しれっとimport入りです。

あと、@forと(の間にスペース入れると、コンパイルエラーになるんですね。

呼び出し側。

    val people = List(Person("フグ田サザエ", 24),
                      Person("磯野カツオ", 11),
                      Person("磯野ワカメ", 9))
    println(txt.people(people))

実行結果。


ひとびと

  名前:フグ田サザエ
  年齢:24

  名前:磯野カツオ
  年齢:11

  名前:磯野ワカメ
  年齢:9

テンプレートをサブディレクトリに配置して、ifも使ってみます。
src/main/twirl/template/people/list.scala.txt

@import org.littlewings.twirl.Person
@(people: List[Person])

ひとびと
@for(p <- people) {
  名前:@p.name
  @if(p.age < 20) {
  年齢:@p.age
  }
}

呼び出し側。

    val people = List(Person("フグ田サザエ", 24),
                      Person("磯野カツオ", 11),
                      Person("磯野ワカメ", 9))
    println(template.people.txt.list(people))

結果。


ひとびと

  名前:フグ田サザエ
  

  名前:磯野カツオ
  
  年齢:11
  

  名前:磯野ワカメ
  
  年齢:9
  

テンプレートのコンパイルがあるので、その時間がかかるところはちょっと気になるところですが、Scalateと違って実行時にファイルを作成しないところは良いですね。

必要に応じて、選択肢のひとつとして考えておきましょう。