Skip to content

Commit

Permalink
feat(scala3): WIP working on scala 3 cross compilation. playframework…
Browse files Browse the repository at this point in the history
  • Loading branch information
brbrown25 committed Jan 31, 2022
1 parent 3d6185f commit 1efefac
Show file tree
Hide file tree
Showing 18 changed files with 1,096 additions and 29 deletions.
12 changes: 12 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ jobs:
- SCALA_VERSION=2.13.8
- ADOPTOPENJDK=11

- name: "Run tests with Scala 3 and AdoptOpenJDK 11"
script: scripts/test-code.sh
env:
- TRAVIS_SCALA_VERSION=3.1.0
- ADOPTOPENJDK=11

- name: "Run tests with Scala 2.12 and AdoptOpenJDK 8"
script: scripts/test-code.sh
env:
Expand All @@ -40,6 +46,12 @@ jobs:
- SCALA_VERSION=2.13.8
- ADOPTOPENJDK=8

- name: "Run tests with Scala 3 and AdoptOpenJDK 8"
script: scripts/test-code.sh
env:
- TRAVIS_SCALA_VERSION=3.1.0
- ADOPTOPENJDK=8

- stage: docs
script: scripts/validate-docs.sh
name: "Validate documentation"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (C) Lightbend Inc. <https://www.lightbend.com>
*/
package play.twirl.api

import java.util.Optional
import scala.collection.immutable
import scala.jdk.CollectionConverters._
import scala.reflect.ClassTag

