Skip to content

Commit

Permalink
iss #24: config parse rules & helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
maizy committed Jan 16, 2017
1 parent b4d177d commit 0ff4cb5
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,24 @@ package ru.maizy.ambient7.webapp
* Copyright (c) Nikita Kovaliov, maizy.ru, 2016-2017
* See LICENSE.txt for details.
*/

object WebAppLauncher extends App {

val config = WebAppConfigReader
config.fillReader()
val eitherAppConfig = config.readAppConfig(args.toIndexedSeq)
eitherAppConfig match {
case Left(parsingError) =>
// TODO: show usage and error if needed, extract to core function
Console.err.println(
s"\nUnable to launch app.\n\nErrors:\n * ${parsingError.messages.mkString("\n * ")}" +
parsingError.usage.map(u => s"Usage: $u").getOrElse("")
)
// TODO: extract to core
val sep = "\n * "
val errors = if (parsingError.messages.nonEmpty) {
s"Errors:$sep${parsingError.messages.mkString(sep)}\n"
} else {
""
}
val usage = parsingError.usage.map(u => s"Usage: $u").getOrElse("")
val userResult = List(errors, usage).filterNot(_ == "").mkString("\n")
Console.err.println(userResult)
case Right(opts) =>
println(s"Success: $opts")
}
Expand Down
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ lazy val commonDependencies = Seq(
"ch.qos.logback" % "logback-classic" % "1.1.7",
"com.typesafe.scala-logging" %% "scala-logging" % "3.5.0",
"com.typesafe" % "config" % "1.3.1",
"com.github.kxbmap" %% "configs" % "0.4.4",
"org.scalatest" %% "scalatest" % "3.0.0" % "test"
)
)
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package ru.maizy.ambient7.core.config
* Copyright (c) Nikita Kovaliov, maizy.ru, 2016-2017
* See LICENSE.txt for details.
*/

