Skip to content
This repository has been archived by the owner on Aug 17, 2022. It is now read-only.

hyphen-case config key support #12

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,12 @@ javacOptions ++= Seq("-Xlint:unchecked", "-Xlint:deprecation")
/* dependencies */
libraryDependencies <++= scalaVersion { sv =>
Seq(
"org.specs2" %% "specs2" % "2.3.11" % "test",
"org.scalacheck" %% "scalacheck" % "1.11.3" % "test",
"com.chuusai" %% "shapeless" % "2.0.0" % "test",
"com.typesafe" % "config" % "1.2.1",
"org.scala-lang" % "scala-reflect" % sv % "provided")
"org.specs2" %% "specs2" % "2.3.11" % "test",
"org.scalacheck" %% "scalacheck" % "1.11.3" % "test",
"com.chuusai" %% "shapeless" % "2.0.0" % "test",
"com.typesafe" % "config" % "1.2.1",
"com.google.guava" % "guava" % "18.0",
"org.scala-lang" % "scala-reflect" % sv % "provided")
}

/* you may need these repos */
Expand Down
39 changes: 33 additions & 6 deletions src/main/scala/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,56 @@ import net.ceedubs.ficus.util.ReflectionUtils
import com.typesafe.config.Config
import scala.language.experimental.macros
import scala.reflect.internal.{StdNames, SymbolTable, Definitions}
import com.google.common.base.CaseFormat

trait ArbitraryTypeReader {
implicit def arbitraryTypeValueReader[T]: ValueReader[T] = macro ArbitraryTypeReaderMacros.arbitraryTypeValueReader[T]
}

object ArbitraryTypeReader extends ArbitraryTypeReader

trait HyphenCaseArbitraryTypeReader {
implicit def arbitraryTypeValueReader[T]: ValueReader[T] = macro ArbitraryTypeReaderMacros.arbitraryTypeValueReaderWithHyphenCase[T]
}

object HyphenCaseArbitraryTypeReader extends HyphenCaseArbitraryTypeReader

object ArbitraryTypeReaderMacros {
import scala.reflect.macros.blackbox.Context

def arbitraryTypeValueReader[T : c.WeakTypeTag](c: Context): c.Expr[ValueReader[T]] = {
trait NameMapper {
def map(name: String): String
}

object defaultMapper extends NameMapper {
def map(name: String) = name
}

object hyphenCaseMapper extends NameMapper {
def map(name: String) = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, name)
}

def arbitraryTypeValueReaderWithParamMapper[T : c.WeakTypeTag](c: Context, paramMapper: NameMapper): c.Expr[ValueReader[T]] = {
import c.universe._

reify {
new ValueReader[T] {
def read(config: Config, path: String): T = instantiateFromConfig[T](c)(
def read(config: Config, path: String): T = instantiateFromConfig[T](c, paramMapper)(
config = c.Expr[Config](Ident(TermName("config"))),
path = c.Expr[String](Ident(TermName("path")))).splice
}
}
}

def instantiateFromConfig[T : c.WeakTypeTag](c: Context)(config: c.Expr[Config], path: c.Expr[String]): c.Expr[T] = {
def arbitraryTypeValueReader[T : c.WeakTypeTag](c: Context): c.Expr[ValueReader[T]] = {
arbitraryTypeValueReaderWithParamMapper(c, defaultMapper)
}

def arbitraryTypeValueReaderWithHyphenCase[T : c.WeakTypeTag](c: Context): c.Expr[ValueReader[T]] = {
arbitraryTypeValueReaderWithParamMapper(c, hyphenCaseMapper)
}

def instantiateFromConfig[T : c.WeakTypeTag](c: Context, paramMapper: NameMapper)(config: c.Expr[Config], path: c.Expr[String]): c.Expr[T] = {
import c.universe._

val returnType = c.weakTypeOf[T]
Expand All @@ -40,7 +67,7 @@ object ArbitraryTypeReaderMacros {

val instantiationMethod = ReflectionUtils.instantiationMethod[T](c, fail)

val instantiationArgs = extractMethodArgsFromConfig[T](c)(method = instantiationMethod,
val instantiationArgs = extractMethodArgsFromConfig[T](c, paramMapper)(method = instantiationMethod,
companionObjectMaybe = companionSymbol, config = config, path = path, fail = fail)
val instantiationObject = companionSymbol.filterNot(_ =>
instantiationMethod.isConstructor
Expand All @@ -49,7 +76,7 @@ object ArbitraryTypeReaderMacros {
c.Expr[T](Apply(instantiationCall, instantiationArgs))
}

def extractMethodArgsFromConfig[T : c.WeakTypeTag](c: Context)(method: c.universe.MethodSymbol, companionObjectMaybe: Option[c.Symbol],
def extractMethodArgsFromConfig[T : c.WeakTypeTag](c: Context, paramMapper: NameMapper)(method: c.universe.MethodSymbol, companionObjectMaybe: Option[c.Symbol],
config: c.Expr[Config], path: c.Expr[String], fail: String => Nothing): List[c.Tree] = {
import c.universe._

Expand All @@ -58,7 +85,7 @@ object ArbitraryTypeReaderMacros {
if (!method.isPublic) fail(s"'$decodedMethodName' method is not public")

method.paramLists.head.zipWithIndex map { case (param, index) =>
val name = param.name.decodedName.toString
val name = paramMapper.map(param.name.decodedName.toString)
val key = q"""$path + "." + $name"""
val returnType: Type = param.typeSignatureIn(c.weakTypeOf[T])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class ArbitraryTypeReaderSpec extends Spec { def is = s2"""
instantiate with no apply method but a single constructor with multiple params $instantiateMultiParamConstructor
instantiate with multiple apply methods if only one returns the correct type $multipleApply
instantiate with primary constructor when no apply methods and multiple constructors $multipleConstructors
instantiate with camel case fields and hyphen case config keys $withCamelCaseFields
use another implicit value reader for a field $withOptionField
fall back to a default value on an apply method $fallBackToApplyMethodDefaultValue
fall back to default values on an apply method if base key isn't in config $fallBackToApplyMethodDefaultValueNoKey
Expand Down Expand Up @@ -85,6 +86,14 @@ class ArbitraryTypeReaderSpec extends Spec { def is = s2"""
instance.foo must_== foo
}

def withCamelCaseFields = {
import Ficus.{stringValueReader}
import HyphenCaseArbitraryTypeReader._
val cfg = ConfigFactory.parseString(s"withCamelCaseFields { a-camel-case-field: foo, another-camel-case-field: bar }")
val instance = arbitraryTypeValueReader[ClassWithCamelCaseFields].read(cfg, "withCamelCaseFields")
(instance.aCamelCaseField must_== "foo") and (instance.anotherCamelCaseField must_== "bar")
}

def fallBackToApplyMethodDefaultValue = {
import Ficus.{optionValueReader, stringValueReader}
import ArbitraryTypeReader._
Expand Down Expand Up @@ -247,8 +256,9 @@ object ArbitraryTypeReaderSpec {
case class WithReaderInCompanion(foo: String)

object WithReaderInCompanion {
implicit val reader: ValueReader[WithReaderInCompanion] =
implicit val reader: ValueReader[WithReaderInCompanion] =
ValueReader.relative(_ => WithReaderInCompanion("from-companion"))
}

class ClassWithCamelCaseFields(val aCamelCaseField: String, val anotherCamelCaseField: String)
}