Skip to content

Advanced topics

Recovering argument positions

One can recover the exact positions of each options and remaining arguments.

For options, one should use the Indexed class, like

import caseapp.core.Indexed

case class Options(
  foo: Indexed[String] = Indexed("")
)

val (options, _) = CaseApp.parse[Options](Seq("a", "--foo", "thing")).toOption.get
// options: Options = Options(
//   foo = Indexed(index = 1, length = 2, value = "thing")
// )

For arguments that are not options or option values, indices are retained in the RemainingArgs instance:

val (_, args) = CaseApp.detailedParse[Options](Seq("a", "--foo", "2", "b")).toOption.get
args.indexed
// res1: Seq[Indexed[String]] = List(
//   Indexed(index = 0, length = 1, value = "a"),
//   Indexed(index = 3, length = 1, value = "b")
// )

Partial parsing

One can stop parsing arguments at the first argument that is not an option, or that is an unknown option. This can be useful when "pre-processing" arguments passed to another application later on: case-app parses as much arguments as possible, then stops, so that the other application can parse the remaining arguments later on.

case class Options(
  foo: String = ""
)

object Options {
  implicit lazy val parser: Parser[Options] = {
    val parser0: Parser[Options] = Parser.derive
    parser0.stopAtFirstUnrecognized
  }
  implicit lazy val help: Help[Options] = Help.derive
}
val (options, args) = CaseApp.parse[Options](Seq("--foo", "a", "--other", "thing")).toOption.get
// options: Options = Options(foo = "a")
// args: Seq[String] = List("--other", "thing")

Alternatively, when extending CaseApp, one can do:

object MyApp extends CaseApp[Options] {
  override def stopAtFirstUnrecognized = true
  def run(options: Options, args: RemainingArgs): Unit = {
    ???
  }
}

Ignore unrecognized options

One can ignore non-recognized options, like

case class Options(
  foo: String = ""
)

object Options {
  implicit lazy val parser: Parser[Options] = {
    val parser0: Parser[Options] = Parser.derive
    parser0.ignoreUnrecognized
  }
  implicit lazy val help: Help[Options] = Help.derive
}

val (options, args) = CaseApp.parse[Options](Seq("--other", "thing", "--foo", "a")).toOption.get
// options: Options = Options(foo = "a")
// args: Seq[String] = List("--other", "thing")

Unrecognized options end up in the "remaining arguments", along with non-option or non-option values arguments.

Alternatively, when extending CaseApp, one can do:

object MyApp extends CaseApp[Options] {
  override def ignoreUnrecognized = true
  def run(options: Options, args: RemainingArgs): Unit = {
    ???
  }
}

Check for duplicate options in tests

When using the @Recurse or @Name annotations, some options might be given the same name. In practice, at runtime, one will shadow the other.

To ensure your options don't contain duplicates, you can call ensureNoDuplicates on the Help type class instance of your options, or on the CaseApp instance if you defined one:

case class Options(
  foo: String = "",
  @Name("foo")
    count: Int = 0
)

object MyApp extends CaseApp[Options] {
  def run(options: Options, args: RemainingArgs): Unit = {
    ???
  }
}

Help[Options].ensureNoDuplicates()
// java.lang.Exception: Found duplicated arguments: --foo (List(Arg(Name(foo), List(), Some(ValueDescription(string)), None, false, false, None, Some(Options), List()), Arg(Name(count), List(Name(foo)), Some(ValueDescription(int)), None, false, false, None, Some(Options), List())) times).
//  at caseapp.core.help.Help.ensureNoDuplicates(Help.scala:90)
//  at repl.MdocSession$MdocApp8$$anonfun$56.apply$mcV$sp(advanced.md:208)
//  at repl.MdocSession$MdocApp8$$anonfun$56.apply(advanced.md:208)
//  at repl.MdocSession$MdocApp8$$anonfun$56.apply(advanced.md:208)
MyApp.ensureNoDuplicates()
// java.lang.Exception: Found duplicated arguments: --foo (List(Arg(Name(foo), List(), Some(ValueDescription(string)), None, false, false, None, Some(Options), List()), Arg(Name(count), List(Name(foo)), Some(ValueDescription(int)), None, false, false, None, Some(Options), List())) times).
//  at caseapp.core.help.Help.ensureNoDuplicates(Help.scala:90)
//  at caseapp.core.app.CaseApp.ensureNoDuplicates(CaseApp.scala:108)
//  at repl.MdocSession$MdocApp8$$anonfun$57.apply$mcV$sp(advanced.md:218)
//  at repl.MdocSession$MdocApp8$$anonfun$57.apply(advanced.md:218)
//  at repl.MdocSession$MdocApp8$$anonfun$57.apply(advanced.md:218)