Skip to content

Commit

Permalink
Merge 45224ab into d0dc6c2
Browse files Browse the repository at this point in the history
  • Loading branch information
andyglow committed Oct 27, 2020
2 parents d0dc6c2 + 45224ab commit b3fe70c
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 5 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
target
.idea
.bsp
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,25 @@ val iterator3 = config.get[Iterator[ConfigMemorySize]]("path")
Also this can be extended by providing implementations for `FromConf` and/or `ConfType`
(used for collections and might be implicitly reused for `FromConf`)
For example take a look at `com.github.andyglow.config.ConfType#juDateT` implementation
and spec at `com.github.andyglow.config.JavaUtilDateExtSpec`
and spec at `com.github.andyglow.config.JavaUtilDateExtSpec`

### Flatten
Often we need to transform configs into some more trivial structures like Maps or java Properties.
For these sort or problems we provide `Flatten` function.
It map take `Config` or `ConfigValue` and produce either Properties or Map.
Example:
```scala
val conf: Config = ???

// for config
val properties = Flatten[java.util.Properties](conf)
val stringMap = Flatten[Map[String, String]](conf)

// for value
val properties = Flatten[java.util.Properties](conf.getValue("some-prop"))
val stringMap = Flatten[Map[String, String]](conf.getValue("some-prop"))

// also you can flatten config into already initialized instance of either Properties of Map
val propertiesWithConfig = Flatten(conf.getValue("some-prop"), properties)
val stringMapWithConfig = Flatten(conf.getValue("some-prop"), stringMap)
```
5 changes: 2 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ organizationName := "andyglow"

publishTo := sonatypePublishTo.value

scalaVersion := "2.13.1"
scalaVersion := "2.12.11"

crossScalaVersions := Seq("2.13.1", "2.12.10", "2.11.12")
crossScalaVersions := Seq("2.13.2", "2.12.11", "2.11.12")