case class BaseScalaTemplate[T <: Appendable[T], F <: Format[T]](format: F) {
// The overloaded methods are here for speed. The compiled templates
// can take advantage of them for a 12% performance boost
def _display_(x: AnyVal): T = format.escape(x.toString)
def _display_(x: String): T = if (x eq null) format.empty else format.escape(x)
def _display_(x: Unit): T = format.empty
def _display_(x: scala.xml.NodeSeq): T = if (x eq null) format.empty else format.raw(x.toString())
def _display_(x: T): T = if (x eq null) format.empty else x

def _display_(o: Any)(implicit m: ClassTag[T]): T = {
o match {
case escaped if escaped != null && escaped.getClass == m.runtimeClass => escaped.asInstanceOf[T]
case () => format.empty
case None => format.empty
case Some(v) => _display_(v)
case key: Optional[_] =>
(if (key.isPresent) Some(key.get) else None) match {
case None => format.empty
case Some(v) => _display_(v)
case _ => format.empty
}
case xml: scala.xml.NodeSeq => format.raw(xml.toString())
case escapeds: immutable.Seq[_] => format.fill(escapeds.map(_display_))
case escapeds: TraversableOnce[_] => format.fill(escapeds.iterator.map(_display_).toList)
case escapeds: Array[_] => format.fill(escapeds.view.map(_display_).toList)
case escapeds: java.util.List[_] =>
format.fill(escapeds.asScala.map(_display_).toList)
case string: String => format.escape(string)
case v if v != null => format.escape(v.toString)
case _ => format.empty
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (C) Lightbend Inc. <https://www.lightbend.com>
*/
package play.twirl.api

import scala.language.implicitConversions

/**
* Imports for useful Twirl helpers.
*/
object TwirlHelperImports {

/** Allows Java collections to be used as if they were Scala collections. */
implicit def twirlJavaCollectionToScala[T](x: java.lang.Iterable[T]): Iterable[T] = {
import scala.jdk.CollectionConverters._
x.asScala
}

/** Allows inline formatting of java.util.Date */
implicit class TwirlRichDate(date: java.util.Date) {
def format(pattern: String): String = {
new java.text.SimpleDateFormat(pattern).format(date)
}
}

/** Adds a when method to Strings to control when they are rendered. */
implicit class TwirlRichString(string: String) {
def when(predicate: => Boolean): String = {
predicate match {
case true => string
case false => ""
}
}
}
}
47 changes: 47 additions & 0 deletions api/shared/src/main/scala-2.13/play/twirl/api/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (C) Lightbend Inc. <https://www.lightbend.com>
*/
package play.twirl

import scala.reflect.ClassTag

package object api {

/**
* Brings the template engine as a
* [[http://docs.scala-lang.org/overviews/core/string-interpolation.html string interpolator]].
*
* Basic usage:
*
* {{{
* import play.twirl.api.StringInterpolation
*
* val name = "Martin"
* val htmlFragment: Html = html"&lt;div&gt;Hello \$name&lt;/div&gt;"
* }}}
*
* Three interpolators are available: `html`, `xml` and `js`.
*/
implicit class StringInterpolation(val sc: StringContext) extends AnyVal {
def html(args: Any*): Html = interpolate(args, HtmlFormat)

def xml(args: Any*): Xml = interpolate(args, XmlFormat)

def js(args: Any*): JavaScript = interpolate(args, JavaScriptFormat)

def interpolate[A <: Appendable[A]: ClassTag](args: Seq[Any], format: Format[A]): A = {
StringContext.checkLengths(args, sc.parts)
val array = Array.ofDim[Any](args.size + sc.parts.size)
val strings = sc.parts.iterator
val expressions = args.iterator
array(0) = format.raw(strings.next())
var i = 1
while (strings.hasNext) {
array(i) = expressions.next()
array(i + 1) = format.raw(strings.next())
i += 2
}
new BaseScalaTemplate[A, Format[A]](format)._display_(array)
}
}
}
43 changes: 43 additions & 0 deletions api/shared/src/main/scala-3/play/twirl/api/BaseScalaTemplate.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (C) Lightbend Inc. <https://www.lightbend.com>
*/
package play.twirl.api

import java.util.Optional
import scala.collection.immutable
import scala.jdk.CollectionConverters._
import scala.reflect.ClassTag

case class BaseScalaTemplate[T <: Appendable[T], F <: Format[T]](format: F) {
// The overloaded methods are here for speed. The compiled templates
// can take advantage of them for a 12% performance boost
def _display_(x: AnyVal): T = format.escape(x.toString)
def _display_(x: String): T = if (x eq null) format.empty else format.escape(x)
def _display_(x: Unit): T = format.empty
def _display_(x: scala.xml.NodeSeq): T = if (x eq null) format.empty else format.raw(x.toString())
def _display_(x: T): T = if (x eq null) format.empty else x

def _display_(o: Any)(implicit m: ClassTag[T]): T = {
o match {
case escaped if escaped != null && escaped.getClass == m.runtimeClass => escaped.asInstanceOf[T]
case () => format.empty
case None => format.empty
case Some(v) => _display_(v)
case key: Optional[_] =>
(if (key.isPresent) Some(key.get) else None) match {
case None => format.empty
case Some(v) => _display_(v)
case null => format.empty
}
case xml: scala.xml.NodeSeq => format.raw(xml.toString())
case escapeds: immutable.Seq[_] => format.fill(escapeds.map(_display_))
case escapeds: TraversableOnce[_] => format.fill(escapeds.iterator.map(_display_).toList)
case escapeds: Array[_] => format.fill(escapeds.view.map(_display_).toList)
case escapeds: java.util.List[_] =>
format.fill(escapeds.asScala.map(_display_).toList)
case string: String => format.escape(string)
case v if v != null => format.escape(v.toString)
case null => format.empty
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (C) Lightbend Inc. <https://www.lightbend.com>
*/
package play.twirl.api

import scala.language.implicitConversions

/**
* Imports for useful Twirl helpers.
*/
object TwirlHelperImports {

/** Allows Java collections to be used as if they were Scala collections. */
implicit def twirlJavaCollectionToScala[T](x: java.lang.Iterable[T]): Iterable[T] = {
import scala.jdk.CollectionConverters._
x.asScala
}

/** Allows inline formatting of java.util.Date */
implicit class TwirlRichDate(date: java.util.Date) {
def format(pattern: String): String = {
new java.text.SimpleDateFormat(pattern).format(date)
}
}

/** Adds a when method to Strings to control when they are rendered. */
implicit class TwirlRichString(string: String) {
def when(predicate: => Boolean): String = {
predicate match {
case true => string
case false => ""
}
}
}
}
47 changes: 47 additions & 0 deletions api/shared/src/main/scala-3/play/twirl/api/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (C) Lightbend Inc. <https://www.lightbend.com>
*/
package play.twirl

import scala.reflect.ClassTag

package object api {

/**
* Brings the template engine as a
* [[http://docs.scala-lang.org/overviews/core/string-interpolation.html string interpolator]].
*
* Basic usage:
*
* {{{
* import play.twirl.api.StringInterpolation
*
* val name = "Martin"
* val htmlFragment: Html = html"&lt;div&gt;Hello \$name&lt;/div&gt;"
* }}}
*
* Three interpolators are available: `html`, `xml` and `js`.
*/
implicit class StringInterpolation(val sc: StringContext) extends AnyVal {
def html(args: Any*): Html = interpolate(args, HtmlFormat)

def xml(args: Any*): Xml = interpolate(args, XmlFormat)

def js(args: Any*): JavaScript = interpolate(args, JavaScriptFormat)

def interpolate[A <: Appendable[A]: ClassTag](args: Seq[Any], format: Format[A]): A = {
StringContext.checkLengths(args, sc.parts)
val array = Array.ofDim[Any](args.size + sc.parts.size)
val strings = sc.parts.iterator
val expressions = args.iterator
array(0) = format.raw(strings.next())
var i = 1
while (strings.hasNext) {
array(i) = expressions.next()
array(i + 1) = format.raw(strings.next())
i += 2
}
new BaseScalaTemplate[A, Format[A]](format)._display_(array)
}
}
}
35 changes: 27 additions & 8 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import Dependencies._

import sbtcrossproject.crossProject
import sbtcrossproject.CrossPlugin.autoImport.crossProject
import org.scalajs.jsenv.nodejs.NodeJSEnv

Global / onChangedBuildSource := ReloadOnSourceChanges

// Binary compatibility is this version
val previousVersion: Option[String] = Some("1.5.0")

Expand Down Expand Up @@ -56,6 +58,8 @@ lazy val api = crossProject(JVMPlatform, JSPlatform)
.enablePlugins(Common, Playdoc, Omnidoc)
.configs(Docs)
.settings(
scalaVersion := Scala212,
crossScalaVersions := ScalaVersions,
mimaSettings,
name := "twirl-api",
jsEnv := nodeJs,
Expand Down Expand Up @@ -83,6 +87,8 @@ lazy val parser = project
.in(file("parser"))
.enablePlugins(Common, Omnidoc)
.settings(
scalaVersion := Scala212,
crossScalaVersions := ScalaVersions,
mimaSettings,
name := "twirl-parser",
libraryDependencies += parserCombinators(scalaVersion.value) % Optional,
Expand All @@ -93,14 +99,24 @@ lazy val parser = project
lazy val compiler = project
.in(file("compiler"))
.enablePlugins(Common, Omnidoc)
.dependsOn(apiJvm, parser % "compile;test->test")
.settings(
scalaVersion := Scala212,
crossScalaVersions := ScalaVersions,
mimaSettings,
name := "twirl-compiler",
libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value,
libraryDependencies += parserCombinators(scalaVersion.value) % "optional",
run / fork := true,
name := "twirl-compiler",
libraryDependencies ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, _)) =>
// only for scala < 3
Seq("org.scala-lang" % "scala-compiler" % scalaVersion.value)
case _ => Seq("org.scala-lang" %% "scala3-compiler" % scalaVersion.value)
}
},
libraryDependencies += parserCombinators(scalaVersion.value) % Optional,
run / fork := true
)
.aggregate(apiJvm, parser)
.dependsOn(apiJvm, parser % "compile->compile;test->test")

lazy val plugin = project
.in(file("sbt-twirl"))
Expand All @@ -112,7 +128,10 @@ lazy val plugin = project
scalaVersion := Scala212,
libraryDependencies += "org.scalatest" %%% "scalatest" % ScalaTestVersion % Test,
Compile / resourceGenerators += generateVersionFile.taskValue,
scriptedLaunchOpts += version.apply { v => s"-Dproject.version=$v" }.value,
scriptedLaunchOpts := {
scriptedLaunchOpts.value ++
Seq("-Xmx1024M", s"-Dplugin.version=${version.value}", s"-Dproject.version=${version.value}")
},
// both `locally`s are to work around sbt/sbt#6161
scriptedDependencies := {
locally { val _ = scriptedDependencies.value }
Expand All @@ -127,7 +146,7 @@ lazy val plugin = project
}
()
},
mimaFailOnNoPrevious := false,
mimaFailOnNoPrevious := false
)

// Version file
Expand Down
Loading

0 comments on commit 1efefac

Please sign in to comment.