├── .github ├── mergify.yml ├── scala-steward.conf ├── workflows │ ├── release-drafter.yml │ ├── dependency-graph.yml │ ├── publish.yml │ └── build-test.yml ├── renovate.json └── dependabot.yml ├── sbt-twirl └── src │ ├── sbt-test │ └── twirl │ │ ├── compile │ │ ├── test │ │ ├── src │ │ │ ├── main │ │ │ │ ├── scala │ │ │ │ │ ├── bar │ │ │ │ │ │ └── Default.scala │ │ │ │ │ └── foo │ │ │ │ │ │ └── Default.scala │ │ │ │ └── twirl │ │ │ │ │ └── a │ │ │ │ │ └── b │ │ │ │ │ └── c.scala.html │ │ │ └── test │ │ │ │ ├── twirl │ │ │ │ └── test │ │ │ │ │ ├── shadow.scala.html │ │ │ │ │ ├── importshadow.scala.html │ │ │ │ │ └── template.scala.html │ │ │ │ └── scala │ │ │ │ └── test │ │ │ │ └── Test.scala │ │ ├── project │ │ │ └── plugins.sbt │ │ └── build.sbt │ │ ├── compile-scala3 │ │ ├── test │ │ ├── src │ │ │ ├── main │ │ │ │ ├── scala │ │ │ │ │ ├── bar │ │ │ │ │ │ └── Default.scala │ │ │ │ │ └── foo │ │ │ │ │ │ └── Default.scala │ │ │ │ └── twirl │ │ │ │ │ └── a │ │ │ │ │ └── b │ │ │ │ │ └── c.scala.html │ │ │ └── test │ │ │ │ ├── twirl │ │ │ │ └── test │ │ │ │ │ ├── shadow.scala.html │ │ │ │ │ ├── importshadow.scala.html │ │ │ │ │ ├── template.scala.html │ │ │ │ │ └── manyargstemplate.scala.html │ │ │ │ └── scala │ │ │ │ └── test │ │ │ │ └── Test.scala │ │ ├── project │ │ │ └── plugins.sbt │ │ └── build.sbt │ │ └── scalajs-compile │ │ ├── test │ │ ├── src │ │ └── main │ │ │ ├── twirl │ │ │ ├── shadow.scala.html │ │ │ ├── importshadow.scala.html │ │ │ ├── a │ │ │ │ └── b │ │ │ │ │ └── c.scala.html │ │ │ ├── template.scala.html │ │ │ └── xml_test.scala.html │ │ │ └── scala │ │ │ ├── bar │ │ │ └── Default.scala │ │ │ ├── foo │ │ │ └── Default.scala │ │ │ └── Test.scala │ │ ├── project │ │ └── plugins.sbt │ │ └── build.sbt │ ├── main │ ├── scala-3 │ │ └── play │ │ │ └── twirl │ │ │ └── sbt │ │ │ └── SbtTwirlCompat.scala │ ├── scala-2 │ │ └── play │ │ │ └── twirl │ │ │ └── sbt │ │ │ └── SbtTwirlCompat.scala │ └── scala │ │ └── play │ │ └── twirl │ │ └── sbt │ │ └── TemplateCompiler.scala │ └── test │ └── scala │ └── play │ └── twirl │ └── sbt │ └── test │ ├── TemplateMappingSpec.scala │ └── TemplatePositionSpec.scala ├── maven-twirl └── src │ ├── maven-test │ └── simple │ │ ├── test │ │ ├── src │ │ ├── main │ │ │ ├── templates │ │ │ │ └── a │ │ │ │ │ └── b │ │ │ │ │ └── f.scala.html │ │ │ └── twirl │ │ │ │ └── a │ │ │ │ └── b │ │ │ │ └── c.scala.html │ │ └── test │ │ │ ├── twirl │ │ │ └── a │ │ │ │ └── b │ │ │ │ └── d.scala.html │ │ │ └── java │ │ │ └── play │ │ │ └── twirl │ │ │ └── maven │ │ │ └── SimpleProjectTest.java │ │ └── pom.xml │ └── main │ └── java │ └── play │ └── twirl │ └── maven │ ├── TwirlCompileMojo.java │ └── TwirlTestCompileMojo.java ├── gradle-twirl ├── .gitignore ├── gradle │ ├── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ └── libs.versions.toml ├── src │ ├── test │ │ ├── resources │ │ │ └── simple │ │ │ │ ├── settings.gradle.kts.ftlh │ │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ └── twirl │ │ │ │ │ └── a │ │ │ │ │ └── b │ │ │ │ │ └── c.scala.html │ │ │ │ └── build.gradle.kts.ftlh │ │ └── java │ │ │ └── play │ │ │ └── twirl │ │ │ └── gradle │ │ │ ├── TwirlPluginTest.java │ │ │ └── AbstractFunctionalTest.java │ └── main │ │ └── java │ │ └── play │ │ └── twirl │ │ └── gradle │ │ ├── TwirlExtension.java │ │ ├── internal │ │ ├── Gradle7TwirlSourceDirectorySet.java │ │ ├── TwirlCompileParams.java │ │ ├── DefaultTwirlSourceDirectorySet.java │ │ └── TwirlCompileAction.java │ │ ├── TwirlSourceDirectorySet.java │ │ └── TwirlCompile.java ├── settings.gradle.kts ├── .gitattributes ├── gradle.properties └── gradlew.bat ├── docs ├── manual │ └── working │ │ ├── javaGuide │ │ └── main │ │ │ └── templates │ │ │ ├── images │ │ │ └── templatesError.png │ │ │ ├── index.toc │ │ │ ├── JavaTemplatesDependencyInjection.md │ │ │ ├── JavaCustomTemplateFormat.md │ │ │ └── JavaTemplateUseCases.md │ │ └── scalaGuide │ │ └── main │ │ └── templates │ │ ├── images │ │ ├── templatesError.png │ │ └── displayScalaTypes.png │ │ ├── code │ │ ├── scalaguide │ │ │ └── templates │ │ │ │ ├── defaultParameters.scala.html │ │ │ │ ├── curriedParameters.scala.html │ │ │ │ ├── simpleParameters.scala.html │ │ │ │ ├── constructor.scala.html │ │ │ │ ├── views │ │ │ │ └── Application │ │ │ │ │ └── index.scala.html │ │ │ │ ├── firstLineComment.scala.html │ │ │ │ ├── importStatement.scala.html │ │ │ │ ├── displayScalaTypes.scala.html │ │ │ │ └── snippets.scala.html │ │ └── AbsoluteImportTester.scala │ │ ├── index.toc │ │ ├── ScalaTemplatesDependencyInjection.md │ │ ├── ScalaTemplateUseCases.md │ │ └── ScalaCustomTemplateFormat.md ├── project │ ├── build.properties │ └── plugins.sbt └── build.sbt ├── project ├── build.properties ├── Dependencies.scala ├── plugins.sbt ├── CopyrightHeader.scala ├── Playdoc.scala ├── Common.scala └── Omnidoc.scala ├── parser └── src │ ├── test │ └── resources │ │ ├── case.scala.js │ │ ├── static.scala.html │ │ ├── elseIf.scala.html │ │ ├── simple.scala.html │ │ ├── unclosedBracket.scala.html │ │ ├── imports.scala.html │ │ ├── invalidAt.scala.html │ │ ├── unclosedBracket2.scala.html │ │ └── complicated.scala.html │ └── main │ └── scala │ └── play │ └── twirl │ └── parser │ ├── TwirlIO.scala │ └── TreeNodes.scala ├── compiler └── src │ ├── test │ ├── scala │ │ └── play │ │ │ └── twirl │ │ │ └── compiler │ │ │ └── test │ │ │ ├── imports │ │ │ ├── bar │ │ │ │ └── Default.scala │ │ │ └── foo │ │ │ │ └── Default.scala │ │ │ ├── Benchmark.scala │ │ │ └── TemplateUtilsSpec.scala │ ├── resources │ │ ├── utf8.scala.html │ │ ├── static.scala.html │ │ ├── error.scala.html │ │ ├── importsDefault.scala.html │ │ ├── existential.scala.html │ │ ├── triplequotes.scala.html │ │ ├── using.scala.html │ │ ├── injectNoArgs.scala.html │ │ ├── errorInTemplateArgs.scala.html │ │ ├── helloNull.scala.html │ │ ├── htmlParam.scala.html │ │ ├── callByName.scala.html │ │ ├── varArgsExistential.scala.html │ │ ├── hello.scala.html │ │ ├── inject.scala.html │ │ ├── escapebrace.scala.html │ │ ├── ifWithBrackets.scala.html │ │ ├── set.scala.html │ │ ├── blockWithTuple.scala.html │ │ ├── injectParamGroups.scala.html │ │ ├── blockWithNestedTuple.scala.html │ │ ├── ifWithoutBrackets.scala.html │ │ ├── zipWithIndex.scala.html │ │ ├── elseIf.scala.html │ │ ├── argImports.scala.html │ │ ├── injectComments.scala.html │ │ ├── real.scala.html │ │ ├── htmlInner.scala.html │ │ ├── codeBlockOrder.scala.html │ │ ├── ifWithoutBracketsComplex.scala.html │ │ ├── localDef.scala.html │ │ ├── patternMatching.scala.html │ │ ├── varsTemplateBlocks.scala.html │ │ ├── varsPureCodeBlocks.scala.html │ │ ├── nestedTemplates.scala.html │ │ └── vals.scala.html │ ├── java │ │ └── play │ │ │ └── japi │ │ │ └── twirl │ │ │ └── compiler │ │ │ └── TwirlCompilerTest.java │ └── scala-2 │ │ └── play │ │ └── twirl │ │ └── compiler │ │ └── test │ │ └── Helper.scala │ └── main │ └── java │ └── play │ └── japi │ └── twirl │ └── compiler │ └── TwirlCompiler.java ├── .gitignore ├── scripts └── test-code.sh ├── .scalafmt.conf ├── .git-blame-ignore-revs └── api └── shared └── src ├── main ├── scala-3 │ └── play │ │ └── twirl │ │ └── api │ │ ├── TwirlHelperImports.scala │ │ ├── package.scala │ │ └── BaseScalaTemplate.scala ├── scala-2.12 │ └── play │ │ └── twirl │ │ └── api │ │ ├── TwirlHelperImports.scala │ │ ├── package.scala │ │ └── BaseScalaTemplate.scala ├── scala-2.13 │ └── play │ │ └── twirl │ │ └── api │ │ ├── TwirlHelperImports.scala │ │ ├── package.scala │ │ └── BaseScalaTemplate.scala └── scala │ └── play │ └── twirl │ └── api │ ├── Format.scala │ ├── utils │ └── StringEscapeUtils.scala │ ├── TwirlFeatureImports.scala │ ├── Content.scala │ └── Template.scala └── test └── scala └── play └── twirl └── api └── test ├── StringInterpolationSpec.scala ├── FormatSpec.scala └── BufferedContentSpec.scala /.github/mergify.yml: -------------------------------------------------------------------------------- 1 | extends: .github 2 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile/test: -------------------------------------------------------------------------------- 1 | > Test/run 2 | -------------------------------------------------------------------------------- /maven-twirl/src/maven-test/simple/test: -------------------------------------------------------------------------------- 1 | > clean verify -e 2 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile-scala3/test: -------------------------------------------------------------------------------- 1 | > Test/run 2 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/scalajs-compile/test: -------------------------------------------------------------------------------- 1 | > run 2 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile/src/main/scala/bar/Default.scala: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile/src/main/scala/foo/Default.scala: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile/src/test/twirl/test/shadow.scala.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/scalajs-compile/src/main/twirl/shadow.scala.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile-scala3/src/main/scala/bar/Default.scala: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile-scala3/src/main/scala/foo/Default.scala: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile-scala3/src/test/twirl/test/shadow.scala.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile/src/test/twirl/test/importshadow.scala.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/scalajs-compile/src/main/scala/bar/Default.scala: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/scalajs-compile/src/main/scala/foo/Default.scala: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/scalajs-compile/src/main/twirl/importshadow.scala.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile-scala3/src/test/twirl/test/importshadow.scala.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gradle-twirl/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | 4 | # Ignore Gradle build output directory 5 | build 6 | -------------------------------------------------------------------------------- /gradle-twirl/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playframework/twirl/main/gradle-twirl/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle-twirl/src/test/resources/simple/settings.gradle.kts.ftlh: -------------------------------------------------------------------------------- 1 | buildCache { 2 | local { 3 | directory = File(rootProject.projectDir, "build-cache") 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /docs/manual/working/javaGuide/main/templates/images/templatesError.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playframework/twirl/main/docs/manual/working/javaGuide/main/templates/images/templatesError.png -------------------------------------------------------------------------------- /docs/manual/working/scalaGuide/main/templates/images/templatesError.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playframework/twirl/main/docs/manual/working/scalaGuide/main/templates/images/templatesError.png -------------------------------------------------------------------------------- /docs/manual/working/scalaGuide/main/templates/code/scalaguide/templates/defaultParameters.scala.html: -------------------------------------------------------------------------------- 1 | @* #default-parameters *@ 2 | @(title: String = "Home") 3 | @* #default-parameters *@ 4 | 5 | @title -------------------------------------------------------------------------------- /docs/manual/working/scalaGuide/main/templates/images/displayScalaTypes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playframework/twirl/main/docs/manual/working/scalaGuide/main/templates/images/displayScalaTypes.png -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | # Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 2 | 3 | sbt.version=1.11.7 4 | -------------------------------------------------------------------------------- /docs/project/build.properties: -------------------------------------------------------------------------------- 1 | # Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 2 | 3 | sbt.version=1.11.7 4 | -------------------------------------------------------------------------------- /docs/manual/working/scalaGuide/main/templates/code/scalaguide/templates/curriedParameters.scala.html: -------------------------------------------------------------------------------- 1 | @* #curried-parameters *@ 2 | @(title: String)(body: Html) 3 | @* #curried-parameters *@ 4 | 5 | @title @body -------------------------------------------------------------------------------- /docs/manual/working/javaGuide/main/templates/index.toc: -------------------------------------------------------------------------------- 1 | JavaTemplates:Templates syntax 2 | JavaTemplatesDependencyInjection:Dependency Injection with Templates 3 | JavaTemplateUseCases:Common use cases 4 | JavaCustomTemplateFormat:Custom formats -------------------------------------------------------------------------------- /gradle-twirl/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | rootProject.name = "gradle-twirl" 6 | -------------------------------------------------------------------------------- /docs/manual/working/scalaGuide/main/templates/index.toc: -------------------------------------------------------------------------------- 1 | ScalaTemplates:Templates syntax 2 | ScalaTemplatesDependencyInjection:Dependency Injection with Templates 3 | ScalaTemplateUseCases:Common use cases 4 | ScalaCustomTemplateFormat:Custom format -------------------------------------------------------------------------------- /parser/src/test/resources/case.scala.js: -------------------------------------------------------------------------------- 1 | @() 2 | 3 | var varName = "ben"; 4 | switch (varName) { 5 | case "larry": 6 | alert('Hey'); 7 | break; 8 | default: 9 | alert('hello world'); 10 | break; 11 | } 12 | -------------------------------------------------------------------------------- /docs/manual/working/scalaGuide/main/templates/code/scalaguide/templates/simpleParameters.scala.html: -------------------------------------------------------------------------------- 1 | @import scalaguide.templates._ 2 | 3 | @* #simple-parameters *@ 4 | @(customer: Customer, orders: List[Order]) 5 | @* #simple-parameters *@ 6 | 7 | @customer @orders -------------------------------------------------------------------------------- /gradle-twirl/.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # Linux start script should use lf 5 | /gradlew text eol=lf 6 | 7 | # These are Windows script files and should use crlf 8 | *.bat text eol=crlf 9 | 10 | -------------------------------------------------------------------------------- /docs/manual/working/scalaGuide/main/templates/code/scalaguide/templates/constructor.scala.html: -------------------------------------------------------------------------------- 1 | @import scalaguide.templates._ 2 | 3 | @* #constructor *@ 4 | @this(myComponent: MyComponent) 5 | 6 | @(customer: Customer, orders: List[Order]) 7 | @* #constructor *@ 8 | 9 | @myComponent @customer @orders -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 2 | 3 | addSbtPlugin("org.playframework.twirl" % "sbt-twirl" % sys.props("project.version")) 4 | -------------------------------------------------------------------------------- /sbt-twirl/src/main/scala-3/play/twirl/sbt/SbtTwirlCompat.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.sbt 6 | 7 | private[sbt] object SbtTwirlCompat 8 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile-scala3/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 2 | 3 | addSbtPlugin("org.playframework.twirl" % "sbt-twirl" % sys.props("project.version")) 4 | -------------------------------------------------------------------------------- /.github/scala-steward.conf: -------------------------------------------------------------------------------- 1 | commits.message = "${artifactName} ${nextVersion} (was ${currentVersion})" 2 | 3 | pullRequests.grouping = [ 4 | { name = "patches", "title" = "Patch updates", "filter" = [{"version" = "patch"}] } 5 | ] 6 | 7 | buildRoots = [ 8 | ".", 9 | "docs", 10 | ] 11 | 12 | updates.pin = [ 13 | ] 14 | -------------------------------------------------------------------------------- /compiler/src/test/scala/play/twirl/compiler/test/imports/bar/Default.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.compiler.test.imports.bar 6 | 7 | // Used to test import priorities 8 | object Default 9 | -------------------------------------------------------------------------------- /compiler/src/test/scala/play/twirl/compiler/test/imports/foo/Default.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.compiler.test.imports.foo 6 | 7 | // Used to test import priorities 8 | object Default 9 | -------------------------------------------------------------------------------- /gradle-twirl/gradle.properties: -------------------------------------------------------------------------------- 1 | # Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 2 | 3 | org.gradle.parallel=true 4 | org.gradle.caching=true 5 | 6 | # Need to remove default gradleApi from compileOnly configuration 7 | systemProp.org.gradle.unsafe.suppress-gradle-api=true 8 | -------------------------------------------------------------------------------- /docs/manual/working/scalaGuide/main/templates/code/AbsoluteImportTester.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package company.product.core 6 | 7 | object AbsoluteImportTester { 8 | def test = "absolute import is working" 9 | } 10 | -------------------------------------------------------------------------------- /docs/manual/working/scalaGuide/main/templates/code/scalaguide/templates/views/Application/index.scala.html: -------------------------------------------------------------------------------- 1 | @import scalaguide.templates._ 2 | @* #example-template *@ 3 | @(customer: Customer, orders: List[Order]) 4 | 5 |

Welcome @customer.name!

6 | 7 |
    8 | @for(order <- orders) { 9 |
  • @order.title
  • 10 | } 11 |
12 | @* #example-template *@ -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/scalajs-compile/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 2 | 3 | addSbtPlugin("org.playframework.twirl" % "sbt-twirl" % sys.props("project.version")) 4 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.20.1") 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # sbt specific 5 | .bsp/ 6 | dist/* 7 | target/ 8 | lib_managed/ 9 | src_managed/ 10 | project/boot/ 11 | project/plugins/project/ 12 | 13 | # Scala-IDE specific 14 | .scala_dependencies 15 | .idea 16 | 17 | compiler/version.properties 18 | 19 | .vscode/ 20 | 21 | # Metals 22 | .metals/ 23 | .bloop/ 24 | project/metals.sbt 25 | metals.sbt 26 | -------------------------------------------------------------------------------- /docs/manual/working/scalaGuide/main/templates/code/scalaguide/templates/firstLineComment.scala.html: -------------------------------------------------------------------------------- 1 | @* #comment 2 | @************************************* 3 | * Home page. * 4 | * * 5 | * @param msg The message to display * 6 | *************************************@ 7 | @(msg: String) 8 | 9 |

@msg

10 | @* #comment *@ 11 | -------------------------------------------------------------------------------- /project/Dependencies.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | object Dependencies { 6 | val Scala212 = "2.12.21" 7 | val Scala213 = "2.13.18" 8 | val Scala3 = "3.3.7" 9 | val ScalaVersions = Seq(Scala212, Scala213, Scala3) 10 | } 11 | -------------------------------------------------------------------------------- /docs/manual/working/scalaGuide/main/templates/code/scalaguide/templates/importStatement.scala.html: -------------------------------------------------------------------------------- 1 | @import scalaguide.templates._ 2 | 3 | @* #import *@ 4 | @(customer: Customer, orders: List[Order]) 5 | 6 | @import utils._ 7 | 8 | ... 9 | @* #import *@ 10 | 11 | @ImportTester.test 12 | 13 | @* #absolute *@ 14 | @import _root_.company.product.core._ 15 | @* #absolute *@ 16 | 17 | @AbsoluteImportTester.test -------------------------------------------------------------------------------- /gradle-twirl/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=f86344275d1b194688dd330abf9f6f2344cd02872ffee035f2d1ea2fd60cf7f3 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-all.zip 5 | networkTimeout=10000 6 | validateDistributionUrl=true 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /sbt-twirl/src/main/scala-2/play/twirl/sbt/SbtTwirlCompat.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.sbt 6 | 7 | private[sbt] object SbtTwirlCompat { 8 | implicit class DefOps(private val self: _root_.sbt.Def.type) extends AnyVal { 9 | def uncached[A](a: A): A = a 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile/src/test/scala/test/Test.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import play.twirl.api._ 4 | 5 | object Test extends App { 6 | def test(template: HtmlFormat.Appendable, expected: String) = { 7 | assert(template.body == expected, s"Found '$template' but expected '$expected'") 8 | } 9 | 10 | test(a.b.html.c.render("world"), "Hello, world.\n") 11 | 12 | test(html.template.render("42"), "Answer: 42\n") 13 | } 14 | -------------------------------------------------------------------------------- /docs/manual/working/scalaGuide/main/templates/code/scalaguide/templates/displayScalaTypes.scala.html: -------------------------------------------------------------------------------- 1 | @import scalaguide.templates._ 2 | 3 | @() 4 | 5 | @* #display-scala-types *@ 6 |
    7 |
  • @Option("value inside option")
  • 8 |
  • @List("first", "last")
  • 9 |
  • @User("Foo", "Bar")
  • 10 |
  • @List("hello", User("Foo", "Bar"), Option("value inside option"), List("first", "last"))
  • 11 |
12 | @* #display-scala-types *@ -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/scalajs-compile/build.sbt: -------------------------------------------------------------------------------- 1 | // Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 2 | 3 | logLevel := Level.Debug 4 | 5 | lazy val root = (project in file(".")) 6 | .enablePlugins(ScalaJSPlugin, SbtTwirl) 7 | .settings( 8 | scalaJSUseMainModuleInitializer := true, 9 | mainClass := Some("Test") 10 | ) 11 | -------------------------------------------------------------------------------- /scripts/test-code.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 4 | 5 | sbt "++$MATRIX_SCALA test; mavenPlugin/scripted" || exit 1 6 | sbt +publishLocal +compiler/publishM2 +apiJVM/publishM2 +plugin/test plugin/scripted || exit 1 7 | (cd gradle-twirl && ./gradlew clean check -x spotlessCheck --no-daemon -Pscala.version="$MATRIX_SCALA") || exit 1 8 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/scalajs-compile/src/main/scala/Test.scala: -------------------------------------------------------------------------------- 1 | import play.twirl.api._ 2 | 3 | object Test extends App { 4 | def test(template: HtmlFormat.Appendable, expected: String) = { 5 | assert(template.body == expected, s"Found '$template' but expected '$expected'") 6 | } 7 | 8 | test(a.b.html.c.render("world"), "Hello, world.\n") 9 | 10 | test(html.template.render("42"), "Answer: 42\n") 11 | 12 | test(html.xml_test.render(foo), "hello xml: foo\n") 13 | } 14 | -------------------------------------------------------------------------------- /compiler/src/test/resources/utf8.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | €, ö, or ü 6 | -------------------------------------------------------------------------------- /parser/src/test/resources/static.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 |

