diff --git a/changelog.md b/changelog.md index 76a496b..2b73174 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,6 @@ +2016-12-12 - 0.7.0 + - capture model build warnings + 2016-12-01 - 0.7.0 - another model revision diff --git a/src/main/scala/tscfg/Main.scala b/src/main/scala/tscfg/Main.scala index 8757287..9922857 100644 --- a/src/main/scala/tscfg/Main.scala +++ b/src/main/scala/tscfg/Main.scala @@ -23,16 +23,16 @@ object Main { val defaultDestDir: String = "/tmp" val usage: String = s""" - |tscfg $version - |Usage: tscfg.Main --spec inputFile [options] - |Options (default): - | --pn (${defaultGenOpts.packageName}) - | --cn (${defaultGenOpts.className}) - | --dd ($defaultDestDir) - | --j7 generate code for java <= 7 (8) - | --scala generate scala code (java) - | --java generate java code (the default) - |Output is written to $$destDir/$$className.ext + |tscfg $version + |Usage: tscfg.Main --spec inputFile [options] + |Options (default): + | --pn (${defaultGenOpts.packageName}) + | --cn (${defaultGenOpts.className}) + | --dd ($defaultDestDir) + | --j7 generate code for java <= 7 (8) + | --scala generate scala code (java) + | --java generate java code (the default) + |Output is written to $$destDir/$$className.ext """.stripMargin case class CmdLineOpts(inputFilename: Option[String] = None, @@ -41,7 +41,7 @@ object Main { destDir: String = defaultDestDir, j7: Boolean = false, language: String = "java" - ) + ) def main(args: Array[String]): Unit = { generate(getOpts(args.toList)) @@ -113,9 +113,17 @@ object Main { println(s"parsing: $inputFilename") val source = io.Source.fromFile(new File(inputFilename)).mkString.trim - val objectType = ModelBuilder(source) + + val buildResult = ModelBuilder(source) + val objectType = buildResult.objectType + //println("\nobjectType:\n |" + objectType.format().replaceAll("\n", "\n |")) + if (buildResult.warnings.nonEmpty) { + println("WARNINGS:") + buildResult.warnings.foreach(w ⇒ println(s" line ${w.line}: ${w.source}: ${w.message}")) + } + println(s"generating: $destFile") val pw = out match { case w: PrintWriter => w @@ -137,14 +145,14 @@ object Main { out.close() -/* - opts.templates foreach { genTemplate => - val destFile = new File(genTemplate.filename) - printf("%10s: %s\n", genTemplate.what, destFile.getAbsolutePath) - val out = new PrintWriter(destFile) - templateGenerator.generate(genTemplate.what, root, out) - out.close() - } -*/ + /* + opts.templates foreach { genTemplate => + val destFile = new File(genTemplate.filename) + printf("%10s: %s\n", genTemplate.what, destFile.getAbsolutePath) + val out = new PrintWriter(destFile) + templateGenerator.generate(genTemplate.what, root, out) + out.close() + } + */ } } diff --git a/src/main/scala/tscfg/ModelBuilder.scala b/src/main/scala/tscfg/ModelBuilder.scala index 54237b3..70f892f 100644 --- a/src/main/scala/tscfg/ModelBuilder.scala +++ b/src/main/scala/tscfg/ModelBuilder.scala @@ -2,15 +2,45 @@ package tscfg import com.typesafe.config._ import tscfg.generators.tsConfigUtil -import tscfg.model.{DURATION, ObjectType} +import tscfg.model.DURATION import scala.collection.JavaConversions._ +case class ModelBuildResult(objectType: model.ObjectType, + warnings: List[buildWarnings.Warning]) + +object buildWarnings { + sealed abstract class Warning(ln: Int, + src: String, + msg: String = "") { + val line: Int = ln + val source: String = src + val message: String = msg + } + + case class MultElemListWarning(ln: Int, src: String) extends + Warning(ln, src, "only first element will be considered") + + case class OptListElemWarning(ln: Int, src: String) extends + Warning(ln, src, "ignoring optional mark in list's element type") + + case class DefaultListElemWarning(ln: Int, default: String, elemType: String) extends + Warning(ln, default, s"ignoring default value='$default' in list's element type: $elemType") +} class ModelBuilder { import collection._ + import buildWarnings._ + + def build(conf: Config): ModelBuildResult = { + warns.clear() + ModelBuildResult(objectType = fromConfig(conf), + warnings = warns.toList.sortBy(_.line)) + } - def fromConfig(conf: Config): model.ObjectType = { + private val warns = collection.mutable.ArrayBuffer[Warning]() + + private def fromConfig(conf: Config): model.ObjectType = { val memberStructs = getMemberStructs(conf) val members: immutable.Map[String, model.AnnType] = memberStructs.map { childStruct ⇒ val name = childStruct.name @@ -116,8 +146,8 @@ class ModelBuilder { } } - def toAnnBasicType(valueString: String): - Option[(model.BasicType, Boolean, Option[String])] = { + private def toAnnBasicType(valueString: String): + Option[(model.BasicType, Boolean, Option[String])] = { val tokens = valueString.split("""\s*\|\s*""") val typePart = tokens(0).toLowerCase @@ -154,7 +184,7 @@ class ModelBuilder { val line = cv.origin().lineNumber() val options: ConfigRenderOptions = ConfigRenderOptions.defaults .setFormatted(false).setComments(false).setOriginComments(false) - println(s"$line: ${cv.render(options)}: WARN: only first element will be considered") + warns += MultElemListWarning(line, cv.render(options)) } val cv0: ConfigValue = cv.get(0) @@ -167,10 +197,10 @@ class ModelBuilder { toAnnBasicType(valueString) match { case Some((basicType, isOpt, defaultValue)) ⇒ if (isOpt) - println(s"WARN: ignoring optional mark in list's element type: $valueString") + warns += OptListElemWarning(cv0.origin().lineNumber(), valueString) if (defaultValue.isDefined) - println(s"WARN: ignoring default value='${defaultValue.get}' in list's element type: $valueString") + warns += DefaultListElemWarning(cv0.origin().lineNumber(), defaultValue.get, valueString) basicType @@ -213,11 +243,12 @@ class ModelBuilder { object ModelBuilder { import java.io.File + import com.typesafe.config.ConfigFactory - def apply(source: String): ObjectType = { + def apply(source: String): ModelBuildResult = { val config = ConfigFactory.parseString(source).resolve() - new ModelBuilder().fromConfig(config) + new ModelBuilder().build(config) } // $COVERAGE-OFF$ @@ -226,9 +257,9 @@ object ModelBuilder { val file = new File(filename) val source = io.Source.fromFile(file).mkString.trim println("source:\n |" + source.replaceAll("\n", "\n |")) - val objectType = ModelBuilder(source) - println(s"objectType:") - println(model.util.format(objectType)) + val result = ModelBuilder(source) + println("objectType:") + println(model.util.format(result.objectType)) } // $COVERAGE-ON$ } diff --git a/src/main/scala/tscfg/generators/java/JavaGen.scala b/src/main/scala/tscfg/generators/java/JavaGen.scala index 5eae6e7..f992864 100644 --- a/src/main/scala/tscfg/generators/java/JavaGen.scala +++ b/src/main/scala/tscfg/generators/java/JavaGen.scala @@ -112,8 +112,8 @@ class JavaGen(genOpts: GenOpts) extends Generator(genOpts) { } private def generateForList(lt: ListType, - classNamePrefixOpt: Option[String], - className: String): Res = { + classNamePrefixOpt: Option[String], + className: String): Res = { val className2 = className + (if (className.endsWith("$Elm")) "" else "$Elm") val elem = generate(lt.t, classNamePrefixOpt, className2) val elemRefType = toObjectType(elem.javaType) @@ -208,9 +208,9 @@ class JavaGen(genOpts: GenOpts) extends Generator(genOpts) { } private def listMethodName(javaType: ListJavaType, lt: ListType, path: String) - (implicit listAccessors: collection.mutable.Map[String, String], - methodNames: MethodNames - ): String = { + (implicit listAccessors: collection.mutable.Map[String, String], + methodNames: MethodNames + ): String = { val (_, methodName) = rec(javaType, lt, "") methodName + s"""(c.getList("$path"))""" @@ -260,7 +260,7 @@ class JavaGen(genOpts: GenOpts) extends Generator(genOpts) { } private def listMethodDefinition(elemMethodName: String, javaType: JavaType) - (implicit methodNames: MethodNames): (String, String) = { + (implicit methodNames: MethodNames): (String, String) = { val elem = if (elemMethodName.startsWith(methodNames.listPrefix)) s"$elemMethodName((com.typesafe.config.ConfigList)cv)" @@ -293,10 +293,10 @@ object JavaGen { // $COVERAGE-OFF$ def generate(filename: String, showOut: Boolean = false): GenResult = { val file = new File(filename) - val src = io.Source.fromFile(file).mkString.trim + val source = io.Source.fromFile(file).mkString.trim if (showOut) - println("src:\n |" + src.replaceAll("\n", "\n |")) + println("source:\n |" + source.replaceAll("\n", "\n |")) val className = "Java" + { val noPath = filename.substring(filename.lastIndexOf('/') + 1) @@ -305,9 +305,15 @@ object JavaGen { util.upperFirst(symbol) + "Cfg" } - val objectType = ModelBuilder(src) - if (showOut) - println("\nobjSpec:\n |" + model.util.format(objectType).replaceAll("\n", "\n |")) + val buildResult = ModelBuilder(source) + val objectType = buildResult.objectType + if (showOut) { + println("\nobjectType:\n |" + model.util.format(objectType).replaceAll("\n", "\n |")) + if (buildResult.warnings.nonEmpty) { + println("warnings:") + buildResult.warnings.foreach(w ⇒ println(s" line ${w.line}: ${w.source}: ${w.message}")) + } + } val genOpts = GenOpts("tscfg.example", className, j7 = false) diff --git a/src/main/scala/tscfg/generators/scala/ScalaGen.scala b/src/main/scala/tscfg/generators/scala/ScalaGen.scala index 8cb58b0..0b1f500 100644 --- a/src/main/scala/tscfg/generators/scala/ScalaGen.scala +++ b/src/main/scala/tscfg/generators/scala/ScalaGen.scala @@ -161,7 +161,7 @@ object ScalaGen { val source = io.Source.fromFile(file).mkString.trim if (showOut) - println("src:\n |" + source.replaceAll("\n", "\n |")) + println("source:\n |" + source.replaceAll("\n", "\n |")) val className = "Scala" + { val noPath = filename.substring(filename.lastIndexOf('/') + 1) @@ -170,9 +170,15 @@ object ScalaGen { util.upperFirst(symbol) + "Cfg" } - val objectType = ModelBuilder(source) - if (showOut) - println("\nobjSpec:\n |" + model.util.format(objectType).replaceAll("\n", "\n |")) + val buildResult = ModelBuilder(source) + val objectType = buildResult.objectType + if (showOut) { + println("\nobjectType:\n |" + model.util.format(objectType).replaceAll("\n", "\n |")) + if (buildResult.warnings.nonEmpty) { + println("warnings:") + buildResult.warnings.foreach(w ⇒ println(s" line ${w.line}: ${w.source}: ${w.message}")) + } + } val genOpts = GenOpts("tscfg.example", className, j7 = false) diff --git a/src/test/scala/tscfg/ModelBuilderSpec.scala b/src/test/scala/tscfg/ModelBuilderSpec.scala index c55cb33..efc8e77 100644 --- a/src/test/scala/tscfg/ModelBuilderSpec.scala +++ b/src/test/scala/tscfg/ModelBuilderSpec.scala @@ -2,22 +2,24 @@ package tscfg import org.specs2.mutable.Specification import model.durations._ +import tscfg.buildWarnings.{DefaultListElemWarning, MultElemListWarning, OptListElemWarning} import tscfg.model._ class ModelBuilderSpec extends Specification { - def build(source: String, showOutput: Boolean = false): ObjectType = { + def build(source: String, showOutput: Boolean = false): ModelBuildResult = { if (showOutput) println("\nsource:\n |" + source.replaceAll("\n", "\n |")) - val objSpec = ModelBuilder(source) + val result = ModelBuilder(source) + val objectType = result.objectType if (showOutput) - println("\nobjSpec:\n |" + model.util.format(objSpec).replaceAll("\n", "\n |")) + println("\nobjectType:\n |" + model.util.format(objectType).replaceAll("\n", "\n |")) - objSpec + result } private def verify(objType: ObjectType, @@ -35,16 +37,16 @@ class ModelBuilderSpec extends Specification { } "with empty input" should { - val spec = build("") + val result = build("") "build empty ObjectType" in { - spec === ObjectType() + result.objectType === ObjectType() } } "with empty list" should { def a = build( """ - |list: [ ] + |my_list: [ ] """.stripMargin) "throw IllegalArgumentException" in { @@ -53,84 +55,82 @@ class ModelBuilderSpec extends Specification { } "with list with multiple elements" should { - build( - """ - |list: [ true, false ] - """.stripMargin) + val result = build("my_list: [ true, false ]") "generate warning" in { - 1===1 // not actually verified here + val warns = result.warnings.filter(_.isInstanceOf[MultElemListWarning]) + warns.map(_.source) must contain("[true,false]") } } "with list element indicating optional" should { - build( - """ - |list: [ "string?" ] - """.stripMargin) + val result = build("""my_list: [ "string?" ]""") "generate warning" in { - 1===1 // not actually verified here + val warns = result.warnings.filter(_.isInstanceOf[OptListElemWarning]) + warns.map(_.source) must contain("string?") } } "with list element indicating a default value" should { - build( + val result = build( """ - |list: [ "double | 3.14" ] + |my_list: [ "double | 3.14" ] """.stripMargin) "generate warning" in { - 1===1 // not actually verified here + val warns = result.warnings.filter(_.isInstanceOf[DefaultListElemWarning]). + asInstanceOf[List[DefaultListElemWarning]] + warns.map(_.default) must contain("3.14") } } "with list with literal int" should { - val ot = build( + val result = build( """ - |list: [ 99999999 ] + |my_list: [ 99999999 ] """.stripMargin) "translate into ListType(INTEGER)" in { - ot.members("list").t === ListType(INTEGER) + result.objectType.members("my_list").t === ListType(INTEGER) } } "with list with literal long" should { - val ot = build( + val result = build( """ - |list: [ 99999999999 ] + |my_list: [ 99999999999 ] """.stripMargin) "translate into ListType(LONG)" in { - ot.members("list").t === ListType(LONG) + result.objectType.members("my_list").t === ListType(LONG) } } "with list with literal double" should { - val ot = build( + val result = build( """ - |list: [ 3.14 ] + |my_list: [ 3.14 ] """.stripMargin) "translate into ListType(DOUBLE)" in { - ot.members("list").t === ListType(DOUBLE) + result.objectType.members("my_list").t === ListType(DOUBLE) } } "with list with literal boolean" should { - val ot = build( + val result = build( """ - |list: [ false ] + |my_list: [ false ] """.stripMargin) "translate into ListType(BOOLEAN)" in { - ot.members("list").t === ListType(BOOLEAN) + result.objectType.members("my_list").t === ListType(BOOLEAN) } } "with good input" should { - val objType = build( + val result = build( """ |foo { | reqStr = string @@ -168,6 +168,8 @@ class ModelBuilderSpec extends Specification { |} """.stripMargin) + val objType = result.objectType + "build expected objType" in { objType.members.keySet === Set("foo") val foo = objType.members("foo")