Skip to content

Commit

Permalink
0.9.0: resolve #30 "scala: option to use back ticks"
Browse files Browse the repository at this point in the history
also adjustments regarding keys with $ and quoted strings:
- key containing $ is left alone (even if it's quoted).
  This mainly due to Config restrictions on keys involving $
- otherwise, the key is unquoted (if quoted of course)
  • Loading branch information
carueda committed Feb 12, 2018
1 parent 4c508f5 commit d630696
Show file tree
Hide file tree
Showing 14 changed files with 196 additions and 86 deletions.
2 changes: 1 addition & 1 deletion build.sbt
@@ -1,4 +1,4 @@
lazy val tscfgVersion = setVersion("0.8.4")
lazy val tscfgVersion = setVersion("0.9.0")

organization := "com.github.carueda"
name := "tscfg"
Expand Down
8 changes: 8 additions & 0 deletions changelog.md
@@ -1,3 +1,11 @@
2018-02-11 - 0.9.0

- resolve #30 "scala: option to use back ticks"
- Adjustments regarding keys with $ and quoted strings:
- key containing $ is left alone (even if it's quoted).
This mainly due to Config restrictions on keys involving $
- otherwise, the key is unquoted (if quoted of course)

2018-02-11 - 0.8.4

- internal: use scala 2.12 (but cross compile to 2.11 too)
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/application.conf
@@ -1 +1 @@
tscfg.version = 0.8.4
tscfg.version = 0.9.0
8 changes: 7 additions & 1 deletion src/main/scala/tscfg/Main.scala
Expand Up @@ -34,6 +34,7 @@ object Main {
| --dd <destDir> ($defaultDestDir)
| --j7 generate code for java <= 7 (8)
| --scala generate scala code (java)
| --scala:` use backsticks (false)
| --java generate java code (the default)
| --tpl <filename> generate config template (no default)
| --tpl.ind <string> template indentation string ("${templateOpts.indent}")
Expand All @@ -47,6 +48,7 @@ object Main {
destDir: String = defaultDestDir,
j7: Boolean = false,
language: String = "java",
useBacksticks: Boolean = false,
tplFilename: Option[String] = None
)

Expand Down Expand Up @@ -86,6 +88,9 @@ object Main {
case "--scala" :: rest =>
traverseList(rest, opts.copy(language = "scala"))

case "--scala:`" :: rest =>
traverseList(rest, opts.copy(useBacksticks = true))

case "--java" :: rest =>
traverseList(rest, opts.copy(language = "java"))

Expand Down Expand Up @@ -127,7 +132,8 @@ object Main {
val destFile = new File(destFilename)
val out = new PrintWriter(destFile)

val genOpts = GenOpts(opts.packageName, opts.className, opts.j7)
val genOpts = GenOpts(opts.packageName, opts.className, opts.j7,
useBacksticks = opts.useBacksticks)

println(s"parsing: $inputFilename")
val source = io.Source.fromFile(new File(inputFilename)).mkString.trim
Expand Down
6 changes: 5 additions & 1 deletion src/main/scala/tscfg/ModelBuilder.scala
Expand Up @@ -73,7 +73,11 @@ class ModelBuilder {
val optFromComments = comments.exists(_.trim.startsWith("@optional"))
val commentsOpt = if (comments.isEmpty) None else Some(comments.mkString("\n"))

name -> model.AnnType(childType,
// per Lightbend Config restrictions involving $, leave the key alone if
// contains $, otherwise unquote the key in case is quoted.
val adjName = if (name.contains("$")) name else name.replaceAll("^\"|\"$", "")

adjName -> model.AnnType(childType,
optional = optional || optFromComments,
default = default,
comments = commentsOpt
Expand Down
3 changes: 2 additions & 1 deletion src/main/scala/tscfg/generators/Generator.scala
Expand Up @@ -25,7 +25,8 @@ abstract class Generator(genOpts: GenOpts) {
*/
case class GenOpts(packageName: String,
className: String,
j7: Boolean
j7: Boolean,
useBacksticks: Boolean = false
)

case class GenResult(code: String = "?",
Expand Down
17 changes: 11 additions & 6 deletions src/main/scala/tscfg/generators/scala/ScalaGen.scala
@@ -1,7 +1,6 @@
package tscfg.generators.scala

import tscfg.{ModelBuilder, model}
import tscfg.generators.scala.scalaUtil.scalaIdentifier
import tscfg.generators._
import tscfg.model._
import tscfg.util.escapeString
Expand All @@ -14,6 +13,9 @@ class ScalaGen(genOpts: GenOpts) extends Generator(genOpts) {
val getter = Getter(hasPath, accessors, methodNames)
import methodNames._

val scalaUtil: ScalaUtil = new ScalaUtil(useBacksticks = genOpts.useBacksticks)
import scalaUtil.{scalaIdentifier, getClassName}

def generate(objectType: ObjectType): GenResult = {
genResults = GenResult()

Expand Down Expand Up @@ -53,11 +55,10 @@ class ScalaGen(genOpts: GenOpts) extends Generator(genOpts) {
def padId(id: String) = id + (" " * (padScalaIdLength - id.length))

val results = symbols.map { symbol
val scalaId = scalaIdentifier(symbol)
val a = ot.members(symbol)
val res = generate(a.t,
classNamesPrefix = className+"." :: classNamesPrefix,
className = scalaUtil.getClassName(symbol)
className = getClassName(symbol)
)
(symbol, res)
}
Expand Down Expand Up @@ -158,7 +159,10 @@ object ScalaGen {
import tscfg.util

// $COVERAGE-OFF$
def generate(filename: String, showOut: Boolean = false): GenResult = {
def generate(filename: String,
showOut: Boolean = false,
useBacksticks: Boolean = false
): GenResult = {
val file = new File("src/main/tscfg/" + filename)
val source = io.Source.fromFile(file).mkString.trim

Expand All @@ -182,7 +186,8 @@ object ScalaGen {
}
}

val genOpts = GenOpts("tscfg.example", className, j7 = false)
val genOpts = GenOpts("tscfg.example", className, j7 = false,
useBacksticks = useBacksticks)

val generator = new ScalaGen(genOpts)

Expand Down Expand Up @@ -440,7 +445,7 @@ private[scala] class Accessors {
val methodDef =
s"""
|private def $methodName(cl:com.typesafe.config.ConfigList): scala.List[$scalaType] = {
| import scala.collection.JavaConverters._
| import scala.collection.JavaConverters._
| cl.asScala.map(cv => $elem).toList
|}""".stripMargin.trim
(methodName, methodDef)
Expand Down
101 changes: 101 additions & 0 deletions src/main/scala/tscfg/generators/scala/ScalaUtil.scala
@@ -0,0 +1,101 @@
package tscfg.generators.scala

import tscfg.generators.java.javaUtil
import tscfg.util

/**
* By default [[scalaIdentifier]] uses underscores as needed for various cases
* that need translation of the given identifier to make it valid Scala.
* Similarly, [[getClassName]] also does some related logic.
*
* With this flag set to true, both methods will change their logic to uses
* backsticks instead of replacing or removing the characters that would
* make the resulting identifiers invalid.
*
* @param useBacksticks False by default
*/
class ScalaUtil(useBacksticks: Boolean = false) {
import ScalaUtil._

/**
* Returns a valid scala identifier from the given symbol:
*
* - encloses the symbol in backticks if the symbol is a scala reserved word;
* - appends an underscore if the symbol corresponds to a no-arg method in scope;
* - otherwise:
* - returns symbol if it is a valid java identifier
* - otherwise:
* if useBacksticks is true, enclose symbol in backsticks
* otherwise, returns `javaGenerator.javaIdentifier(symbol)`
*/
def scalaIdentifier(symbol: String): String = {
if (scalaReservedWords.contains(symbol)) "`" + symbol + "`"
else if (noArgMethodInScope.contains(symbol)) symbol + "_"
else if (javaUtil.isJavaIdentifier(symbol)) symbol
else if (useBacksticks) "`" + symbol + "`"
else javaUtil.javaIdentifier(symbol)
}

/**
* Returns a class name from the given symbol.
* If useBacksticks:
* This is basically capitalizing the first character that
* can be capitalized. If none, then a `U` is prepended.
* Otherwise:
* Since underscores are specially used in generated code,
* this method camelizes the symbol in case of any underscores.
*/
def getClassName(symbol: String): String = {
if (useBacksticks) {
val scalaId = scalaIdentifier(symbol)
val search = scalaId.zipWithIndex.find { case (c, _) c.toUpper != c }
search match {
case Some((c, index))
scalaId.substring(0, index) + c.toUpper + scalaId.substring(index + 1)

case None
if (scalaId.head == '`')
"`U" + scalaId.substring(1)
else
"U" + scalaId
}
}
else { // preserve behavior until v0.8.4
// regular javaUtil.javaIdentifier as it might generate underscores:
val id = javaUtil.javaIdentifier(symbol)
// note: not scalaIdentifier because we are going to camelize anyway
val parts = id.split("_")
val name = parts.map(util.upperFirst).mkString
if (name.nonEmpty) scalaIdentifier(name)
else "U" + id.count(_ == '_') // named based on # of underscores ;)
}
}
}

object ScalaUtil {
/**
* The ones from Sect 1.1 of the Scala Language Spec, v2.9
* plus `_`
*/
val scalaReservedWords: List[String] = List(
"_",
"abstract", "case", "catch", "class", "def",
"do", "else", "extends", "false", "final",
"finally", "for", "forSome", "if", "implicit",
"import", "lazy", "match", "new", "null",
"object", "override", "package", "private", "protected",
"return", "sealed", "super", "this", "throw",
"trait", "try", "true", "type", "val",
"var", "while", "with", "yield"
)

val noArgMethodInScope: List[String] = List(
"clone",
"finalize",
"getClass",
"notify",
"notifyAll",
"toString",
"wait"
)
}
63 changes: 0 additions & 63 deletions src/main/scala/tscfg/generators/scala/scalaUtil.scala

This file was deleted.

9 changes: 8 additions & 1 deletion src/main/scala/tscfg/model.scala
Expand Up @@ -74,7 +74,14 @@ object model {
def apply(elems: (String, AnnType)*): ObjectType = {
val x = elems.groupBy(_._1).mapValues(_.length).filter(_._2 > 1)
if (x.nonEmpty) throw new RuntimeException(s"key repeated in object: ${x.head}")
ObjectType(Map(elems : _*))

val noQuotes = elems map { case (k, v)
// per Lightbend Config restrictions involving $, leave the key alone if
// contains $, otherwise unquote the key in case is quoted.
val adjName = if (k.contains("$")) k else k.replaceAll("^\"|\"$", "")
adjName.replaceAll("^\"|\"$", "") -> v
}
ObjectType(Map(noQuotes : _*))
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/main/tscfg/example/issue30.spec.conf
@@ -0,0 +1,5 @@
foo-object {
bar-baz: string
0: string
}
"other#stuff": int
10 changes: 5 additions & 5 deletions src/test/scala/tscfg/generators/java/JavaMainSpec.scala
Expand Up @@ -299,12 +299,12 @@ class JavaMainSpec extends Specification {
}

"issue19" should {
"""replace leading and trailing " with _""" in {
"""put underscores for key having $""" in {
val r = JavaGen.generate("example/issue19.spec.conf")
r.classNames === Set("JavaIssue19Cfg")
r.fields === Map(
"_do_log_" "boolean",
"_$_foo_" "java.lang.String"
"do_log" "boolean",
"_$_foo_" "java.lang.String"
)
}

Expand All @@ -315,8 +315,8 @@ class JavaMainSpec extends Specification {
|"$_foo" : some string
""".stripMargin
))
c._do_log_ === true
c._$_foo_ === "some string"
c.do_log === true
c._$_foo_ === "some string"
}
}

Expand Down

0 comments on commit d630696

Please sign in to comment.