Skip to content

Commit

Permalink
Merge pull request #35 from olafurpg/docs
Browse files Browse the repository at this point in the history
Create metaconfig-docs module
  • Loading branch information
olafurpg authored Jan 22, 2018
2 parents d91ce7a + 45c6263 commit 82ea4de
Show file tree
Hide file tree
Showing 24 changed files with 516 additions and 203 deletions.
21 changes: 17 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import java.util.Date
lazy val ScalaVersions = Seq("2.11.11", "2.12.2")

organization in ThisBuild := "com.geirsson"
version in ThisBuild := customVersion.getOrElse(version.in(ThisBuild).value)
version in ThisBuild ~= { old =>
customVersion.getOrElse(old).replace('+', '-')
}
allSettings
noPublish

Expand All @@ -15,16 +17,24 @@ commands += Command.command("release") { s =>
s
}

lazy val `metaconfig-docs` = project
.settings(
allSettings,
libraryDependencies ++= List(
"com.lihaoyi" %% "scalatags" % "0.6.7"
)
)
.dependsOn(`metaconfig-coreJVM`)

lazy val website = project
.settings(
allSettings,
tutNameFilter := "README.md".r,
tutSourceDirectory := baseDirectory.in(ThisBuild).value / "docs",
sourceDirectory.in(Preprocess) := tutTargetDirectory.value,
sourceDirectory.in(GitBook) := target.in(Preprocess).value,
siteSubdirName in GitBook := "preprocess",
preprocessVars in Preprocess := Map(
"VERSION" -> version.value,
"VERSION" -> version.value.replaceAll("-.*", ""),
"DATE" -> new Date().toString
),
siteSourceDirectory := target.in(GitBook).value,
Expand All @@ -39,7 +49,10 @@ lazy val website = project
GitBookPlugin,
TutPlugin
)
.dependsOn(`metaconfig-coreJVM`, `metaconfig-typesafe-config`)
.dependsOn(
`metaconfig-docs`,
`metaconfig-typesafe-config`
)

lazy val MetaVersion = "2.0.0-M3"

