com.ossuminc.riddl.command.CommonOptionsHelper.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of riddl-command_3 Show documentation
Show all versions of riddl-command_3 Show documentation
Command infrastructure needed to define a command
/*
* Copyright 2019 Ossum, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
package com.ossuminc.riddl.command
import com.ossuminc.riddl.command.CommandOptions.optional
import com.ossuminc.riddl.language.Messages.*
import com.ossuminc.riddl.language.{CommonOptions, Messages}
import com.ossuminc.riddl.utils.StringHelpers.toPrettyString
import com.ossuminc.riddl.utils.{RiddlBuildInfo, SysLogger}
import pureconfig.{ConfigCursor, ConfigObjectCursor, ConfigReader, ConfigSource}
import pureconfig.error.ConfigReaderFailures
import scopt.*
import java.io.File
import java.nio.file.Path
import java.util.Calendar
import scala.concurrent.duration.FiniteDuration
/** Handle processing of Language module's CommonOptions */
object CommonOptionsHelper {
private def year: Int = Calendar.getInstance().get(Calendar.YEAR)
private val start: String = RiddlBuildInfo.startYear
val blurb: String =
s"""RIDDL Compiler © $start-$year Ossum Inc. All rights reserved."
|Version: ${RiddlBuildInfo.version}
|
|This program parses, validates and translates RIDDL sources to other kinds
|of documents. RIDDL is a language for system specification based on Domain
|Drive Design, Reactive Architecture, and distributed system principles.
|
|""".stripMargin
lazy val commonOptionsParser: OParser[Unit, CommonOptions] = {
val builder: OParserBuilder[CommonOptions] = OParser.builder[CommonOptions]
import builder.*
OParser.sequence(
programName("riddlc"),
head(blurb),
opt[Unit]('t', name = "show-times")
.optional()
.action((_, c) => c.copy(showTimes = true))
.text("Show parsing phase execution times"),
opt[Unit]('I', name = "show-include-times")
.optional()
.action((_, c) => c.copy(showIncludeTimes = true))
.text("Show parsing of included files execution times"),
opt[Unit]('d', "dry-run")
.optional()
.action((_, c) => c.copy(dryRun = true))
.text("go through the motions but don't write any changes"),
opt[Unit]('v', "verbose")
.optional()
.action((_, c) => c.copy(verbose = true))
.text("Provide verbose output detailing actions taken by riddlc"),
opt[Unit]('D', "debug")
.optional()
.action((_, c) => c.copy(debug = true))
.text("Enable debug output. Only useful for riddlc developers"),
opt[Unit]('q', "quiet")
.optional()
.action((_, c) => c.copy(quiet = true))
.text("Do not print out any output, just do the requested command"),
opt[Unit]('a', "noANSIMessages")
.optional()
.action((_, c) => c.copy(noANSIMessages = true))
.text("Do not print messages with ANSI formatting"),
opt[Unit]('w', name = "suppress-warnings")
.optional()
.action((_, c) =>
c.copy(
showWarnings = false,
showMissingWarnings = false,
showStyleWarnings = false,
showUsageWarnings = false
)
)
.text("Suppress all warning messages so only errors are shown"),
opt[Unit]('m', name = "suppress-missing-warnings")
.optional()
.action((_, c) => c.copy(showMissingWarnings = false))
.text("Suppress warnings about things that are missing"),
opt[Unit]('s', name = "suppress-style-warnings")
.optional()
.action((_, c) => c.copy(showStyleWarnings = false))
.text("Suppress warnings about questionable input style. "),
opt[Unit]('u', name = "suppress-usage-warnings")
.optional()
.action((_, c) => c.copy(showUsageWarnings = false))
.text("Suppress warnings about usage of definitions. "),
opt[Unit]('i', name = "suppress-info-messages")
.optional()
.action((_, c) => c.copy(showInfoMessages = false))
.text("Suppress information output"),
opt[Unit]('w', name = "hide-warnings")
.optional()
.action((_, c) =>
c.copy(
showWarnings = false,
showMissingWarnings = false,
showStyleWarnings = false,
showUsageWarnings = false
)
),
opt[Unit]('m', name = "hide-missing-warnings")
.optional()
.action((_, c) => c.copy(showMissingWarnings = false))
.text("Hide warnings about things that are missing"),
opt[Unit]('s', name = "hide-style-warnings")
.optional()
.action((_, c) => c.copy(showStyleWarnings = false))
.text("Hide warnings about questionable input style. "),
opt[Unit]('u', name = "hide-usage-warnings")
.optional()
.action((_, c) => c.copy(showUsageWarnings = false))
.text("Hide warnings about usage of definitions. "),
opt[Unit](name = "hide-info-messages")
.optional()
.action((_, c) => c.copy(showInfoMessages = false))
.text("Hide information output"),
opt[File]('P', name = "plugins-dir")
.optional()
.action((file, c) => c.copy(pluginsDir = Some(file.toPath)))
.text("Load riddlc command extension plugins from this directory."),
opt[Boolean]('S', name = "sort-warnings-by-location")
.optional()
.action((_, c) => c.copy(sortMessagesByLocation = true))
.text(
"Print all messages sorted by the file name and line number in which they occur."
),
opt[Boolean]('G', name = "group-messages-by-kind")
.optional()
.action((_,c) => c.copy(groupMessagesByKind = true))
.text(
"Print all messages by their severity kind"
),
opt[Int]('x', name = "max-parallel-parsing")
.optional()
.action((v, c) => c.copy(maxParallelParsing = v))
.text(
"Controls the maximum number of include files that will be parsed in parallel"
),
opt[Int](name = "max-include-wait")
.optional()
.action((v, c) => c.copy(maxIncludeWait = FiniteDuration(v, "seconds")))
.text("Maximum time that parsing an include file will wait for it to complete"),
opt[Boolean]("warnings-are-fatal")
.optional()
.action((_, c) => c.copy(warningsAreFatal = true))
.text(
"Makes validation warnings fatal to encourage code perfection"
)
)
}
def parseCommonOptions(
args: Array[String]
): (Option[CommonOptions], Array[String]) = {
val setup: OParserSetup = new DefaultOParserSetup {
override def showUsageOnError: Option[Boolean] = Option(false)
override def renderingMode: RenderingMode.TwoColumns.type =
RenderingMode.TwoColumns
}
val dontTerminate: DefaultOEffectSetup = new DefaultOEffectSetup {
val log: SysLogger = SysLogger()
override def displayToOut(msg: String): Unit = { log.info(msg) }
override def displayToErr(msg: String): Unit = { log.error(msg) }
override def reportError(msg: String): Unit = { log.error(msg) }
override def reportWarning(msg: String): Unit = { log.warn(msg) }
// ignore terminate
override def terminate(exitState: Either[String, Unit]): Unit = ()
}
val saneArgs = args.map(_.trim).filter(_.nonEmpty)
val options = saneArgs.takeWhile(_.startsWith("-"))
val remainingOptions = saneArgs.dropWhile(_.startsWith("-"))
val (common, effects1) = OParser.runParser[CommonOptions](
commonOptionsParser,
options,
com.ossuminc.riddl.language.CommonOptions(),
setup
)
OParser.runEffects(effects1, dontTerminate)
common -> remainingOptions
}
private def noBool = Option.empty[Boolean]
implicit val commonOptionsReader: ConfigReader[CommonOptions] = { (cur: ConfigCursor) => {
val default = CommonOptions()
for
topCur <- cur.asObjectCursor
topRes <- topCur.atKey("common")
objCur <- topRes.asObjectCursor
showTimes <- optional(objCur, "show-times", default.showTimes)(c => c.asBoolean)
showIncludeTimes <- optional(objCur, "show-include-times", default.showIncludeTimes)(c => c.asBoolean)
verbose <- optional(objCur, "verbose", default.verbose)(cc => cc.asBoolean)
dryRun <- optional(objCur, "dry-run", default.dryRun)(cc => cc.asBoolean)
quiet <- optional(objCur, "quiet", default.quiet)(cc => cc.asBoolean)
debug <- optional(objCur, "debug", default.debug)(cc => cc.asBoolean)
noANSIMessages <- optional(objCur, "no-ansi-messages", default.noANSIMessages)(cc => cc.asBoolean)
sortMessages <- optional(objCur, "sort-messages-by-location", default.sortMessagesByLocation)(cc =>
cc.asBoolean
)
groupMessagesByKind <- optional(objCur, "group-messages-by-kind", default.groupMessagesByKind)(cc =>
cc.asBoolean
)
suppressWarnings <- optional(objCur, "suppress-warnings", noBool)(cc => cc.asBoolean.map(Option(_)))
suppressStyleWarnings <- optional(objCur, "suppress-style-warnings", noBool)(cc => cc.asBoolean.map(Option(_)))
suppressMissingWarnings <- optional(objCur, "suppress-missing-warnings", noBool)(cc =>
cc.asBoolean.map(Option(_))
)
suppressUsageWarnings <- optional(objCur, "suppress-usage-warnings", noBool)(cc => cc.asBoolean.map(Option(_)))
suppressInfoMessages <- optional(objCur, "suppress-info-messages", noBool)(cc => cc.asBoolean.map(Option(_)))
hideWarnings <- optional(objCur, "hide-warnings", noBool)(cc => cc.asBoolean.map(Option(_)))
hideStyleWarnings <- optional(objCur, "hide-style-warnings", noBool)(cc => cc.asBoolean.map(Option(_)))
hideMissingWarnings <- optional(objCur, "hide-missing-warnings", noBool)(cc => cc.asBoolean.map(Option(_)))
hideUsageWarnings <- optional(objCur, "hide-usage-warnings", noBool)(cc => cc.asBoolean.map(Option(_)))
hideInfoMessages <- optional(objCur, "hide-info-messages", noBool)(cc => cc.asBoolean.map(Option(_)))
showWarnings <- optional[Boolean](objCur, "show-warnings", default.showWarnings)(cc => cc.asBoolean)
showStyleWarnings <- optional[Boolean](objCur, "show-style-warnings", default.showStyleWarnings)(cc =>
cc.asBoolean)
showMissingWarnings <- optional[Boolean](objCur, "show-missing-warnings", default.showMissingWarnings)(cc =>
cc.asBoolean)
showUsageWarnings <- optional[Boolean](objCur, "show-usage-warnings", default.showUsageWarnings)(cc =>
cc.asBoolean)
showInfoMessages <- optional[Boolean](objCur, "show-info-messages", default.showInfoMessages)(cc =>
cc.asBoolean)
pluginsDir <- optional(objCur, "plugins-dir", Option.empty[Path])(cc =>
cc.asString.map(f => Option(Path.of(f)))
)
maxParallel <- optional[Int](objCur, "max-parallel-parsing", default.maxParallelParsing)(cc => cc.asInt)
maxIncludeWait <- optional[Long](objCur, "max-include-wait", default.maxIncludeWait.toSeconds)(cc => cc.asLong)
warnsAreFatal <- optional(objCur, "warnings-are-fatal", default.warningsAreFatal)(cc => cc.asBoolean)
yield {
val shouldShowWarnings = suppressWarnings
.map(!_)
.getOrElse(hideWarnings.map(!_).getOrElse(showWarnings))
val shouldShowMissing = suppressMissingWarnings
.map(!_)
.getOrElse(hideMissingWarnings.map(!_).getOrElse(showMissingWarnings))
val shouldShowStyle = suppressStyleWarnings
.map(!_)
.getOrElse(hideStyleWarnings.map(!_).getOrElse(showStyleWarnings))
val shouldShowUsage = suppressUsageWarnings
.map(!_)
.getOrElse(hideUsageWarnings.map(!_).getOrElse(showUsageWarnings))
val shouldShowInfos = suppressInfoMessages
.map(!_)
.getOrElse(hideInfoMessages.map(!_).getOrElse(showInfoMessages))
CommonOptions(
showTimes,
showIncludeTimes,
verbose,
dryRun,
quiet,
shouldShowWarnings,
shouldShowMissing,
shouldShowStyle,
shouldShowUsage,
shouldShowInfos,
debug,
pluginsDir,
sortMessagesByLocation = sortMessages,
groupMessagesByKind = groupMessagesByKind,
noANSIMessages = noANSIMessages,
maxParallelParsing = maxParallel,
maxIncludeWait = FiniteDuration(maxIncludeWait, "seconds"),
warningsAreFatal = warnsAreFatal
)
}
}}
final def loadCommonOptions(
path: Path
): Either[Messages, CommonOptions] = {
ConfigSource.file(path.toFile).load[CommonOptions] match {
case Right(options) =>
if options.debug then {
println(toPrettyString(options, 1, Some("Loaded common options:")))
}
Right(options)
case Left(failures) =>
Left(
errors(
s"Failed to load options from $path because:\n" +
failures.prettyPrint(1)
)
)
}
}
}