Skip to content

Commit 6ca133f

Browse files
committed
Added HOCON and XML (broken in Scala.js, see scala/scala-xml#198) support.
1 parent 543d4dd commit 6ca133f

File tree

7 files changed

+95
-6
lines changed

7 files changed

+95
-6
lines changed

README.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,23 @@ developer and let them get their job done. To this end we support a unified conf
2424
environment variables, system properties, and configuration files to provide maximum flexibility of defining, defaulting,
2525
and overriding configuration in your application.
2626

27+
# File Formats
28+
29+
* JSON (automatically picked up from config.json, configuration.json, app.json, application.json, and defaults.json)
30+
* Properties (automatically picked up from config.properties, configuration.properties, app.properties, application.properties, and defaults.properties)
31+
* YAML (automatically picked up from config.yml, config.yaml, configuration.yml, configuration.yaml, app.yml, app.yaml, application.yml, application.yaml, defaults.yml, defaults.yaml)
32+
* HOCON (automatically picked up from config.hocon, configuration.hocon, app.hocon, application.hocon, and defaults.hocon)
33+
* XML (automatically picked up from config.xml, configuration.xml, app.xml, application.xml, and defaults.xml)
34+
2735
# Setup
2836

2937
## SBT Configuration
3038

3139
Profig is published to Sonatype OSS and synchronized to Maven Central supporting JVM and Scala.js on 2.11 and 2.12:
3240

3341
```
34-
libraryDependencies += "com.outr" %% "profig" % "2.1.1" // Scala
35-
libraryDependencies += "com.outr" %%% "profig" % "2.1.1" // Scala.js / Cross-Build
42+
libraryDependencies += "com.outr" %% "profig" % "2.2.0" // Scala
43+
libraryDependencies += "com.outr" %%% "profig" % "2.2.0" // Scala.js / Cross-Build
3644
```
3745

3846
## Getting Started
@@ -105,7 +113,9 @@ ScalaDocs and the specs: https://github.com/outr/profig/blob/master/core/shared/
105113
## 2.2.0 (In-Progress)
106114

107115
* [ ] Better README documentation
108-
* [ ] HOCON support (integrate https://github.com/unicredit/shocon)
116+
* [X] HOCON support (integrate https://github.com/akka-js/shocon)
117+
* [ ] XML support
118+
* [X] Resolve explicit work-arounds for use in Macros
109119

110120
## 2.1.0 (Released 03.08.2018)
111121

build.sbt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import sbtcrossproject.{CrossType, crossProject}
22

33
organization in ThisBuild := "com.outr"
4-
version in ThisBuild := "2.1.2-SNAPSHOT"
4+
version in ThisBuild := "2.2.0-SNAPSHOT"
55
scalaVersion in ThisBuild := "2.12.4"
66
crossScalaVersions in ThisBuild := List("2.12.4", "2.11.12")
77
scalacOptions in ThisBuild ++= Seq("-unchecked", "-deprecation")
@@ -24,6 +24,8 @@ developers in ThisBuild := List(
2424

2525
val circeVersion = "0.9.1"
2626
val circeYamlVersion = "0.7.0"
27+
val shoconVersion = "0.2.0"
28+
val scalaXMLVersion = "1.1.0"
2729
val scalatestVersion = "3.0.4"
2830

2931
lazy val root = project.in(file("."))
@@ -52,13 +54,15 @@ lazy val macros = crossProject(JSPlatform, JVMPlatform)
5254
"io.circe" %%% "circe-generic-extras"
5355
).map(_ % circeVersion),
5456
libraryDependencies ++= Seq(
57+
"org.akka-js" %%% "shocon" % shoconVersion,
58+
"org.scala-lang.modules" %%% "scala-xml" % scalaXMLVersion,
5559
"io.circe" %% "circe-jawn" % circeVersion,
5660
"io.circe" %% "circe-yaml" % circeYamlVersion,
5761
"org.scala-lang" % "scala-reflect" % scalaVersion.value
5862
)
5963
)
6064
.jsSettings(
61-
manipulateBytecode in Compile := {
65+
manipulateBytecode in Compile := { // Allows access to Json parsing at compile-time (for use with Macros)
6266
val result = (manipulateBytecode in Compile).value
6367

6468
val classDir = (classDirectory in Compile).value

config.hocon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test.hocon = "yes"

config.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<test>
2+
<xml>yes</xml>
3+
</test>

core/shared/src/test/scala/spec/ProfigSpec.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ class ProfigSpec extends WordSpec with Matchers {
9090
"verify YAML support works" in {
9191
Profig("test.yaml").as[Option[String]] should be(Some("yes"))
9292
}
93+
"verify HOCON support works" in {
94+
Profig("test.hocon").as[Option[String]] should be(Some("yes"))
95+
}
96+
"verify XML support works" in {
97+
Profig("test.xml").as[Option[String]] should be(Some("yes"))
98+
}
9399
"compile-time Json parsing" in {
94100
val parsed = MacroTest.format("""{"name": "John Doe", "age": 1234}""")
95101
parsed should be("""{

macros/shared/src/main/scala/profig/ConfigurationFileType.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@ object ConfigurationFileType {
77
case object Json extends ConfigurationFileType
88
case object Properties extends ConfigurationFileType
99
case object Yaml extends ConfigurationFileType
10+
case object Hocon extends ConfigurationFileType
11+
case object XML extends ConfigurationFileType
1012
}

macros/shared/src/main/scala/profig/ConfigurationPath.scala

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,40 @@ object ConfigurationPath {
1818
ConfigurationPath("config.properties", ConfigurationFileType.Properties, LoadType.Merge),
1919
ConfigurationPath("config.yml", ConfigurationFileType.Yaml, LoadType.Merge),
2020
ConfigurationPath("config.yaml", ConfigurationFileType.Yaml, LoadType.Merge),
21+
ConfigurationPath("config.hocon", ConfigurationFileType.Hocon, LoadType.Merge),
22+
ConfigurationPath("config.xml", ConfigurationFileType.XML, LoadType.Merge),
2123

2224
ConfigurationPath("configuration.json", ConfigurationFileType.Json, LoadType.Merge),
2325
ConfigurationPath("configuration.conf", ConfigurationFileType.Auto, LoadType.Merge),
2426
ConfigurationPath("configuration.properties", ConfigurationFileType.Properties, LoadType.Merge),
2527
ConfigurationPath("configuration.yml", ConfigurationFileType.Yaml, LoadType.Merge),
2628
ConfigurationPath("configuration.yaml", ConfigurationFileType.Yaml, LoadType.Merge),
29+
ConfigurationPath("configuration.hocon", ConfigurationFileType.Hocon, LoadType.Merge),
30+
ConfigurationPath("configuration.xml", ConfigurationFileType.XML, LoadType.Merge),
31+
32+
ConfigurationPath("app.json", ConfigurationFileType.Json, LoadType.Merge),
33+
ConfigurationPath("app.conf", ConfigurationFileType.Auto, LoadType.Merge),
34+
ConfigurationPath("app.properties", ConfigurationFileType.Properties, LoadType.Merge),
35+
ConfigurationPath("app.yml", ConfigurationFileType.Yaml, LoadType.Merge),
36+
ConfigurationPath("app.yaml", ConfigurationFileType.Yaml, LoadType.Merge),
37+
ConfigurationPath("app.hocon", ConfigurationFileType.Hocon, LoadType.Merge),
38+
ConfigurationPath("app.xml", ConfigurationFileType.XML, LoadType.Merge),
2739

2840
ConfigurationPath("application.json", ConfigurationFileType.Json, LoadType.Merge),
2941
ConfigurationPath("application.conf", ConfigurationFileType.Auto, LoadType.Merge),
3042
ConfigurationPath("application.properties", ConfigurationFileType.Properties, LoadType.Merge),
3143
ConfigurationPath("application.yml", ConfigurationFileType.Yaml, LoadType.Merge),
3244
ConfigurationPath("application.yaml", ConfigurationFileType.Yaml, LoadType.Merge),
45+
ConfigurationPath("application.hocon", ConfigurationFileType.Hocon, LoadType.Merge),
46+
ConfigurationPath("application.xml", ConfigurationFileType.XML, LoadType.Merge),
3347

3448
ConfigurationPath("defaults.json", ConfigurationFileType.Json, LoadType.Defaults),
3549
ConfigurationPath("defaults.conf", ConfigurationFileType.Auto, LoadType.Defaults),
3650
ConfigurationPath("defaults.properties", ConfigurationFileType.Properties, LoadType.Defaults),
3751
ConfigurationPath("defaults.yml", ConfigurationFileType.Yaml, LoadType.Defaults),
38-
ConfigurationPath("defaults.yaml", ConfigurationFileType.Yaml, LoadType.Defaults)
52+
ConfigurationPath("defaults.yaml", ConfigurationFileType.Yaml, LoadType.Defaults),
53+
ConfigurationPath("defaults.hocon", ConfigurationFileType.Hocon, LoadType.Defaults),
54+
ConfigurationPath("defaults.xml", ConfigurationFileType.XML, LoadType.Defaults)
3955
)
4056

4157
def toStrings(entries: List[ConfigurationPath] = defaults): List[(ConfigurationPath, String)] = if (entries.isEmpty) {
@@ -128,10 +144,57 @@ object ConfigurationPath {
128144
case Right(value) => value
129145
}
130146

147+
def hoconString2Json(string: String): Json = {
148+
import eu.unicredit.shocon._
149+
150+
def toJson(value: Config.Value): Json = value match {
151+
case Config.Array(elements) => Json.arr(elements.map(toJson): _*)
152+
case Config.Object(fields) => Json.obj(fields.map {
153+
case (k, v) => k -> toJson(v)
154+
}.toSeq: _*)
155+
case Config.NumberLiteral(v) => Json.fromDouble(v.toDouble).getOrElse(throw new RuntimeException(s"Unable to convert $v to JsonNumber"))
156+
case Config.StringLiteral(v) => Json.fromString(v)
157+
case Config.BooleanLiteral(v) => Json.fromBoolean(v)
158+
case Config.NullLiteral => Json.Null
159+
case _ => throw new UnsupportedOperationException(s"Unsupported HOCON value: $value")
160+
}
161+
162+
toJson(Config(string))
163+
}
164+
165+
def xmlString2Json(string: String): Json = {
166+
import scala.xml._
167+
168+
def toJson(node: Node): Option[Json] = node match {
169+
case elem: Elem => {
170+
val attributes: List[(String, Json)] = elem.attributes.map(md => toJson(md.value.head).map(md.key -> _)).toList.flatten
171+
val children = elem.child.toList.collect {
172+
case child: Elem => toJson(child).map(child.label -> _)
173+
}.flatten
174+
val text = elem.text.trim
175+
if (attributes.isEmpty && children.isEmpty) {
176+
if (text.isEmpty) {
177+
None
178+
} else {
179+
Some(Json.fromString(text))
180+
}
181+
} else {
182+
Some(Json.obj(attributes ::: children: _*))
183+
}
184+
}
185+
case _ => None
186+
}
187+
188+
val root = XML.loadString(string)
189+
Json.obj(root.label -> toJson(root).getOrElse(Json.Null))
190+
}
191+
131192
def toJson(string: String, `type`: ConfigurationFileType): Json = `type` match {
132193
case ConfigurationFileType.Json => jsonString2Json(string)
133194
case ConfigurationFileType.Properties => propertiesString2Json(string)
134195
case ConfigurationFileType.Yaml => yamlConversion.map(c => c(string)).getOrElse(throw new RuntimeException(s"YAML conversion not supported."))
196+
case ConfigurationFileType.Hocon => hoconString2Json(string)
197+
case ConfigurationFileType.XML => xmlString2Json(string)
135198
case ConfigurationFileType.Auto => if (string.trim.startsWith("{")) {
136199
jsonString2Json(string)
137200
} else {

0 commit comments

Comments
 (0)