trait MainDbConfigReader extends UniversalConfigReader {

import UniversalConfigReader._
Expand All @@ -30,18 +31,30 @@ trait MainDbConfigReader extends UniversalConfigReader {
.action { (value, opts) => mainDbOpts(opts)(_.copy(url = Some(value))) }
.text(s"URL for connecting to h2 database")

appendOptionalSimpleConfigRule[String]("db.url") { (value, opts) =>
mainDbOpts(opts)(_.copy(url = Some(value)))
}

appendDbOptsCheck{ dbOpts => Either.cond(dbOpts.url.isDefined, (), ParsingError.withMessage("db-url is required")) }


cliParser.opt[String]("db-user")
.action { (value, opts) => mainDbOpts(opts)(_.copy(user = value)) }
.text("database user")

appendOptionalSimpleConfigRule[String]("db.user") {
(value, opts) => mainDbOpts(opts)(_.copy(user = value))
}


cliParser.opt[String]("db-password")
.action { (value, opts) => mainDbOpts(opts)(_.copy(password = value)) }
.text("database password")

appendOptionalSimpleConfigRule[String]("db.password") { (value, opts) =>
mainDbOpts(opts)(_.copy(password = value))
}

()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ object ParsingError {
def withMessage(message: String): ParsingError =
ParsingError(IndexedSeq(message))

def withMessages(messages: String*): ParsingError =
def withMessages(messages: Seq[String]): ParsingError =
ParsingError(messages.toIndexedSeq)

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,20 @@ package ru.maizy.ambient7.core.config
*/

import java.io.File
import java.nio.file.Files
import java.nio.file.{ Files, Path }
import scala.annotation.tailrec
import scala.util.{ Failure, Try, Success }
import com.typesafe.config.{ Config, ConfigFactory }
import configs.Configs
import scopt.OptionParser
import ru.maizy.ambient7.core.config.helper.ConfigRuleOps.IfSuccessOp

object UniversalConfigReader {
type CheckResult = Either[ParsingError, Unit]
type Check = Ambient7Options => CheckResult
type ParseResult = Either[ParsingError, Ambient7Options]
type Postprocessor = Ambient7Options => ParseResult
type ConfigRule = (Config, Ambient7Options) => ParseResult
}

trait UniversalConfigReader {
Expand All @@ -24,7 +29,8 @@ trait UniversalConfigReader {
private var postprocessors_ = List[Postprocessor]()

private var isConfigEnabled_ = false
// private var isConfigRequired_ = false
private var isConfigRequired_ = false
private var configRules_ = List[ConfigRule]()

private val isCliOptionsEnabled_ = true

Expand Down Expand Up @@ -77,7 +83,7 @@ trait UniversalConfigReader {

protected def fillConfigOptions(requireUniversalConfig: Boolean = false): Unit = {
isConfigEnabled_ = true
// isConfigRequired_ = requireUniversalConfig
isConfigRequired_ = requireUniversalConfig

def addConfigOption(parser: OptionParser[Ambient7Options], required: Boolean = false): Unit = {
val opt = parser.opt[File]("config")
Expand All @@ -101,6 +107,8 @@ trait UniversalConfigReader {

def isConfigEnabled: Boolean = isConfigEnabled_

def isConfigRequired: Boolean = isConfigRequired_

def isCliOptionsEnabled: Boolean = isCliOptionsEnabled_

def appName: String
Expand All @@ -116,6 +124,30 @@ trait UniversalConfigReader {
protected def appendPostprocessor(postprocessor: Postprocessor): Unit =
postprocessors_ = postprocessor :: postprocessors_

protected def appendConfigRule(rule: ConfigRule): Unit =
configRules_ = rule :: configRules_

protected def appendSimpleConfigRule[T](configPath: String)(
saveValue: (T, Ambient7Options) => Ambient7Options)(implicit reader: Configs[T]): Unit =
{
appendConfigRule { (config, opts) =>
Configs.apply[T].get(config, configPath).ifSuccess(value => saveValue(value, opts))
}
}

protected def appendOptionalSimpleConfigRule[T](configPath: String)(
save: (T, Ambient7Options) => Ambient7Options)(implicit reader: Configs[Option[T]]): Unit =
{
appendSimpleConfigRule[Option[T]](configPath) { (mayBeValue, opts) =>
mayBeValue match {
case Some(value) => save(value, opts)
case None => opts
}
}
}

def configRules: List[ConfigRule] = configRules_.reverse

private def processHelpOption(result: ParseResult): ParseResult = {
result match {
// show usage if --help option exists
Expand All @@ -124,26 +156,37 @@ trait UniversalConfigReader {
}
}

private def safeLoadConfig(universalConfigPath: Path): Either[IndexedSeq[String], Config] = {
Try(ConfigFactory.parseFile(universalConfigPath.toFile)) match {
case Failure(exception) => Left(IndexedSeq(s"reading config error ${exception.getMessage}"))
case Success(config) => Right(config)
}
}

private def parseConfig(opts: Ambient7Options, args: IndexedSeq[String]): ParseResult = {
configLogger.debug("config enabled")
val optsWithConfigPath = configPathCliParser.parse(args, opts).getOrElse(opts)
var parseResult: ParseResult = Right(optsWithConfigPath)
parseResult = processHelpOption(parseResult)
parseResult.right.flatMap { optsWithConfigPath =>
optsWithConfigPath.universalConfigPath match {
case Some(universalConfigPath) =>
if (Files.isReadable(universalConfigPath)) {
configLogger.info(s"parse config from $universalConfigPath")

// FIXME: implements

Right(optsWithConfigPath)
case Some(configPath) =>
if (Files.isReadable(configPath)) {
configLogger.info(s"parse config from $configPath")
safeLoadConfig(configPath)
.left.map(ParsingError.withMessages(_))
.right.flatMap { config =>
val configRulesApplingResult = configRules.foldLeft[ParseResult](Right(optsWithConfigPath)) {
case (error@Left(_), _) => error
case (res@Right(_), rule) => res.right.flatMap(opts => rule(config, opts))
}
configRulesApplingResult
}
} else {
val message = s"unable to read config from $universalConfigPath"
val message = s"unable to read config from $configPath"
configLogger.error(message)
Left(ParsingError.withMessage(message))
}

case _ =>
configLogger.info("universal config path not defined")
Right(opts)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ru.maizy.ambient7.core.config.helper

/**
* Copyright (c) Nikita Kovaliov, maizy.ru, 2017
* See LICENSE.txt for details.
*/

import ru.maizy.ambient7.core.config.{ Ambient7Options, ParsingError, UniversalConfigReader }

object ConfigRuleOps {

import UniversalConfigReader._

implicit class IfSuccessOp[T](configRes: configs.Result[T]) {

def ifSuccess(saveValue: T => Ambient7Options): ParseResult = {
configRes match {
case configs.Result.Failure(error) => Left(ParsingError.withMessages(error.messages))
case configs.Result.Success(value) => Right(saveValue(value))
}
}
}
}

0 comments on commit 0ff4cb5

Please sign in to comment.