Expand Down
84 changes: 79 additions & 5 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,14 @@ All of the following code examples assume that you have `import metaconfig._` in
* [Getting started](#getting-started)
* [Conf](#conf)
* [Conf.parse](#confparse)
* [ConfDecoder.instance](#confdecoderinstance)
* [ConfDecoder](#confdecoder)
* [ConfError](#conferror)
* [Configured](#configured)
* [generic.deriveSurface](#genericderivesurface)
* [generic.deriveDecoder](#genericderivedecoder)
* [DeprecatedName](#deprecatedname)
* [Limitations](#limitations)
* [@DeprecatedName](#deprecatedname)
* [Docs](#docs)

<!-- /TOC -->

Expand Down Expand Up @@ -82,7 +84,7 @@ For a Scala.js alternative, depend on the `metaconfig-hocon` module and replace
import metaconfig.hocon._
```

## ConfDecoder.instance
## ConfDecoder

To convert `Conf` into higher-level data structures you need a `ConfDecoder[T]` instance.
Convert a partial function from `Conf` to your target type using `ConfDecoder.instance[T]`.
Expand Down Expand Up @@ -120,6 +122,18 @@ age = "Susan"
"""))
```

You can also use existing decoders to build more complex decoders

```tut
val fileDecoder = ConfDecoder.stringConfDecoder.flatMap { string =>
val file = new java.io.File(string)
if (file.exists()) Configured.ok(file)
else ConfError.fileDoesNotExist(file).notOk
}
fileDecoder.read(Conf.fromString(".scalafmt.conf"))
fileDecoder.read(Conf.fromString(".foobar"))
```

## ConfError

`ConfError` is a helper to produce readable and potentially aggregated error messages.
Expand Down Expand Up @@ -178,6 +192,7 @@ Configured.ok(42).get
To use automatic derivation, you first need a `Surface[T]` typeclass instance

```tut
import metaconfig.generic._
implicit val userSurface: Surface[User] =
generic.deriveSurface[User]
```
Expand Down Expand Up @@ -225,14 +240,22 @@ This will fail wiith a fail cryptic compile error
implicit val decoder = generic.deriveDecoder[Funky](Funky(new File("")))
```

Observer that the error message is complaining about a missing `metaconfig.ConfDecoder[java.io.File]` implicit.
Observe that the error message is complaining about a missing `metaconfig.ConfDecoder[java.io.File]` implicit.

### Limitations

The following features are not supported by generic derivation

* derivation for objects, sealed traits or non-case classes, only case classes are supported
* parameterized types, it's possible to derive decoders for a concrete parameterized type like `Option[Foo]` but note that the type field (`Field.tpe`) will be pretty-printed to the generic representation of that field: `Option[T].value: T`.

## DeprecatedName
## @DeprecatedName

As your configuration evolves, you may want to rename some settings but you have existing users who are using the old name.
Use the `@DeprecatedName` annotation to continue supporting the old name even if you go ahead with the rename.

```tut:silent
import metaconfig.annotation._
case class EvolvingConfig(
@DeprecatedName("goodName", "Use isGoodName instead", "1.0")
isGoodName: Boolean
Expand All @@ -246,3 +269,54 @@ decoder.read(Conf.Obj("goodName" -> Conf.fromBoolean(false)))
decoder.read(Conf.Obj("isGoodName" -> Conf.fromBoolean(false)))
decoder.read(Conf.Obj("gooodName" -> Conf.fromBoolean(false)))
```

## Docs

To generate documentation for you configuration, add a dependency to the following module

```scala
libraryDependencies += "com.geirsson" %% "metaconfig-docs" % "@VERSION@"
```

First define your configuration

```tut:silent
case class Home(
@Description("Address description")
address: String = "Lakelands 2",
@Description("Country description")
country: String = "Iceland"
)
implicit val homeSurface = generic.deriveSurface[Home]
case class User(
@Description("Name description")
name: String = "John",
@Description("Age description")
age: Int = 42,
home: Home = Home()
)
implicit val userSurface = generic.deriveSurface[User]
```

To generate html documentation, pass in a default value

```tut
docs.Docs.html(User())
```

The output will look like this when rendered in a markdown or html document

```tut:passthrough
println(docs.Docs.html(User()))
```

The `Docs.html` method does nothing magical, it's possible to implement custom renderings by inspecting `Settings[T]` directly.

```tut
Settings[User].settings
val flat = Settings[User].flat(User())
flat.map { case (setting, defaultValue) =>
s"Setting ${setting.name} of type ${setting.tpe} has default value $defaultValue"
}.mkString("\n==============\n")
```
12 changes: 5 additions & 7 deletions metaconfig-core/shared/src/main/scala/metaconfig/Conf.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import java.io.File
import scala.meta.inputs.Position
import scala.util.Try
import metaconfig.Extractors._
import metaconfig.generic.Setting
import metaconfig.internal.ConfGet
import org.langmeta.inputs.Input
import org.scalameta.logger

Expand All @@ -26,17 +28,13 @@ sealed abstract class Conf extends Product with Serializable {
ev.read(this)
def getSettingOrElse[T](setting: Setting, default: T)(
implicit ev: ConfDecoder[T]): Configured[T] =
Metaconfig.getOrElse(
this,
default,
setting.name,
setting.alternativeNames: _*)
ConfGet.getOrElse(this, default, setting.name, setting.alternativeNames: _*)
def get[T](path: String, extraNames: String*)(
implicit ev: ConfDecoder[T]): Configured[T] =
Metaconfig.get(this, path, extraNames: _*)
ConfGet.get(this, path, extraNames: _*)
def getOrElse[T](path: String, extraNames: String*)(default: T)(
implicit ev: ConfDecoder[T]): Configured[T] =
Metaconfig.getOrElse(this, default, path, extraNames: _*)
ConfGet.getOrElse(this, default, path, extraNames: _*)
}

object Conf {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import scala.language.higherKinds
import scala.collection.generic.CanBuildFrom
import scala.reflect.ClassTag
import metaconfig.Configured._
import metaconfig.generic.Settings
import metaconfig.internal.CanBuildFromDecoder
import metaconfig.internal.NoTyposDecoder

trait ConfDecoder[A] { self =>

def read(conf: Conf): Configured[A]
final def read(conf: Configured[Conf]): Configured[A] =
conf.andThen(self.read)

def map[B](f: A => B): ConfDecoder[B] =
self.flatMap(x => Ok(f(x)))
def orElse(other: ConfDecoder[A]): ConfDecoder[A] =
Expand Down Expand Up @@ -62,6 +65,10 @@ object ConfDecoder {
)
}

def constant[T](value: T): ConfDecoder[T] = new ConfDecoder[T] {
override def read(conf: Conf): Configured[T] = Configured.ok(value)
}

implicit val intConfDecoder: ConfDecoder[Int] =
instanceExpect[Int]("Number") {
case Conf.Num(x) => Ok(x.toInt)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import java.nio.file.Path
import scala.meta.inputs.Position
import scala.meta.internal.inputs._
import metaconfig.ConfError.TypeMismatch
import metaconfig.annotation.DeprecatedName
import metaconfig.error.CompositeException

sealed abstract class ConfError(val msg: String) extends Serializable { self =>
def extra: List[String] = Nil
Expand Down

This file was deleted.

30 changes: 0 additions & 30 deletions metaconfig-core/shared/src/main/scala/metaconfig/Field.scala

This file was deleted.

59 changes: 9 additions & 50 deletions metaconfig-core/shared/src/main/scala/metaconfig/Metaconfig.scala
Original file line number Diff line number Diff line change
@@ -1,62 +1,21 @@
package metaconfig

import scala.collection.mutable
import scala.meta.inputs.Input
import scala.meta.inputs.Position
import scala.meta.internal.inputs._

trait MetaconfigParser {
def fromInput(input: Input): Configured[Conf]
}
import metaconfig.internal.ConfGet

@deprecated(
"Moved to metaconfig.internal.ConfGet. Use Conf.get* variants instead.",
"0.6")
object Metaconfig {

def getKey(obj: Conf, keys: Seq[String]): Option[Conf] =
if (keys.isEmpty) None
else {
obj match {
case obj @ Conf.Obj(_) =>
obj.values
.collectFirst { case (key, value) if key == keys.head => value }
.orElse(getKey(obj, keys.tail))
case _ => None
}
}
ConfGet.getKey(obj, keys)

def getOrElse[T](conf: Conf, default: T, path: String, extraNames: String*)(
implicit ev: ConfDecoder[T]): Configured[T] = {
getKey(conf, path +: extraNames) match {
case Some(value) => ev.read(value)
case None => Configured.Ok(default)
}
}
implicit ev: ConfDecoder[T]): Configured[T] =
ConfGet.getOrElse[T](conf, default, path)

def get[T](conf: Conf, path: String, extraNames: String*)(
implicit ev: ConfDecoder[T]): Configured[T] = {
getKey(conf, path +: extraNames) match {
case Some(value) => ev.read(value)
case None =>
conf match {
case obj @ Conf.Obj(_) => ConfError.missingField(obj, path).notOk
case _ =>
ConfError
.typeMismatch(s"Conf.Obj with field $path", conf, path)
.notOk
}
}
}
implicit ev: ConfDecoder[T]): Configured[T] =
ConfGet.get[T](conf, path, extraNames: _*)

// Copy-pasted from scala.meta inputs because it's private.
// TODO(olafur) expose utility in inputs to get offset from line
private[metaconfig] def getOffsetByLine(chars: Array[Char]): Array[Int] = {
val buf = new mutable.ArrayBuffer[Int]
buf += 0
var i = 0
while (i < chars.length) {
if (chars(i) == '\n') buf += (i + 1)
i += 1
}
if (buf.last != chars.length) buf += chars.length // sentinel value used for binary search
buf.toArray
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package metaconfig

import org.langmeta.inputs.Input

trait MetaconfigParser {
def fromInput(input: Input): Configured[Conf]
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package metaconfig

@deprecated("Copy paste the implementation to your project", "0.6")
object String2AnyMap {
def unapply(arg: Any): Option[Map[String, Any]] = arg match {
case someMap: Map[_, _] =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package metaconfig
package metaconfig.annotation

import scala.annotation.StaticAnnotation

Expand Down
Loading

0 comments on commit 82ea4de

Please sign in to comment.