Skip to content

Commit

Permalink
add option to use other renderer easily
Browse files Browse the repository at this point in the history
  • Loading branch information
axos88 committed Jan 6, 2017
1 parent 7b8675d commit 0f74e82
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 125 deletions.
2 changes: 1 addition & 1 deletion src/main/scala/sbtbuildinfo/BuildInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ object BuildInfo {

def makeFile(file: File): File = {
val values = results(keys, options, proj, state)
val lines = renderer.header ++ renderer.renderKeys(values) ++ renderer.footer
val lines = renderer.renderKeys(values)
IO.writeLines(file, lines, IO.utf8)
file
}
Expand Down
1 change: 1 addition & 0 deletions src/main/scala/sbtbuildinfo/BuildInfoKeys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Keys._
trait BuildInfoKeys {
lazy val buildInfo = taskKey[Seq[File]]("The task that generates the build info.")
lazy val buildInfoRenderer = settingKey[BuildInfoRenderer]("The renderer to use to generate the build info.")
lazy val buildInfoRenderFactory = settingKey[BuildInfoRenderer.Factory]("The renderFactory to used to build the renderer.")
lazy val buildInfoObject = settingKey[String]("The name for the generated object.")
lazy val buildInfoPackage = settingKey[String]("The name for the generated package.")
lazy val buildInfoUsePackageAsPath = settingKey[Boolean]("If true, the generated object is placed in the folder of the package instead of \"sbt-buildinfo\".")
Expand Down
5 changes: 3 additions & 2 deletions src/main/scala/sbtbuildinfo/BuildInfoPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ object BuildInfoPlugin extends sbt.AutoPlugin {
resourceGenerators ++= {
if(buildInfoRenderer.value.isResource) Seq(buildInfo.taskValue) else Nil
},
buildInfoRenderer := ScalaClassRenderer(
buildInfoRenderer := buildInfoRenderFactory.value.apply(
buildInfoOptions.value,
buildInfoPackage.value,
buildInfoObject.value)
Expand All @@ -87,6 +87,7 @@ object BuildInfoPlugin extends sbt.AutoPlugin {
buildInfoUsePackageAsPath := false,
buildInfoKeys := Seq(name, version, scalaVersion, sbtVersion),
buildInfoBuildNumber := buildNumberTask(baseDirectory.value, 1),
buildInfoOptions := Seq()
buildInfoOptions := Seq(),
buildInfoRenderFactory := ScalaCaseObjectRenderer.apply
)
}
6 changes: 4 additions & 2 deletions src/main/scala/sbtbuildinfo/BuildInfoRenderer.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package sbtbuildinfo

object BuildInfoRenderer {
type Factory = (Seq[BuildInfoOption], String, String) => BuildInfoRenderer
}

trait BuildInfoRenderer {

def fileType: BuildInfoType
def extension: String
def header: Seq[String]
def renderKeys(infoKeysNameAndValues: Seq[BuildInfoResult]): Seq[String]
def footer: Seq[String]

def isSource = fileType == BuildInfoType.Source
def isResource = fileType == BuildInfoType.Resource
Expand Down
72 changes: 72 additions & 0 deletions src/main/scala/sbtbuildinfo/ScalaCaseClassRenderer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package sbtbuildinfo

case class ScalaCaseClassRenderer(options: Seq[BuildInfoOption], pkg: String, obj: String) extends ScalaRenderer {
override def fileType = BuildInfoType.Source
override def extension = "scala"

val traitNames = options.collect{case BuildInfoOption.Traits(ts @ _*) => ts}.flatten
val objTraits = if (traitNames.isEmpty) "" else " extends " ++ traitNames.mkString(" with ")

// It is safe to add `import scala.Predef` even though we need to keep `-Ywarn-unused-import` in mind
// because we always generate code that has a reference to `String`. If the "base" generated code were to be
// changed and no longer contain a reference to `String`, we would need to remove `import scala.Predef` and
// fully qualify every reference. Note it is NOT safe to use `import scala._` because of the possibility of
// the project using `-Ywarn-unused-import` because we do not always generated references that are part of
// `scala` such as `scala.Option`.
def header = List(
s"package $pkg",
"",
"import scala.Predef._",
"",
s"/** This file was generated by sbt-buildinfo. */"
)

override def renderKeys(buildInfoResults: Seq[BuildInfoResult]) =
header ++
caseClassDefinitionBegin ++
buildInfoResults.flatMap(caseClassParameter).mkString(",\n").split("\n") ++
caseClassDefinitionEnd ++
toMapMethod(buildInfoResults) ++
caseClassEnd ++
List("") ++
caseObjectLine(buildInfoResults)

private def caseClassDefinitionBegin = List(
s"case class $obj$objTraits("
)

private def caseClassParameter(r: BuildInfoResult): Seq[String] = {
val typeDecl = getType(r.typeExpr) getOrElse "Any"

List(
s" ${r.identifier}: $typeDecl"
)
}

private def toMapMethod(results: Seq[BuildInfoResult]) =
if (options.contains(BuildInfoOption.ToMap))
results
.map(result => " \"%s\" -> %s".format(result.identifier, result.identifier))
.mkString(" def toMap: Map[String, Any] = Map[String, Any](\n", ",\n", ")")
.split("\n")
.toList ::: List("")
else Nil

private def caseClassDefinitionEnd = List(") {", "")
private def caseClassEnd = List("}")

private def caseObjectLine(buildInfoResults: Seq[BuildInfoResult]) = List(
s"case object $obj {",
s" val data: $obj = new $obj(${buildInfoResults.map(_.value).map(quote).mkString(",")})",
s"}"
)

def toMapLine(results: Seq[BuildInfoResult]): Seq[String] =
if (options.contains(BuildInfoOption.ToMap) || options.contains(BuildInfoOption.ToJson))
results
.map(result => " \"%s\" -> %s".format(result.identifier, result.identifier))
.mkString(" val toMap: Map[String, Any] = Map[String, Any](\n", ",\n", ")")
.split("\n")
.toList ::: List("")
else Nil
}
68 changes: 68 additions & 0 deletions src/main/scala/sbtbuildinfo/ScalaCaseObjectRenderer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package sbtbuildinfo

private[sbtbuildinfo] case class ScalaCaseObjectRenderer(options: Seq[BuildInfoOption], pkg: String, obj: String) extends ScalaRenderer {

override def fileType = BuildInfoType.Source
override def extension = "scala"
val traitNames = options.collect{case BuildInfoOption.Traits(ts @ _*) => ts}.flatten
val objTraits = if (traitNames.isEmpty) "" else " extends " ++ traitNames.mkString(" with ")

// It is safe to add `import scala.Predef` even though we need to keep `-Ywarn-unused-import` in mind
// because we always generate code that has a reference to `String`. If the "base" generated code were to be
// changed and no longer contain a reference to `String`, we would need to remove `import scala.Predef` and
// fully qualify every reference. Note it is NOT safe to use `import scala._` because of the possibility of
// the project using `-Ywarn-unused-import` because we do not always generated references that are part of
// `scala` such as `scala.Option`.
def header = List(
s"package $pkg",
"",
"import scala.Predef._",
"",
s"/** This object was generated by sbt-buildinfo. */",
s"case object $obj$objTraits {"
)

def footer = List("}")

override def renderKeys(buildInfoResults: Seq[BuildInfoResult]) =
header ++
buildInfoResults.flatMap(line) ++ Seq(toStringLines(buildInfoResults)) ++
toMapLine(buildInfoResults) ++ toJsonLine ++
footer

private def line(result: BuildInfoResult): Seq[String] = {
import result._
val typeDecl = getType(result.typeExpr) map { ": " + _ } getOrElse ""

List(
s" /** The value is ${quote(value)}. */",
s" val $identifier$typeDecl = ${quote(value)}"
)
}

def toStringLines(results: Seq[BuildInfoResult]): String = {
val idents = results.map(_.identifier)
val fmt = idents.map("%s: %%s" format _).mkString(", ")
val vars = idents.mkString(", ")
s""" override val toString: String = {
| "$fmt" format (
| $vars
| )
| }""".stripMargin
}

def toMapLine(results: Seq[BuildInfoResult]): Seq[String] =
if (options.contains(BuildInfoOption.ToMap) || options.contains(BuildInfoOption.ToJson))
results
.map(result => " \"%s\" -> %s".format(result.identifier, result.identifier))
.mkString(" val toMap: Map[String, Any] = Map[String, Any](\n", ",\n", ")")
.split("\n")
.toList ::: List("")
else Nil

def toJsonLine: Seq[String] =
if (options contains BuildInfoOption.ToJson)
List(""" val toJson: String = toMap.map(i => "\"" + i._1 + "\":\"" + i._2 + "\"").mkString("{", ", ", "}")""")
else Nil

}
120 changes: 0 additions & 120 deletions src/main/scala/sbtbuildinfo/ScalaClassRenderer.scala

This file was deleted.

63 changes: 63 additions & 0 deletions src/main/scala/sbtbuildinfo/ScalaRenderer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package sbtbuildinfo

/**
* Created by akos on 1/6/17.
*/
abstract class ScalaRenderer extends BuildInfoRenderer {
protected def getType(typeExpr: TypeExpression): Option[String] = {
def tpeToReturnType(tpe: TypeExpression): Option[String] =
tpe match {
case TypeExpression("Any", Nil) => None
case TypeExpression("Int", Nil) => Some("scala.Int")
case TypeExpression("Long", Nil) => Some("scala.Long")
case TypeExpression("Double", Nil) => Some("scala.Double")
case TypeExpression("Boolean", Nil) => Some("scala.Boolean")
case TypeExpression("scala.Symbol", Nil) => Some("scala.Symbol")
case TypeExpression("java.lang.String", Nil) => Some("String")
case TypeExpression("java.net.URL", Nil) => Some("java.net.URL")
case TypeExpression("sbt.URL", Nil) => Some("java.net.URL")
case TypeExpression("java.io.File", Nil) => Some("java.io.File")
case TypeExpression("sbt.File", Nil) => Some("java.io.File")
case TypeExpression("scala.xml.NodeSeq", Nil) => Some("scala.xml.NodeSeq")

case TypeExpression("sbt.ModuleID", Nil) => Some("String")
case TypeExpression("sbt.Resolver", Nil) => Some("String")

case TypeExpression("scala.Option", Seq(arg)) =>
tpeToReturnType(arg) map { x => s"scala.Option[$x]" }
case TypeExpression("scala.collection.Seq", Seq(arg)) =>
tpeToReturnType(arg) map { x => s"scala.collection.Seq[$x]" }
case TypeExpression("scala.collection.immutable.Map", Seq(arg0, arg1)) =>
for {
x0 <- tpeToReturnType(arg0)
x1 <- tpeToReturnType(arg1)
} yield s"Map[$x0, $x1]"
case TypeExpression("scala.Tuple2", Seq(arg0, arg1)) =>
for {
x0 <- tpeToReturnType(arg0)
x1 <- tpeToReturnType(arg1)
} yield s"($x0, $x1)"
case _ => None
}
tpeToReturnType(typeExpr)
}

protected def quote(v: Any): String = v match {
case x @ ( _: Int | _: Double | _: Boolean | _: Symbol) => x.toString
case x: Long => x.toString + "L"
case node: scala.xml.NodeSeq if node.toString().trim.nonEmpty => node.toString()
case (k, _v) => "(%s -> %s)" format(quote(k), quote(_v))
case mp: Map[_, _] => mp.toList.map(quote(_)).mkString("Map(", ", ", ")")
case seq: Seq[_] => seq.map(quote).mkString("scala.collection.Seq(", ", ", ")")
case op: Option[_] => op map { x => "scala.Some(" + quote(x) + ")" } getOrElse {"scala.None"}
case url: java.net.URL => "new java.net.URL(%s)" format quote(url.toString)
case file: java.io.File => "new java.io.File(%s)" format quote(file.toString)
case s => "\"%s\"" format encodeStringLiteral(s.toString)
}

protected def encodeStringLiteral(str: String): String =
str.replace("\\","\\\\").replace("\n","\\n").replace("\b","\\b").replace("\r","\\r").
replace("\t","\\t").replace("\'","\\'").replace("\f","\\f").replace("\"","\\\"")


}

0 comments on commit 0f74e82

Please sign in to comment.