Last active
October 13, 2015 10:56
-
-
Save jonas/e1849af4b142378f79aa to your computer and use it in GitHub Desktop.
Read Typesafe config settings using approach from http://rapture.io/ 's CLI module
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************************************************************************************************************\ | |
* Rapture CLI, version 2.0.0. Copyright 2010-2015 Jon Pretty, Propensive Ltd. * | |
* * | |
* The primary distribution site is http://rapture.io/ * | |
* * | |
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in complance * | |
* with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * | |
* * | |
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed * | |
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License * | |
* for the specific language governing permissions and limitations under the License. * | |
\******************************************************************************************************************/ | |
import com.typesafe.config.{ ConfigFactory, Config => UntypedConfig } | |
import scala.util.Try | |
import scala.collection.immutable.ListMap | |
import scala.annotation.tailrec | |
import scala.reflect._, runtime.universe.TypeTag | |
trait MethodConstraint | |
trait `Settings.parse` extends MethodConstraint | |
case class MissingSetting(name: String) extends | |
Exception(s"the Settingeter --$name was missing") | |
//@implicitNotFound("Can not combine elements of type ${A} and ${B}") | |
trait Construct[-A <: Settings, -B <: Settings] { construct => | |
type And <: Settings | |
type Or <: Settings | |
def and(a: A, b: B): ProductSettings[And] | |
def or(a: A, b: B): CoproductSettings[Or] | |
def swap: Construct[B, A] { type And = construct.And; type Or = construct.Or } = | |
new Construct[B, A] { | |
type And = construct.And | |
type Or = construct.Or | |
def and(a: B, b: A): ProductSettings[And] = construct.and(b, a) | |
def or(a: B, b: A): CoproductSettings[Or] = construct.or(b, a) | |
} | |
} | |
trait Construct_1 { | |
implicit def general[A <: Settings, B <: Settings]: Construct[A, B] { type And = | |
A with B; type Or = A with B } = { | |
new Construct[A, B] { | |
type And = A with B | |
type Or = A with B | |
def and(a: A, b: B) = ProductSettings[A with B](Set(a, b)) | |
def or(a: A, b: B) = CoproductSettings[A with B](Vector(a, b)) | |
} | |
} | |
} | |
object Construct extends Construct_1 { | |
implicit def leftProduct[A <: Settings, B <: SimpleSetting[_]]: Construct[ProductSettings[A], B] { | |
type And = A with B; type Or = ProductSettings[A] with B } = { | |
new Construct[ProductSettings[A], B] { | |
type And = A with B | |
type Or = ProductSettings[A] with B | |
def and(a: ProductSettings[A], b: B) = ProductSettings[A with B](a.elements + b) | |
def or(a: ProductSettings[A], b: B) = | |
CoproductSettings[ProductSettings[A] with B](Vector(a, b)) | |
} | |
} | |
implicit def rightProduct[A <: SimpleSetting[_], B <: Settings]: Construct[A, ProductSettings[B]] { | |
type And = B with A; type Or = ProductSettings[B] with A } = leftProduct[B, A].swap | |
implicit def leftCoproduct[A <: Settings, B <: SimpleSetting[_]]: Construct[CoproductSettings[A], | |
B] { type And = CoproductSettings[A] with B; type Or = A with B } = { | |
new Construct[CoproductSettings[A], B] { | |
type And = CoproductSettings[A] with B | |
type Or = A with B | |
def and(a: CoproductSettings[A], b: B) = | |
ProductSettings[CoproductSettings[A] with B](Set(a, b)) | |
def or(a: CoproductSettings[A], b: B) = CoproductSettings[A with B](a.elements :+ b) | |
} | |
} | |
implicit def rightCoproduct[A <: SimpleSetting[_], B <: Settings]: Construct[A, | |
CoproductSettings[B]] { type And = CoproductSettings[B] with A; type Or = B with A } = | |
leftCoproduct[B, A].swap | |
} | |
trait Settings { Settings => | |
type Result | |
def parse(args: UntypedConfig): Result | |
def &[B <: Settings](b: B)(implicit con: Construct[Settings.type, b.type]): | |
ProductSettings[con.And] = { | |
con.and(this, b) | |
} | |
def |[B <: Settings](b: B)(implicit con: Construct[Settings.type, b.type]): | |
CoproductSettings[con.Or] = { | |
con.or(this, b) | |
} | |
def unary_~ : OptionSettings[this.type] = OptionSettings(this) | |
def by[R](fn: Result => R): Setting.Handler[this.type, R] = | |
new Setting.Handler[this.type, R](this) { | |
type From = Result | |
def handle(v: From): R = fn(v) | |
} | |
} | |
case class OptionSettings[Ps <: Settings](Settings: Ps) extends Settings { | |
type Result = Option[Settings.Result] | |
def parse(args: UntypedConfig): Result = | |
Try(Settings.parse(args)).toOption | |
override def toString = s"[$Settings]" | |
} | |
case class ProductSettings[Ps <: Settings](elements: Set[Settings]) extends Settings { | |
type ProductTypes = Ps | |
type Result = Product[ProductTypes] | |
def parse(args: UntypedConfig): Result = { | |
val map = elements.map(key => key -> key.parse(args)) | |
new Product[Ps](map.toMap) | |
} | |
override def toString = elements.mkString("( ", " & ", " )") | |
} | |
case class CoproductSettings[Ps <: Settings](elements: Vector[Settings]) extends Settings { | |
type CoproductTypes = Ps | |
type Result = Coproduct[CoproductTypes] | |
def parse(args: UntypedConfig): Result = { | |
val elems = elements.to[List].flatMap { k => | |
Option(k.parse(args)).map(k -> _) | |
} | |
elems match { | |
case (key, res) :: _ => Coproduct[CoproductTypes](key -> res) | |
case Nil => throw new MissingSetting(toString) | |
} | |
} | |
override def toString = elements.mkString("( ", " | ", " )") | |
} | |
case class SimpleSetting[T: Setting.Extractor](val path: String) extends Settings { | |
simpleSetting => | |
type Result = T | |
def checkValue: Option[T] = None | |
def filter(fn: T => Boolean): SimpleSetting[T] = new SimpleSetting[T](path) { | |
override def checkValue = simpleSetting.checkValue | |
} | |
protected val extractor: Setting.Extractor[T] = implicitly[Setting.Extractor[T]] | |
def parse(args: UntypedConfig): Result = | |
extractor.extract(args, path) | |
// Also consider `extractor` in `hashCode` and `equals` | |
override def hashCode = path.hashCode ^ extractor.hashCode | |
override def equals(that: Any) = that match { | |
case that: SimpleSetting[_] => | |
path == that.path && that.extractor == extractor | |
case _ => | |
false | |
} | |
override def toString = path | |
def of(v: T): SimpleSetting[T] = new SimpleSetting[T](path) { | |
override def checkValue = Some(v) | |
} | |
} | |
object Setting { | |
object Extractor { | |
implicit val stringExtractor: Extractor[String] = new Extractor[String] { | |
def extract(config: UntypedConfig, path: String): String = | |
config.getString(path) | |
} | |
implicit val intExtractor: Extractor[Int] = new Extractor[Int] { | |
def extract(config: UntypedConfig, path: String): Int = | |
config.getInt(path) | |
} | |
} | |
trait Extractor[T] { def extract(config: UntypedConfig, path: String): T } | |
abstract class Handler[-K, +H](val Settings: Settings) { | |
type From | |
def handle(v: From): H | |
} | |
def apply[T: Extractor](path: String): SimpleSetting[T] = SimpleSetting[T](path) | |
} | |
//@implicitNotFound("product does not contain this value") | |
trait ProductContainsSetting[V, T] | |
object ProductContainsSetting { | |
implicit def acceptable[V, T <: V]: ProductContainsSetting[V, T] = null | |
} | |
//@implicitNotFound("coproduct cannot contain this value") | |
trait CoproductContainsSetting[V, T] | |
object CoproductContainsSetting { | |
implicit def acceptable[V, T <: V]: CoproductContainsSetting[V, T] = null | |
} | |
case class Product[T <: Settings](tmap: Map[Settings, Any]) { | |
def apply[V <: Settings](value: V)(implicit acc: ProductContainsSetting[V, T]): | |
value.Result = tmap(value).asInstanceOf[value.Result] | |
override def toString = tmap.map { case (k, v) => s"$k: $v" }.mkString(", ") | |
} | |
case class Coproduct[T <: Settings](value: (Settings, Any)) { | |
def handle[K, R](handlers: Setting.Handler[K, R]*)(implicit ev: K <:< T): R = { | |
val h = handlers.find(_.Settings == value._1).get | |
h.handle(value._2.asInstanceOf[h.From]) | |
} | |
override def toString = s"${value._1}: ${value._2}" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
scala> val config = com.typesafe.config.ConfigFactory.parseString(""" | |
| a.b.c = 42 | |
| d.e.f = hello world | |
| g.h.i = 2m | |
| """) | |
config: com.typesafe.config.Config = Config(SimpleConfigObject({"a":{"b":{"c":42}},"d":{"e":{"f":"hello world"}},"g":{"h":{"i":"2m"}}})) | |
scala> val Alpha = Setting[Int]("a.b.c") | |
Alpha: commands.common.SimpleSetting[Int] = a.b.c | |
scala> Alpha.parse(config) | |
res0: Alpha.Result = 42 | |
scala> val Beta = Setting[String]("d.e.f") | |
Beta: commands.common.SimpleSetting[String] = d.e.f | |
scala> (Alpha & Beta).parse(config) | |
res1: commands.common.Product[Alpha.type with Beta.type] = a.b.c: 42, d.e.f: hello world | |
scala> .apply(Beta) | |
res2: Beta.Result = hello world |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment