bkirwi / decline

A composable command-line parser for Scala.
http://monovore.com/decline/
Apache License 2.0
647 stars 71 forks source link

WIP add configurable composable paring API #543

Closed SpaceCowboyMax closed 5 months ago

SpaceCowboyMax commented 5 months ago

I've added some sort of public API for customize a Parser args consumer, while maintaining backward compatibility and preserving the default Parser logic. However, I still need to refine the handling of default implicits, naming things, and the final API design.

Proposed API - still WIP

implicit val customLongOptParser: ParserConfiguration[(String, String)] =
  ParserConfiguration(
    prepend = false,
    parserConfig = LongOptWithEqualsParser[(String, String)](pattern = "\\+(.+?)\\+(.+)".r) ::
      LongOptParser[(String, String)](skipUnknown = true) :: Nil
  )

val opts = (whatever, ghost).tupled
val cmd = Command("", "")(opts)

val result = cmd.parse(List("--whatever", "man", "+ghost+dad", "--unknownLongOpt"))
result should equal(Right("man", "dad"))

Feedback needed on topic: "Is it worth trying?"

I know the main idea of decline, but I hope this approach will provide some wiggle room.

bkirwi commented 5 months ago

Thanks for the contribution!

Feedback needed on topic: "Is it worth trying?"

Honestly, I'm pretty hesitant! It seems to fall outside the original scope of decline, expand the API in a pretty substantial way, and make error reporting etc. more difficult... and it's not totally clear to me what it's for.

Can you say a little more about the use-case that motivated this kind of change?

SpaceCowboyMax commented 5 months ago

In my case, I would like to use decline for parsing a lot of non-deterministic lists of different options and arguments from files without any particular ordering and with gnu long_opt_only format via --foo which -foo. At first glance, it could be a good solution to write a custom parser, but with arg parsing, things could become very complicated and ugly. The same for input preprocessing.

With such kind of API gnu getopt_long_only could be added and many other small things based on already pre-existed parsers

def gnuGetoptLongOnly[T](nonStrict: Boolean): List[ParserConfig[T]] =
  List(
    LongOptWithEqualsParser[T](skipUnknown = nonStrict, pattern = "-(.+?)=(.+)".r),
    LongOptParser[T](skipUnknown = nonStrict, pattern = "-(.+)".r)
  )

def skipCommentsParser[T]: ParserConfig[T] = new ParserConfig[T] {
  override val consumer: Consumer[T] = {
    case Consumable(arg :: rest, accumulator) if arg.startsWith("//") =>
      continue(Consumable(rest, accumulator))
  }
}

def parserConfig[T](nonStrict: Boolean): ParserConfiguration[T] =
  ParserConfiguration(
    parserConfig = skipCommentsParser[T] +: gnuGetoptLongOnly[T](nonStrict)
  )

val defaultCommand = command("default", opts)(Command.defaultParserConfig)
val strictCommand = command("strict", opts)(parserConfig(false))
val nonStrictCommand = command("non-strict", opts)(parserConfig(true))

defaultCommand.parse(
  List("--foo", "foo", "--bar", "bar", "buzz")
) shouldEqual Right("foo", "bar", "buzz")

strictCommand.parse(
  List("-foo", "foo", "//comment-this-out", "-bar", "bar", "buzz")
) shouldEqual Right("foo", "bar", "buzz")

nonStrictCommand.parse(
  List("-foo", "foo", "//comment-this-out", "-bar", "bar", "buzz", "-unkonw")
) shouldEqual Right("foo", "bar", "buzz")
bkirwi commented 5 months ago

Got it, thanks!

It's a neat idea, and I can see where you're coming from, but I don't think it's a good fit for decline.

Thinking, in case it's interesting to you or some future reader:

I'm sorry - I know this isn't the answer you were hoping for! Best of luck with your project -