Skip to content

Commit

Permalink
0.8.3: resolve #6: "generate conf template"
Browse files Browse the repository at this point in the history
  • Loading branch information
carueda committed Sep 5, 2017
1 parent 0e8b906 commit b0182c3
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 45 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
lazy val tscfgVersion = setVersion("0.8.2")
lazy val tscfgVersion = setVersion("0.8.3")

organization := "com.github.carueda"
name := "tscfg"
Expand Down
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
2017-09-05 - 0.8.3

- resolve #6: "generate conf template"

2017-07-13

- basic experimentation with static-config
Expand Down
10 changes: 10 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ The generated code only depends on the Typesafe Config library.
- [list type](#list-type)
- [object type](#object-type)
- [optional object or list](#optional-object-or-list)
- [configuration template](#configuration-template)
- [FAQ](#faq)
- [tests](#tests)

Expand Down Expand Up @@ -189,6 +190,9 @@ Options (default):
--j7 generate code for java <= 7 (8)
--scala generate scala code (java)
--java generate java code (the default)
--tpl <filename> generate config template (not generated)
--tpl.ind <string> template indentation string (" ")
--tpl.cp <string> prefix for template comments ("##")
Output is written to $destDir/$className.ext
```
Expand Down Expand Up @@ -405,6 +409,12 @@ value will be `null` (`None` in Scala) when the corresponding actual entry is mi
a given configuration instance.
## configuration template
tscfg can also generate user-oriented configuration templates from given config specs.
See [this wiki](https://github.com/carueda/tscfg/wiki/template-generation).
## FAQ
**But I can just access the configuration values directly with Typesafe Config
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/application.conf
Original file line number Diff line number Diff line change
@@ -1 +1 @@
tscfg.version = 0.8.2
tscfg.version = 0.8.3
66 changes: 42 additions & 24 deletions src/main/scala/tscfg/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import java.util.Date
import com.typesafe.config.ConfigFactory
import tscfg.generators.java.JavaGen
import tscfg.generators.scala.ScalaGen
import tscfg.generators.{Generator, GenOpts}
import tscfg.generators.{GenOpts, Generator, TemplateOpts, TemplateGenerator}

/**
* The main program. Run with no arguments to see usage.
Expand All @@ -22,25 +22,32 @@ object Main {

val defaultDestDir: String = "/tmp"

val usage: String = s"""
|tscfg $version
|Usage: tscfg.Main --spec inputFile [options]
|Options (default):
| --pn <packageName> (${defaultGenOpts.packageName})
| --cn <className> (${defaultGenOpts.className})
| --dd <destDir> ($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
var templateOpts = TemplateOpts()

val usage: String =
s"""
|tscfg $version
|Usage: tscfg.Main --spec inputFile [options]
|Options (default):
| --pn <packageName> (${defaultGenOpts.packageName})
| --cn <className> (${defaultGenOpts.className})
| --dd <destDir> ($defaultDestDir)
| --j7 generate code for java <= 7 (8)
| --scala generate scala code (java)
| --java generate java code (the default)
| --tpl <filename> generate config template (no default)
| --tpl.ind <string> template indentation string ("${templateOpts.indent}")
| --tpl.cp <string> prefix for template comments ("${templateOpts.commentPrefix}")
|Output is written to $$destDir/$$className.ext
""".stripMargin

case class CmdLineOpts(inputFilename: Option[String] = None,
packageName: String = defaultGenOpts.packageName,
className: String = defaultGenOpts.className,
destDir: String = defaultDestDir,
j7: Boolean = false,
language: String = "java"
language: String = "java",
tplFilename: Option[String] = None
)

def main(args: Array[String]): Unit = {
Expand Down Expand Up @@ -82,11 +89,22 @@ object Main {
case "--java" :: rest =>
traverseList(rest, opts.copy(language = "java"))

case opt :: nil =>
case "--tpl" :: filename :: rest =>
traverseList(rest, opts.copy(tplFilename = Some(filename)))

case "--tpl.ind" :: indent :: rest =>
templateOpts = templateOpts.copy(indent = indent)
traverseList(rest, opts)

case "--tpl.cp" :: prefixComment :: rest =>
templateOpts = templateOpts.copy(commentPrefix = prefixComment)
traverseList(rest, opts)

case opt :: _ =>
println( s"""missing argument or unknown option: $opt""")
sys.exit(0)

case nil => opts
case Nil => opts
}
}

Expand Down Expand Up @@ -145,14 +163,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.tplFilename foreach { filename
println(s"generating template $filename")
val destFile = new File(filename)
val out = new PrintWriter(destFile)
val templater = new TemplateGenerator(templateOpts)
val template = templater.generate(objectType)
out.println(template)
out.close()
}
}
}
87 changes: 87 additions & 0 deletions src/main/scala/tscfg/generators/TemplateGenerator.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package tscfg.generators

import tscfg.model._

case class TemplateOpts(indent: String = " ",
commentPrefix: String = "##"
)

class TemplateGenerator(opts: TemplateOpts) {
def generate(o: ObjectType): String = genObjectTypeMembers(o)

private def genObjectType(o: ObjectType): String = {
val members = opts.indent + genObjectTypeMembers(o).replaceAll("\n", "\n" + opts.indent)
s"""{
|$members
|}""".stripMargin
}

private def genObjectTypeMembers(o: ObjectType): String = {
val symbols = o.members.keys.toList.sorted
symbols.map { symbol
val a = o.members(symbol)

val (annotations, comments) = a.comments match {
case None (List.empty, List.empty)
case Some(str)
val lines = str.split("\n").toList.filterNot(_.startsWith("!"))
lines.partition(_.startsWith("@"))
}

val cmn = if (comments.isEmpty) "" else {
opts.commentPrefix + comments.mkString("\n" + opts.commentPrefix) + "\n"
}

val opt = if (a.isOptional) "optional" else "required"
val dfl = a.default.map(d s". Default: $d").getOrElse("")
val (typ, abbrev) = gen(a.t)
val abbrev2 = if (abbrev == abbrevObject) "section" else abbrev
val decl = opts.commentPrefix + s" '$symbol': $opt $abbrev2$dfl.\n"

val sysPropOpt: Option[String] = annotations.find(_.startsWith(sysPropAnn))
.map(_.substring(sysPropAnn.length).trim)

val envVarOpt: Option[String] = annotations.find(_.startsWith(envVarAnn))
.map(_.substring(envVarAnn.length).trim)

val assign = if (abbrev == abbrevObject)
symbol + " " + typ // do not include `=' for an object
else if (isBasicAbbrev(abbrev))
symbol + " = " + "?"
else
symbol + " = " + typ

val assignSysProp = sysPropOpt.map(e s"\n$symbol = $${$e}").getOrElse("")
val assignEnvVar = envVarOpt.map(e s"\n$symbol = $${?$e}").getOrElse("")

decl +
cmn +
(if (a.isOptional) "#" + assign.replaceAll("\n", "\n#") else assign) +
assignSysProp +
assignEnvVar

}.mkString("\n\n")
}

private def gen(typ: Type): (String,String) = typ match {
case o:ObjectType
(genObjectType(o), abbrevObject)

case b:BasicType
val lc = b.toString.toLowerCase
(lc, lc)

case ListType(t)
val (subTyp, abbrev) = gen(t)
(s"[$subTyp, ...]", abbrevListOf + " " + abbrev)
}

private def isBasicAbbrev(abbrev: String): Boolean =
abbrev != abbrevObject && !abbrev.startsWith(abbrevListOf)

private val abbrevObject = "object"
private val abbrevListOf = "list of"

private val envVarAnn = "@envvar"
private val sysPropAnn = "@sysprop"
}
19 changes: 0 additions & 19 deletions src/main/tscfg/example/example-4tpl.spec.conf

This file was deleted.

33 changes: 33 additions & 0 deletions src/main/tscfg/example/example4tpl.spec.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#! Comments starting with #! are not transferred to template.
# Description of the required endpoint section.
endpoint {
# The path associated with the endpoint.
# For example, "/home/foo/bar"
#@envvar ENDPOINT_PATH
path = "string"

# Port for the endpoint service.
#@envvar ENDPOINT_PORT
port = "int | 8080"

# Configuration for notifications
notifications {
# Emails to send notifications to.
emails = [ {
email: string

name: "string?"
} ]
}

# Some optional stuff.
#@optional
stuff {
# Coeficient matrix
coefs: [[double]]

#@sysprop endpoint.port
port2: int
}
}

40 changes: 40 additions & 0 deletions src/main/tscfg/example/example4tpl.template.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## 'endpoint': required section.
## Description of the required endpoint section.
endpoint {
## 'notifications': required section.
## Configuration for notifications
notifications {
## 'emails': required list of object.
## Emails to send notifications to.
emails = [{
## 'email': required string.
email = ?

## 'name': optional string.
#name = ?
}, ...]
}

## 'path': required string.
## The path associated with the endpoint.
## For example, "/home/foo/bar"
path = ?
path = ${?ENDPOINT_PATH}

## 'port': optional integer. Default: 8080.
## Port for the endpoint service.
#port = ?
port = ${?ENDPOINT_PORT}

## 'stuff': optional section.
## Some optional stuff.
#stuff {
# ## 'coefs': required list of list of double.
# ## Coeficient matrix
# coefs = [[double, ...], ...]
#
# ## 'port2': required integer.
# port2 = ?
# port2 = ${endpoint.port}
#}
}

0 comments on commit b0182c3

Please sign in to comment.