summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIurii Malchenko <yurique@pm.me>2019-01-04 18:32:57 +0200
committerLi Haoyi <haoyi.sg@gmail.com>2019-01-04 08:32:57 -0800
commiteb497a8f5db949ea661042371fbbc908f6524ea4 (patch)
treee30eac430ae2faf3fc2317fbc33a692cc5bd91a0
parent54b797c2b682b0583a2f87b7da22ca9a59b8df37 (diff)
downloadmill-eb497a8f5db949ea661042371fbbc908f6524ea4.tar.gz
mill-eb497a8f5db949ea661042371fbbc908f6524ea4.tar.bz2
mill-eb497a8f5db949ea661042371fbbc908f6524ea4.zip
adding support for customizable constructor annotations, codec and "inclusive dot" (#481)
-rw-r--r--contrib/twirllib/src/TwirlModule.scala6
-rw-r--r--contrib/twirllib/src/TwirlWorker.scala79
-rw-r--r--contrib/twirllib/test/resources/hello-world-inclusive-dot/core/views/hello.scala.html8
-rw-r--r--contrib/twirllib/test/resources/hello-world-inclusive-dot/core/views/wrapper.scala.html5
-rw-r--r--contrib/twirllib/test/resources/hello-world/core/views/hello.scala.html2
-rw-r--r--contrib/twirllib/test/src/HelloWorldTests.scala74
-rw-r--r--docs/pages/9 - Contrib Modules.md6
7 files changed, 133 insertions, 47 deletions
diff --git a/contrib/twirllib/src/TwirlModule.scala b/contrib/twirllib/src/TwirlModule.scala
index 985765fc..22e4a43a 100644
--- a/contrib/twirllib/src/TwirlModule.scala
+++ b/contrib/twirllib/src/TwirlModule.scala
@@ -35,11 +35,11 @@ trait TwirlModule extends mill.Module {
def twirlAdditionalImports: Seq[String] = Nil
- private def twirlConstructorAnnotations: Seq[String] = Nil
+ def twirlConstructorAnnotations: Seq[String] = Nil
- private def twirlCodec: Codec = Codec(Properties.sourceEncoding)
+ def twirlCodec: Codec = Codec(Properties.sourceEncoding)
- private def twirlInclusiveDot: Boolean = false
+ def twirlInclusiveDot: Boolean = false
def compileTwirl: T[mill.scalalib.api.CompilationResult] = T.persistent {
TwirlWorkerApi.twirlWorker
diff --git a/contrib/twirllib/src/TwirlWorker.scala b/contrib/twirllib/src/TwirlWorker.scala
index 09376a6f..19eb4725 100644
--- a/contrib/twirllib/src/TwirlWorker.scala
+++ b/contrib/twirllib/src/TwirlWorker.scala
@@ -4,6 +4,7 @@ package twirllib
import java.io.File
import java.lang.reflect.Method
import java.net.URLClassLoader
+import java.nio.charset.Charset
import mill.api.PathRef
import mill.scalalib.api.CompilationResult
@@ -22,24 +23,29 @@ class TwirlWorker {
// Switched to using the java api because of the hack-ish thing going on later.
//
- // * we'll need to construct a collection of additional imports
- // * it will need to consider the defaults
- // * and add the user-provided additional imports
+ // * we'll need to construct a collection of additional imports (will need to also include the defaults and add the user-provided additional imports)
+ // * we'll need to construct a collection of constructor annotations// *
// * the default collection in scala api is a Seq[String]
// * but it is defined in a different classloader (namely in cl)
// * so we can not construct our own Seq and pass it to the method - it will be from our classloader, and not compatible
- // * the java api has a Collection as the type for this param, for which it is much more doable to append things to it using reflection
+ // * the java api uses java collections, manipulating which using reflection is much simpler
//
- // NOTE: I tried creating the cl classloader passing the current classloader as the parent:
+ // NOTE: When creating the cl classloader with passing the current classloader as the parent:
// val cl = new URLClassLoader(twirlClasspath.map(_.toIO.toURI.toURL).toArray, getClass.getClassLoader)
- // in that case it was possible to cast the default to a Seq[String], construct our own Seq[String], and pass it to the method invoke- it was compatible.
- // And the tests passed. But when run in a different mill project, I was getting exceptions like this:
+ // it is possible to cast the default to a Seq[String], construct our own Seq[String], and pass it to the method invoke -
+ // classe will be compatible (the tests passed).
+ // But when run in an actual mill project with this module enabled, there were exceptions like this:
// scala.reflect.internal.MissingRequirementError: object scala in compiler mirror not found.
val twirlCompilerClass = cl.loadClass("play.japi.twirl.compiler.TwirlCompiler")
- // this one is only to get the codec: Codec parameter default value
- val twirlScalaCompilerClass = cl.loadClass("play.twirl.compiler.TwirlCompiler")
+ val codecClass = cl.loadClass("scala.io.Codec")
+ val charsetClass = cl.loadClass("java.nio.charset.Charset")
+ val arrayListClass = cl.loadClass("java.util.ArrayList")
+ val hashSetClass = cl.loadClass("java.util.HashSet")
+
+ val codecApplyMethod = codecClass.getMethod("apply", charsetClass)
+ val charsetForNameMethod = charsetClass.getMethod("forName", classOf[java.lang.String])
val compileMethod = twirlCompilerClass.getMethod("compile",
classOf[java.io.File],
@@ -51,11 +57,9 @@ class TwirlWorker {
cl.loadClass("scala.io.Codec"),
classOf[Boolean])
- val arrayListClass = cl.loadClass("java.util.ArrayList")
- val hashSetClass = cl.loadClass("java.util.HashSet")
+ val defaultImportsMethod = twirlCompilerClass.getField("DEFAULT_IMPORTS")
- val defaultAdditionalImportsMethod = twirlCompilerClass.getField("DEFAULT_IMPORTS")
- val defaultCodecMethod = twirlScalaCompilerClass.getMethod("compile$default$7")
+ val hashSetConstructor = hashSetClass.getConstructor(cl.loadClass("java.util.Collection"))
val instance = new TwirlWorkerApi {
override def compileTwirl(source: File,
@@ -66,27 +70,42 @@ class TwirlWorker {
constructorAnnotations: Seq[String],
codec: Codec,
inclusiveDot: Boolean) {
- val defaultAdditionalImports = defaultAdditionalImportsMethod.get(null) // unmodifiable collection
- // copying it into a modifiable hash set and adding all additional imports
- val allAdditionalImports =
- hashSetClass
- .getConstructor(cl.loadClass("java.util.Collection"))
- .newInstance(defaultAdditionalImports)
- .asInstanceOf[Object]
- val hashSetAddMethod =
- allAdditionalImports
- .getClass
- .getMethod("add", classOf[Object])
- additionalImports.foreach(hashSetAddMethod.invoke(allAdditionalImports, _))
-
+ // val defaultImports = play.japi.twirl.compiler.TwirlCompiler.DEFAULT_IMPORTS()
+ // val twirlAdditionalImports = new HashSet(defaultImports)
+ // additionalImports.foreach(twirlAdditionalImports.add)
+ val defaultImports = defaultImportsMethod.get(null) // unmodifiable collection
+ val twirlAdditionalImports = hashSetConstructor.newInstance(defaultImports).asInstanceOf[Object]
+ val hashSetAddMethod = twirlAdditionalImports.getClass.getMethod("add", classOf[Object])
+ additionalImports.foreach(hashSetAddMethod.invoke(twirlAdditionalImports, _))
+
+ // Codec.apply(Charset.forName(codec.charSet.name()))
+ val twirlCodec = codecApplyMethod.invoke(null, charsetForNameMethod.invoke(null, codec.charSet.name()))
+
+ // val twirlConstructorAnnotations = new ArrayList()
+ // constructorAnnotations.foreach(twirlConstructorAnnotations.add)
+ val twirlConstructorAnnotations = arrayListClass.newInstance().asInstanceOf[Object]
+ val arrayListAddMethod = twirlConstructorAnnotations.getClass.getMethod("add", classOf[Object])
+ constructorAnnotations.foreach(arrayListAddMethod.invoke(twirlConstructorAnnotations, _))
+
+ // JavaAPI
+ // public static Optional<File> compile(
+ // File source,
+ // File sourceDirectory,
+ // File generatedDirectory,
+ // String formatterType,
+ // Collection<String> additionalImports,
+ // List<String> constructorAnnotations,
+ // Codec codec,
+ // boolean inclusiveDot
+ // )
val o = compileMethod.invoke(null, source,
sourceDirectory,
generatedDirectory,
formatterType,
- allAdditionalImports,
- arrayListClass.newInstance().asInstanceOf[Object], // empty list seems to be the default
- defaultCodecMethod.invoke(null),
- Boolean.box(false)
+ twirlAdditionalImports,
+ twirlConstructorAnnotations,
+ twirlCodec,
+ Boolean.box(inclusiveDot)
)
}
}
diff --git a/contrib/twirllib/test/resources/hello-world-inclusive-dot/core/views/hello.scala.html b/contrib/twirllib/test/resources/hello-world-inclusive-dot/core/views/hello.scala.html
new file mode 100644
index 00000000..16053d72
--- /dev/null
+++ b/contrib/twirllib/test/resources/hello-world-inclusive-dot/core/views/hello.scala.html
@@ -0,0 +1,8 @@
+@this(title: String)
+@wrapper {
+ <html>
+ <body>
+ <h1>@title</h1>
+ </body>
+ </html>
+} \ No newline at end of file
diff --git a/contrib/twirllib/test/resources/hello-world-inclusive-dot/core/views/wrapper.scala.html b/contrib/twirllib/test/resources/hello-world-inclusive-dot/core/views/wrapper.scala.html
new file mode 100644
index 00000000..af1f5d8e
--- /dev/null
+++ b/contrib/twirllib/test/resources/hello-world-inclusive-dot/core/views/wrapper.scala.html
@@ -0,0 +1,5 @@
+@(content: Html)
+
+@defining("test") { className =>
+ <div class="@className">@content</div>
+} \ No newline at end of file
diff --git a/contrib/twirllib/test/resources/hello-world/core/views/hello.scala.html b/contrib/twirllib/test/resources/hello-world/core/views/hello.scala.html
index acadf615..3603fe07 100644
--- a/contrib/twirllib/test/resources/hello-world/core/views/hello.scala.html
+++ b/contrib/twirllib/test/resources/hello-world/core/views/hello.scala.html
@@ -1,4 +1,4 @@
-@(title: String)
+@this(title: String)
<html>
<body>
<h1>@title</h1>
diff --git a/contrib/twirllib/test/src/HelloWorldTests.scala b/contrib/twirllib/test/src/HelloWorldTests.scala
index 71f25ff4..67344ea6 100644
--- a/contrib/twirllib/test/src/HelloWorldTests.scala
+++ b/contrib/twirllib/test/src/HelloWorldTests.scala
@@ -13,28 +13,40 @@ object HelloWorldTests extends TestSuite {
}
trait HelloWorldModule extends mill.twirllib.TwirlModule {
- def twirlVersion = "1.0.0"
- override def twirlAdditionalImports: Seq[String] = additionalImports
+
+ def twirlVersion = "1.3.15"
+
}
object HelloWorld extends HelloBase {
object core extends HelloWorldModule {
- override def twirlVersion = "1.3.15"
+ override def twirlAdditionalImports: Seq[String] = testAdditionalImports
+ override def twirlConstructorAnnotations: Seq[String] = testConstructorAnnotations
}
+
}
- val resourcePath: os.Path = os.pwd / 'contrib / 'twirllib / 'test / 'resources / "hello-world"
+ object HelloWorldWithInclusiveDot extends HelloBase {
+
+ object core extends HelloWorldModule {
+ override def twirlInclusiveDot: Boolean = true
+ }
+
+ }
def workspaceTest[T](
m: TestUtil.BaseModule,
- resourcePath: os.Path = resourcePath
+ resourcePathSuffix: String
)(t: TestEvaluator => T)(implicit tp: TestPath): T = {
val eval = new TestEvaluator(m)
os.remove.all(m.millSourcePath)
os.remove.all(eval.outPath)
os.makeDir.all(m.millSourcePath / os.up)
- os.copy(resourcePath, m.millSourcePath)
+ os.copy(
+ os.pwd / 'contrib / 'twirllib / 'test / 'resources / resourcePathSuffix,
+ m.millSourcePath
+ )
t(eval)
}
@@ -52,15 +64,20 @@ object HelloWorldTests extends TestSuite {
"import _root_.play.twirl.api.Xml"
)
- def additionalImports: Seq[String] = Seq(
+ def testAdditionalImports: Seq[String] = Seq(
"mill.twirl.test.AdditionalImport1._",
"mill.twirl.test.AdditionalImport2._"
)
+ def testConstructorAnnotations = Seq(
+ "@org.springframework.stereotype.Component()",
+ "@something.else.Thing()"
+ )
+
def tests: Tests = Tests {
'twirlVersion - {
- 'fromBuild - workspaceTest(HelloWorld) { eval =>
+ 'fromBuild - workspaceTest(HelloWorld, "hello-world") { eval =>
val Right((result, evalCount)) =
eval.apply(HelloWorld.core.twirlVersion)
@@ -70,7 +87,7 @@ object HelloWorldTests extends TestSuite {
)
}
}
- 'compileTwirl - workspaceTest(HelloWorld) { eval =>
+ 'compileTwirl - workspaceTest(HelloWorld, "hello-world") { eval =>
val Right((result, evalCount)) = eval.apply(HelloWorld.core.compileTwirl)
val outputFiles = os.walk(result.classes.path).filter(_.last.endsWith(".scala"))
@@ -86,8 +103,43 @@ object HelloWorldTests extends TestSuite {
evalCount > 0,
outputFiles.forall { p =>
val lines = os.read.lines(p).map(_.trim)
- (expectedDefaultImports ++ additionalImports.map(s => s"import $s")).forall(lines.contains)
- }
+ (expectedDefaultImports ++ testAdditionalImports.map(s => s"import $s")).forall(lines.contains)
+ },
+ outputFiles.filter(_.toString().contains("hello.template.scala")).forall { p =>
+ val lines = os.read.lines(p).map(_.trim)
+ val expectedClassDeclaration = s"class hello ${testConstructorAnnotations.mkString}"
+ lines.exists(_.startsWith(expectedClassDeclaration))
+ },
+
+ )
+
+ // don't recompile if nothing changed
+ val Right((_, unchangedEvalCount)) =
+ eval.apply(HelloWorld.core.compileTwirl)
+
+ assert(unchangedEvalCount == 0)
+ }
+ 'compileTwirlInclusiveDot - workspaceTest(HelloWorldWithInclusiveDot, "hello-world-inclusive-dot") { eval =>
+ val Right((result, evalCount)) = eval.apply(HelloWorldWithInclusiveDot.core.compileTwirl)
+
+ val outputFiles = os.walk(result.classes.path).filter(_.last.endsWith(".scala"))
+ val expectedClassfiles = compileClassfiles.map( name =>
+ eval.outPath / 'core / 'compileTwirl / 'dest / 'html / name.toString().replace(".template.scala", "$$TwirlInclusiveDot.template.scala")
+ )
+
+ println(s"outputFiles: $outputFiles")
+
+ assert(
+ result.classes.path == eval.outPath / 'core / 'compileTwirl / 'dest,
+ outputFiles.nonEmpty,
+ outputFiles.forall(expectedClassfiles.contains),
+ outputFiles.size == 2,
+ evalCount > 0,
+ outputFiles.filter(_.toString().contains("hello.template.scala")).forall { p =>
+ val lines = os.read.lines(p).map(_.trim)
+ lines.exists(_.contains("$$TwirlInclusiveDot"))
+ },
+
)
// don't recompile if nothing changed
diff --git a/docs/pages/9 - Contrib Modules.md b/docs/pages/9 - Contrib Modules.md
index cf63039a..b5ef8fc5 100644
--- a/docs/pages/9 - Contrib Modules.md
+++ b/docs/pages/9 - Contrib Modules.md
@@ -168,8 +168,10 @@ object app extends ScalaModule with TwirlModule {
#### Configuration options
* `def twirlVersion: T[String]` (mandatory) - the version of the twirl compiler to use, like "1.3.15"
-* `def twirlAdditionalImports: Seq[String] = Nil` - the additional imports that will be added by twirl compiler to the top
- of all templates
+* `def twirlAdditionalImports: Seq[String] = Nil` - the additional imports that will be added by twirl compiler to the top of all templates
+* `def twirlConstructorAnnotations: Seq[String] = Nil` - annotations added to the generated classes' constructors (note it only applies to templates with `@this(...)` constructors)
+* `def twirlCodec = Codec(Properties.sourceEncoding)` - the codec used to generate the files (the default is the same sbt plugin uses)
+* `def twirlInclusiveDot: Boolean = false`
#### Details