Skip to content

Commit

Permalink
working member-based settings
Browse files Browse the repository at this point in the history
  • Loading branch information
frohoff committed May 13, 2015
1 parent 54cb7d1 commit 57de54d
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 46 deletions.
56 changes: 19 additions & 37 deletions src/main/scala/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,58 +9,34 @@ import scala.annotation.StaticAnnotation
import scala.collection.JavaConversions._

trait ArbitraryTypeReader {
@ArbitraryTypeReader.settings(identity, false)
implicit def arbitraryTypeValueReader[T]: ValueReader[T] = macro ArbitraryTypeReaderMacros.arbitraryTypeValueReader[T]
}

object ArbitraryTypeReader extends ArbitraryTypeReader {
class settings(paramKeyMapper: String => String = identity[String], exhaustiveMapping: Boolean = false) extends StaticAnnotation
}

trait OtherArbitraryTypeReader {
@ArbitraryTypeReader.settings(_.toLowerCase, true)
implicit def arbitraryTypeValueReader[T]: ValueReader[T] = macro ArbitraryTypeReaderMacros.arbitraryTypeValueReader[T]
}

object OtherArbitraryTypeReader extends OtherArbitraryTypeReader

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

val checkExhaustivity: Boolean = false
def mapParam(n: String): String = n
def assertExhaustive(cfg: Config, path: String, mappedPaths: Seq[String]): Unit = {
val relevantPaths = cfg.getObject(path).keySet.map(k => s"$path.$k")
val unmappedPaths = relevantPaths -- mappedPaths
if (! unmappedPaths.isEmpty) throw new IllegalArgumentException(s"config paths not exhaustively mapped: $unmappedPaths")
}
}

def arbitraryTypeValueReader[T : c.WeakTypeTag](c: Context): c.Expr[ValueReader[T]] = {
import c.universe._
object ArbitraryTypeReader extends ArbitraryTypeReader

try {
val annotationParamExpr
= c.macroApplication.symbol.annotations
.filter(_.tree.tpe <:< typeOf[ArbitraryTypeReader.settings]).head.tree.children.tail
object ArbitraryTypeReaderMacros {
import scala.reflect.macros.blackbox.Context

val paramMapperExpr :: exhaustiveMappingExpr :: Nil = annotationParamExpr

//println(paramMapperExpr)
def arbitraryTypeValueReader[T : c.WeakTypeTag](c: Context): c.Expr[ValueReader[T]] = {
import c.universe._

val readerExpr = reify {
new ValueReader[T] {
val paramMapper: String => String = c.Expr[String => String](c.resetLocalAttrs(paramMapperExpr)).splice
val exhaustiveMapping: Boolean = c.Expr[Boolean](c.resetLocalAttrs(exhaustiveMappingExpr)).splice
def read(config: Config, path: String): T = instantiateFromConfig[T](c)(
config = c.Expr[Config](Ident(TermName("config"))),
path = c.Expr[String](Ident(TermName("path")))).splice
}
}

//println(show(readerExpr))

readerExpr
} catch { // FIXME: remove
case e => e.printStackTrace(); throw e
}
}

def instantiateFromConfig[T : c.WeakTypeTag](c: Context)(config: c.Expr[Config], path: c.Expr[String]): c.Expr[T] = {
Expand All @@ -85,11 +61,15 @@ object ArbitraryTypeReaderMacros {
).map(Ident(_)).getOrElse(New(Ident(returnType.typeSymbol)))
val instantiationCall = Select(instantiationObject, instantiationMethod.name)

val assertion = reify {
assertExhaustive(config.splice, path.splice, c.Expr[List[String]](q"List(..$instantiationKeys)").splice)
}
val enclosingObject = c.Expr[ArbitraryTypeReader](c.prefix.tree)

val conditionalAssertion = If(Ident(TermName("exhaustiveMapping")), assertion.tree, Literal(Constant(())))
val keyListExpr = c.Expr[List[String]](q"List(..$instantiationKeys)")

val conditionalAssertion = reify {
if (enclosingObject.splice.checkExhaustivity) {
enclosingObject.splice.assertExhaustive(config.splice, path.splice, keyListExpr.splice)
}
}.tree
val instantiation = Apply(instantiationCall, instantiationArgs)
c.Expr[T](Block(List(conditionalAssertion), instantiation))
}
Expand All @@ -102,9 +82,11 @@ object ArbitraryTypeReaderMacros {

if (!method.isPublic) fail(s"'$decodedMethodName' method is not public")

val enclosingObject = c.Expr[ArbitraryTypeReader](c.prefix.tree)

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

(companionObjectMaybe.filter(_ => param.asTerm.isParamWithDefault) map { companionObject =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ class ArbitraryTypeReaderSpec extends Spec { def is = s2"""
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
throw an exception with non-exhuastive key mapping $withExhaustivityCheck
throw an exception with non-exhuastive key mapping $withFailingExhaustivityCheck
succeed with an exhuastive key mapping $withPassingExhaustivityCheck
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 @@ -89,20 +90,29 @@ class ArbitraryTypeReaderSpec extends Spec { def is = s2"""

def withCamelCaseFields = {
import Ficus.{stringValueReader}
import OtherArbitraryTypeReader._
import LowercaseArbitraryTypeReader._
val cfg = ConfigFactory.parseString(s"withCamelCaseFields { acamelcasefield: foo, anothercamelcasefield: bar }")
val instance = arbitraryTypeValueReader[ClassWithCamelCaseFields].read(cfg, "withCamelCaseFields")
(instance.aCamelCaseField must_== "foo") and (instance.anotherCamelCaseField must_== "bar")
}

def withExhaustivityCheck = {
import Ficus.{stringValueReader}
import OtherArbitraryTypeReader._
val cfg = ConfigFactory.parseString(s"withCamelCaseFields { acamelcasefield: foo, anothercamelcasefield: bar, anextrafield: error }")
val reader = arbitraryTypeValueReader[ClassWithCamelCaseFields]
def doRead: Unit = reader.read(cfg, "withCamelCaseFields")
def withPassingExhaustivityCheck = {
import Ficus._
import ExhaustiveArbitraryTypeReader._
val cfg = ConfigFactory.parseString(s"withExhaustive { foo: foo, bar: 1 }")
val reader = arbitraryTypeValueReader[ClassWithMultipleParams]
val instance = reader.read(cfg, "withExhaustive")
(instance.foo must_== "foo") and (instance.bar must_== 1)
}

def withFailingExhaustivityCheck = {
import Ficus._
import ExhaustiveArbitraryTypeReader._
val cfg = ConfigFactory.parseString(s"withExhaustive { foo: foo, bar: 1, anextrafield: error }")
val reader = arbitraryTypeValueReader[ClassWithMultipleParams]
def doRead: Unit = reader.read(cfg, "withExhaustive")
doRead must throwA[IllegalArgumentException].like {
case e:IllegalArgumentException => e.getMessage should contain("withCamelCaseFields.anextrafield")
case e:IllegalArgumentException => e.getMessage should contain("withExhaustive.anextrafield")
}
}

Expand Down Expand Up @@ -273,4 +283,12 @@ object ArbitraryTypeReaderSpec {
}

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

object LowercaseArbitraryTypeReader extends ArbitraryTypeReader {
override def mapParam(n: String) = n.toLowerCase()
}

object ExhaustiveArbitraryTypeReader extends ArbitraryTypeReader {
override val checkExhaustivity = true
}
}

0 comments on commit 57de54d

Please sign in to comment.