Skip to content

Commit

Permalink
hyphen-case config key support
Browse files Browse the repository at this point in the history
  • Loading branch information
frohoff committed May 6, 2015
1 parent 5d8456b commit 8f60a99
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 12 deletions.
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)
}

0 comments on commit 8f60a99

Please sign in to comment.