It works

-------------------------------------------------------------------------------- /compiler/src/test/resources/static.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 |

It works

-------------------------------------------------------------------------------- /compiler/src/test/resources/error.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 |

Hello @world

6 | -------------------------------------------------------------------------------- /compiler/src/test/resources/importsDefault.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @() 6 | @{"" ?: "foo"} -------------------------------------------------------------------------------- /compiler/src/test/resources/existential.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(list: List[_]) 6 | @list 7 | -------------------------------------------------------------------------------- /compiler/src/test/resources/triplequotes.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | """ 6 | 7 | """"" 8 | 9 | """""" -------------------------------------------------------------------------------- /compiler/src/test/resources/using.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(x: String)(using y: String) 6 | @x @y 7 | -------------------------------------------------------------------------------- /compiler/src/test/resources/injectNoArgs.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @this() 6 | 7 | @(a: String) 8 | 9 | @a -------------------------------------------------------------------------------- /compiler/src/test/resources/errorInTemplateArgs.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(name) 6 | 7 |

Hello @name

8 | -------------------------------------------------------------------------------- /compiler/src/test/resources/helloNull.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(name: String) 6 | 7 |

Hello @name!

8 | -------------------------------------------------------------------------------- /compiler/src/test/resources/htmlParam.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(content: Html) 6 | 7 | Content: 8 | 9 | @content -------------------------------------------------------------------------------- /compiler/src/test/resources/callByName.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(name: => String) 6 | 7 |

Hello @name!

8 | -------------------------------------------------------------------------------- /compiler/src/test/resources/varArgsExistential.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(list: List[_]*) 6 | @list.map(_.mkString) 7 | -------------------------------------------------------------------------------- /compiler/src/test/resources/hello.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(name:String) 6 | 7 |

Hello @name!

@{

xml

} -------------------------------------------------------------------------------- /compiler/src/test/resources/inject.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @this(a: String, b: Int) 6 | 7 | @(c: String) 8 | 9 | @a @b @c -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile/src/main/twirl/a/b/c.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(name: String) 6 | Hello, @name. 7 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile/src/test/twirl/test/template.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(a: String) 6 | Answer: @a 7 | -------------------------------------------------------------------------------- /compiler/src/test/resources/escapebrace.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(maybe: Option[String]) 6 | @for(s <- maybe) { 7 | @s: @} 8 | } -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile-scala3/src/main/twirl/a/b/c.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(name: String) 6 | Hello, @name. 7 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile-scala3/src/test/twirl/test/template.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(a: String) 6 | Answer: @a 7 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/scalajs-compile/src/main/twirl/a/b/c.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(name: String) 6 | Hello, @name. 7 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/scalajs-compile/src/main/twirl/template.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(a: String) 6 | Answer: @a 7 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/scalajs-compile/src/main/twirl/xml_test.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(a: scala.xml.NodeSeq) 6 | hello xml: @a 7 | -------------------------------------------------------------------------------- /maven-twirl/src/maven-test/simple/src/main/templates/a/b/f.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @this(str: String) 6 | @(name: String) 7 | Hello, @name. 8 | -------------------------------------------------------------------------------- /maven-twirl/src/maven-test/simple/src/main/twirl/a/b/c.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @this(str: String) 6 | @(name: String) 7 | Hello, @name. 8 | -------------------------------------------------------------------------------- /maven-twirl/src/maven-test/simple/src/test/twirl/a/b/d.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @this(str: String) 6 | @(name: String) 7 | Hello, @name. 8 | -------------------------------------------------------------------------------- /compiler/src/test/resources/ifWithBrackets.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(name:String) 6 | 7 | @if(name == "twirl") { 8 | @name 9 | } 10 | -------------------------------------------------------------------------------- /compiler/src/test/resources/set.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(test: Set[String]) 6 | 7 | @for(p <- test.toSeq.sorted) { 8 | @p 9 | } 10 | -------------------------------------------------------------------------------- /gradle-twirl/src/test/resources/simple/src/main/twirl/a/b/c.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @this(str: String) 6 | @(name: String) 7 | Hello, @name. 8 | -------------------------------------------------------------------------------- /compiler/src/test/resources/blockWithTuple.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(args: Seq[(String, String)]) 6 | 7 | @args.map { case (k, v) => 8 | @k => @v 9 | } -------------------------------------------------------------------------------- /compiler/src/test/resources/injectParamGroups.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @this(a: String)(b: Int)(implicit c: String) 6 | 7 | @(d: String) 8 | 9 | @a @b @c @d -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | update_release_draft: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: release-drafter/release-drafter@v6 13 | with: 14 | name: "Twirl $RESOLVED_VERSION" 15 | config-name: release-drafts/increasing-minor-version.yml # located in .github/ in the default branch within this or the .github repo 16 | commitish: ${{ github.ref_name }} 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /compiler/src/test/resources/blockWithNestedTuple.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(args: Seq[(String, String)]) 6 | 7 | @args.zipWithIndex.map { case ((k, v), index) => 8 | @k => @v 9 | } -------------------------------------------------------------------------------- /compiler/src/test/resources/ifWithoutBrackets.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(name:String, surname: String) 6 | 7 | @if(name == "twirl") @{if(surname == "play") "twirl-play" else "twirl"} -------------------------------------------------------------------------------- /compiler/src/test/resources/zipWithIndex.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(names:Seq[String]) 6 | 7 | @names.zipWithIndex.map { case (name, ix) => 8 |

@ix Hello @name!

