caseapp.core.app.CaseApp.scala Maven / Gradle / Ivy
The newest version!
package caseapp.core.app
import caseapp.Name
import caseapp.core.Scala3Helpers._
import caseapp.core.{Error, RemainingArgs}
import caseapp.core.complete.{Completer, CompletionItem, HelpCompleter}
import caseapp.core.help.{Help, HelpFormat, WithFullHelp, WithHelp}
import caseapp.core.parser.Parser
import caseapp.core.util.Formatter
abstract class CaseApp[T](implicit val parser0: Parser[T], val messages: Help[T]) {
def name: String =
help.progName
def hasHelp: Boolean = true
def hasFullHelp: Boolean = false
def help: Help[T] = messages
def parser: Parser[T] = {
val p = parser0.nameFormatter(nameFormatter)
if (ignoreUnrecognized)
p.ignoreUnrecognized
else if (stopAtFirstUnrecognized)
p.stopAtFirstUnrecognized
else
p
}
def completer: Completer[T] =
new HelpCompleter[T](messages)
def complete(args: Seq[String], index: Int): List[CompletionItem] =
if (hasFullHelp)
parser.withFullHelp.complete(
args,
index,
completer.withFullHelp,
stopAtFirstUnrecognized,
ignoreUnrecognized
)
else if (hasHelp)
parser.withHelp.complete(
args,
index,
completer.withHelp,
stopAtFirstUnrecognized,
ignoreUnrecognized
)
else
parser.complete(
args,
index,
completer,
stopAtFirstUnrecognized,
ignoreUnrecognized
)
def run(options: T, remainingArgs: RemainingArgs): Unit
def exit(code: Int): Nothing =
PlatformUtil.exit(code)
def printLine(line: String, toStderr: Boolean): Unit =
if (toStderr)
System.err.println(line)
else
println(line)
final def printLine(line: String): Unit =
printLine(line, toStderr = false)
def error(message: Error): Nothing = {
printLine(message.message, toStderr = true)
exit(1)
}
lazy val finalHelp: Help[_] = {
val h =
if (hasFullHelp) messages.withFullHelp
else if (hasHelp) messages.withHelp
else messages
if (name == h.progName) h
else h.withProgName(name)
}
def fullHelpAsked(progName: String): Nothing = {
val help = if (progName.isEmpty) finalHelp else finalHelp.withProgName(progName)
printLine(help.help(helpFormat, showHidden = true))
exit(0)
}
def helpAsked(progName: String, maybeOptions: Either[Error, T]): Nothing = {
val help = if (progName.isEmpty) finalHelp else finalHelp.withProgName(progName)
printLine(help.help(helpFormat, showHidden = false))
exit(0)
}
def usageAsked(progName: String, maybeOptions: Either[Error, T]): Nothing = {
val help = if (progName.isEmpty) finalHelp else finalHelp.withProgName(progName)
printLine(help.usage(helpFormat))
exit(0)
}
def helpFormat: HelpFormat =
HelpFormat.default()
def ensureNoDuplicates(): Unit =
finalHelp.ensureNoDuplicates()
/** Arguments are expanded then parsed. By default, argument expansion is the identity function.
* Overriding this method allows plugging in an arbitrary argument expansion logic.
*
* One such expansion logic involves replacing each argument of the form '@' with the
* contents of that file where each line in the file becomes a distinct argument. To enable this
* behavior, override this method as shown below.
*
* @example
* {{{
* import caseapp.core.parser.PlatformArgsExpander
* override def expandArgs(args: List[String]): List[String]
* = PlatformArgsExpander.expand(args)
* }}}
*
* @param args
* @return
*/
def expandArgs(args: List[String]): List[String] = args
/** Whether to stop parsing at the first unrecognized argument.
*
* That is, stop parsing at the first non option (not starting with "-"), or the first
* unrecognized option. The unparsed arguments are put in the `args` argument of `run`.
*/
def stopAtFirstUnrecognized: Boolean =
false
/** Whether to ignore unrecognized arguments.
*
* That is, if there are unrecognized arguments, the parsing still succeeds. The unparsed
* arguments are put in the `args` argument of `run`.
*/
def ignoreUnrecognized: Boolean =
false
def nameFormatter: Formatter[Name] =
Formatter.DefaultNameFormatter
def main(args: Array[String]): Unit =
main(finalHelp.progName, PlatformUtil.arguments(args))
def main(progName: String, args: Array[String]): Unit =
if (hasFullHelp)
parser.withFullHelp.detailedParse(
expandArgs(args.toList),
stopAtFirstUnrecognized,
ignoreUnrecognized
) match {
case Left(err) => error(err)
case Right((WithFullHelp(_, true), _)) => fullHelpAsked(progName)
case Right((WithFullHelp(WithHelp(_, true, maybeOptions), _), _)) =>
helpAsked(progName, maybeOptions)
case Right((WithFullHelp(WithHelp(true, _, maybeOptions), _), _)) =>
usageAsked(progName, maybeOptions)
case Right((WithFullHelp(WithHelp(_, _, Left(err)), _), _)) => error(err)
case Right((WithFullHelp(WithHelp(_, _, Right(t)), _), remainingArgs)) =>
run(t, remainingArgs)
}
else if (hasHelp)
parser.withHelp.detailedParse(
expandArgs(args.toList),
stopAtFirstUnrecognized,
ignoreUnrecognized
) match {
case Left(err) => error(err)
case Right((WithHelp(_, true, maybeOptions), _)) => helpAsked(progName, maybeOptions)
case Right((WithHelp(true, _, maybeOptions), _)) => usageAsked(progName, maybeOptions)
case Right((WithHelp(_, _, Left(err)), _)) => error(err)
case Right((WithHelp(_, _, Right(t)), remainingArgs)) => run(t, remainingArgs)
}
else
parser.detailedParse(
expandArgs(args.toList),
stopAtFirstUnrecognized,
ignoreUnrecognized
) match {
case Left(err) => error(err)
case Right((t, remainingArgs)) => run(t, remainingArgs)
}
}
object CaseApp {
def process[T](args: Seq[String])(implicit
parser: Parser[T],
help: Help[T]
): (T, RemainingArgs) = {
var values = Option.empty[(T, RemainingArgs)]
val app: CaseApp[T] = new CaseApp[T] {
def run(options: T, args: RemainingArgs): Unit = {
values = Some((options, args))
}
}
app.main(args.toArray)
values.getOrElse {
sys.error("should not happen")
}
}
def parse[T: Parser](args: Seq[String]): Either[Error, (T, Seq[String])] =
Parser[T].parse(args)
def detailedParse[T: Parser](args: Seq[String]): Either[Error, (T, RemainingArgs)] =
Parser[T].detailedParse(args)
def parseWithHelp[T](args: Seq[String])(implicit
parser: Parser[T]
): Either[Error, (Either[Error, T], Boolean, Boolean, Seq[String])] =
parser.withHelp.parse(args).map {
case (WithHelp(usage, help, base), rem) =>
(base, help, usage, rem)
}
def detailedParseWithHelp[T](args: Seq[String])(implicit
parser: Parser[T]
): Either[Error, (Either[Error, T], Boolean, Boolean, RemainingArgs)] =
parser.withHelp.detailedParse(args).map {
case (WithHelp(usage, help, base), rem) =>
(base, help, usage, rem)
}
def helpMessage[T: Help]: String =
Help[T].help
def usageMessage[T: Help]: String =
Help[T].usage
def printHelp[T: Help](err: Boolean = false): Unit =
(if (err) Console.err else Console.out) println Help[T].help
def printUsage[T: Help](err: Boolean = false): Unit =
(if (err) Console.err else Console.out) println Help[T].usage
}