squiggly is a Scala 3 string templating engine, cross-built for the JVM, Scala.js, and Scala Native.
squiggly is a language, a Scala library, and a small command-line application for doing string templating. It can be compared to Mustache, Go templates, or Liquid — a string template composed of text and tags (instructions in squiggly) is applied to context data, producing textual output.
Unlike Mustache, squiggly is not logic-less; it allows basic logic in templates. The expression language is inspired by Hugo and Liquid, with infix operators, comparison chains, pipes, and method calls.
The name "squiggly" is a colloquialism for curly braces — the tag delimiters.
Full reference, syntax guide, and built-in function index: https://edadma.github.io/squiggly/
Add the dependency to your build.sbt:
libraryDependencies += "io.github.edadma" %%% "squiggly" % "0.2.3"%%% cross-builds against whatever target your project uses (JVM, Scala.js, or
Scala Native).
Use the following import in your code:
import io.github.edadma.squiggly._import io.github.edadma.squiggly._
@main def runDemo(): Unit =
val data =
Map(
"user" -> "ed",
"tasks" -> List(
Map("task" -> "Improve Parser and Renderer API", "done" -> true),
Map("task" -> "Code template example", "done" -> false),
Map("task" -> "Update README", "done" -> false),
),
)
val template =
"""<!DOCTYPE html>
|<html>
| <body>
| <p>To-do list for user '{{ .user }}'</p>
| <ul>
| {{ for .tasks -}}
| <li>{{ .task }} — {{ if .done }}done{{ else }}pending{{ end }}</li>
| {{- end }}
| </ul>
| </body>
|</html>
|""".stripMargin
val ast = TemplateParser.default.parse(template)
TemplateRenderer.default.render(data, ast)TemplateRenderer.render takes any Scala value (Map, Seq, String, BigDecimal,
Boolean, case class, …) — there is no special data-loading layer.
The CLI is invoked through sbt while developing. From the project root:
sbt 'squigglyJVM/run "Hello, {{ 1 + 2 }}!"'Hello, 3!
Equivalently on Scala Native:
sbt 'squigglyNative/run "Hello, {{ 1 + 2 }}!"'For Scala.js, link the program first and run with node:
sbt squigglyJS/fastLinkJS
node js/target/scala-3.8.3/squiggly-fastopt/main.js "Hello, {{ 1 + 2 }}!"Squiggly Template Engine v0.2.3
Usage: squiggly [options] [[<template>]]
-a, --ast pretty print AST
-d, --data <JSON> JSON document (string)
-f, --template <file> template file
-h, --help prints this usage text
-v, --version prints the version
-j, --json <file> JSON data file
[<template>] template string
Example with inline JSON data (note shell quoting; for anything more complex
than the simplest case prefer -j <file>):
echo '{"a": 3, "b": 4}' > /tmp/data.json
sbt 'squigglyJVM/run -j /tmp/data.json "{{ .a }} + {{ .b }} = {{ .a + .b }}"'3 + 4 = 7
Squiggly templates are inspired by Hugo and Liquid. The goal is low boilerplate and readability. Hugo is compact but strictly prefix; Liquid has nicer infix syntax but more boilerplate. Squiggly takes the readable parts of each.
null— represents no value; renders as an empty string. Maps to Scalanull.true,false— booleans.- numbers — internally
BigDecimal(exact decimal arithmetic, arbitrary-precision integers, no floating-point surprises). - strings — between
'…'or"…", whichever is convenient. Standard escapes:\n,\t,α,\\, etc. - lists —
[a, b, c]. - maps —
{key: value, key: value}.
There is also an undefined value (Scala ()) that cannot be written literally
— it represents a missing property and renders as an empty string. It is
distinct from null because a present property may be explicitly null.
Everything you'd expect: arithmetic (+ - * / mod ^ \ for integer division,
++ for string/list concat), comparison chains (a < b <= c), boolean
(and, or, not), conditional (if x then a else b), index (.a[0]),
method ('hello'.upper), function application (upper 'hello'), and pipes
('hello' | upper).
Renders a value into the output. {{ .title }} substitutes the title field
of the current context.
Binds the inner context to value; runs the falsy branch if the value is falsy.
Iterates over a Seq or Map. Optional element/index bindings.
Standard branching. else if is spelled elsif.
Switch on a value.
Reusable named blocks with override-by-define.
Bind a variable in the current context.
Halt rendering and yield a value back from TemplateRenderer.render.
Template comments.
~67 builtins: numeric (abs, ceil, floor, round, min, max, sum,
number); string (upper, lower, capitalize, length, trim, ltrim,
rtrim, htmlEscape, newline_to_br, urlize, urlEncode, urlDecode,
split, startsWith, substring, truncate, remove, removeFirst);
collection (head, last, tail, append, prepend, reverse,
distinct, compact, drop, dropRight, take, takeRight, slice,
join, contains, length, isEmpty, nonEmpty, toSeq, toString);
set ops (intersect, union, symdiff, complement); higher-order
(filter, filterNot, map, default); regex (findRE); date/time
(now, time, unix, format with named formats :date_full /
:date_long / :date_medium / :date_short and any java.time
DateTimeFormatter pattern); misc (querify, partial, fileExists,
random, shuffle, print, println, context).
sbt squigglyJVM/test
sbt squigglyJS/test
sbt squigglyNative/testOr run the whole cross-build at once:
sbt testPRs welcome. New features and bug fixes should ship with tests; scalafmt is
configured.