9 | } 10 | -------------------------------------------------------------------------------- /gradle-twirl/src/test/resources/simple/build.gradle.kts.ftlh: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | id("org.playframework.twirl") 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | mavenLocal() 9 | } 10 | 11 | dependencies { 12 | implementation("org.playframework.twirl:twirl-api_${scalaVersion}:${twirlVersion}") 13 | } 14 | 15 | twirl { 16 | scalaVersion.set("${scalaVersion}") 17 | } 18 | 19 | sourceSets { 20 | main { 21 | twirl { 22 | templateImports.add("java.lang._") 23 | templateImports.add("a.b.%format%._") 24 | constructorAnnotations.add("@java.lang.Deprecated()") 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /compiler/src/test/resources/elseIf.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(test : Int) 6 | 7 | @if(test == 0) { 8 | hello 9 | } else if (test == 1) { 10 | world 11 | } else { 12 | fail! 13 | } 14 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile/build.sbt: -------------------------------------------------------------------------------- 1 | // Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 2 | 3 | lazy val root = (project in file(".")).enablePlugins { 4 | // Make sure scalajs plugin is not available 5 | val sjsPluginName = "org.scalajs.sbtplugin.ScalaJSPlugin" 6 | try Class.forName(sjsPluginName) 7 | catch { 8 | case _: ClassNotFoundException => // do nothing 9 | case _ => throw new IllegalStateException(s"Found $sjsPluginName, but scalajs should not be required!") 10 | } 11 | // Add the twirl plugin 12 | SbtTwirl 13 | } 14 | -------------------------------------------------------------------------------- /docs/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 2 | 3 | lazy val plugins = (project in file(".")).dependsOn(sbtTwirl) 4 | 5 | lazy val sbtTwirl = ProjectRef(Path.fileProperty("user.dir").getParentFile, "plugin") 6 | 7 | resolvers ++= DefaultOptions.resolvers(snapshot = true) 8 | 9 | addSbtPlugin("org.playframework" % "play-docs-sbt-plugin" % sys.props.getOrElse("play.version", "3.0.9")) 10 | addSbtPlugin("com.github.sbt" % "sbt-header" % "5.11.0") 11 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.5") 12 | -------------------------------------------------------------------------------- /parser/src/test/resources/elseIf.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(input : Int) 6 | 7 | @if(input == 5) { 8 | hello 9 | } else if (input == 6) { 10 | world1 11 | } else if(input == 8) { 12 | world2 13 | } else { 14 | fail! 15 | } 16 | -------------------------------------------------------------------------------- /parser/src/test/resources/simple.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(name:String, users:List[Account]) 6 | 7 |

@name

8 | 9 |

@users.size

10 | 11 |
    12 | @users.map { u => 13 | 14 |
  • @u.name
  • 15 | 16 | } 17 |
-------------------------------------------------------------------------------- /parser/src/test/resources/unclosedBracket.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(name:String, users:List[Account]) 6 | 7 |

@name

8 | 9 |

@users.size

10 | 11 |
    12 | @users.map { u => 13 | 14 |
  • @u.name
  • 15 | 16 |
-------------------------------------------------------------------------------- /compiler/src/test/resources/argImports.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @import java.io.File 6 | @import java.net.URL 7 | 8 | @* template documentation comment *@ 9 | @(file: File, url: URL) 10 | 11 | @import java.net.URI 12 | 13 |

file: @file, url: @url

14 | -------------------------------------------------------------------------------- /parser/src/test/resources/imports.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @import java.io.File 6 | 7 | @import java.net.URL 8 | 9 | @* template documentation comment *@ 10 | 11 | @(file: File, url: URL) 12 | 13 | @import java.net.URI 14 | 15 |

file: @file, url: @url

16 | -------------------------------------------------------------------------------- /parser/src/test/resources/invalidAt.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(name:String, users:List[Account]) 6 | 7 |

@name

8 | 9 | @ 10 | 11 |

@users.size

12 | 13 |
    14 | @users.map { u => 15 | 16 |
  • @u.name
  • 17 | 18 | } 19 |
-------------------------------------------------------------------------------- /compiler/src/test/resources/injectComments.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @** 6 | * A template 7 | * 8 | * @param a The a 9 | * @param b The b 10 | *@ 11 | @this(a: String, b: Int) 12 | 13 | @** 14 | * The method 15 | * 16 | * @param c The c 17 | *@ 18 | @(c: String) 19 | 20 | @a @b @c -------------------------------------------------------------------------------- /gradle-twirl/src/main/java/play/twirl/gradle/TwirlExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | package play.twirl.gradle; 5 | 6 | import org.gradle.api.provider.Property; 7 | 8 | /** 9 | * The extension of the plugin allowing for configuring the target Scala version used for the 10 | * application. 11 | */ 12 | public abstract class TwirlExtension { 13 | 14 | /** 15 | * Scala version used for compilation Twirl templates. 16 | * 17 | *
{@code
18 |    * twirl {
19 |    *   scalaVersion.set("3")
20 |    * }
21 |    * }
22 | */ 23 | public abstract Property getScalaVersion(); 24 | } 25 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ], 6 | "enabledManagers": ["gradle-wrapper"], 7 | "labels": [ 8 | "type:updates" 9 | ], 10 | "env": { 11 | "GRADLE_OPTS": "-Dtwirl.compiler.version=0 -Dorg.gradle.parallel=true -Dorg.gradle.configureondemand=true -Dorg.gradle.daemon=false -Dorg.gradle.caching=false" 12 | }, 13 | "baseBranches": ["$default", "2.0.x"], 14 | "commitMessageTopic": "`{{depName}}`", 15 | "commitMessageExtra": "to `{{#if isPinDigest}}{{{newDigestShort}}}{{else}}{{#if isMajor}}v{{{newMajor}}}{{else}}{{#if isSingleVersion}}v{{{newVersion}}}{{else}}{{#if newValue}}{{{newValue}}}{{else}}{{{newDigestShort}}}{{/if}}{{/if}}{{/if}}{{/if}}` (was `{{currentVersion}}`)" 16 | } 17 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile-scala3/build.sbt: -------------------------------------------------------------------------------- 1 | // Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 2 | 3 | lazy val root = (project in file(".")) 4 | .enablePlugins { 5 | // Make sure scalajs plugin is not available 6 | val sjsPluginName = "org.scalajs.sbtplugin.ScalaJSPlugin" 7 | try Class.forName(sjsPluginName) 8 | catch { 9 | case _: ClassNotFoundException => // do nothing 10 | case _: Throwable => throw new IllegalStateException(s"Found $sjsPluginName, but scalajs should not be required!") 11 | } 12 | // Add the twirl plugin 13 | SbtTwirl 14 | } 15 | .settings( 16 | scalaVersion := "3.3.7", 17 | scalacOptions ++= Seq("-source:future", "-feature") 18 | ) 19 | -------------------------------------------------------------------------------- /compiler/src/test/resources/real.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(name:String, items:List[String])(repeat:Int) 6 | 7 |

Hello @name

8 | 9 |

You have @items.size items = @(items.size)

10 | 11 | @items.map { i => 12 | 13 |
  • 14 | @if(repeat % 2 == 0) { 15 | E@i 16 | } else { 17 | O@i 18 | } 19 |
  • 20 | 21 | } -------------------------------------------------------------------------------- /compiler/src/test/resources/htmlInner.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(name:String, items:List[String])(repeat:Int) 6 | 7 |

    Hello @name

    8 | 9 |

    You have @items.size items = @(items.size)

    10 | 11 | @items.map { i => 12 | 13 |
  • 14 | @if(repeat % 2 == 0) { 15 | E@i 16 | } else { 17 | O@i 18 | } 19 |
  • 20 | 21 | } -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 3.10.2 2 | 3 | project.layout=StandardConvention 4 | 5 | runner.dialect = scala213 6 | 7 | align.preset = true 8 | danglingParentheses.preset = true 9 | 10 | assumeStandardLibraryStripMargin = true 11 | docstrings.style = asterisk 12 | maxColumn = 120 13 | project.git = true 14 | rewrite.rules = [ AvoidInfix, ExpandImportSelectors, RedundantParens, SortModifiers ] 15 | rewrite.sortModifiers.order = [ "private", "protected", "final", "sealed", "abstract", "implicit", "override", "lazy" ] 16 | spaces.inImportCurlyBraces = true # more idiomatic to include whitepsace in import x.{ yyy } 17 | trailingCommas = preserve 18 | rewrite.scala3.convertToNewSyntax = true 19 | runner.dialectOverride { 20 | allowSignificantIndentation = false 21 | allowAsForImportRename = false 22 | allowStarWildcardImport = false 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/dependency-graph.yml: -------------------------------------------------------------------------------- 1 | name: Dependency Graph 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | concurrency: 8 | # Only run once for latest commit per ref and cancel other (previous) runs. 9 | group: dependency-graph-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | permissions: 13 | contents: write # this permission is needed to submit the dependency graph 14 | 15 | jobs: 16 | dependency-graph: 17 | name: Submit dependencies to GitHub 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v6 21 | with: 22 | fetch-depth: 0 23 | ref: ${{ inputs.ref }} 24 | - uses: sbt/setup-sbt@v1 25 | - uses: scalacenter/sbt-dependency-submission@v3 # for root project 26 | - uses: scalacenter/sbt-dependency-submission@v3 27 | with: 28 | working-directory: ./docs/ 29 | -------------------------------------------------------------------------------- /compiler/src/test/resources/codeBlockOrder.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @() 6 | @val x1 = @{"1"} 7 | @val x2 = {@{x1}2} 8 | @val x3 = @{x2.toString + "3"} 9 | @val x4 = {@{x3}4} 10 | 11 | @x5 = @{x4.toString + "5"} 12 | @x6 = {@{x5}6} 13 | @x7 = @{x6.toString + "7"} 14 | @x8 = {@{x7}8} 15 | 16 | @lazy val x9 = @{x8.toString + "9"} 17 | @lazy val x10 = {@{x9}10} 18 | @lazy val x11 = @{x10.toString + "11"} 19 | @lazy val x12 = {@{x11}12} 20 | 21 | @x12 22 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Scala Steward: Reformat with scalafmt 3.5.8 2 | 3f6f5c9d187ed87c23c5321799dbce756a840a11 3 | 4 | # Scala Steward: Reformat with scalafmt 3.6.0 5 | c846f08f0eba509d8f0f39114578cbe61465106d 6 | 7 | # Scala Steward: Reformat with scalafmt 3.7.0 8 | 3c4978dc8f174746eea3ce8c1588907c737baffc 9 | 10 | # Reformat with sbt-java-formatter 0.8.0 11 | 7dd5751d6a229092064deef13d121e34572ca59a 12 | 13 | # Scala Steward: Reformat with scalafmt 3.7.5 14 | c3b04b90d9863ca2c7b7e56bc0bda148ce392642 15 | 16 | # Scala Steward: Reformat with scalafmt 3.8.1 17 | cb6d4261c6253f685d50b24177064616e5d22143 18 | 19 | # Scala Steward: Reformat with scalafmt 3.9.8 20 | e316169276b40dbdc76142d03895233ba103f417 21 | 22 | # Scala Steward: Reformat with scalafmt 3.9.10 23 | 89b611c1e61946d665a457ae7b49f111e7d45806 24 | 25 | # Scala Steward: Reformat with scalafmt 3.10.2 26 | 35105b07d61e040af23c721fbb4ffca7d3512a6f 27 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile-scala3/src/test/twirl/test/manyargstemplate.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(a: String, b: String, c: String, d: String, e: String, f: String, g: String, h: String, i: String, j: String, k: String, l: String, m: String, n: String, o: String, p: String, q: String, r: String, s: String, t: String, u: String, v: String, w: String, x: String, y: String, z: String) 6 | 26 args: @a @b @c @d @e @f @g @h @i @j @k @l @m @n @o @p @q @r @s @t @u @v @w @x @y @z 7 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 2 | 3 | addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.11.2") 4 | 5 | // For the Cross Build 6 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.20.1") 7 | addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2") 8 | 9 | addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.4") 10 | addSbtPlugin("com.github.sbt" % "sbt-header" % "5.11.0") 11 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.5") 12 | addSbtPlugin("com.lightbend.sbt" % "sbt-java-formatter" % "0.8.0") 13 | addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "2.0.17") 14 | addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.13.1") 15 | addSbtPlugin("com.github.sbt" % "sbt-maven-plugin" % "0.0.2") 16 | -------------------------------------------------------------------------------- /gradle-twirl/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | gradle-plugin-publish = "2.0.0" 3 | nexus-publish = "2.0.0" 4 | spotless = "8.1.0" 5 | assertj = "3.27.6" 6 | commons-io = "2.21.0" 7 | commons-lang = "3.20.0" 8 | freemarker = "2.3.34" 9 | 10 | [libraries] 11 | assertj = { group = "org.assertj", name = "assertj-core", version.ref = "assertj" } 12 | commons-io = { group = "commons-io", name = "commons-io", version.ref = "commons-io" } 13 | commons-lang = { group = "org.apache.commons", name = "commons-lang3", version.ref = "commons-lang" } 14 | freemarker = { group = "org.freemarker", name = "freemarker", version.ref = "freemarker" } 15 | 16 | [plugins] 17 | gradle-plugin-publish = { id = "com.gradle.plugin-publish", version.ref = "gradle-plugin-publish" } 18 | nexus-publish = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexus-publish" } 19 | spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } 20 | -------------------------------------------------------------------------------- /compiler/src/test/resources/ifWithoutBracketsComplex.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(name:String, surname: String) 6 | 7 | @main(content: Html) = { 8 |
    @content
    9 | } 10 | 11 | @link(path: String, page: Int) = @{ 12 | s"$path/$page" 13 | } 14 | 15 | @header(p1: String, p2: String) = { 16 |
    17 | @link(p1, 0) 18 |
    19 | } 20 | 21 | @main { 22 | @header(name, surname) 23 | } -------------------------------------------------------------------------------- /compiler/src/test/resources/localDef.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @() 6 | 7 | @person(firstname: String, lastname: String) = @{ 8 | s"$firstname-$lastname" 9 | } 10 | @country(city: String, country: String): String = @{ 11 | s"$city-$country" 12 | } 13 | @region(continent: String):String=@{ 14 | s"$continent" 15 | } 16 | @slogan()=@{ s"The High Velocity Web Framework For Java and Scala" } 17 | @year=@{ s"2023" } 18 | 19 | @person("Play", "Framework")-@country("Vienna", "Austria")-@region("Europe")-@slogan()-@year -------------------------------------------------------------------------------- /gradle-twirl/src/main/java/play/twirl/gradle/internal/Gradle7TwirlSourceDirectorySet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | package play.twirl.gradle.internal; 5 | 6 | import javax.inject.Inject; 7 | import org.gradle.api.file.SourceDirectorySet; 8 | import org.gradle.api.model.ObjectFactory; 9 | import play.twirl.gradle.TwirlSourceDirectorySet; 10 | 11 | /** 12 | * Implementation of {@link TwirlSourceDirectorySet} is needed to support Gradle 7. 13 | * 14 | * @deprecated 15 | */ 16 | @Deprecated 17 | @SuppressWarnings("DeprecatedIsStillUsed") 18 | public class Gradle7TwirlSourceDirectorySet extends DefaultTwirlSourceDirectorySet { 19 | 20 | @Inject 21 | public Gradle7TwirlSourceDirectorySet( 22 | SourceDirectorySet sourceDirectorySet, ObjectFactory objectFactory) { 23 | super(sourceDirectorySet, objectFactory); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /parser/src/test/resources/unclosedBracket2.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(name:String, users:List[Account])(another:Secured[User])(implicit request:Request) 6 | 7 | @loop[A,B](a:A, b:B) = { 8 | 9 |

    @(a.yop).map @{ 10 | _.toString 11 | }

    12 | 13 | } 14 | 15 |

    @name

    16 | 17 | @if(users.isEmpty) { 18 | 19 |

    @users.size

    20 | 21 |
      22 | @users.map { u => 23 | 24 |
    • @u.name
    • 25 | 26 | }.getOrElse { 27 |

      Nothing

      28 | 29 |
    30 | 31 | } else { 32 | 33 | @coco 34 | 35 | } 36 | -------------------------------------------------------------------------------- /sbt-twirl/src/sbt-test/twirl/compile-scala3/src/test/scala/test/Test.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import play.twirl.api.* 4 | 5 | object Test extends App { 6 | def test(template: HtmlFormat.Appendable, expected: String) = { 7 | assert(template.body == expected, s"Found '$template' but expected '$expected'") 8 | } 9 | 10 | test(a.b.html.c.render("world"), "Hello, world.\n") 11 | 12 | test(html.template.render("42"), "Answer: 42\n") 13 | 14 | test( 15 | html.manyargstemplate.render( 16 | "1", 17 | "2", 18 | "3", 19 | "5", 20 | "4", 21 | "7", 22 | "6", 23 | "8", 24 | "9", 25 | "10", 26 | "11", 27 | "12", 28 | "13", 29 | "14", 30 | "15", 31 | "16", 32 | "17", 33 | "18", 34 | "19", 35 | "20", 36 | "21", 37 | "22", 38 | "23", 39 | "24", 40 | "25", 41 | "26" 42 | ), 43 | "26 args: 1 2 3 5 4 7 6 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26\n" 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /compiler/src/test/resources/patternMatching.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(test:String) 6 | 7 | @test match { 8 | case test => {@@test} 9 | } 10 | @test.length match { 11 | case 5 => {@@test.length} 12 | } 13 | @test.length.toInt match { 14 | case 5 => {@@test.length.toInt} 15 | } 16 | 17 | @(test) match { 18 | case test => {@@(test)} 19 | } 20 | @(test.length) match { 21 | case 5 => {@@(test.length)} 22 | } 23 | @(test.length + 1) match { 24 | case 6 => {@@(test.length + 1)} 25 | } 26 | @(test.+(3)) match { 27 | case _ => {@@(test.+(3))} 28 | } 29 | 30 | @test.length match @@test.length -------------------------------------------------------------------------------- /parser/src/test/resources/complicated.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(name:String, users:List[Account])(another:Secured[User])(implicit request:Request) 6 | 7 | @loop[A,B](a:A, b:B) = { 8 | 9 |

    @(a.yop).map @{ 10 | _.toString 11 | }

    12 | 13 | } 14 | 15 |

    @name

    16 | 17 | @if(users.isEmpty) { 18 | 19 |

    @users.size

    20 | 21 | @***** YOP *****@ 22 | 23 |
      24 | @users.map { u => 25 | 26 |
    • @u.name
    • 27 | 28 | }.getOrElse { 29 |

      Nothing

      30 | } 31 |
    32 | 33 | } else { 34 | 35 | @coco 36 | 37 | } 38 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gradle" 4 | directory: "gradle-twirl" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "gradle" 8 | directory: "gradle-twirl" 9 | schedule: 10 | interval: "weekly" 11 | target-branch: "2.0.x" 12 | commit-message: 13 | prefix: "[2.0.x] " 14 | - package-ecosystem: "maven" 15 | directory: "maven-twirl" 16 | schedule: 17 | interval: "weekly" 18 | - package-ecosystem: "maven" 19 | directory: "maven-twirl" 20 | schedule: 21 | interval: "weekly" 22 | target-branch: "2.0.x" 23 | commit-message: 24 | prefix: "[2.0.x] " 25 | - package-ecosystem: "github-actions" 26 | directory: "/" 27 | schedule: 28 | interval: "weekly" 29 | - package-ecosystem: "github-actions" 30 | directory: "/" 31 | schedule: 32 | interval: "weekly" 33 | target-branch: "2.0.x" 34 | commit-message: 35 | prefix: "[2.0.x] " 36 | - package-ecosystem: "github-actions" 37 | directory: "/" 38 | schedule: 39 | interval: "weekly" 40 | target-branch: "1.6.x" 41 | commit-message: 42 | prefix: "[1.6.x] " 43 | -------------------------------------------------------------------------------- /gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileParams.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | package play.twirl.gradle.internal; 5 | 6 | import org.gradle.api.file.DirectoryProperty; 7 | import org.gradle.api.file.RegularFileProperty; 8 | import org.gradle.api.provider.ListProperty; 9 | import org.gradle.api.provider.Property; 10 | import org.gradle.api.provider.SetProperty; 11 | import org.gradle.work.ChangeType; 12 | import org.gradle.workers.WorkParameters; 13 | 14 | /** Parameters of compilation work action. */ 15 | public interface TwirlCompileParams extends WorkParameters { 16 | 17 | Property getChangeType(); 18 | 19 | RegularFileProperty getSourceFile(); 20 | 21 | DirectoryProperty getSourceDirectory(); 22 | 23 | DirectoryProperty getDestinationDirectory(); 24 | 25 | Property getFormatterType(); 26 | 27 | Property getFormatExtension(); 28 | 29 | SetProperty getTemplateImports(); 30 | 31 | ListProperty getConstructorAnnotations(); 32 | 33 | Property getSourceEncoding(); 34 | } 35 | -------------------------------------------------------------------------------- /api/shared/src/main/scala-3/play/twirl/api/TwirlHelperImports.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.api 6 | 7 | import scala.language.implicitConversions 8 | 9 | /** 10 | * Imports for useful Twirl helpers. 11 | */ 12 | object TwirlHelperImports { 13 | 14 | /** Allows Java collections to be used as if they were Scala collections. */ 15 | implicit def twirlJavaCollectionToScala[T](x: java.lang.Iterable[T]): Iterable[T] = { 16 | import scala.jdk.CollectionConverters.* 17 | x.asScala 18 | } 19 | 20 | /** Allows inline formatting of java.util.Date */ 21 | implicit class TwirlRichDate(date: java.util.Date) { 22 | def format(pattern: String): String = { 23 | new java.text.SimpleDateFormat(pattern).format(date) 24 | } 25 | } 26 | 27 | /** Adds a when method to Strings to control when they are rendered. */ 28 | implicit class TwirlRichString(string: String) { 29 | def when(predicate: => Boolean): String = { 30 | predicate match { 31 | case true => string 32 | case false => "" 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /api/shared/src/main/scala-2.12/play/twirl/api/TwirlHelperImports.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.api 6 | 7 | import scala.language.implicitConversions 8 | 9 | /** 10 | * Imports for useful Twirl helpers. 11 | */ 12 | object TwirlHelperImports { 13 | 14 | /** Allows Java collections to be used as if they were Scala collections. */ 15 | implicit def twirlJavaCollectionToScala[T](x: java.lang.Iterable[T]): Iterable[T] = { 16 | import scala.collection.JavaConverters._ 17 | x.asScala 18 | } 19 | 20 | /** Allows inline formatting of java.util.Date */ 21 | implicit class TwirlRichDate(date: java.util.Date) { 22 | def format(pattern: String): String = { 23 | new java.text.SimpleDateFormat(pattern).format(date) 24 | } 25 | } 26 | 27 | /** Adds a when method to Strings to control when they are rendered. */ 28 | implicit class TwirlRichString(string: String) { 29 | def when(predicate: => Boolean): String = { 30 | predicate match { 31 | case true => string 32 | case false => "" 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /api/shared/src/main/scala-2.13/play/twirl/api/TwirlHelperImports.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.api 6 | 7 | import scala.language.implicitConversions 8 | 9 | /** 10 | * Imports for useful Twirl helpers. 11 | */ 12 | object TwirlHelperImports { 13 | 14 | /** Allows Java collections to be used as if they were Scala collections. */ 15 | implicit def twirlJavaCollectionToScala[T](x: java.lang.Iterable[T]): Iterable[T] = { 16 | import scala.jdk.CollectionConverters._ 17 | x.asScala 18 | } 19 | 20 | /** Allows inline formatting of java.util.Date */ 21 | implicit class TwirlRichDate(date: java.util.Date) { 22 | def format(pattern: String): String = { 23 | new java.text.SimpleDateFormat(pattern).format(date) 24 | } 25 | } 26 | 27 | /** Adds a when method to Strings to control when they are rendered. */ 28 | implicit class TwirlRichString(string: String) { 29 | def when(predicate: => Boolean): String = { 30 | predicate match { 31 | case true => string 32 | case false => "" 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /compiler/src/test/resources/varsTemplateBlocks.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(condition: Integer, secondCondition: Integer) 6 | @var field = { bar1 } 7 | @if(condition == 1) { 8 | @field 9 | @field = { bar2 } 10 | @field 11 | @if(secondCondition == 1) { 12 | @field 13 | @field = { bar5 } 14 | @field 15 | } 16 | @field 17 | } else if(condition == 2) { 18 | @field 19 | @field = { bar3 } 20 | @field 21 | @if(secondCondition == 1) { 22 | @field 23 | @field = { bar6 } 24 | @field 25 | } 26 | @field 27 | } else { 28 | @field 29 | @field = { bar4 } 30 | @field 31 | @if(secondCondition == 1) { 32 | @field 33 | @field = { bar7 } 34 | @field 35 | } 36 | @field 37 | } 38 | @field 39 | -------------------------------------------------------------------------------- /compiler/src/test/resources/varsPureCodeBlocks.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @(condition: Integer, secondCondition: Integer) 6 | @var field = @{ "bar1 " } 7 | @if(condition == 1) { 8 | @field 9 | @field = @{ "bar2 " } 10 | @field 11 | @if(secondCondition == 1) { 12 | @field 13 | @field = @{ "bar5 " } 14 | @field 15 | } 16 | @field 17 | } else if(condition == 2) { 18 | @field 19 | @field = @{ "bar3 " } 20 | @field 21 | @if(secondCondition == 1) { 22 | @field 23 | @field = @{ "bar6 " } 24 | @field 25 | } 26 | @field 27 | } else { 28 | @field 29 | @field = @{ "bar4 " } 30 | @field 31 | @if(secondCondition == 1) { 32 | @field 33 | @field = @{ "bar7 " } 34 | @field 35 | } 36 | @field 37 | } 38 | @field 39 | -------------------------------------------------------------------------------- /project/CopyrightHeader.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | import sbtheader.HeaderPlugin.autoImport.HeaderPattern.commentBetween 6 | import sbtheader.CommentStyle 7 | import sbtheader.FileType 8 | import sbtheader.HeaderPlugin 9 | import sbtheader.LineCommentCreator 10 | import sbt._ 11 | 12 | object CopyrightHeader extends AutoPlugin { 13 | import HeaderPlugin.autoImport._ 14 | 15 | override def requires = HeaderPlugin 16 | override def trigger = allRequirements 17 | 18 | override def projectSettings = 19 | Seq( 20 | headerLicense := Some( 21 | HeaderLicense.Custom( 22 | "Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. " 23 | ) 24 | ), 25 | headerMappings ++= Map( 26 | FileType("sbt") -> HeaderCommentStyle.cppStyleLineComment, 27 | FileType("properties") -> HeaderCommentStyle.hashLineComment, 28 | FileType("md") -> CommentStyle(new LineCommentCreator(""), commentBetween("")), 29 | FileType("html") -> HeaderCommentStyle.twirlStyleFramedBlockComment 30 | ), 31 | ) 32 | 33 | } 34 | -------------------------------------------------------------------------------- /project/Playdoc.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | import sbt.Keys._ 6 | 7 | import sbt._ 8 | import sbt.io.IO 9 | 10 | object Playdoc extends AutoPlugin { 11 | 12 | object autoImport { 13 | final val Docs = config("docs") 14 | val playdocDirectory = settingKey[File]("Base directory of play documentation") 15 | val playdocPackage = taskKey[File]("Package play documentation") 16 | } 17 | 18 | import autoImport._ 19 | 20 | override def requires = sbt.plugins.JvmPlugin 21 | 22 | override def trigger = noTrigger 23 | 24 | override def projectSettings = 25 | Defaults.packageTaskSettings(playdocPackage, playdocPackage / mappings) ++ 26 | Seq( 27 | playdocDirectory := (ThisBuild / baseDirectory).value / "docs" / "manual", 28 | playdocPackage / mappings := { 29 | val base: File = playdocDirectory.value 30 | base.allPaths.pair(IO.relativize(base.getParentFile(), _)) 31 | }, 32 | playdocPackage / artifactClassifier := Some("playdoc"), 33 | playdocPackage / artifact ~= { _.withConfigurations(Vector(Docs)) } 34 | ) ++ 35 | addArtifact(playdocPackage / artifact, playdocPackage) 36 | 37 | } 38 | -------------------------------------------------------------------------------- /api/shared/src/main/scala/play/twirl/api/Format.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.api 6 | 7 | import scala.collection.immutable 8 | 9 | /** 10 | * A type that works with BaseScalaTemplate This used to support +=, but no longer is required to. 11 | * @todo 12 | * Change name to reflect not appendable 13 | */ 14 | trait Appendable[T] 15 | 16 | /** 17 | * A template format defines how to properly integrate content for a type `T` (e.g. to prevent cross-site scripting 18 | * attacks) 19 | * @tparam T 20 | * The underlying type that this format applies to. 21 | */ 22 | trait Format[T <: Appendable[T]] { 23 | type Appendable = T 24 | 25 | /** 26 | * Integrate `text` without performing any escaping process. 27 | * @param text 28 | * Text to integrate 29 | */ 30 | def raw(text: String): T 31 | 32 | /** 33 | * Integrate `text` after escaping special characters. e.g. for HTML, “<” becomes “&lt;” 34 | * @param text 35 | * Text to integrate 36 | */ 37 | def escape(text: String): T 38 | 39 | /** 40 | * Generate an empty appendable 41 | */ 42 | def empty: T 43 | 44 | /** 45 | * Fill an appendable with the elements 46 | */ 47 | def fill(elements: immutable.Seq[T]): T 48 | } 49 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: # Snapshots 6 | - main 7 | tags: ["**"] # Releases 8 | 9 | jobs: 10 | publish-snapshot-artifacts: 11 | name: Publish / Artifacts (Snapshot) 12 | if: github.ref_type == 'branch' # Snapshots 13 | uses: playframework/.github/.github/workflows/publish.yml@v4 14 | with: 15 | gradle-build-root: gradle-twirl 16 | cmd: | 17 | sbt +compiler/publishM2 ci-release 18 | cd gradle-twirl 19 | ./gradlew --no-daemon publishToSonatype -x test -PsonatypeUsername="$SONATYPE_USERNAME" -PsonatypePassword="$SONATYPE_PASSWORD" 20 | secrets: inherit 21 | 22 | publish-release: 23 | name: Publish / Artifacts (Stable Release) 24 | if: github.ref_type == 'tag' # Releases 25 | uses: playframework/.github/.github/workflows/publish.yml@v4 26 | secrets: inherit 27 | 28 | publish-release-gradle: 29 | name: Publish / Artifacts (Stable Gradle Plugin) 30 | if: github.ref_type == 'tag' # Releases 31 | uses: playframework/.github/.github/workflows/publish.yml@v4 32 | with: 33 | gradle-build-root: gradle-twirl 34 | cmd: | 35 | sbt +compiler/publishM2 36 | cd gradle-twirl 37 | ./gradlew --no-daemon publishPlugins -x test -Pgradle.publish.key="$GRADLE_PUBLISH_KEY" -Pgradle.publish.secret="$GRADLE_PUBLISH_SECRET" 38 | secrets: inherit 39 | -------------------------------------------------------------------------------- /api/shared/src/test/scala/play/twirl/api/test/StringInterpolationSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.api 6 | package test 7 | 8 | import java.util.ArrayList 9 | import java.util.Optional 10 | import java.util.{ List => JList } 11 | import org.scalatest.matchers.must.Matchers 12 | import org.scalatest.wordspec.AnyWordSpec 13 | 14 | class StringInterpolationSpec extends AnyWordSpec with Matchers { 15 | 16 | "StringInterpolation" should { 17 | "leave string parts untouched" in { 18 | val p = html"

    " 19 | p.body mustBe "

    " 20 | } 21 | "escape interpolated arguments" in { 22 | val arg = "<" 23 | val p = html"

    $arg

    " 24 | p.body mustBe "

    <

    " 25 | } 26 | "leave nested templates untouched" in { 27 | val p = html"

    " 28 | val div = html"
    $p
    " 29 | div.body mustBe "

    " 30 | } 31 | "display arguments as they would be displayed in a template" in { 32 | html"${Some("a")} $None".body mustBe "a " 33 | html"${Optional.of("a")} $None".body mustBe "a " 34 | html"${Seq("a", "b")}".body mustBe "ab" 35 | val javaList: JList[String] = new ArrayList(); 36 | javaList.add("a") 37 | javaList.add("b") 38 | html"${javaList}".body mustBe "ab" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /api/shared/src/test/scala/play/twirl/api/test/FormatSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.api 6 | package test 7 | 8 | import org.scalatest.matchers.must.Matchers 9 | import org.scalatest.wordspec.AnyWordSpec 10 | 11 | class FormatSpec extends AnyWordSpec with Matchers { 12 | 13 | "Formats" should { 14 | "show null text values as empty" in { 15 | val text: String = null 16 | 17 | Html(text).body mustBe empty 18 | new Html(text).body mustBe empty 19 | 20 | Txt(text).body mustBe empty 21 | new Txt(text).body mustBe empty 22 | 23 | Xml(text).body mustBe empty 24 | new Xml(text).body mustBe empty 25 | 26 | JavaScript(text).body mustBe empty 27 | new JavaScript(text).body mustBe empty 28 | } 29 | } 30 | 31 | "HtmlFormat" should { 32 | "escape '<', '&' and '>'" in { 33 | HtmlFormat.escape("foo < bar & baz >").body mustBe "foo < bar & baz >" 34 | } 35 | 36 | "escape single quotes" in { 37 | HtmlFormat.escape("'single quotes'").body mustBe "'single quotes'" 38 | } 39 | 40 | "escape double quotes" in { 41 | HtmlFormat.escape("\"double quotes\"").body mustBe ""double quotes"" 42 | } 43 | 44 | "not escape non-ASCII characters" in { 45 | HtmlFormat.escape("こんにちは").body mustBe "こんにちは" 46 | } 47 | } 48 | 49 | "JavaScriptFormat" should { 50 | """escape ''', '"' and '\'""" in { 51 | JavaScriptFormat.escape("""foo ' bar " baz \""").body must be("""foo \' bar \" baz \\""") 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /api/shared/src/main/scala-2.12/play/twirl/api/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl 6 | 7 | import scala.reflect.ClassTag 8 | 9 | package object api { 10 | 11 | /** 12 | * Brings the template engine as a 13 | * [[https://docs.scala-lang.org/overviews/core/string-interpolation.html string interpolator]]. 14 | * 15 | * Basic usage: 16 | * 17 | * {{{ 18 | * import play.twirl.api.StringInterpolation 19 | * 20 | * val name = "Martin" 21 | * val htmlFragment: Html = html"<div>Hello \$name</div>" 22 | * }}} 23 | * 24 | * Three interpolators are available: `html`, `xml` and `js`. 25 | */ 26 | implicit class StringInterpolation(val sc: StringContext) extends AnyVal { 27 | def html(args: Any*): Html = interpolate(args, HtmlFormat) 28 | 29 | def xml(args: Any*): Xml = interpolate(args, XmlFormat) 30 | 31 | def js(args: Any*): JavaScript = interpolate(args, JavaScriptFormat) 32 | 33 | def interpolate[A <: Appendable[A]: ClassTag](args: Seq[Any], format: Format[A]): A = { 34 | sc.checkLengths(args) 35 | val array = Array.ofDim[Any](args.size + sc.parts.size) 36 | val strings = sc.parts.iterator 37 | val expressions = args.iterator 38 | array(0) = format.raw(strings.next()) 39 | var i = 1 40 | while (strings.hasNext) { 41 | array(i) = expressions.next() 42 | array(i + 1) = format.raw(strings.next()) 43 | i += 2 44 | } 45 | new BaseScalaTemplate[A, Format[A]](format)._display_(array) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /api/shared/src/main/scala-2.13/play/twirl/api/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl 6 | 7 | import scala.reflect.ClassTag 8 | 9 | package object api { 10 | 11 | /** 12 | * Brings the template engine as a 13 | * [[http://docs.scala-lang.org/overviews/core/string-interpolation.html string interpolator]]. 14 | * 15 | * Basic usage: 16 | * 17 | * {{{ 18 | * import play.twirl.api.StringInterpolation 19 | * 20 | * val name = "Martin" 21 | * val htmlFragment: Html = html"<div>Hello \$name</div>" 22 | * }}} 23 | * 24 | * Three interpolators are available: `html`, `xml` and `js`. 25 | */ 26 | implicit class StringInterpolation(val sc: StringContext) extends AnyVal { 27 | def html(args: Any*): Html = interpolate(args, HtmlFormat) 28 | 29 | def xml(args: Any*): Xml = interpolate(args, XmlFormat) 30 | 31 | def js(args: Any*): JavaScript = interpolate(args, JavaScriptFormat) 32 | 33 | def interpolate[A <: Appendable[A]: ClassTag](args: Seq[Any], format: Format[A]): A = { 34 | StringContext.checkLengths(args, sc.parts) 35 | val array = Array.ofDim[Any](args.size + sc.parts.size) 36 | val strings = sc.parts.iterator 37 | val expressions = args.iterator 38 | array(0) = format.raw(strings.next()) 39 | var i = 1 40 | while (strings.hasNext) { 41 | array(i) = expressions.next() 42 | array(i + 1) = format.raw(strings.next()) 43 | i += 2 44 | } 45 | new BaseScalaTemplate[A, Format[A]](format)._display_(array) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /api/shared/src/main/scala-3/play/twirl/api/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl 6 | 7 | import scala.reflect.ClassTag 8 | 9 | package object api { 10 | 11 | /** 12 | * Brings the template engine as a 13 | * [[http://docs.scala-lang.org/overviews/core/string-interpolation.html string interpolator]]. 14 | * 15 | * Basic usage: 16 | * 17 | * {{{ 18 | * import play.twirl.api.StringInterpolation 19 | * 20 | * val name = "Martin" 21 | * val htmlFragment: Html = html"<div>Hello \$name</div>" 22 | * }}} 23 | * 24 | * Three interpolators are available: `html`, `xml` and `js`. 25 | */ 26 | implicit class StringInterpolation(val sc: StringContext) extends AnyVal { 27 | def html(args: Any*): Html = interpolate(args, HtmlFormat) 28 | 29 | def xml(args: Any*): Xml = interpolate(args, XmlFormat) 30 | 31 | def js(args: Any*): JavaScript = interpolate(args, JavaScriptFormat) 32 | 33 | def interpolate[A <: Appendable[A]: ClassTag](args: Seq[Any], format: Format[A]): A = { 34 | StringContext.checkLengths(args, sc.parts) 35 | val array = Array.ofDim[Any](args.size + sc.parts.size) 36 | val strings = sc.parts.iterator 37 | val expressions = args.iterator 38 | array(0) = format.raw(strings.next()) 39 | var i = 1 40 | while strings.hasNext do { 41 | array(i) = expressions.next() 42 | array(i + 1) = format.raw(strings.next()) 43 | i += 2 44 | } 45 | new BaseScalaTemplate[A, Format[A]](format)._display_(array) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | pull_request: 5 | 6 | push: 7 | branches: 8 | - main # Check branch after merge 9 | 10 | concurrency: 11 | # Only run once for latest commit per ref and cancel other (previous) runs. 12 | group: ci-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | # Run Gradle Wrapper Validation to verify the wrapper's checksum 17 | gradle-validation: 18 | name: Gradle Wrapper 19 | uses: playframework/.github/.github/workflows/gradle-wrapper-validation.yml@v4 20 | 21 | check-code-style: 22 | name: Code Style 23 | needs: 24 | - "gradle-validation" 25 | uses: playframework/.github/.github/workflows/cmd.yml@v4 26 | with: 27 | gradle-build-root: gradle-twirl 28 | cmd: | 29 | sbt validateCode 30 | sbt +compiler/publishM2 31 | cd gradle-twirl && ./gradlew clean spotlessCheck --no-daemon 32 | 33 | check-binary-compatibility: 34 | name: Binary Compatibility 35 | uses: playframework/.github/.github/workflows/binary-check.yml@v4 36 | 37 | check-docs: 38 | name: Docs 39 | uses: playframework/.github/.github/workflows/cmd.yml@v4 40 | with: 41 | cmd: | 42 | cd docs 43 | sbt evaluateSbtFiles +validateDocs headerCheckAll +test 44 | 45 | tests: 46 | name: Tests 47 | needs: 48 | - "check-code-style" 49 | - "check-binary-compatibility" 50 | - "check-docs" 51 | uses: playframework/.github/.github/workflows/cmd.yml@v4 52 | with: 53 | java: 21, 17 54 | scala: 2.12.x, 2.13.x, 3.x 55 | cmd: scripts/test-code.sh 56 | gradle-build-root: gradle-twirl 57 | 58 | finish: 59 | name: Finish 60 | if: github.event_name == 'pull_request' 61 | needs: # Should be last 62 | - "tests" 63 | uses: playframework/.github/.github/workflows/rtm.yml@v4 64 | -------------------------------------------------------------------------------- /compiler/src/test/scala/play/twirl/compiler/test/Benchmark.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.compiler.test 6 | 7 | import java.io.File 8 | import play.twirl.api.Html 9 | import play.twirl.compiler.test.Helper.CompilerHelper 10 | import play.twirl.parser.TwirlIO 11 | 12 | /** 13 | * Easiest way to run this: 14 | * 15 | * sbt "compiler/Test/runMain play.twirl.compiler.test.Benchmark" 16 | */ 17 | object Benchmark extends App { 18 | val sourceDir = new File("src/test/resources") 19 | val generatedDir = new File("target/test/benchmark-templates") 20 | val generatedClasses = new File("target/test/benchmark-classes") 21 | 22 | TwirlIO.deleteRecursively(generatedDir) 23 | TwirlIO.deleteRecursively(generatedClasses) 24 | generatedClasses.mkdirs() 25 | 26 | val helper = new CompilerHelper(sourceDir, generatedDir, generatedClasses) 27 | 28 | println("Compiling template...") 29 | val template = helper.compile[((String, List[String]) => (Int) => Html)]("real.scala.html", "html.real").static 30 | val input = (1 to 100).map(_.toString).toList 31 | 32 | val text = "world " * 100 33 | 34 | // warmup 35 | println("Warming up...") 36 | for (i <- 1 to 10000) { 37 | template(text, input)(4).body 38 | } 39 | 40 | println("Starting first run...") 41 | val start1 = System.currentTimeMillis() 42 | for (i <- 1 to 100000) { 43 | template(text, input)(4).body 44 | } 45 | println("First run: " + (System.currentTimeMillis() - start1) + "ms") 46 | 47 | println("Starting second run...") 48 | val start2 = System.currentTimeMillis() 49 | for (i <- 1 to 100000) { 50 | template(text, input)(4).body 51 | } 52 | println("Second run: " + (System.currentTimeMillis() - start2) + "ms") 53 | } 54 | -------------------------------------------------------------------------------- /maven-twirl/src/maven-test/simple/src/test/java/play/twirl/maven/SimpleProjectTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.maven; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | import static org.assertj.core.util.Lists.list; 9 | 10 | import java.nio.file.Path; 11 | import java.nio.file.Paths; 12 | import org.junit.jupiter.api.DisplayName; 13 | import org.junit.jupiter.api.Test; 14 | 15 | @DisplayName("Simple Maven project with Twirl HTML template") 16 | public class SimpleProjectTest { 17 | 18 | @Test 19 | @DisplayName("Test common build") 20 | public void testCommonBuild() { 21 | var compiledTemplates = 22 | list( 23 | projectBuildPath("generated-sources/twirl/a/b/html/c.template.scala"), 24 | projectBuildPath("generated-sources/twirl/a/b/html/f.template.scala"), 25 | projectBuildPath("generated-test-sources/twirl/a/b/html/d.template.scala")); 26 | 27 | assertThat(compiledTemplates) 28 | .allSatisfy( 29 | file -> 30 | assertThat(file) 31 | .isNotEmptyFile() 32 | .binaryContent() 33 | .asString() 34 | .contains( 35 | "import java.lang._", "@java.lang.Deprecated()", "import a.b.html._")); 36 | 37 | var compiledScalaSources = 38 | list( 39 | projectBuildPath("classes/a/b/html/c.class"), 40 | projectBuildPath("classes/a/b/html/f.class"), 41 | projectBuildPath("test-classes/a/b/html/d.class")); 42 | 43 | assertThat(compiledScalaSources).allSatisfy(file -> assertThat(file).isNotEmptyFile()); 44 | } 45 | 46 | protected Path projectBuildPath(String path) { 47 | return Paths.get("target", path); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /api/shared/src/main/scala/play/twirl/api/utils/StringEscapeUtils.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.api.utils 6 | 7 | object StringEscapeUtils { 8 | def escapeEcmaScript(input: String): String = { 9 | val s = new StringBuilder() 10 | val len = input.length 11 | var pos = 0 12 | while (pos < len) { 13 | input.charAt(pos) match { 14 | // Standard Lookup 15 | case '\'' => s.append("\\'") 16 | case '\"' => s.append("\\\"") 17 | case '\\' => s.append("\\\\") 18 | case '/' => s.append("\\/") 19 | // JAVA_CTRL_CHARS 20 | case '\b' => s.append("\\b") 21 | case '\n' => s.append("\\n") 22 | case '\t' => s.append("\\t") 23 | case '\f' => s.append("\\f") 24 | case '\r' => s.append("\\r") 25 | // Ignore any character below ' ' 26 | case c if c < ' ' => 27 | // if it not matches any characters above, just append it 28 | case c => s.append(c) 29 | } 30 | pos += 1 31 | } 32 | 33 | s.toString() 34 | } 35 | 36 | def escapeXml11(input: String): String = { 37 | // Implemented per XML spec: 38 | // http://www.w3.org/International/questions/qa-controls 39 | val s = new StringBuilder() 40 | val len = input.length 41 | var pos = 0 42 | 43 | while (pos < len) { 44 | input.charAt(pos) match { 45 | case '<' => s.append("<") 46 | case '>' => s.append(">") 47 | case '&' => s.append("&") 48 | case '"' => s.append(""") 49 | case '\n' => s.append('\n') 50 | case '\r' => s.append('\r') 51 | case '\t' => s.append('\t') 52 | case c if c < ' ' => 53 | case c => s.append(c) 54 | } 55 | pos += 1 56 | } 57 | 58 | s.toString() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /api/shared/src/main/scala/play/twirl/api/TwirlFeatureImports.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.api 6 | 7 | import scala.language.implicitConversions 8 | 9 | /** 10 | * Imports that provide Twirl language features. 11 | * 12 | * This includes: 13 | * 14 | * - \@defining 15 | * - \@using 16 | * - iterable/option/string as boolean for if statements 17 | * - default values (maybeFoo ? defaultFoo) 18 | */ 19 | object TwirlFeatureImports { 20 | 21 | /** 22 | * Provides the `@defining` language feature, that lets you set a local val that can be reused. 23 | * 24 | * @param t 25 | * The defined val. 26 | * @param handler 27 | * The block to handle it. 28 | */ 29 | def defining[T](t: T)(handler: T => Any): Any = { 30 | handler(t) 31 | } 32 | 33 | /** Provides the `@using` language feature. */ 34 | def using[T](t: T): T = t 35 | 36 | /** Adds "truthiness" to iterables, making them false if they are empty. */ 37 | implicit def twirlIterableToBoolean(x: Iterable[?]): Boolean = x != null && !x.isEmpty 38 | 39 | /** Adds "truthiness" to options, making them false if they are empty. */ 40 | implicit def twirlOptionToBoolean(x: Option[?]): Boolean = x != null && x.isDefined 41 | 42 | /** Adds "truthiness" to strings, making them false if they are empty. */ 43 | implicit def twirlStringToBoolean(x: String): Boolean = x != null && !x.isEmpty 44 | 45 | /** 46 | * Provides default values, such that an empty sequence, string, option, false boolean, or null will render the 47 | * default value. 48 | */ 49 | implicit class TwirlDefaultValue(default: Any) { 50 | def ?:(x: Any): Any = 51 | x match { 52 | case "" => default 53 | case Nil => default 54 | case false => default 55 | case 0 => default 56 | case None => default 57 | case _ => x 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /docs/manual/working/scalaGuide/main/templates/code/scalaguide/templates/snippets.scala.html: -------------------------------------------------------------------------------- 1 | @import scalaguide.templates._ 2 | 3 | @(products: Seq[Product], user: User, article: Article) 4 | 5 | 6 | @* #escape-at *@ 7 | My email is bob@@example.com 8 | @* #escape-at *@ 9 | 10 | 11 | @* #for-loop *@ 12 |
      13 | @for(p <- products) { 14 |
    • @p.name ($@p.price)
    • 15 | } 16 |
    17 | @* #for-loop *@ 18 |
    19 | 20 | @defining(List("a", "b")) { items => 21 | @* #conditional *@ 22 | @if(items.isEmpty) { 23 |

    Nothing to display

    24 | } else { 25 |

    @items.size items!

    26 | } 27 | @* #conditional *@ 28 | } 29 | 30 | 31 | @* #reusable *@ 32 | @display(product: Product) = { 33 | @product.name ($@product.price) 34 | } 35 | 36 |
      37 | @for(product <- products) { 38 | @display(product) 39 | } 40 |
    41 | @* #reusable *@ 42 |
    43 | 44 | @* #pure-reusable *@ 45 | @title(text: String) = @{ 46 | text.split(' ').map(_.capitalize).mkString(" ") 47 | } 48 | 49 |

    @title("hello world")

    50 | @* #pure-reusable *@ 51 | 52 | @* #implicits *@ 53 | @implicitFieldConstructor = @{ MyFieldConstructor() } 54 | @* #implicits *@ 55 | @ImplicitTester.test 56 | 57 | @* #defining *@ 58 | @defining(user.firstName + " " + user.lastName) { fullName => 59 |
    Hello @fullName
    60 | } 61 | @* #defining *@ 62 | 63 | 64 | @* #vals *@ 65 | @val fullName = @{ user.firstName + " " + user.lastName } 66 | @* lazy is also supported: *@ 67 | @lazy val nextId = @{ java.util.UUID.randomUUID() } 68 | @* #vals *@ 69 | @fullName 70 | 71 | 72 | 73 | @* #tmpl-vals *@ 74 | @lazy val productSnippet = { 75 | @for(p <- products) { 76 |
  • @p.name ($@p.price)
  • 77 | } 78 | } 79 | @* #tmpl-vals *@ 80 | @productSnippet 81 |
    82 | 83 | @* #comment *@ 84 | @********************* 85 | * This is a comment * 86 | *********************@ 87 | @* #comment *@ 88 | 89 | @* #raw-html *@ 90 |

    91 | @Html(article.content) 92 |

    93 | @* #raw-html *@ 94 | -------------------------------------------------------------------------------- /project/Common.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | import sbt.Keys._ 6 | 7 | import sbt._ 8 | import sbt.plugins.JvmPlugin 9 | import Dependencies._ 10 | 11 | object Common extends AutoPlugin { 12 | 13 | override def trigger = noTrigger 14 | 15 | override def requires = JvmPlugin 16 | 17 | val repoName = "twirl" 18 | 19 | val javacParameters = Seq( 20 | "-encoding", 21 | "UTF-8", 22 | "-Xlint:-options", 23 | "--release", 24 | "17", 25 | "-Xlint:deprecation", 26 | "-Xlint:unchecked" 27 | ) 28 | 29 | val scalacParameters = Seq( 30 | "-release:17", 31 | "-deprecation", 32 | "-feature", 33 | "-unchecked", 34 | "-encoding", 35 | "utf8" 36 | ) 37 | 38 | def crossScalacOptions(version: String) = { 39 | CrossVersion.partialVersion(version) match { 40 | case Some((2, 13)) => 41 | scalacParameters ++ Seq( 42 | "-Xsource:3" 43 | ) 44 | case _ => scalacParameters 45 | } 46 | } 47 | 48 | override def projectSettings = 49 | Seq( 50 | scalaVersion := Scala212, 51 | crossScalaVersions := ScalaVersions, 52 | scalacOptions ++= crossScalacOptions(scalaVersion.value), 53 | javacOptions ++= javacParameters 54 | ) 55 | 56 | override def globalSettings = 57 | Seq( 58 | organization := "org.playframework.twirl", 59 | organizationName := "The Play Framework Project", 60 | organizationHomepage := Some(url("https://playframework.com/")), 61 | homepage := Some(url(s"https://github.com/playframework/${repoName}")), 62 | licenses := Seq("Apache-2.0" -> url("https://www.apache.org/licenses/LICENSE-2.0.html")), 63 | developers += Developer( 64 | "playframework", 65 | "The Play Framework Contributors", 66 | "contact@playframework.com", 67 | url("https://github.com/playframework") 68 | ), 69 | description := "Twirl" 70 | ) 71 | 72 | } 73 | -------------------------------------------------------------------------------- /maven-twirl/src/main/java/play/twirl/maven/TwirlCompileMojo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.maven; 6 | 7 | import static org.apache.maven.plugins.annotations.LifecyclePhase.GENERATE_SOURCES; 8 | 9 | import java.io.File; 10 | import org.apache.maven.plugin.MojoExecutionException; 11 | import org.apache.maven.plugin.MojoFailureException; 12 | import org.apache.maven.plugins.annotations.Mojo; 13 | import org.apache.maven.plugins.annotations.Parameter; 14 | 15 | @Mojo(name = "compile", defaultPhase = GENERATE_SOURCES, threadSafe = true) 16 | public class TwirlCompileMojo extends AbstractTwirlCompileMojo { 17 | 18 | /** 19 | * The directories in which the Twirl templates is found. 20 | * 21 | *

    Default: ${project.basedir}/src/main/twirl 22 | * 23 | *

    Example: 24 | * 25 | *

    {@code
    26 |    * ${project.basedir}/src/main/templates
    27 |    * }
    28 | */ 29 | @Parameter(defaultValue = "${project.basedir}/src/main/twirl") 30 | private File sourceDir; 31 | 32 | /** 33 | * The directory where the compiled Twirl templates are placed. 34 | * 35 | *

    Default: {@code ${project.build.directory}/generated-sources/twirl} 36 | * 37 | *

    Example: 38 | * 39 | *

    {@code
    40 |    * ${project.build.directory}/generated-sources/twirl
    41 |    * }
    42 | */ 43 | @Parameter(defaultValue = "${project.build.directory}/generated-sources/twirl") 44 | private File outputDir; 45 | 46 | @Override 47 | protected File getSourceDirectory() { 48 | return sourceDir; 49 | } 50 | 51 | @Override 52 | protected File getOutputDirectory() { 53 | return outputDir; 54 | } 55 | 56 | @Override 57 | public void execute() throws MojoExecutionException, MojoFailureException { 58 | super.execute(); 59 | project.addCompileSourceRoot(getOutputDirectory().getAbsolutePath()); 60 | getLog().info("Added generated Scala sources: " + getOutputDirectory()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /docs/manual/working/scalaGuide/main/templates/ScalaTemplatesDependencyInjection.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Dependency Injection with Templates 4 | 5 | Twirl templates can be generated as a class rather than a static object by declaring a constructor using a special @this(args) syntax at the top of the template. This means that Twirl templates can be injected into controllers directly and can manage their own dependencies, rather than the controller having to manage dependencies not only for itself, but also for the templates it has to render. 6 | 7 | As an example, suppose a template has a dependency on a component `Summarizer`, which is not used by the controller: 8 | 9 | ```scala 10 | trait Summarizer { 11 | /** Provide short form of string if over a certain length */ 12 | def summarize(item: String) 13 | } 14 | ``` 15 | 16 | Create a file `app/views/IndexTemplate.scala.html` using the `@this` syntax for the constructor: 17 | 18 | ```scala 19 | @this(summarizer: Summarizer) 20 | @(item: String) 21 | 22 | @{summarizer.summarize(item)} 23 | ``` 24 | 25 | And finally define the controller in Play by injecting the template in the constructor: 26 | 27 | ```scala 28 | public MyController @Inject()(template: views.html.IndexTemplate, 29 | cc: ControllerComponents) 30 | extends AbstractController(cc) { 31 | 32 | def index = Action { implicit request => 33 | val item = "some extremely long text" 34 | Ok(template(item)) 35 | } 36 | } 37 | ``` 38 | 39 | Once the template is defined with its dependencies, then the controller can have the template injected into the controller, but the controller does not see `Summarizer`. 40 | 41 | If you are using Twirl outside of a Play application, you will have to manually add the `@Inject` annotation saying that dependency injection should be used here: 42 | 43 | ```scala 44 | TwirlKeys.constructorAnnotations += "@javax.inject.Inject()" 45 | ``` 46 | 47 | Inside a play application, this is already included in the default settings. -------------------------------------------------------------------------------- /maven-twirl/src/main/java/play/twirl/maven/TwirlTestCompileMojo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.maven; 6 | 7 | import static org.apache.maven.plugins.annotations.LifecyclePhase.GENERATE_TEST_SOURCES; 8 | 9 | import java.io.File; 10 | import org.apache.maven.plugin.MojoExecutionException; 11 | import org.apache.maven.plugin.MojoFailureException; 12 | import org.apache.maven.plugins.annotations.Mojo; 13 | import org.apache.maven.plugins.annotations.Parameter; 14 | 15 | @Mojo(name = "testCompile", defaultPhase = GENERATE_TEST_SOURCES, threadSafe = true) 16 | public class TwirlTestCompileMojo extends AbstractTwirlCompileMojo { 17 | 18 | /** 19 | * The directories in which the Twirl templates is found. 20 | * 21 | *

    Default: ${project.basedir}/src/test/twirl 22 | * 23 | *

    Example: 24 | * 25 | *

    {@code
    26 |    * ${project.basedir}/src/test/templates
    27 |    * }
    28 | */ 29 | @Parameter(defaultValue = "${project.basedir}/src/test/twirl") 30 | private File sourceDir; 31 | 32 | /** 33 | * The directory where the compiled Twirl templates are placed. 34 | * 35 | *

    Default: {@code ${project.build.directory}/generated-test-sources/twirl} 36 | * 37 | *

    Example: 38 | * 39 | *

    {@code
    40 |    * ${project.build.directory}/generated-test-sources/twirl
    41 |    * }
    42 | */ 43 | @Parameter(defaultValue = "${project.build.directory}/generated-test-sources/twirl") 44 | private File outputDir; 45 | 46 | @Override 47 | protected File getSourceDirectory() { 48 | return sourceDir; 49 | } 50 | 51 | @Override 52 | protected File getOutputDirectory() { 53 | return outputDir; 54 | } 55 | 56 | @Override 57 | public void execute() throws MojoExecutionException, MojoFailureException { 58 | super.execute(); 59 | project.addTestCompileSourceRoot(getOutputDirectory().getAbsolutePath()); 60 | getLog().info("Added generated Test Scala sources: " + getOutputDirectory()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /gradle-twirl/src/main/java/play/twirl/gradle/TwirlSourceDirectorySet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | package play.twirl.gradle; 5 | 6 | import org.gradle.api.file.SourceDirectorySet; 7 | import org.gradle.api.provider.ListProperty; 8 | import org.gradle.api.provider.MapProperty; 9 | import org.gradle.api.provider.Property; 10 | import org.gradle.api.provider.SetProperty; 11 | 12 | /** 13 | * A {@code TwirlSourceDirectorySet} defines the properties and methods added to a {@link 14 | * org.gradle.api.tasks.SourceSet} by the {@code TwirlPlugin}. 15 | */ 16 | public interface TwirlSourceDirectorySet extends SourceDirectorySet { 17 | 18 | /** 19 | * Custom template formats configured for this source directory set. 20 | * 21 | *
    {@code
    22 |    * sourceSets {
    23 |    *   main {
    24 |    *     twirl {
    25 |    *       templateFormats.put("csv", "play.twirl.api.TxtFormat")
    26 |    *     }
    27 |    *   }
    28 |    * }
    29 |    * }
    30 | */ 31 | MapProperty getTemplateFormats(); 32 | 33 | /** 34 | * Imports that should be added to generated source files. 35 | * 36 | *
    {@code
    37 |    * sourceSets {
    38 |    *   main {
    39 |    *     twirl {
    40 |    *       templateImports.add("org.example._")
    41 |    *     }
    42 |    *   }
    43 |    * }
    44 |    * }
    45 | */ 46 | SetProperty getTemplateImports(); 47 | 48 | /** 49 | * Annotations added to constructors in injectable templates. 50 | * 51 | *
    {@code
    52 |    * sourceSets {
    53 |    *   main {
    54 |    *     twirl {
    55 |    *       constructorAnnotations.add("@org.example.MyAnnotation()")
    56 |    *     }
    57 |    *   }
    58 |    * }
    59 |    * }
    60 | */ 61 | ListProperty getConstructorAnnotations(); 62 | 63 | /** 64 | * Source encoding for template files and generated scala files. 65 | * 66 | *
    {@code
    67 |    * sourceSets {
    68 |    *   main {
    69 |    *     twirl {
    70 |    *       sourceEncoding.set("")
    71 |    *     }
    72 |    *   }
    73 |    * }
    74 |    * }
    75 | */ 76 | Property getSourceEncoding(); 77 | } 78 | -------------------------------------------------------------------------------- /compiler/src/test/scala/play/twirl/compiler/test/TemplateUtilsSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.compiler 6 | package test 7 | 8 | import play.twirl.api._ 9 | import scala.collection.immutable 10 | import org.scalatest.matchers.must.Matchers 11 | import org.scalatest.wordspec.AnyWordSpec 12 | 13 | class TemplateUtilsSpec extends AnyWordSpec with Matchers { 14 | 15 | "Templates" should { 16 | "provide a HASH util" in { 17 | Hash("itShouldWork".getBytes, Nil) must be("31c0c4e0e142fe9b605fff44528fedb3dd8ae254") 18 | } 19 | 20 | "provide a Format API" when { 21 | "HTML for example" in { 22 | case class Html(_text: String) extends BufferedContent[Html](Nil, _text) { 23 | val contentType = "text/html" 24 | } 25 | 26 | object HtmlFormat extends Format[Html] { 27 | def raw(text: String) = Html(text) 28 | def escape(text: String) = Html(text.replace("<", "<")) 29 | def empty = Html("") 30 | def fill(elements: immutable.Seq[Html]) = Html("") 31 | } 32 | 33 | val html = HtmlFormat.raw("

    ").body + HtmlFormat.escape("Hello ").body + HtmlFormat.raw("

    ").body 34 | 35 | html must be("

    Hello <world>

    ") 36 | } 37 | 38 | "Text for example" in { 39 | case class Text(_text: String) extends BufferedContent[Text](Nil, _text) { 40 | val contentType = "text/plain" 41 | } 42 | 43 | object TextFormat extends Format[Text] { 44 | def raw(text: String) = Text(text) 45 | def escape(text: String) = Text(text) 46 | def empty = Text("") 47 | def fill(elements: immutable.Seq[Text]) = Text("") 48 | } 49 | 50 | val text = TextFormat.raw("

    ").body + TextFormat.escape("Hello ").body + TextFormat.raw("

    ").body 51 | 52 | text must be("

    Hello

    ") 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /api/shared/src/main/scala/play/twirl/api/Content.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.api 6 | 7 | import scala.collection.immutable 8 | 9 | /** 10 | * Generic type representing content to be sent over an HTTP response. 11 | */ 12 | trait Content { 13 | 14 | /** 15 | * The content String. 16 | */ 17 | def body: String 18 | 19 | /** 20 | * The default Content type to use for this content. 21 | */ 22 | def contentType: String 23 | } 24 | 25 | /** 26 | * Appendable content using a StringBuilder. Either specify elements or text, not both. 27 | * 28 | * Using an Either[TraversableOnce[A], String] impacts performance in an already contentious part of code, so it has 29 | * been done with both parameters instead. 30 | * 31 | * @param elements 32 | * Sub elements to traverse when creating the resultant string 33 | * @param text 34 | * Formatted content 35 | * @tparam A 36 | * self-type 37 | */ 38 | abstract class BufferedContent[A <: BufferedContent[A]]( 39 | protected val elements: immutable.Seq[A], 40 | protected val text: String 41 | ) extends Appendable[A] 42 | with Content { this: A => 43 | protected def buildString(builder: StringBuilder): Unit = { 44 | if (!elements.isEmpty) { 45 | elements.foreach { e => e.buildString(builder) } 46 | } else { 47 | builder.append(text) 48 | } 49 | } 50 | 51 | /** 52 | * This should only ever be called at the top level element to avoid unneeded memory allocation. 53 | */ 54 | private lazy val builtBody = { 55 | val builder = new StringBuilder() 56 | buildString(builder) 57 | builder.toString 58 | } 59 | 60 | override def toString = builtBody 61 | 62 | def body = builtBody 63 | 64 | override def equals(obj: Any): Boolean = 65 | obj match { 66 | case other: BufferedContent[?] if this.getClass == other.getClass => body == other.body 67 | case _ => false 68 | } 69 | 70 | override def hashCode(): Int = this.getClass.hashCode() + body.hashCode() 71 | } 72 | -------------------------------------------------------------------------------- /docs/manual/working/javaGuide/main/templates/JavaTemplatesDependencyInjection.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Dependency Injection with Templates 4 | 5 | Twirl templates can be generated as a class rather than a static object by declaring a constructor using a special @this(args) syntax at the top of the template. This means that Twirl templates can be injected into controllers directly and can manage their own dependencies, rather than the controller having to manage dependencies not only for itself, but also for the templates it has to render. 6 | 7 | As an example, suppose a template has a dependency on a component `Summarizer`, which is not used by the controller: 8 | 9 | ```java 10 | public interface Summarizer { 11 | /** Provide short form of string if over a certain length */ 12 | String summarize(String item); 13 | } 14 | ``` 15 | 16 | Create a file `app/views/IndexTemplate.scala.html` using the `@this` syntax for the constructor: 17 | 18 | ```scala 19 | @this(summarizer: Summarizer) 20 | @(item: String) 21 | 22 | @{summarizer.summarize(item)} 23 | ``` 24 | 25 | And finally define the controller in Play by injecting the template in the constructor: 26 | 27 | ```java 28 | public class MyController extends Controller { 29 | 30 | private final views.html.IndexTemplate template; 31 | 32 | @Inject 33 | public MyController(views.html.IndexTemplate template) { 34 | this.template = template; 35 | } 36 | 37 | public Result index() { 38 | String item = "some extremely long text"; 39 | return ok(template.render(item)); 40 | } 41 | 42 | } 43 | ``` 44 | 45 | Once the template is defined with its dependencies, then the controller can have the template injected into the controller, but the controller does not see `Summarizer`. 46 | 47 | If you are using Twirl outside of a Play application, you will have to manually add the `@Inject` annotation saying that dependency injection should be used here: 48 | 49 | ```scala 50 | TwirlKeys.constructorAnnotations += "@javax.inject.Inject()" 51 | ``` 52 | 53 | Inside a play application, this is already included in the default settings. -------------------------------------------------------------------------------- /compiler/src/test/resources/nestedTemplates.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @() 6 | @* a template can contain imports, localMembers, templates, mixeds *@ 7 | @import java.lang.String 8 | @localdef = @{"_defstr_"} 9 | 10 | @* let's define a sub template which itself also contains templates which then also contain templates. *@ 11 | @tmpl(title: String) = { 12 | @* a sub template is parsed like the top (page) template and can also contain imports, localMembers, templates, mixeds *@ 13 | @import java.lang.Boolean 14 | @tmpl_localdef = @{"_tmpl-defstr_"} 15 | @val tmpl_val = @{"_tmpl-valstr_" + java.util.UUID.randomUUID().toString() } 16 | @lazy val tmpl_lazy_val = @{"tmpl-lazy-valstr_" + java.util.UUID.randomUUID().toString() } 17 | @tmpl_inner1(good: Boolean) = { 18 | @import java.lang.Integer 19 | @tmpl_localdef 20 | @* just some comment in between *@ 21 | @localdef 22 | @if(good) { 23 | good 24 | } else { 25 | bad 26 | } 27 | text_in_between 28 | @tmpl_inner1_inner = { 29 | @import java.lang.Long 30 | _inner_inner_tmpl_ 31 | } 32 | @tmpl_inner1_localdef = @{ 33 | val somevar = "__tmplinner_defstr__" 34 | somevar 35 | } 36 | @tmpl_inner2_inner = { 37 | @tmpl_inner1_localdef 38 | } 39 | @tmpl_inner1_inner 40 | @tmpl_inner2_inner 41 | } 42 | @if(tmpl_val == tmpl_val) { _same_random_in_val_ } else { _different_random_in_val_ } 43 | @if(tmpl_lazy_val == tmpl_lazy_val) { _same_random_in_lazy_val_ } else { _different_random_in_lazy_val_ } 44 | @tmpl_inner2 = { @inner_def_samename=@{"_samedefname1_"} @inner_tmpl_samename={_samename1_@inner_def_samename} @inner_tmpl_samename } 45 | @tmpl_inner3 = { @inner_def_samename=@{"_samedefname2_"} @inner_tmpl_samename={_samename2_@inner_def_samename} @inner_tmpl_samename } 46 | @title@localdef@tmpl_inner1(true)@tmpl_inner2@tmpl_inner3 47 | } 48 | @tmpl("_first_") 49 | @tmpl("_second_") -------------------------------------------------------------------------------- /sbt-twirl/src/test/scala/play/twirl/sbt/test/TemplateMappingSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.sbt 6 | package test 7 | 8 | import org.scalatest.Inspectors 9 | import play.twirl.sbt.TemplateProblem.TemplateMapping 10 | import play.twirl.sbt.TemplateProblem.TemplateMapping.Location 11 | import org.scalatest.matchers.must.Matchers 12 | import org.scalatest.wordspec.AnyWordSpec 13 | 14 | class TemplateMappingSpec extends AnyWordSpec with Matchers with Inspectors { 15 | 16 | "TemplateMapping" should { 17 | "handle empty templates" in { 18 | val mapping = TemplateMapping(Seq.empty) 19 | 20 | mapping.location(offset = 0) mustBe None 21 | mapping.location(offset = 7) mustBe None 22 | 23 | mapping.location(line = 1, column = 0) mustBe None 24 | mapping.location(line = 7, column = 7) mustBe None 25 | } 26 | 27 | "map positions from offset or (line, column)" in { 28 | val mapping = TemplateMapping(Seq("ab", "c", "", "d")) 29 | 30 | val testLocations = Seq( 31 | Location(1, 0, 0, "ab"), 32 | Location(1, 2, 2, "ab"), 33 | Location(2, 1, 4, "c"), 34 | Location(3, 0, 5, ""), 35 | Location(4, 1, 7, "d") 36 | ) 37 | 38 | forAll(testLocations) { location => 39 | mapping.location(location.offset) mustBe Some(location) 40 | mapping.location(location.line, location.column) mustBe Some(location) 41 | } 42 | } 43 | 44 | "map invalid positions to nearest location" in { 45 | val mapping = TemplateMapping(Seq("ab", "c", "", "d")) 46 | 47 | val testOffsets = Seq( 48 | -1 -> Location(1, 0, 0, "ab"), 49 | 10 -> Location(4, 1, 7, "d") 50 | ) 51 | 52 | forAll(testOffsets) { case (offset, location) => 53 | mapping.location(offset) mustBe Some(location) 54 | } 55 | 56 | val testPositions = Seq( 57 | (0, 7) -> Location(1, 0, 0, "ab"), 58 | (1, -1) -> Location(1, 0, 0, "ab"), 59 | (1, 7) -> Location(1, 2, 2, "ab"), 60 | (3, 7) -> Location(3, 0, 5, ""), 61 | (4, 7) -> Location(4, 1, 7, "d"), 62 | (5, -1) -> Location(4, 1, 7, "d"), 63 | (5, 0) -> Location(4, 1, 7, "d") 64 | ) 65 | 66 | forAll(testPositions) { case ((line, column), location) => 67 | mapping.location(line, column) mustBe Some(location) 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /compiler/src/test/resources/vals.scala.html: -------------------------------------------------------------------------------- 1 | @**************************************************************************************************************************************************** 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. * 3 | ****************************************************************************************************************************************************@ 4 | 5 | @() 6 | @val counter = @{ new java.util.concurrent.atomic.AtomicInteger(0) } 7 | @lazy val lazycounter = @{ 8 | // will do nothing yet because we are lazy here 9 | counter.getAndIncrement() 10 | counter.getAndIncrement() 11 | counter.getAndIncrement() 12 | counter.getAndIncrement() 13 | new java.util.concurrent.atomic.AtomicInteger(0) 14 | } 15 | @{ 16 | "step1:" + counter.getAndIncrement() 17 | } 18 | @{ 19 | "_step2:" + lazycounter.getAndIncrement() 20 | } 21 | @{ 22 | "_step3:" + lazycounter.getAndIncrement() 23 | } 24 | @{ 25 | "_step4:" + counter.get() 26 | } 27 | @********************************************************************* 28 | * Same again but as def, which will always create new AtomicIntegers * 29 | **********************************************************************@ 30 | @defcounter = @{ new java.util.concurrent.atomic.AtomicInteger(0) } 31 | @defcounter2 = @{ 32 | // will do nothing yet because we are lazy here 33 | counter.getAndIncrement() 34 | counter.getAndIncrement() 35 | counter.getAndIncrement() 36 | counter.getAndIncrement() 37 | new java.util.concurrent.atomic.AtomicInteger(0) 38 | } 39 | @{ 40 | "_step5:" + defcounter.getAndIncrement() 41 | } 42 | @{ 43 | "_step6:" + defcounter2.getAndIncrement() 44 | } 45 | @{ 46 | "_step7:" + defcounter2.getAndIncrement() 47 | } 48 | @{ 49 | "_step8:" + defcounter.get() 50 | } 51 | @********************** 52 | * Same with templates * 53 | ***********************@ 54 | @val counter_tmpl = @{ new java.util.concurrent.atomic.AtomicInteger(0) } 55 | @lazy val lazycounter_tmpl = { 56 | @* we are in a (sub) template now *@ 57 | @{ 58 | // will do nothing yet because we are lazy here 59 | counter_tmpl.getAndIncrement() 60 | counter_tmpl.getAndIncrement() 61 | counter_tmpl.getAndIncrement() 62 | counter_tmpl.getAndIncrement() 63 | } 64 | @* empty *@ 65 | } 66 | @{ 67 | "_step9:" + counter_tmpl.getAndIncrement() 68 | } 69 | @{ 70 | "_step10:" + lazycounter_tmpl 71 | } 72 | @{ 73 | "_step11:" + lazycounter_tmpl 74 | } 75 | @{ 76 | "_step12:" + counter_tmpl.get() 77 | } 78 | -------------------------------------------------------------------------------- /api/shared/src/main/scala-2.13/play/twirl/api/BaseScalaTemplate.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.api 6 | 7 | import java.util.Optional 8 | import scala.collection.immutable 9 | import scala.jdk.CollectionConverters._ 10 | import scala.reflect.ClassTag 11 | 12 | // The exotic name $twirl__format is on purpose to avoid clashes with user defined vars in templates (#112) 13 | case class BaseScalaTemplate[T <: Appendable[T], F <: Format[T]]($twirl__format: F) { 14 | // The overloaded methods are here for speed. The compiled templates 15 | // can take advantage of them for a 12% performance boost 16 | def _display_(x: AnyVal): T = $twirl__format.escape(x.toString) 17 | def _display_(x: String): T = if (x eq null) $twirl__format.empty else $twirl__format.escape(x) 18 | def _display_(x: Unit): T = $twirl__format.empty 19 | def _display_(x: scala.xml.NodeSeq): T = if (x eq null) $twirl__format.empty else $twirl__format.raw(x.toString()) 20 | def _display_(x: T): T = if (x eq null) $twirl__format.empty else x 21 | 22 | def _display_(o: Any)(implicit m: ClassTag[T]): T = { 23 | o match { 24 | case escaped if escaped != null && m.runtimeClass.isInstance(escaped) => escaped.asInstanceOf[T] 25 | case () => $twirl__format.empty 26 | case None => $twirl__format.empty 27 | case Some(v) => _display_(v) 28 | case key: Optional[?] => 29 | (if (key.isPresent) Some(key.get) else None) match { 30 | case None => $twirl__format.empty 31 | case Some(v) => _display_(v) 32 | case _ => $twirl__format.empty 33 | } 34 | case xml: scala.xml.NodeSeq => $twirl__format.raw(xml.toString()) 35 | case escapeds: immutable.Seq[?] => $twirl__format.fill(escapeds.map(_display_)) 36 | case escapeds: IterableOnce[?] => $twirl__format.fill(escapeds.iterator.map(_display_).toList) 37 | case escapeds: Array[?] => $twirl__format.fill(escapeds.view.map(_display_).toList) 38 | case escapeds: java.util.List[?] => 39 | $twirl__format.fill(escapeds.asScala.map(_display_).toList) 40 | case string: String => $twirl__format.escape(string) 41 | case v if v != null => $twirl__format.escape(v.toString) 42 | case _ => $twirl__format.empty 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /api/shared/src/main/scala-3/play/twirl/api/BaseScalaTemplate.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.api 6 | 7 | import java.util.Optional 8 | import scala.collection.immutable 9 | import scala.jdk.CollectionConverters.* 10 | import scala.reflect.ClassTag 11 | 12 | // The exotic name $twirl__format is on purpose to avoid clashes with user defined vars in templates (#112) 13 | case class BaseScalaTemplate[T <: Appendable[T], F <: Format[T]]($twirl__format: F) { 14 | // The overloaded methods are here for speed. The compiled templates 15 | // can take advantage of them for a 12% performance boost 16 | def _display_(x: AnyVal): T = $twirl__format.escape(x.toString) 17 | def _display_(x: String): T = if x eq null then $twirl__format.empty else $twirl__format.escape(x) 18 | def _display_(x: Unit): T = $twirl__format.empty 19 | def _display_(x: scala.xml.NodeSeq): T = if x eq null then $twirl__format.empty else $twirl__format.raw(x.toString()) 20 | def _display_(x: T): T = if x eq null then $twirl__format.empty else x 21 | 22 | def _display_(o: Any)(implicit m: ClassTag[T]): T = { 23 | o match { 24 | case escaped if escaped != null && m.runtimeClass.isInstance(escaped) => escaped.asInstanceOf[T] 25 | case () => $twirl__format.empty 26 | case None => $twirl__format.empty 27 | case Some(v) => _display_(v) 28 | case key: Optional[?] => 29 | (if key.isPresent then Some(key.get) else None) match { 30 | case None => $twirl__format.empty 31 | case Some(v) => _display_(v) 32 | case null => $twirl__format.empty 33 | } 34 | case xml: scala.xml.NodeSeq => $twirl__format.raw(xml.toString()) 35 | case escapeds: immutable.Seq[?] => $twirl__format.fill(escapeds.map(_display_)) 36 | case escapeds: IterableOnce[?] => $twirl__format.fill(escapeds.iterator.map(_display_).toList) 37 | case escapeds: Array[?] => $twirl__format.fill(escapeds.view.map(_display_).toList) 38 | case escapeds: java.util.List[?] => 39 | $twirl__format.fill(escapeds.asScala.map(_display_).toList) 40 | case string: String => $twirl__format.escape(string) 41 | case v if v != null => $twirl__format.escape(v.toString) 42 | case null => $twirl__format.empty 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /compiler/src/test/java/play/japi/twirl/compiler/TwirlCompilerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.japi.twirl.compiler; 6 | 7 | import static org.junit.Assert.*; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.nio.file.FileVisitResult; 12 | import java.nio.file.Files; 13 | import java.nio.file.Path; 14 | import java.nio.file.SimpleFileVisitor; 15 | import java.nio.file.attribute.BasicFileAttributes; 16 | import java.util.ArrayList; 17 | import java.util.Optional; 18 | import org.junit.Test; 19 | 20 | public class TwirlCompilerTest { 21 | 22 | @Test 23 | public void compile() { 24 | File sourceDirectory = new File("compiler/src/test/resources"); 25 | File source = new File(sourceDirectory, "real.scala.html"); 26 | File generatedDirectory = new File("compiler/target/test/japi-compiler/generated-templates"); 27 | 28 | deleteRecursively(generatedDirectory); 29 | generatedDirectory.mkdirs(); 30 | 31 | Optional result = 32 | TwirlCompiler.compile( 33 | source, 34 | sourceDirectory, 35 | generatedDirectory, 36 | "play.twirl.api.HtmlFormat", 37 | TwirlCompiler.DEFAULT_IMPORTS, 38 | new ArrayList()); 39 | assertTrue(result.isPresent()); 40 | 41 | Optional recompilationResult = 42 | TwirlCompiler.compile( 43 | source, 44 | sourceDirectory, 45 | generatedDirectory, 46 | "play.twirl.api.HtmlFormat", 47 | TwirlCompiler.DEFAULT_IMPORTS, 48 | new ArrayList()); 49 | assertFalse(recompilationResult.isPresent()); 50 | } 51 | 52 | private static void deleteRecursively(File directory) { 53 | if (!directory.exists()) { 54 | return; 55 | } 56 | Path path = directory.toPath(); 57 | try { 58 | Files.walkFileTree( 59 | path, 60 | new SimpleFileVisitor() { 61 | @Override 62 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 63 | throws IOException { 64 | Files.delete(file); 65 | return FileVisitResult.CONTINUE; 66 | } 67 | 68 | @Override 69 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) 70 | throws IOException { 71 | Files.delete(dir); 72 | return FileVisitResult.CONTINUE; 73 | } 74 | }); 75 | } catch (IOException e) { 76 | throw new RuntimeException(e); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /api/shared/src/main/scala-2.12/play/twirl/api/BaseScalaTemplate.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.api 6 | 7 | import java.util.Optional 8 | import scala.collection.immutable 9 | import scala.collection.JavaConverters 10 | import scala.reflect.ClassTag 11 | 12 | // The exotic name $twirl__format is on purpose to avoid clashes with user defined vars in templates (#112) 13 | case class BaseScalaTemplate[T <: Appendable[T], F <: Format[T]]($twirl__format: F) { 14 | // The overloaded methods are here for speed. The compiled templates 15 | // can take advantage of them for a 12% performance boost 16 | def _display_(x: AnyVal): T = $twirl__format.escape(x.toString) 17 | def _display_(x: String): T = if (x eq null) $twirl__format.empty else $twirl__format.escape(x) 18 | def _display_(x: Unit): T = $twirl__format.empty 19 | def _display_(x: scala.xml.NodeSeq): T = if (x eq null) $twirl__format.empty else $twirl__format.raw(x.toString()) 20 | def _display_(x: T): T = if (x eq null) $twirl__format.empty else x 21 | 22 | def _display_(o: Any)(implicit m: ClassTag[T]): T = { 23 | o match { 24 | case escaped if escaped != null && m.runtimeClass.isInstance(escaped) => escaped.asInstanceOf[T] 25 | case () => $twirl__format.empty 26 | case None => $twirl__format.empty 27 | case Some(v) => _display_(v) 28 | case key: Optional[?] => 29 | (if (key.isPresent) Some(key.get) else None) match { 30 | case None => $twirl__format.empty 31 | case Some(v) => _display_(v) 32 | case _ => $twirl__format.empty 33 | } 34 | case xml: scala.xml.NodeSeq => $twirl__format.raw(xml.toString()) 35 | case escapeds: immutable.Seq[?] => $twirl__format.fill(escapeds.map(_display_)) 36 | case escapeds: TraversableOnce[?] => $twirl__format.fill(escapeds.map(_display_).toList) 37 | case escapeds: Array[?] => $twirl__format.fill(escapeds.view.map(_display_).toList) 38 | case escapeds: java.util.List[?] => 39 | $twirl__format.fill(JavaConverters.collectionAsScalaIterableConverter(escapeds).asScala.map(_display_).toList) 40 | case string: String => $twirl__format.escape(string) 41 | case v if v != null => $twirl__format.escape(v.toString) 42 | case _ => $twirl__format.empty 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /api/shared/src/test/scala/play/twirl/api/test/BufferedContentSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.api.test 6 | 7 | import play.twirl.api._ 8 | import scala.collection.immutable 9 | import org.scalatest.matchers.must.Matchers 10 | import org.scalatest.wordspec.AnyWordSpec 11 | 12 | class BufferedContentSpec extends AnyWordSpec with Matchers { 13 | 14 | "equality checking" should { 15 | "return false for BufferedContents with the same body but different implementations" in { 16 | Html("hello") must not be Xml("hello") 17 | Html("hello") must not be Txt("hello") 18 | Xml("hello") must not be Txt("hello") 19 | } 20 | 21 | "return false for BufferedContents with different bodies but the same implementations" in { 22 | HtmlFormat.fill(immutable.Seq(Html("foo"), Html("bar"))) must not be HtmlFormat.fill( 23 | immutable.Seq(Html("fizz"), Html("buzz")) 24 | ) 25 | HtmlFormat.fill(immutable.Seq(Html("foo"), Html("bar"))) must not be Html("fizzbuzz") 26 | XmlFormat.fill(immutable.Seq(Xml("foo"), Xml("bar"))) must not be XmlFormat.fill( 27 | immutable.Seq(Xml("fizz"), Xml("buzz")) 28 | ) 29 | XmlFormat.fill(immutable.Seq(Xml("foo"), Xml("bar"))) must not be Xml("fizzbuzz") 30 | TxtFormat.fill(immutable.Seq(Txt("foo"), Txt("bar"))) must not be TxtFormat.fill( 31 | immutable.Seq(Txt("fizz"), Txt("buzz")) 32 | ) 33 | TxtFormat.fill(immutable.Seq(Txt("foo"), Txt("bar"))) must not be Txt("fizzbuzz") 34 | Html("hello") must not be Html("boom") 35 | Txt("hello") must not be Txt("boom") 36 | Xml("hello") must not be Xml("boom") 37 | } 38 | 39 | "return true for BufferedContents with the same body and the same implementation" in { 40 | HtmlFormat.fill(immutable.Seq(Html("foo"), Html("bar"))) mustEqual HtmlFormat.fill( 41 | immutable.Seq(Html("foo"), Html("bar")) 42 | ) 43 | HtmlFormat.fill(immutable.Seq(Html("foo"), Html("bar"))) mustEqual Html("foobar") 44 | XmlFormat.fill(immutable.Seq(Xml("foo"), Xml("bar"))) mustEqual XmlFormat.fill( 45 | immutable.Seq(Xml("foo"), Xml("bar")) 46 | ) 47 | XmlFormat.fill(immutable.Seq(Xml("foo"), Xml("bar"))) mustEqual Xml("foobar") 48 | TxtFormat.fill(immutable.Seq(Txt("foo"), Txt("bar"))) mustEqual TxtFormat.fill( 49 | immutable.Seq(Txt("foo"), Txt("bar")) 50 | ) 51 | TxtFormat.fill(immutable.Seq(Txt("foo"), Txt("bar"))) mustEqual Txt("foobar") 52 | Html("hello") mustEqual Html("hello") 53 | Txt("hello") mustEqual Txt("hello") 54 | Xml("hello") mustEqual Xml("hello") 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /project/Omnidoc.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | import sbt.Keys._ 6 | 7 | import sbt.Package.ManifestAttributes 8 | import sbt._ 9 | 10 | /** 11 | * Copied in from https://github.com/playframework/interplay/blob/main/src/main/scala/interplay/Omnidoc.scala 12 | * 13 | * This AutoPlugin adds the `Omnidoc-Source-URL` key on the MANIFEST.MF of artifact-sources.jar so later Omnidoc can use 14 | * that value to link scaladocs to GitHub sources. 15 | */ 16 | object Omnidoc extends AutoPlugin { 17 | 18 | object autoImport { 19 | // lazy val omnidocGithubRepo = settingKey[String]("Github repository for source URL") 20 | lazy val omnidocSnapshotBranch = settingKey[String]("Git branch for development versions") 21 | // lazy val omnidocTagPrefix = settingKey[String]("Prefix before git tagged versions") 22 | lazy val omnidocPathPrefix = settingKey[String]("Prefix before source directory paths") 23 | lazy val omnidocSourceUrl = settingKey[Option[String]]("Source URL for scaladoc linking") 24 | } 25 | 26 | val omnidocGithubRepo = Some(s"playframework/${Common.repoName}") 27 | val omnidocTagPrefix = Some("") 28 | 29 | val SourceUrlKey = "Omnidoc-Source-URL" 30 | 31 | override def requires = sbt.plugins.JvmPlugin 32 | 33 | override def trigger = noTrigger 34 | 35 | import autoImport._ 36 | 37 | override def projectSettings = 38 | Seq( 39 | omnidocSourceUrl := omnidocGithubRepo.map { repo => 40 | val development: String = (omnidocSnapshotBranch ?? "main").value 41 | val tagged: String = omnidocTagPrefix.getOrElse("v") + version.value 42 | val tree: String = if (isSnapshot.value) development else tagged 43 | val prefix: String = "/" + (omnidocPathPrefix ?? "").value 44 | val path: String = { 45 | val buildDir: File = (ThisBuild / baseDirectory).value 46 | val projDir: File = baseDirectory.value 47 | val rel: Option[String] = IO.relativize(buildDir, projDir) 48 | rel match { 49 | case None if buildDir == projDir => "" // Same dir (sbt 0.13) 50 | case Some("") => "" // Same dir (sbt 1.0) 51 | case Some(childDir) => prefix + childDir // Child dir 52 | case None => "" // Disjoint dirs (Rich: I'm not sure if this can happen) 53 | } 54 | } 55 | s"https://github.com/${repo}/tree/${tree}${path}" 56 | }, 57 | (Compile / packageSrc / packageOptions) ++= omnidocSourceUrl.value.toSeq.map { url => 58 | ManifestAttributes(SourceUrlKey -> url) 59 | } 60 | ) 61 | 62 | } 63 | -------------------------------------------------------------------------------- /docs/build.sbt: -------------------------------------------------------------------------------- 1 | // Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 2 | 3 | import sbtheader.HeaderPlugin.autoImport.HeaderPattern.commentBetween 4 | import sbtheader.CommentStyle 5 | import sbtheader.FileType 6 | import sbtheader.LineCommentCreator 7 | 8 | lazy val docs = project 9 | .in(file(".")) 10 | .enablePlugins(PlayDocsPlugin) 11 | .configs(Configuration.of("Docs", "docs")) 12 | .settings( 13 | scalaVersion := "2.13.18", 14 | // use special snapshot play version for now 15 | resolvers ++= DefaultOptions.resolvers(snapshot = true), 16 | libraryDependencies += component("play-test") % "test", 17 | libraryDependencies += component("play-specs2") % "test", 18 | PlayDocsKeys.javaManualSourceDirectories := (baseDirectory.value / "manual" / "working" / "javaGuide" ** "code").get, 19 | PlayDocsKeys.scalaManualSourceDirectories := (baseDirectory.value / "manual" / "working" / "scalaGuide" ** "code").get, 20 | headerLicense := { 21 | Some( 22 | HeaderLicense.Custom( 23 | s"Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. " 24 | ) 25 | ) 26 | }, 27 | headerMappings ++= Map( 28 | FileType("sbt") -> HeaderCommentStyle.cppStyleLineComment, 29 | FileType("properties") -> HeaderCommentStyle.hashLineComment, 30 | FileType("md") -> CommentStyle(new LineCommentCreator(""), commentBetween("")), 31 | ), 32 | (Compile / headerSources) ++= 33 | ((baseDirectory.value ** ("*.properties" || "*.sbt" || "*.md" || "*.scala")) --- (baseDirectory.value ** "target" ** "*")).get 34 | ) 35 | .settings(overrideTwirlSettings: _*) 36 | .dependsOn(twirlApi) 37 | 38 | // The changes in Twirl imports cause a problem with the PlayDocsPlugin, which defines its own twirl compile tasks 39 | // and doesn't use the default imports provided by Twirl but defines its own by scratch, and since the defaults 40 | // have changed, this breaks. So, first we need to set all source generators in test to Nil, then we can redefine the 41 | // twirl settings. 42 | def overrideTwirlSettings: Seq[Setting[?]] = 43 | Seq( 44 | Test / sourceGenerators := Nil 45 | ) ++ inConfig(Test)(SbtTwirl.twirlSettings) ++ SbtTwirl.defaultSettings ++ SbtTwirl.positionSettings ++ Seq( 46 | Test / TwirlKeys.compileTemplates / sourceDirectories ++= 47 | (PlayDocsKeys.javaManualSourceDirectories.value ++ PlayDocsKeys.scalaManualSourceDirectories.value) 48 | ) 49 | 50 | // the twirl plugin automatically adds this dependency, but this overrides it so 51 | // it can be an interproject dependency, rather than requiring it to be published 52 | // first 53 | lazy val twirlApi = ProjectRef(Path.fileProperty("user.dir").getParentFile, "apiJVM") 54 | -------------------------------------------------------------------------------- /gradle-twirl/src/main/java/play/twirl/gradle/internal/DefaultTwirlSourceDirectorySet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | package play.twirl.gradle.internal; 5 | 6 | import static java.nio.charset.StandardCharsets.UTF_8; 7 | 8 | import javax.inject.Inject; 9 | import org.gradle.api.file.SourceDirectorySet; 10 | import org.gradle.api.internal.file.DefaultSourceDirectorySet; 11 | import org.gradle.api.internal.tasks.TaskDependencyFactory; 12 | import org.gradle.api.model.ObjectFactory; 13 | import org.gradle.api.provider.ListProperty; 14 | import org.gradle.api.provider.MapProperty; 15 | import org.gradle.api.provider.Property; 16 | import org.gradle.api.provider.SetProperty; 17 | import play.twirl.gradle.TwirlSourceDirectorySet; 18 | 19 | /** Default implementation of {@link TwirlSourceDirectorySet}. */ 20 | public class DefaultTwirlSourceDirectorySet extends DefaultSourceDirectorySet 21 | implements TwirlSourceDirectorySet { 22 | 23 | private final MapProperty templateFormats; 24 | 25 | private final SetProperty templateImports; 26 | 27 | private final ListProperty constructorAnnotations; 28 | 29 | private final Property sourceEncoding; 30 | 31 | @Inject 32 | public DefaultTwirlSourceDirectorySet( 33 | SourceDirectorySet sourceDirectorySet, 34 | TaskDependencyFactory taskDependencyFactory, 35 | ObjectFactory objectFactory) { 36 | super(sourceDirectorySet, taskDependencyFactory); // Gradle 8+ 37 | this.templateFormats = objectFactory.mapProperty(String.class, String.class); 38 | this.templateImports = objectFactory.setProperty(String.class); 39 | this.constructorAnnotations = objectFactory.listProperty(String.class); 40 | this.sourceEncoding = objectFactory.property(String.class).convention(UTF_8.name()); 41 | } 42 | 43 | /** 44 | * @deprecated Constructor for support Gradle 7.x 45 | */ 46 | @Deprecated 47 | public DefaultTwirlSourceDirectorySet( 48 | SourceDirectorySet sourceDirectorySet, ObjectFactory objectFactory) { 49 | super(sourceDirectorySet); 50 | this.templateFormats = objectFactory.mapProperty(String.class, String.class); 51 | this.templateImports = objectFactory.setProperty(String.class); 52 | this.constructorAnnotations = objectFactory.listProperty(String.class); 53 | this.sourceEncoding = objectFactory.property(String.class).convention(UTF_8.name()); 54 | } 55 | 56 | @Override 57 | public MapProperty getTemplateFormats() { 58 | return templateFormats; 59 | } 60 | 61 | @Override 62 | public SetProperty getTemplateImports() { 63 | return templateImports; 64 | } 65 | 66 | @Override 67 | public ListProperty getConstructorAnnotations() { 68 | return constructorAnnotations; 69 | } 70 | 71 | @Override 72 | public Property getSourceEncoding() { 73 | return sourceEncoding; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /sbt-twirl/src/test/scala/play/twirl/sbt/test/TemplatePositionSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.sbt.test 6 | 7 | import java.io.File 8 | import play.twirl.sbt.TemplateProblem.TemplateMapping 9 | import play.twirl.sbt.TemplateProblem.TemplatePosition 10 | import org.scalatest.Inspectors 11 | import org.scalatest.matchers.must.Matchers 12 | import org.scalatest.wordspec.AnyWordSpec 13 | 14 | class TemplatePositionSpec extends AnyWordSpec with Matchers with Inspectors { 15 | 16 | "TemplatePosition" should { 17 | "toString" should { 18 | "have the source path" in { 19 | val file = new File("/some/path/file.scala.html") 20 | val location = TemplateMapping.Location(line = 10, column = 2, offset = 22, content = "some content") 21 | val tp = new TemplatePosition(Option(file), Option(location)) 22 | 23 | tp.toString mustBe "/some/path/file.scala.html:10\nsome content" 24 | } 25 | 26 | "not have the source path when it is empty" in { 27 | val location = TemplateMapping.Location(line = 10, column = 2, offset = 22, content = "some content") 28 | val tp = new TemplatePosition(None, Option(location)) 29 | 30 | tp.toString mustNot include("/some/path/file.scala.html") 31 | } 32 | 33 | "have line if present" in { 34 | val file = new File("/some/path/file.scala.html") 35 | val location = TemplateMapping.Location(line = 10, column = 2, offset = 22, content = "some content") 36 | val tp = new TemplatePosition(Option(file), Option(location)) 37 | 38 | tp.toString must include("10") 39 | } 40 | 41 | "not have line when it is empty" in { 42 | val file = new File("/some/path/file.scala.html") 43 | val tp = new TemplatePosition(Option(file), None /* means no location for the error, then no line */ ) 44 | 45 | tp.toString mustBe "/some/path/file.scala.html" 46 | } 47 | 48 | "have line content if present" in { 49 | val file = new File("/some/path/file.scala.html") 50 | val location = TemplateMapping.Location(line = 10, column = 2, offset = 22, content = "some content") 51 | val tp = new TemplatePosition(Option(file), Option(location)) 52 | 53 | tp.toString must include("some content") 54 | } 55 | 56 | "not have line content when it is missing" in { 57 | val file = new File("/some/path/file.scala.html") 58 | val tp = new TemplatePosition(Option(file), None /* means no location for the error, then no offset */ ) 59 | 60 | tp.toString mustBe "/some/path/file.scala.html" 61 | } 62 | 63 | "not have line content when it is empty" in { 64 | val file = new File("/some/path/file.scala.html") 65 | val location = TemplateMapping.Location(line = 10, column = 2, offset = 22, content = "") 66 | val tp = new TemplatePosition(Option(file), Option(location)) 67 | 68 | tp.toString mustBe "/some/path/file.scala.html:10" 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /gradle-twirl/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | 74 | 75 | @rem Execute Gradle 76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 77 | 78 | :end 79 | @rem End local scope for the variables with windows NT shell 80 | if %ERRORLEVEL% equ 0 goto mainEnd 81 | 82 | :fail 83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 84 | rem the _cmd.exe /c_ return code! 85 | set EXIT_CODE=%ERRORLEVEL% 86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 88 | exit /b %EXIT_CODE% 89 | 90 | :mainEnd 91 | if "%OS%"=="Windows_NT" endlocal 92 | 93 | :omega 94 | -------------------------------------------------------------------------------- /compiler/src/main/java/play/japi/twirl/compiler/TwirlCompiler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.japi.twirl.compiler; 6 | 7 | import java.io.File; 8 | import java.nio.charset.Charset; 9 | import java.util.ArrayList; 10 | import java.util.Collection; 11 | import java.util.List; 12 | import java.util.Optional; 13 | import java.util.Set; 14 | import scala.Option; 15 | import scala.collection.JavaConverters$; 16 | import scala.collection.Seq; 17 | import scala.io.Codec; 18 | import scala.util.Properties$; 19 | 20 | public class TwirlCompiler { 21 | 22 | public static final Set DEFAULT_IMPORTS; 23 | 24 | static { 25 | String scalaVersion = play.twirl.compiler.BuildInfo$.MODULE$.scalaVersion(); 26 | DEFAULT_IMPORTS = 27 | Set.copyOf( 28 | toJavaList(play.twirl.compiler.TwirlCompiler$.MODULE$.defaultImports(scalaVersion))); 29 | } 30 | 31 | public static Optional compile( 32 | File source, 33 | File sourceDirectory, 34 | File generatedDirectory, 35 | String formatterType, 36 | Collection additionalImports, 37 | List constructorAnnotations) { 38 | Charset sourceEncoding = Charset.forName(Properties$.MODULE$.sourceEncoding()); 39 | return compile( 40 | source, 41 | sourceDirectory, 42 | generatedDirectory, 43 | formatterType, 44 | additionalImports, 45 | constructorAnnotations, 46 | new Codec(sourceEncoding), 47 | false); 48 | } 49 | 50 | public static Optional compile( 51 | File source, 52 | File sourceDirectory, 53 | File generatedDirectory, 54 | String formatterType, 55 | Collection additionalImports, 56 | List constructorAnnotations, 57 | Codec codec, 58 | boolean inclusiveDot) { 59 | String scalaVersion = play.twirl.compiler.BuildInfo$.MODULE$.scalaVersion(); 60 | Seq scalaAdditionalImports = toScalaSeq(additionalImports); 61 | Seq scalaConstructorAnnotations = toScalaSeq(constructorAnnotations); 62 | 63 | Option option = 64 | play.twirl.compiler.TwirlCompiler.compile( 65 | source, 66 | sourceDirectory, 67 | generatedDirectory, 68 | formatterType, 69 | scala.Option.apply(scalaVersion), 70 | scalaAdditionalImports, 71 | scalaConstructorAnnotations, 72 | codec, 73 | inclusiveDot); 74 | return Optional.ofNullable(option.nonEmpty() ? option.get() : null); 75 | } 76 | 77 | public static Collection formatImports( 78 | Collection templateImports, String extension) { 79 | return toJavaList( 80 | play.twirl.compiler.TwirlCompiler.formatImports( 81 | toScalaSeq(templateImports).toSeq(), extension)); 82 | } 83 | 84 | private static Seq toScalaSeq(Collection collection) { 85 | return JavaConverters$.MODULE$.asScalaBufferConverter(new ArrayList<>(collection)).asScala(); 86 | } 87 | 88 | private static List toJavaList(Seq seq) { 89 | return JavaConverters$.MODULE$.seqAsJavaListConverter(seq).asJava(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /parser/src/main/scala/play/twirl/parser/TwirlIO.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.parser 6 | 7 | import java.io._ 8 | import scala.io.Codec 9 | import java.net.URL 10 | 11 | /** 12 | * IO utilites for Twirl. 13 | * 14 | * This is intentionally not public API. 15 | */ 16 | private[twirl] object TwirlIO { 17 | val defaultEncoding = scala.util.Properties.sourceEncoding 18 | 19 | val defaultCodec = Codec(defaultEncoding) 20 | 21 | /** 22 | * Read the given stream into a byte array. 23 | * 24 | * Does not close the stream. 25 | */ 26 | def readStream(stream: InputStream): Array[Byte] = { 27 | val buffer = new Array[Byte](8192) 28 | var len = stream.read(buffer) 29 | val out = new ByteArrayOutputStream() 30 | while (len != -1) { 31 | out.write(buffer, 0, len) 32 | len = stream.read(buffer) 33 | } 34 | out.toByteArray 35 | } 36 | 37 | /** 38 | * Read the file as a String. 39 | */ 40 | def readFile(file: File): Array[Byte] = { 41 | val is = new FileInputStream(file) 42 | try { 43 | readStream(is) 44 | } finally { 45 | closeQuietly(is) 46 | } 47 | } 48 | 49 | /** 50 | * Read the given stream into a String. 51 | * 52 | * Does not close the stream. 53 | */ 54 | def readStreamAsString(stream: InputStream, codec: Codec = defaultCodec): String = { 55 | new String(readStream(stream), codec.name) 56 | } 57 | 58 | /** 59 | * Read the URL as a String. 60 | */ 61 | def readUrlAsString(url: URL, codec: Codec = defaultCodec): String = { 62 | val is = url.openStream() 63 | try { 64 | readStreamAsString(is, codec) 65 | } finally { 66 | closeQuietly(is) 67 | } 68 | } 69 | 70 | /** 71 | * Read the file as a String. 72 | */ 73 | def readFileAsString(file: File, codec: Codec = defaultCodec): String = { 74 | val is = new FileInputStream(file) 75 | try { 76 | readStreamAsString(is, codec) 77 | } finally { 78 | closeQuietly(is) 79 | } 80 | } 81 | 82 | /** 83 | * Write the given String to a file 84 | */ 85 | def writeStringToFile(file: File, contents: String, codec: Codec = defaultCodec) = { 86 | if (!file.getParentFile.exists) { 87 | file.getParentFile.mkdirs() 88 | } 89 | val writer = new OutputStreamWriter(new FileOutputStream(file), codec.name) 90 | try { 91 | writer.write(contents) 92 | } finally { 93 | closeQuietly(writer) 94 | } 95 | } 96 | 97 | /** 98 | * Close the given closeable quietly. 99 | * 100 | * Ignores any IOExceptions encountered. 101 | */ 102 | def closeQuietly(closeable: Closeable) = { 103 | try { 104 | if (closeable != null) { 105 | closeable.close() 106 | } 107 | } catch { 108 | case e: IOException => // Ignore 109 | } 110 | } 111 | 112 | /** 113 | * Delete the given directory recursively. 114 | */ 115 | def deleteRecursively(dir: File): Unit = { 116 | if (dir.isDirectory) { 117 | dir.listFiles().foreach(deleteRecursively) 118 | } 119 | dir.delete() 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /parser/src/main/scala/play/twirl/parser/TreeNodes.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.parser 6 | 7 | import scala.util.parsing.input.Positional 8 | 9 | object TreeNodes { 10 | abstract class TemplateTree 11 | abstract class ScalaExpPart 12 | 13 | trait LocalMember extends Positional { 14 | val name: PosString 15 | val resultType: Option[PosString] 16 | val code: Simple 17 | } 18 | trait BaseTemplate extends Positional { 19 | val imports: collection.Seq[Simple] 20 | val members: collection.Seq[LocalMember] 21 | val sub: collection.Seq[SubTemplate] 22 | val content: collection.Seq[TemplateTree] 23 | } 24 | 25 | case class Constructor(comment: Option[Comment], params: PosString) 26 | case class Template( 27 | constructor: Option[Constructor], 28 | comment: Option[Comment], 29 | params: PosString, 30 | topImports: collection.Seq[Simple], 31 | imports: collection.Seq[Simple], 32 | members: collection.Seq[LocalMember], 33 | sub: collection.Seq[SubTemplate], 34 | content: collection.Seq[TemplateTree] 35 | ) extends BaseTemplate 36 | case class SubTemplate( 37 | declaration: Either[ 38 | Boolean, // left: true->var, false->def 39 | Boolean // right: it is a val. true->lazy, false->eager 40 | ], 41 | name: PosString, 42 | params: PosString, 43 | imports: collection.Seq[Simple], 44 | members: collection.Seq[LocalMember], 45 | sub: collection.Seq[SubTemplate], 46 | content: collection.Seq[TemplateTree] 47 | ) extends BaseTemplate 48 | case class BlockTemplate( 49 | imports: collection.Seq[Simple], 50 | members: collection.Seq[LocalMember], 51 | sub: collection.Seq[SubTemplate], 52 | content: collection.Seq[TemplateTree], 53 | ) extends BaseTemplate { 54 | 55 | /** 56 | * If this template contains more than just basic content: imports, vals, defs, sub-templates,... 57 | */ 58 | def rich() = !imports.isEmpty || !members.isEmpty || !sub.isEmpty 59 | } 60 | case class PosString(str: String) extends Positional { 61 | override def toString: String = str 62 | } 63 | case class Def(name: PosString, params: PosString, resultType: Option[PosString], code: Simple) extends LocalMember 64 | case class Val(name: PosString, isLazy: Boolean, resultType: Option[PosString], code: Simple) extends LocalMember 65 | case class Var(name: PosString, resultType: Option[PosString], code: Simple) extends LocalMember 66 | case class Plain(text: String) extends TemplateTree with Positional 67 | case class Display(exp: ScalaExp) extends TemplateTree with Positional 68 | case class Comment(msg: String) extends TemplateTree with Positional 69 | case class ScalaExp(parts: collection.Seq[ScalaExpPart]) extends TemplateTree with Positional 70 | case class Reassignment(ref: Either[SubTemplate, Var]) extends TemplateTree with Positional 71 | case class Simple(code: String) extends ScalaExpPart with Positional 72 | case class Block(whitespace: String, args: Option[PosString], contents: BlockTemplate) 73 | extends ScalaExpPart 74 | with Positional 75 | } 76 | -------------------------------------------------------------------------------- /maven-twirl/src/maven-test/simple/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | play.twirl.example 8 | simple 9 | 1.0.0-SNAPSHOT 10 | 11 | 12 | 17 13 | ${java.version} 14 | ${java.version} 15 | 16 | 17 | 18 | 19 | org.playframework.twirl 20 | twirl-api_${scala.binaryVersion} 21 | ${plugin.version} 22 | 23 | 24 | org.assertj 25 | assertj-core 26 | 3.24.2 27 | test 28 | 29 | 30 | org.junit.jupiter 31 | junit-jupiter-api 32 | 5.9.2 33 | test 34 | 35 | 36 | 37 | 38 | 39 | 40 | org.playframework.twirl 41 | twirl-maven-plugin_${scala.binaryVersion} 42 | ${plugin.version} 43 | 44 | 45 | java.lang._ 46 | a.b.%format%._ 47 | 48 | 49 | @java.lang.Deprecated() 50 | 51 | 52 | 53 | 54 | 55 | compile 56 | testCompile 57 | 58 | 59 | 60 | templates-compile 61 | 62 | compile 63 | 64 | 65 | ${project.basedir}/src/main/templates 66 | 67 | 68 | 69 | 70 | 71 | net.alchim31.maven 72 | scala-maven-plugin 73 | 4.8.1 74 | 75 | 76 | 77 | compile 78 | testCompile 79 | 80 | 81 | 82 | 83 | 84 | com.diffplug.spotless 85 | spotless-maven-plugin 86 | 2.39.0 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | check 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /sbt-twirl/src/main/scala/play/twirl/sbt/TemplateCompiler.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.sbt 6 | 7 | import sbt._ 8 | import play.twirl.compiler._ 9 | import scala.io.Codec 10 | import sbt.internal.inc.LoggedReporter 11 | 12 | object TemplateCompiler { 13 | 14 | def compile( 15 | sourceDirectories: Seq[File], 16 | targetDirectory: File, 17 | templateFormats: Map[String, String], 18 | templateImports: Seq[String], 19 | constructorAnnotations: Seq[String], 20 | includeFilter: FileFilter, 21 | excludeFilter: FileFilter, 22 | codec: Codec, 23 | log: Logger 24 | ): Seq[File] = compile( 25 | sourceDirectories, 26 | targetDirectory, 27 | templateFormats, 28 | templateImports, 29 | constructorAnnotations, 30 | includeFilter, 31 | excludeFilter, 32 | codec, 33 | log, 34 | "2.13.x" // using a dummy scala version (not starting with "3." to generate Scala 2 code by default) 35 | ) 36 | 37 | def compile( 38 | sourceDirectories: Seq[File], 39 | targetDirectory: File, 40 | templateFormats: Map[String, String], 41 | templateImports: Seq[String], 42 | constructorAnnotations: Seq[String], 43 | includeFilter: FileFilter, 44 | excludeFilter: FileFilter, 45 | codec: Codec, 46 | log: Logger, 47 | scalaVersion: String 48 | ): Seq[File] = { 49 | try { 50 | syncGenerated(targetDirectory, codec) 51 | val templates = collectTemplates(sourceDirectories, templateFormats, includeFilter, excludeFilter) 52 | for ((template, sourceDirectory, extension, format) <- templates) { 53 | val imports = TwirlCompiler.formatImports(templateImports, extension) 54 | TwirlCompiler.compile( 55 | template, 56 | sourceDirectory, 57 | targetDirectory, 58 | format, 59 | Some(scalaVersion), 60 | imports, 61 | constructorAnnotations, 62 | codec, 63 | inclusiveDot = false 64 | ) 65 | } 66 | generatedFiles(targetDirectory).map(_.getAbsoluteFile) 67 | } catch handleError(log, codec) 68 | } 69 | 70 | private def handleError(log: Logger, codec: Codec): PartialFunction[Throwable, Nothing] = { 71 | case TemplateCompilationError(source, message, line, column) => 72 | val exception = TemplateProblem.exception(source, codec, message, line, column) 73 | val reporter = new LoggedReporter(10, log) 74 | exception.problems.foreach { p => reporter.log(p) } 75 | throw exception 76 | case e => throw e 77 | } 78 | 79 | def generatedFiles(targetDirectory: File): Seq[File] = { 80 | (targetDirectory ** "*.template.scala").get() 81 | } 82 | 83 | def syncGenerated(targetDirectory: File, codec: Codec): Unit = { 84 | generatedFiles(targetDirectory).map(GeneratedSource(_, codec)).foreach(_.sync()) 85 | } 86 | 87 | def collectTemplates( 88 | sourceDirectories: Seq[File], 89 | templateFormats: Map[String, String], 90 | includeFilter: FileFilter, 91 | excludeFilter: FileFilter 92 | ): Seq[(File, File, String, String)] = { 93 | sourceDirectories.flatMap { sourceDirectory => 94 | (sourceDirectory ** includeFilter).get().flatMap { file => 95 | val ext = file.name.split('.').last 96 | if (!excludeFilter.accept(file) && templateFormats.contains(ext)) 97 | Some((file, sourceDirectory, ext, templateFormats(ext))) 98 | else 99 | None 100 | } 101 | } 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /gradle-twirl/src/test/java/play/twirl/gradle/TwirlPluginTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | package play.twirl.gradle; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | import static play.twirl.gradle.TwirlPlugin.javaPluginExtension; 8 | 9 | import java.nio.charset.StandardCharsets; 10 | import org.gradle.api.Project; 11 | import org.gradle.api.artifacts.Configuration; 12 | import org.gradle.api.internal.artifacts.configurations.DefaultConfiguration; 13 | import org.gradle.api.tasks.SourceSet; 14 | import org.gradle.testfixtures.ProjectBuilder; 15 | import org.junit.jupiter.api.BeforeEach; 16 | import org.junit.jupiter.api.DisplayName; 17 | import org.junit.jupiter.api.Test; 18 | 19 | /** A simple unit test to check a Twirl Gradle Plugin */ 20 | class TwirlPluginTest { 21 | 22 | private Project project; 23 | 24 | @BeforeEach 25 | void init() { 26 | project = ProjectBuilder.builder().build(); 27 | project.getPluginManager().apply("application"); 28 | project.getPluginManager().apply("org.playframework.twirl"); 29 | } 30 | 31 | @Test 32 | @DisplayName("Twirl extension should be registered") 33 | void extensionShouldBeRegistered() { 34 | TwirlExtension ext = (TwirlExtension) project.getExtensions().findByName("twirl"); 35 | assertThat(ext).isNotNull(); 36 | assertThat((ext).getScalaVersion().getOrNull()).isEqualTo("2.13"); 37 | } 38 | 39 | @Test 40 | @DisplayName("Twirl configuration should be registered") 41 | void configurationShouldBeRegistered() { 42 | Configuration conf = project.getConfigurations().findByName("twirl"); 43 | assertThat(conf).isNotNull(); 44 | assertThat(conf.isTransitive()).isTrue(); 45 | assertThat(conf.isVisible()).isFalse(); 46 | ((DefaultConfiguration) conf).runDependencyActions(); 47 | assertThat(conf.getDependencies()) 48 | .anyMatch( 49 | dependency -> 50 | "org.playframework.twirl".equals(dependency.getGroup()) 51 | && dependency.getName().startsWith("twirl-compiler")); 52 | } 53 | 54 | @Test 55 | @DisplayName("Twirl source directory set should be registered for main source set") 56 | void sourceDirectorySetShouldBeRegisteredForMainSourceSet() { 57 | checkSourceDirectorySet(javaPluginExtension(project).getSourceSets().getByName("main")); 58 | } 59 | 60 | @Test 61 | @DisplayName("Twirl source directory set should be registered for test source set") 62 | void sourceDirectorySetShouldBeRegisteredForTestSourceSet() { 63 | checkSourceDirectorySet(javaPluginExtension(project).getSourceSets().getByName("test")); 64 | } 65 | 66 | @Test 67 | @DisplayName("Twirl compile task should be registered for main/test source sets") 68 | void compileTasksShouldBeRegistered() { 69 | assertThat(project.getTasks().findByName("compileTwirl")).isNotNull(); 70 | assertThat(project.getTasks().findByName("compileTestTwirl")).isNotNull(); 71 | } 72 | 73 | private void checkSourceDirectorySet(SourceSet sourceSet) { 74 | assertThat(sourceSet).isNotNull(); 75 | TwirlSourceDirectorySet twirlSourceSet = 76 | sourceSet.getExtensions().findByType(TwirlSourceDirectorySet.class); 77 | assertThat(twirlSourceSet).isNotNull(); 78 | assertThat(twirlSourceSet.getName()).isEqualTo("twirl"); 79 | assertThat(twirlSourceSet.getSourceEncoding().getOrNull()) 80 | .isEqualTo(StandardCharsets.UTF_8.name()); 81 | assertThat(twirlSourceSet.getTemplateImports().get()).isEmpty(); 82 | assertThat(twirlSourceSet.getConstructorAnnotations().get()).isEmpty(); 83 | assertThat(twirlSourceSet.getTemplateFormats().get()).containsKeys("html", "txt", "js", "xml"); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /docs/manual/working/javaGuide/main/templates/JavaCustomTemplateFormat.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Adding support for a custom format to the template engine 4 | 5 | The built-in template engine supports common template formats (HTML, XML, etc.) but you can easily add support for your own formats, if needed. This page summarizes the steps to follow to support a custom format. 6 | 7 | ## Overview of the the templating process 8 | 9 | The template engine builds its result by appending static and dynamic content parts of a template. Consider for instance the following template: 10 | 11 | ``` 12 | foo @bar baz 13 | ``` 14 | 15 | It consists in two static parts (`foo ` and ` baz`) around one dynamic part (`bar`). The template engine concatenates these parts together to build its result. Actually, in order to prevent cross-site scripting attacks, the value of `bar` can be escaped before being concatenated to the rest of the result. This escaping process is specific to each format: e.g. in the case of HTML you want to transform “<” into “&lt;”. 16 | 17 | How does the template engine know which format correspond to a template file? It looks at its extension: e.g. if it ends with `.scala.html` it associates the HTML format to the file. 18 | 19 | In summary, to support your own template format you need to perform the following steps: 20 | 21 | * Implement the text integration process for the format ; 22 | * Associate a file extension to the format. 23 | 24 | ## Implement a format 25 | 26 | Implement the `play.twirl.api.Format` interface that has the methods `A raw(String text)` and `A escape(String text)` that will be used to integrate static and dynamic template parts, respectively. 27 | 28 | The type parameter `A` of the format defines the result type of the template rendering, e.g. `Html` for a HTML template. This type must be a subtype of the `play.twirl.api.Appendable` trait that defines how to concatenates parts together. 29 | 30 | For convenience, Play provides a `play.twirl.api.BufferedContent` abstract class that implements `play.twirl.api.Appendable` using a `StringBuilder` to build its result and that implements the `play.twirl.api.Content` interface so Play knows how to serialize it as an HTTP response body. 31 | 32 | In short, you need to write two classes: one defining the result (implementing `play.twirl.api.Appendable`) and one defining the text integration process (implementing `play.twirl.api.Format`). For instance, here is how the HTML format could be defined: 33 | 34 | ```java 35 | public class Html extends BufferedContent { 36 | public Html(StringBuilder buffer) { 37 | super(buffer); 38 | } 39 | String contentType() { 40 | return "text/html"; 41 | } 42 | } 43 | 44 | public class HtmlFormat implements Format { 45 | Html raw(String text: String) { … } 46 | Html escape(String text) { … } 47 | public static final HtmlFormat instance = new HtmlFormat(); // The build process needs a static reference to the format (see the next section) 48 | } 49 | ``` 50 | 51 | ## Associate a file extension to the format 52 | 53 | The templates are compiled into a `.scala` files by the build process just before compiling the whole application sources. The `TwirlKeys.templateFormats` key is a sbt setting of type `Map[String, String]` defining the mapping between file extensions and template formats. For instance, if you want Play to use your own HTML format implementation you have to write the following in your build file to associate the `.scala.html` files to your custom `my.HtmlFormat` format: 54 | 55 | ```scala 56 | TwirlKeys.templateFormats += ("html" -> "my.HtmlFormat.instance") 57 | ``` 58 | 59 | Note that the right side of the arrow contains the fully qualified name of a static value of type `play.twirl.api.Format`. 60 | -------------------------------------------------------------------------------- /gradle-twirl/src/main/java/play/twirl/gradle/internal/TwirlCompileAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | package play.twirl.gradle.internal; 5 | 6 | import java.io.File; 7 | import java.util.Collection; 8 | import java.util.List; 9 | import org.gradle.api.logging.Logger; 10 | import org.gradle.api.logging.Logging; 11 | import org.gradle.work.ChangeType; 12 | import org.gradle.workers.WorkAction; 13 | import play.japi.twirl.compiler.TwirlCompiler; 14 | import play.twirl.compiler.TwirlCompiler$; 15 | import scala.io.Codec; 16 | 17 | /** Gradle work action that compile or delete one Twirl template. */ 18 | public abstract class TwirlCompileAction implements WorkAction { 19 | 20 | private static final Logger LOGGER = Logging.getLogger(TwirlCompileAction.class); 21 | 22 | @Override 23 | public void execute() { 24 | if (getParameters().getChangeType().get() == ChangeType.REMOVED) { 25 | delete(); 26 | } else { 27 | compile(); 28 | } 29 | } 30 | 31 | @SuppressWarnings("ResultOfMethodCallIgnored") 32 | private void delete() { 33 | try { 34 | File sourceFile = getParameters().getSourceFile().getAsFile().get(); 35 | File sourceDirectory = getParameters().getSourceDirectory().getAsFile().get(); 36 | File destinationDirectory = getParameters().getDestinationDirectory().getAsFile().get(); 37 | String sourceEncoding = getParameters().getSourceEncoding().get(); 38 | // WA: Need to create a source file temporarily for correct calculate path of compiled 39 | // template to delete 40 | sourceFile.createNewFile(); 41 | File compiledTemplate = 42 | TwirlCompiler$.MODULE$ 43 | .generatedFile( 44 | sourceFile, 45 | Codec.string2codec(sourceEncoding), 46 | sourceDirectory, 47 | destinationDirectory, 48 | false) 49 | ._2 50 | .file(); 51 | if (LOGGER.isInfoEnabled()) { 52 | LOGGER.info("Delete Twirl template {}", compiledTemplate.getCanonicalPath()); 53 | } 54 | // Delete temporary empty source file 55 | sourceFile.delete(); 56 | compiledTemplate.delete(); 57 | } catch (Exception e) { 58 | LOGGER.error(e.getMessage(), e); 59 | throw new RuntimeException(e); 60 | } 61 | } 62 | 63 | private void compile() { 64 | try { 65 | File sourceFile = getParameters().getSourceFile().getAsFile().get(); 66 | File sourceDirectory = getParameters().getSourceDirectory().getAsFile().get(); 67 | File destinationDirectory = getParameters().getDestinationDirectory().getAsFile().get(); 68 | String formatterType = getParameters().getFormatterType().get(); 69 | String extension = getParameters().getFormatExtension().get(); 70 | getParameters().getTemplateImports().addAll(TwirlCompiler.DEFAULT_IMPORTS); 71 | Collection imports = getParameters().getTemplateImports().get(); 72 | List constructorAnnotations = getParameters().getConstructorAnnotations().get(); 73 | String sourceEncoding = getParameters().getSourceEncoding().get(); 74 | if (LOGGER.isInfoEnabled()) { 75 | LOGGER.info( 76 | "Compile Twirl template [{}/{}] {} from {} into {}", 77 | formatterType, 78 | sourceEncoding, 79 | sourceFile.getName(), 80 | sourceDirectory.getCanonicalPath(), 81 | destinationDirectory.getCanonicalPath()); 82 | } 83 | TwirlCompiler.compile( 84 | sourceFile, 85 | sourceDirectory, 86 | destinationDirectory, 87 | formatterType, 88 | TwirlCompiler.formatImports(imports, extension), 89 | constructorAnnotations, 90 | Codec.string2codec(sourceEncoding), 91 | false); 92 | } catch (Exception e) { 93 | LOGGER.error(e.getMessage(), e); 94 | throw new RuntimeException(e); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /gradle-twirl/src/test/java/play/twirl/gradle/AbstractFunctionalTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | package play.twirl.gradle; 5 | 6 | import static java.nio.charset.StandardCharsets.UTF_8; 7 | 8 | import freemarker.template.Configuration; 9 | import freemarker.template.Template; 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.io.StringWriter; 13 | import java.nio.file.Path; 14 | import java.nio.file.Paths; 15 | import java.util.Map; 16 | import java.util.stream.Stream; 17 | import org.apache.commons.io.FileUtils; 18 | import org.apache.commons.lang3.ArrayUtils; 19 | import org.gradle.api.JavaVersion; 20 | import org.gradle.testkit.runner.BuildResult; 21 | import org.gradle.testkit.runner.GradleRunner; 22 | import org.gradle.util.GradleVersion; 23 | import org.junit.jupiter.api.BeforeEach; 24 | import org.junit.jupiter.api.io.TempDir; 25 | 26 | abstract class AbstractFunctionalTest { 27 | 28 | @TempDir File projectDir; 29 | 30 | File projectSourceDir; 31 | 32 | GradleRunner runner; 33 | 34 | Configuration freemarkerConf; 35 | 36 | protected abstract File getProjectSourceDir(); 37 | 38 | protected abstract String getBuildFileContent(); 39 | 40 | protected String getSettingsFileContent() { 41 | return ""; 42 | } 43 | 44 | static String getScalaVersion() { 45 | return System.getProperty("scala.version", TwirlPlugin.DEFAULT_SCALA_VERSION); 46 | } 47 | 48 | static String getTwirlVersion() { 49 | return System.getProperty("twirl.version"); 50 | } 51 | 52 | protected Path projectSourcePath(String path) { 53 | return Paths.get(projectSourceDir.getAbsolutePath(), path); 54 | } 55 | 56 | protected Path projectPath(String path) { 57 | return Paths.get(projectDir.getAbsolutePath(), path); 58 | } 59 | 60 | protected Path projectBuildPath(String path) { 61 | return Paths.get(projectDir.getAbsolutePath(), "build/" + path); 62 | } 63 | 64 | @BeforeEach 65 | void init() throws IOException { 66 | projectSourceDir = getProjectSourceDir(); 67 | runner = 68 | GradleRunner.create() 69 | .withProjectDir(projectDir) 70 | .withPluginClasspath() 71 | .withDebug(true) 72 | .forwardOutput(); 73 | 74 | initFreemarker(); 75 | 76 | FileUtils.writeStringToFile( 77 | projectPath("build.gradle.kts").toFile(), getBuildFileContent(), UTF_8); 78 | FileUtils.writeStringToFile( 79 | projectPath("settings.gradle.kts").toFile(), getSettingsFileContent(), UTF_8); 80 | } 81 | 82 | protected void initFreemarker() throws IOException { 83 | freemarkerConf = new Configuration(Configuration.VERSION_2_3_32); 84 | freemarkerConf.setDirectoryForTemplateLoading(projectSourceDir); 85 | } 86 | 87 | protected String templateProcess(String template, Map params) { 88 | StringWriter writer = new StringWriter(); 89 | try { 90 | Template buildGradle = freemarkerConf.getTemplate(template); 91 | buildGradle.process(params, writer); 92 | } catch (Exception e) { 93 | throw new RuntimeException(e); 94 | } 95 | return writer.toString(); 96 | } 97 | 98 | protected BuildResult build(String gradleVersion, String... args) { 99 | return runner 100 | .withGradleVersion(gradleVersion) 101 | // Enable configuration cache for any build 102 | .withArguments(ArrayUtils.add(args, "--configuration-cache")) 103 | .build(); 104 | } 105 | 106 | static Stream gradleVersions() { 107 | String latest = GradleVersion.current().getVersion(); 108 | // https://docs.gradle.org/current/userguide/compatibility.html 109 | if (JavaVersion.current().compareTo(JavaVersion.VERSION_21) >= 0) { // Gradle 8.4+ 110 | return Stream.of(latest); 111 | } 112 | if (JavaVersion.current().compareTo(JavaVersion.VERSION_17) >= 0) { // Gradle 7.3+ 113 | return Stream.of("7.6.6", "8.14.3", latest); 114 | } 115 | // https://docs.gradle.org/current/userguide/scala_plugin.html#sec:configure_zinc_compiler 116 | if (getScalaVersion().equals("3")) { // Gradle 7.5+ 117 | return Stream.of("7.6.6", "8.14.3", latest); 118 | } 119 | return Stream.of("7.1.1", "7.6.6", "8.14.3", latest); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /gradle-twirl/src/main/java/play/twirl/gradle/TwirlCompile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | package play.twirl.gradle; 5 | 6 | import java.io.File; 7 | import java.util.Map; 8 | import java.util.Map.Entry; 9 | import javax.inject.Inject; 10 | import org.gradle.api.DefaultTask; 11 | import org.gradle.api.GradleException; 12 | import org.gradle.api.file.ConfigurableFileCollection; 13 | import org.gradle.api.file.DirectoryProperty; 14 | import org.gradle.api.file.FileType; 15 | import org.gradle.api.file.RelativePath; 16 | import org.gradle.api.internal.file.RelativeFile; 17 | import org.gradle.api.provider.ListProperty; 18 | import org.gradle.api.provider.MapProperty; 19 | import org.gradle.api.provider.Property; 20 | import org.gradle.api.provider.SetProperty; 21 | import org.gradle.api.tasks.CacheableTask; 22 | import org.gradle.api.tasks.Classpath; 23 | import org.gradle.api.tasks.IgnoreEmptyDirectories; 24 | import org.gradle.api.tasks.Input; 25 | import org.gradle.api.tasks.InputFiles; 26 | import org.gradle.api.tasks.OutputDirectory; 27 | import org.gradle.api.tasks.PathSensitive; 28 | import org.gradle.api.tasks.PathSensitivity; 29 | import org.gradle.api.tasks.TaskAction; 30 | import org.gradle.internal.FileUtils; 31 | import org.gradle.work.FileChange; 32 | import org.gradle.work.Incremental; 33 | import org.gradle.work.InputChanges; 34 | import org.gradle.workers.WorkQueue; 35 | import org.gradle.workers.WorkerExecutor; 36 | import play.twirl.gradle.internal.TwirlCompileAction; 37 | 38 | /** Gradle task for compiling Twirl templates into Scala code. */ 39 | @CacheableTask 40 | public abstract class TwirlCompile extends DefaultTask { 41 | 42 | @InputFiles 43 | @Incremental 44 | @IgnoreEmptyDirectories 45 | @PathSensitive(PathSensitivity.RELATIVE) 46 | public abstract ConfigurableFileCollection getSource(); 47 | 48 | @Classpath 49 | public abstract ConfigurableFileCollection getTwirlClasspath(); 50 | 51 | @OutputDirectory 52 | public abstract DirectoryProperty getDestinationDirectory(); 53 | 54 | @Input 55 | public abstract MapProperty getTemplateFormats(); 56 | 57 | @Input 58 | public abstract SetProperty getTemplateImports(); 59 | 60 | @Input 61 | public abstract ListProperty getConstructorAnnotations(); 62 | 63 | @Input 64 | public abstract Property getSourceEncoding(); 65 | 66 | @Inject 67 | public abstract WorkerExecutor getWorkerExecutor(); 68 | 69 | @TaskAction 70 | void compile(InputChanges changes) { 71 | for (FileChange change : changes.getFileChanges(getSource())) { 72 | if (change.getFileType() == FileType.DIRECTORY) continue; 73 | WorkQueue workQueue = 74 | getWorkerExecutor() 75 | .processIsolation(spec -> spec.getClasspath().from(getTwirlClasspath())); 76 | 77 | Map templateFormats = getTemplateFormats().get(); 78 | RelativeFile sourceFile = 79 | new RelativeFile(change.getFile(), RelativePath.parse(true, change.getNormalizedPath())); 80 | workQueue.submit( 81 | TwirlCompileAction.class, 82 | parameters -> { 83 | parameters.getChangeType().set(change.getChangeType()); 84 | parameters.getSourceFile().set(sourceFile.getFile()); 85 | parameters.getSourceDirectory().set(sourceFile.getBaseDir()); 86 | parameters.getDestinationDirectory().set(getDestinationDirectory()); 87 | Entry format = getFormat(templateFormats, sourceFile.getFile()); 88 | parameters.getFormatExtension().set(format.getKey()); 89 | parameters.getFormatterType().set(format.getValue()); 90 | parameters.getTemplateImports().set(getTemplateImports()); 91 | parameters.getConstructorAnnotations().set(getConstructorAnnotations()); 92 | parameters.getSourceEncoding().set(getSourceEncoding()); 93 | }); 94 | } 95 | } 96 | 97 | private Entry getFormat(Map formats, File file) { 98 | return formats.entrySet().stream() 99 | .filter(f -> FileUtils.hasExtensionIgnoresCase(file.getName(), f.getKey())) 100 | .findFirst() 101 | .orElseThrow( 102 | () -> 103 | new GradleException( 104 | String.format( 105 | "Unknown template format of '%s'. Possible extentions: [%s]", 106 | file.getName(), String.join(", ", formats.keySet())))); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /docs/manual/working/javaGuide/main/templates/JavaTemplateUseCases.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Common template use cases 4 | 5 | Templates, being simple functions, can be composed in any way you want. Below are a few examples of some common scenarios. 6 | 7 | ## Layout 8 | 9 | Let’s declare a `views/main.scala.html` template that will act as a main layout template: 10 | 11 | ```html 12 | @(title: String)(content: Html) 13 | 14 | 15 | 16 | @title 17 | 18 | 19 |
    @content
    20 | 21 | 22 | 23 | ``` 24 | 25 | As you can see, this template takes two parameters: a title and an HTML content block. Now we can use it from another `views/Application/index.scala.html` template: 26 | 27 | ```html 28 | @main(title = "Home") { 29 | 30 |

    Home page

    31 | 32 | } 33 | ``` 34 | 35 | > **Note:** You can use both named parameters (like `@main(title = "Home")` and positional parameters, like `@main("Home")`. Choose whichever is clearer in a specific context. 36 | 37 | Sometimes you need a second page-specific content block for a sidebar or breadcrumb trail, for example. You can do this with an additional parameter: 38 | 39 | ```html 40 | @(title: String)(sidebar: Html)(content: Html) 41 | 42 | 43 | 44 | @title 45 | 46 | 47 |
    @content
    48 | 49 | 50 | 51 | ``` 52 | 53 | Using this from our ‘index’ template, we have: 54 | 55 | ```html 56 | @main("Home") { 57 |

    Sidebar

    58 | 59 | } { 60 |

    Home page

    61 | 62 | } 63 | ``` 64 | 65 | Alternatively, we can declare the sidebar block separately: 66 | 67 | ```html 68 | @sidebar = { 69 |

    Sidebar

    70 | } 71 | 72 | @main("Home")(sidebar) { 73 |

    Home page

    74 | 75 | } 76 | ``` 77 | 78 | 79 | ## Tags (they are just functions right?) 80 | 81 | Let’s write a simple `views/tags/notice.scala.html` tag that displays an HTML notice: 82 | 83 | ```html 84 | @(level: String = "error")(body: (String) => Html) 85 | 86 | @level match { 87 | 88 | case "success" => { 89 |

    90 | @body("green") 91 |

    92 | } 93 | 94 | case "warning" => { 95 |

    96 | @body("orange") 97 |

    98 | } 99 | 100 | case "error" => { 101 |

    102 | @body("red") 103 |

    104 | } 105 | 106 | } 107 | ``` 108 | 109 | And now let’s use it from another template: 110 | 111 | ```html 112 | @import tags._ 113 | 114 | @notice("error") { color => 115 | Oops, something is wrong 116 | } 117 | ``` 118 | 119 | ## Includes 120 | 121 | Again, there’s nothing special here. You can just call any other template you like (or in fact any other function, wherever it is defined): 122 | 123 | ```html 124 |

    Home

    125 | 126 |
    127 | @common.sideBar() 128 |
    129 | ``` 130 | 131 | ## moreScripts and moreStyles equivalents 132 | 133 | To define old moreScripts or moreStyles variables equivalents (like on Play! 1.x) on a Scala template, you can define a variable in the main template like this: 134 | 135 | ```html 136 | @(title: String, scripts: Html = Html(""))(content: Html) 137 | 138 | 139 | 140 | 141 | 142 | @title 143 | 144 | 145 | 146 | @scripts 147 | 148 | 149 |
    156 |
    157 | @content 158 |
    159 | 160 | 161 | ``` 162 | 163 | And on an extended template that need an extra script : 164 | 165 | ```html 166 | @scripts = { 167 | 168 | } 169 | 170 | @main("Title",scripts){ 171 | 172 | Html content here ... 173 | 174 | } 175 | 176 | ``` 177 | 178 | And on an extended template that not need an extra script, just like this : 179 | 180 | ```html 181 | @main("Title"){ 182 | 183 | Html content here ... 184 | 185 | } 186 | ``` 187 | -------------------------------------------------------------------------------- /docs/manual/working/scalaGuide/main/templates/ScalaTemplateUseCases.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Scala templates common use cases 4 | 5 | Templates, being simple functions, can be composed in any way you want. Below are examples of some common scenarios. 6 | 7 | ## Layout 8 | 9 | Let’s declare a `views/main.scala.html` template that will act as a main layout template: 10 | 11 | ```html 12 | @(title: String)(content: Html) 13 | 14 | 15 | 16 | @title 17 | 18 | 19 |
    @content
    20 | 21 | 22 | 23 | ``` 24 | 25 | As you can see, this template takes two parameters: a title and an HTML content block. Now we can use it from another `views/Application/index.scala.html` template: 26 | 27 | ```html 28 | @main(title = "Home") { 29 | 30 |

    Home page

    31 | 32 | } 33 | ``` 34 | 35 | > **Note:** We sometimes use named parameters(like `@main(title = "Home")`, sometimes not like `@main("Home")`. It is as you want, choose whatever is clearer in a specific context. 36 | 37 | Sometimes you need a second page-specific content block for a sidebar or breadcrumb trail, for example. You can do this with an additional parameter: 38 | 39 | ```html 40 | @(title: String)(sidebar: Html)(content: Html) 41 | 42 | 43 | 44 | @title 45 | 46 | 47 | 48 |
    @content
    49 | 50 | 51 | ``` 52 | 53 | Using this from our ‘index’ template, we have: 54 | 55 | ```html 56 | @main("Home") { 57 |

    Sidebar

    58 | 59 | } { 60 |

    Home page

    61 | 62 | } 63 | ``` 64 | 65 | Alternatively, we can declare the sidebar block separately: 66 | 67 | ```html 68 | @sidebar = { 69 |

    Sidebar

    70 | } 71 | 72 | @main("Home")(sidebar) { 73 |

    Home page

    74 | 75 | } 76 | ``` 77 | 78 | 79 | ## Tags (they are just functions, right?) 80 | 81 | Let’s write a simple `views/tags/notice.scala.html` tag that displays an HTML notice: 82 | 83 | ```html 84 | @(level: String = "error")(body: (String) => Html) 85 | 86 | @level match { 87 | 88 | case "success" => { 89 |

    90 | @body("green") 91 |

    92 | } 93 | 94 | case "warning" => { 95 |

    96 | @body("orange") 97 |

    98 | } 99 | 100 | case "error" => { 101 |

    102 | @body("red") 103 |

    104 | } 105 | 106 | } 107 | ``` 108 | 109 | And now let’s use it from another template: 110 | 111 | ```html 112 | @import tags._ 113 | 114 | @notice("error") { color => 115 | Oops, something is wrong 116 | } 117 | ``` 118 | 119 | ## Includes 120 | 121 | Again, there’s nothing special here. You can just call any other template you like (and in fact any other function coming from anywhere at all): 122 | 123 | ```html 124 |

    Home

    125 | 126 |
    127 | @common.sideBar() 128 |
    129 | ``` 130 | ## moreScripts and moreStyles equivalents 131 | 132 | To define old moreScripts or moreStyles variables equivalents (like on Play! 1.x) on a Scala template, you can define a variable in the main template like this : 133 | 134 | ```html 135 | @(title: String, scripts: Html = Html(""))(content: Html) 136 | 137 | 138 | 139 | 140 | 141 | @title 142 | 143 | 144 | 145 | @scripts 146 | 147 | 148 | 155 |
    156 | @content 157 |
    158 | 159 | 160 | ``` 161 | 162 | And on an extended template that need an extra script : 163 | 164 | ```html 165 | @scripts = { 166 | 167 | } 168 | 169 | @main("Title",scripts){ 170 | 171 | Html content here ... 172 | 173 | } 174 | 175 | ``` 176 | 177 | And on an extended template that not need an extra script, just like this : 178 | 179 | ```html 180 | @main("Title"){ 181 | 182 | Html content here ... 183 | 184 | } 185 | ``` -------------------------------------------------------------------------------- /docs/manual/working/scalaGuide/main/templates/ScalaCustomTemplateFormat.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Adding support for a custom format to the template engine 4 | 5 | The built-in template engine supports common template formats (HTML, XML, etc.) but you can easily add support for your own formats, if needed. This page summarizes the steps to follow to support a custom format. 6 | 7 | ## Overview of the templating process 8 | 9 | The template engine builds its result by appending static and dynamic content parts of a template. Consider for instance the following template: 10 | 11 | ``` 12 | foo @bar baz 13 | ``` 14 | 15 | It consists in two static parts (`foo ` and ` baz`) around one dynamic part (`bar`). The template engine concatenates these parts together to build its result. Actually, in order to prevent cross-site scripting attacks, the value of `bar` can be escaped before being concatenated to the rest of the result. This escaping process is specific to each format: e.g. in the case of HTML you want to transform “<” into “&lt;”. 16 | 17 | How does the template engine know which format correspond to a template file? It looks at its extension: e.g. if it ends with `.scala.html` it associates the HTML format to the file. 18 | 19 | Finally, you usually want your template files to be used as the body of your HTTP responses, so you have to define how to make a Play result from a template rendering result. 20 | 21 | In summary, to support your own template format you need to perform the following steps: 22 | 23 | * Implement the text integration process for the format ; 24 | * Associate a file extension to the format ; 25 | * Eventually tell Play how to send the result of a template rendering as an HTTP response body. 26 | 27 | ## Implement a format 28 | 29 | Implement the `play.twirl.api.Format[A]` trait that has the methods `raw(text: String): A` and `escape(text: String): A` that will be used to integrate static and dynamic template parts, respectively. 30 | 31 | The type parameter `A` of the format defines the result type of the template rendering, e.g. `Html` for a HTML template. This type must be a subtype of the `play.twirl.api.Appendable[A]` trait that defines how to concatenates parts together. 32 | 33 | For convenience, Play provides a `play.twirl.api.BufferedContent[A]` abstract class that implements `play.twirl.api.Appendable[A]` using a `StringBuilder` to build its result and that implements the `play.twirl.api.Content` trait so Play knows how to serialize it as an HTTP response body (see the last section of this page for details). 34 | 35 | In short, you need to write to classes: one defining the result (implementing `play.twirl.api.Appendable[A]`) and one defining the text integration process (implementing `play.twirl.api.Format[A]`). For instance, here is how the HTML format is defined: 36 | 37 | ```scala 38 | // The `Html` result type. We extend `BufferedContent[Html]` rather than just `Appendable[Html]` so 39 | // Play knows how to make an HTTP result from a `Html` value 40 | class Html(buffer: StringBuilder) extends BufferedContent[Html](buffer) { 41 | val contentType = MimeTypes.HTML 42 | } 43 | 44 | object HtmlFormat extends Format[Html] { 45 | def raw(text: String): Html = … 46 | def escape(text: String): Html = … 47 | } 48 | ``` 49 | 50 | ## Associate a file extension to the format 51 | 52 | The templates are compiled into a `.scala` files by the build process just before compiling the whole application sources. The `TwirlKeys.templateFormats` key is a sbt setting of type `Map[String, String]` defining the mapping between file extensions and template formats. For instance, if HTML was not supported out of the box by Play, you would have to write the following in your build file to associate the `.scala.html` files to the `play.twirl.api.HtmlFormat` format: 53 | 54 | ```scala 55 | TwirlKeys.templateFormats += ("html" -> "my.HtmlFormat.instance") 56 | ``` 57 | 58 | Note that the right side of the arrow contains the fully qualified name of a value of type `play.twirl.api.Format[_]`. 59 | 60 | ## Tell Play how to make an HTTP result from a template result type 61 | 62 | Play can write an HTTP response body for any value of type `A` for which it exists an implicit `play.api.http.Writeable[A]` value. So all you need is to define such a value for your template result type. For instance, here is how to define such a value for HTTP: 63 | 64 | ```scala 65 | implicit def writableHttp(implicit codec: Codec): Writeable[Http] = 66 | Writeable[Http](result => codec.encode(result.body), Some(ContentTypes.HTTP)) 67 | ``` 68 | 69 | > **Note:** if your template result type extends `play.twirl.api.BufferedContent` you only need to define an 70 | > implicit `play.api.http.ContentTypeOf` value: 71 | > ```scala 72 | > implicit def contentTypeHttp(implicit codec: Codec): ContentTypeOf[Http] = 73 | > ContentTypeOf[Http](Some(ContentTypes.HTTP)) 74 | > ``` 75 | -------------------------------------------------------------------------------- /compiler/src/test/scala-2/play/twirl/compiler/test/Helper.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.compiler 6 | package test 7 | 8 | import java.io._ 9 | import play.twirl.parser.TwirlIO 10 | 11 | object Helper { 12 | case class CompilationError(message: String, line: Int, point: Int) extends RuntimeException(message) 13 | 14 | class CompilerHelper(sourceDir: File, val generatedDir: File, generatedClasses: File) { 15 | import java.net._ 16 | import scala.collection.mutable 17 | import scala.reflect.internal.util.Position 18 | import scala.tools.nsc.reporters.ConsoleReporter 19 | import scala.tools.nsc.Global 20 | import scala.tools.nsc.Settings 21 | 22 | val twirlCompiler = TwirlCompiler 23 | 24 | val classloader = new URLClassLoader( 25 | Array(generatedClasses.toURI.toURL), 26 | Class.forName("play.twirl.compiler.TwirlCompiler").getClassLoader 27 | ) 28 | 29 | // A list of the compile errors from the most recent compiler run 30 | val compileErrors = new mutable.ListBuffer[CompilationError] 31 | 32 | val compiler = { 33 | def additionalClassPathEntry: Option[String] = 34 | Some( 35 | List(play.twirl.compiler.TwirlCompiler.getClass, play.twirl.api.Html.getClass, scala.xml.NodeSeq.getClass) 36 | .map(_.getProtectionDomain.getCodeSource.getLocation) 37 | .mkString(File.pathSeparator) 38 | ) 39 | 40 | val settings = new Settings 41 | val scalaObjectSource = Class.forName("scala.Option").getProtectionDomain.getCodeSource 42 | 43 | // is null in Eclipse/OSGI but luckily we don't need it there 44 | if (scalaObjectSource != null) { 45 | val compilerPath = Class.forName("scala.tools.nsc.Interpreter").getProtectionDomain.getCodeSource.getLocation 46 | val libPath = scalaObjectSource.getLocation 47 | val pathList = List(compilerPath, libPath) 48 | val origBootclasspath = settings.bootclasspath.value 49 | settings.bootclasspath.value = 50 | ((origBootclasspath :: pathList) ::: additionalClassPathEntry.toList).mkString(File.pathSeparator) 51 | settings.outdir.value = generatedClasses.getAbsolutePath 52 | } 53 | 54 | val compiler = new Global( 55 | settings, 56 | new ConsoleReporter(settings) { 57 | override def display(pos: Position, msg: String, severity: Severity): Unit = { 58 | pos match { 59 | case scala.reflect.internal.util.NoPosition => // do nothing 60 | case _ => compileErrors.append(CompilationError(msg, pos.line, pos.point)) 61 | } 62 | } 63 | } 64 | ) 65 | 66 | compiler 67 | } 68 | 69 | class CompiledTemplate[T](className: String) { 70 | private def getF(template: Any) = { 71 | template.getClass.getMethod("f").invoke(template).asInstanceOf[T] 72 | } 73 | 74 | def static: T = { 75 | getF(classloader.loadClass(className + "$").getDeclaredField("MODULE$").get(null)) 76 | } 77 | 78 | def inject(constructorArgs: Any*): T = { 79 | classloader.loadClass(className).getConstructors match { 80 | case Array(single) => getF(single.newInstance(constructorArgs.asInstanceOf[Seq[AnyRef]]: _*)) 81 | case other => 82 | throw new IllegalStateException(className + " does not declare exactly one constructor: " + other) 83 | } 84 | } 85 | } 86 | 87 | def compile[T]( 88 | templateName: String, 89 | className: String, 90 | additionalImports: Seq[String] = Nil 91 | ): CompiledTemplate[T] = { 92 | val scalaVersion = play.twirl.compiler.BuildInfo.scalaVersion 93 | val templateFile = new File(sourceDir, templateName) 94 | val Some(generated) = twirlCompiler.compile( 95 | templateFile, 96 | sourceDir, 97 | generatedDir, 98 | "play.twirl.api.HtmlFormat", 99 | Option(scalaVersion), 100 | additionalImports = TwirlCompiler.defaultImports(scalaVersion) ++ additionalImports, 101 | constructorAnnotations = Nil, 102 | codec = TwirlIO.defaultCodec, 103 | inclusiveDot = false 104 | ) 105 | 106 | val mapper = GeneratedSource(generated) 107 | 108 | val run = new compiler.Run 109 | 110 | compileErrors.clear() 111 | 112 | run.compile(List(generated.getAbsolutePath)) 113 | 114 | compileErrors.headOption.foreach { 115 | case CompilationError(msg, line, point) => { 116 | compileErrors.clear() 117 | throw CompilationError(msg, mapper.mapLine(line), mapper.mapPosition(point)) 118 | } 119 | } 120 | 121 | new CompiledTemplate[T](className) 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /api/shared/src/main/scala/play/twirl/api/Template.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 3 | */ 4 | 5 | package play.twirl.api 6 | 7 | trait Template0[Result] { 8 | def render(): Result 9 | } 10 | 11 | trait Template1[A, Result] { 12 | def render(a: A): Result 13 | } 14 | 15 | trait Template2[A, B, Result] { 16 | def render(a: A, b: B): Result 17 | } 18 | 19 | trait Template3[A, B, C, Result] { 20 | def render(a: A, b: B, c: C): Result 21 | } 22 | 23 | trait Template4[A, B, C, D, Result] { 24 | def render(a: A, b: B, c: C, d: D): Result 25 | } 26 | 27 | trait Template5[A, B, C, D, E, Result] { 28 | def render(a: A, b: B, c: C, d: D, e: E): Result 29 | } 30 | 31 | trait Template6[A, B, C, D, E, F, Result] { 32 | def render(a: A, b: B, c: C, d: D, e: E, f: F): Result 33 | } 34 | 35 | trait Template7[A, B, C, D, E, F, G, Result] { 36 | def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G): Result 37 | } 38 | 39 | trait Template8[A, B, C, D, E, F, G, H, Result] { 40 | def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H): Result 41 | } 42 | 43 | trait Template9[A, B, C, D, E, F, G, H, I, Result] { 44 | def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I): Result 45 | } 46 | 47 | trait Template10[A, B, C, D, E, F, G, H, I, J, Result] { 48 | def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J): Result 49 | } 50 | 51 | trait Template11[A, B, C, D, E, F, G, H, I, J, K, Result] { 52 | def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K): Result 53 | } 54 | 55 | trait Template12[A, B, C, D, E, F, G, H, I, J, K, L, Result] { 56 | def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L): Result 57 | } 58 | 59 | trait Template13[A, B, C, D, E, F, G, H, I, J, K, L, M, Result] { 60 | def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M): Result 61 | } 62 | 63 | trait Template14[A, B, C, D, E, F, G, H, I, J, K, L, M, N, Result] { 64 | def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N): Result 65 | } 66 | 67 | trait Template15[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, Result] { 68 | def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O): Result 69 | } 70 | 71 | trait Template16[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Result] { 72 | def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O, p: P): Result 73 | } 74 | 75 | trait Template17[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, Result] { 76 | def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O, p: P, q: Q) 77 | : Result 78 | } 79 | 80 | trait Template18[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, Result] { 81 | def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O, p: P, q: Q, r: R) 82 | : Result 83 | } 84 | 85 | trait Template19[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, Result] { 86 | def render( 87 | a: A, 88 | b: B, 89 | c: C, 90 | d: D, 91 | e: E, 92 | f: F, 93 | g: G, 94 | h: H, 95 | i: I, 96 | j: J, 97 | k: K, 98 | l: L, 99 | m: M, 100 | n: N, 101 | o: O, 102 | p: P, 103 | q: Q, 104 | r: R, 105 | s: S 106 | ): Result 107 | } 108 | 109 | trait Template20[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, Result] { 110 | def render( 111 | a: A, 112 | b: B, 113 | c: C, 114 | d: D, 115 | e: E, 116 | f: F, 117 | g: G, 118 | h: H, 119 | i: I, 120 | j: J, 121 | k: K, 122 | l: L, 123 | m: M, 124 | n: N, 125 | o: O, 126 | p: P, 127 | q: Q, 128 | r: R, 129 | s: S, 130 | t: T 131 | ): Result 132 | } 133 | 134 | trait Template21[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, Result] { 135 | def render( 136 | a: A, 137 | b: B, 138 | c: C, 139 | d: D, 140 | e: E, 141 | f: F, 142 | g: G, 143 | h: H, 144 | i: I, 145 | j: J, 146 | k: K, 147 | l: L, 148 | m: M, 149 | n: N, 150 | o: O, 151 | p: P, 152 | q: Q, 153 | r: R, 154 | s: S, 155 | t: T, 156 | u: U 157 | ): Result 158 | } 159 | 160 | trait Template22[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, Result] { 161 | def render( 162 | a: A, 163 | b: B, 164 | c: C, 165 | d: D, 166 | e: E, 167 | f: F, 168 | g: G, 169 | h: H, 170 | i: I, 171 | j: J, 172 | k: K, 173 | l: L, 174 | m: M, 175 | n: N, 176 | o: O, 177 | p: P, 178 | q: Q, 179 | r: R, 180 | s: S, 181 | t: T, 182 | u: U, 183 | v: V 184 | ): Result 185 | } 186 | --------------------------------------------------------------------------------