scalacOptions ++= {
val options = Seq(
Expand Down Expand Up @@ -54,7 +54,6 @@ scalacOptions in (Compile, doc) ++= Seq(
"-implicits",
"-no-link-warnings")


fork in Test := true

javaOptions in Test ++= Seq(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.github.andyglow.config

import scala.collection.JavaConverters._

private[config] object ScalaVersionSpecific {

implicit class CollectionsToScala[T](private val coll: java.lang.Iterable[T]) extends AnyVal {

def scala: Iterable[T] = coll.asScala
}

implicit class MapsToScala[K, V](private val coll: java.util.Map[K, V]) extends AnyVal {

def scala: Map[K, V] = coll.asScala.toMap
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.github.andyglow.config

import scala.collection.JavaConverters._

private[config] object ScalaVersionSpecific {

implicit class CollectionsToScala[T](private val coll: java.lang.Iterable[T]) extends AnyVal {

def scala: Iterable[T] = coll.asScala
}

implicit class MapsToScala[K, V](private val coll: java.util.Map[K, V]) extends AnyVal {

def scala: Map[K, V] = coll.asScala.toMap
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.github.andyglow.config

import scala.jdk.CollectionConverters._

private[config] object ScalaVersionSpecific {

implicit class CollectionsToScala[T](private val coll: java.lang.Iterable[T]) extends AnyVal {

def scala: Iterable[T] = coll.asScala
}

implicit class MapsToScala[K, V](private val coll: java.util.Map[K, V]) extends AnyVal {

def scala: Map[K, V] = coll.asScala.toMap
}
}
66 changes: 66 additions & 0 deletions src/main/scala/com/github/andyglow/config/Flatten.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.github.andyglow.config

import java.text.NumberFormat
import java.{util => ju}

import com.typesafe.config._

import ScalaVersionSpecific._

/** Converts typesafe config into java Properties
*/
object Flatten {

final case class Settings(numberFmt: NumberFormat)

final object Settings {
implicit val defaultSettings: Settings = {
val fmt = NumberFormat.getInstance()
fmt.setGroupingUsed(false)

Settings(
numberFmt = fmt)
}
}

sealed trait Adapter[T] {
def init: T
def put(props: T, k: String, v: String): T
}

object Adapter {

implicit object PropertiesAdapter extends Adapter[ju.Properties] {
override def init: ju.Properties = new ju.Properties
override def put(props: ju.Properties, k: String, v: String): ju.Properties = {
props.setProperty(k, v)
props
}
}

implicit object StringMapAdapter extends Adapter[Map[String, String]] {
override def init: Map[String, String] = Map.empty
override def put(props: Map[String, String], k: String, v: String): Map[String, String] = props.updated(k, v)
}
}

def apply[T](v: Config)(implicit adapter: Adapter[T], settings: Settings): T = apply(v, adapter.init)
def apply[T](v: Config, init: T)(implicit adapter: Adapter[T], settings: Settings): T = move(v.root, true, Nil, init)

def apply[T](v: ConfigValue)(implicit adapter: Adapter[T], settings: Settings): T = apply(v, adapter.init)
def apply[T](v: ConfigValue, init: T)(implicit adapter: Adapter[T], settings: Settings): T = move(v, true, Nil, init)

private def move[T](v: ConfigValue, isRoot: Boolean, path: List[String], props: T)(implicit adapter: Adapter[T], settings: Settings): T = {
import settings._
def k = if (isRoot) "root" else path mkString "."

v match {
case ConfStr(v) => adapter.put(props, k, v)
case ConfNum(v) => adapter.put(props, k, numberFmt.format(v))
case ConfBool(v) => adapter.put(props, k, v.toString)
case ConfList(v) => v.scala.zipWithIndex.foldLeft(props) { case (props, (v, i)) => move(v, false, path :+ i.toString, props) }
case ConfObj(v) => v.scala.foldLeft(props) { case (props, (f, v)) => move(v, false, path :+ f, props) }
}
}

}
2 changes: 1 addition & 1 deletion src/main/scala/com/typesafe/config/impl/Internals.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ object Internals {

def parseDuration(x: String, o: ConfigOrigin, p: String): Long = SimpleConfig.parseDuration(x, o, p)

def parseBytes(x: String, o: ConfigOrigin, p: String): Long = SimpleConfig.parseBytes(x, o, p)
def parseBytes(x: String, o: ConfigOrigin, p: String): Long = SimpleConfig.parseBytes(x, o, p).longValue()

def parsePeriod(x: String, o: ConfigOrigin, p: String): Period = SimpleConfig.parsePeriod(x, o, p)
}
131 changes: 131 additions & 0 deletions src/test/scala/com/github/andyglow/config/FlattenSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package com.github.andyglow.config

import java.{ util => ju }
import com.typesafe.config.ConfigFactory
import org.scalatest.matchers.should.Matchers._
import org.scalatest.funsuite._
//import ScalaVersionSpecific._

class FlattenSpec extends AnyFunSuite {
import FlattenSpec._

test("apply[Map](conf)") {
val props = Flatten[Map[String, String]](conf)
props should contain only (
"arr.0" -> "abc",
"arr.1" -> "def",
"bool" -> "true",
"complex.arr.0.id" -> "1",
"complex.arr.0.name" -> "foo",
"complex.arr.1.id" -> "2",
"complex.arr.1.name" -> "bar",
"double" -> "123.67",
"int" -> "20",
"long" -> "987234985769873",
"obj.f1" -> "f1",
"obj.nested.arr.0" -> "0",
"obj.nested.arr.1" -> "2",
"obj.nested.f2" -> "f2",
"str" -> "str")
}

test("apply(conf, init: Map)") {
val props = Flatten(conf, Map("some.initial" -> "value", "bool" -> "false"))
props should contain only (
"some.initial" -> "value",
"arr.0" -> "abc",
"arr.1" -> "def",
"bool" -> "true",
"complex.arr.0.id" -> "1",
"complex.arr.0.name" -> "foo",
"complex.arr.1.id" -> "2",
"complex.arr.1.name" -> "bar",
"double" -> "123.67",
"int" -> "20",
"long" -> "987234985769873",
"obj.f1" -> "f1",
"obj.nested.arr.0" -> "0",
"obj.nested.arr.1" -> "2",
"obj.nested.f2" -> "f2",
"str" -> "str")
}


test("apply[Properties](conf)") {
val props = propsToMap { Flatten[ju.Properties](conf) }
props should contain only (
"arr.0" -> "abc",
"arr.1" -> "def",
"bool" -> "true",
"complex.arr.0.id" -> "1",
"complex.arr.0.name" -> "foo",
"complex.arr.1.id" -> "2",
"complex.arr.1.name" -> "bar",
"double" -> "123.67",
"int" -> "20",
"long" -> "987234985769873",
"obj.f1" -> "f1",
"obj.nested.arr.0" -> "0",
"obj.nested.arr.1" -> "2",
"obj.nested.f2" -> "f2",
"str" -> "str")
}

test("apply(conf, init: Properties)") {
val init = new ju.Properties
init.setProperty("some.initial", "value")
init.setProperty("bool", "false")
val props = propsToMap { Flatten(conf, init) }
props should contain only (
"some.initial" -> "value",
"arr.0" -> "abc",
"arr.1" -> "def",
"bool" -> "true",
"complex.arr.0.id" -> "1",
"complex.arr.0.name" -> "foo",
"complex.arr.1.id" -> "2",
"complex.arr.1.name" -> "bar",
"double" -> "123.67",
"int" -> "20",
"long" -> "987234985769873",
"obj.f1" -> "f1",
"obj.nested.arr.0" -> "0",
"obj.nested.arr.1" -> "2",
"obj.nested.f2" -> "f2",
"str" -> "str")
}

test("apply(value)") {
Flatten[Map[String, String]](conf.getValue("str")) should contain only ("root" -> "str")
Flatten[Map[String, String]](conf.getValue("arr")) should contain only ("0" -> "abc", "1" -> "def")
}
}

object FlattenSpec {

val conf = ConfigFactory.parseString(
"""str = str
|bool = true
|int = 20
|long = 987234985769873
|double = 123.67
|arr = [ abc, def ]
|complex.arr = [
| { id: 1, name: foo },
| { id: 2, name: bar }]
|obj {
| f1 = f1
| nested {
| f2 = f2
| arr = [ 0, 2 ]
| }
|}
|""".stripMargin)

private def propsToMap(props: ju.Properties): Map[String, String] = {
props.stringPropertyNames().toArray(Array.empty[String]).foldLeft[Map[String, String]](Map.empty) { case (acc, k) =>
val v = props.getProperty(k)
acc.updated(k, v)
}
}
}

0 comments on commit b3fe70c

Please sign in to comment.