Scala Templates
I've recently been messing around with Scala and figured I'd tackle a problem I've already solved in other languages. Here is the template kata implemented using a regex to match the placeholders:
class Template(val tmpl: String) {
def render(context: Map[String, Object]): String = {
return """\$(\w+)""".r.replaceAllIn(tmpl, m => {
context.getOrElse(m.group(1), "").toString()
})
}
}
Here's a couple things I like:
- Scala's Maps have a getOrElse method, yay! It's a little more verbose than Python's get method, but at least I don't have to write it my self.
- It's actually shorter than my first implementation in JavaScript, including the type annotations.
- I actually really like the class parameter, implicit field, thing (val tmpl: String). It handles the "The constructor takes and argument and binds it to a field." use-case very nicely.
- Scala's lambda expressions are very concise, and it's hard not to like that.
Here are the tests written in a BDD style using ScalaTest:
import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
class TemplateSpec extends FlatSpec with ShouldMatchers {
val EMPTY = Map[String, Object]()
private def test(tmpl: String, expected: String, ctx: Map[String, Object] = EMPTY) {
new Template(tmpl).render(ctx) should equal (expected)
}
"A template with no tokens" should "return a blank string if the given string is blank" in {
test("", "")
}
it should "return a non-blank string unchanged" in {
val identity = "hello, world"
test(identity, identity)
}
"A template with one token" should "replace missing values with the empty string" in {
test("$name", "")
}
it should "replace the token with value from the context" in {
test("$name", "Aaron", Map("name" -> "Aaron"))
}
it should "leave the rest of the string unchanged" in {
test("Hello $name!", "Hello Aaron!", Map("name" -> "Aaron"))
}
"A template with two tokens" should "replace each token its value from the context" in {
test("Hello $name! It is a $attitude day.", "Hello Aaron! It is a wonderful day.", Map(
"name" -> "Aaron", "attitude" -> "wonderful"
))
}
it should "replace each missing value with the empty string" in {
test("Hello $name! It is a $attitude day.", "Hello Aaron! It is a day.", Map(
"name" -> "Aaron"
))
}
it should "replace adjacent tokens with their respective values" in {
test("$me$you", "AaronJohn", Map("me" ->"Aaron", "you" -> "John"))
}
it should "replace every occurrance of each placeholder" in {
test("$a-$b-$a-$b", "AAA--AAA-", Map("a" -> "AAA"))
}
}
They're a little verbose, but as I mentioned in a previous post, you can extract documentation from tests like these:
TemplateSpec:
A template with no tokens
- should return a blank string if the given string is blank
- should return a non-blank string unchanged
A template with one token
- should replace missing values with the empty string
- should replace the token with value from the context
- should leave the rest of the string unchanged
A template with two tokens
- should replace each token its value from the context
- should replace each missing value with the empty string
- should replace adjacent tokens with their respective values
- should replace every occurrance of each placeholder