case-app icon indicating copy to clipboard operation
case-app copied to clipboard

Positional arguments, parsing scaladocs

Open ryan-williams opened this issue 7 years ago • 11 comments

I love this library, thank you!

Some things I would like to see that afaict it doesn't have now:

  • positional arguments (likely an annotation with an integer index)
  • parse scaladocs for each field of an arguments case class for the help-string
  • automatically recurse parsing (like the @Recurse annotation apparently does); ideally that would just happen automatically, but could be configured to e.g. prepend a string to the front of all the fields in the nested class, or just flatten them into the top-level class's argument/field namepace, etc.

I may try to dig in to this when I have a moment, will post any notable updates here

ryan-williams avatar Jun 29 '17 19:06 ryan-williams

Sorry to answer that a bit late...

  • What do you mean by "positional arguments"? :-)

  • It would be cool to get some infos from the scaladoc, I'm not sure that's straightforward though (maybe with scalameta?).

  • Former versions of case-app (pre 1.0, see this former doc excerpt for example) used to do that. One drawback is that if one had a custom case class type C with its own ArgParser[C], but this parser is mistakenly not in scope, then case-app would happily recurse on the fields of C, instead of considering fields of type C as single arguments.

Maybe auto-recursing can be only enabled via an import. Then I'd fine to merge it I think.

alexarchambault avatar Jul 06 '17 14:07 alexarchambault

Positional Arguments

What do you mean by "positional arguments"? :-)

Arguments that are not associated with a flag, that currently go in RemainingArgs.remainingArgs afaict.

For example, file1 and file2 from man comm:

SYNOPSIS
     comm [-123i] file1 file2

Python argparse treats options that don't start with - as positional, afaict.

I'm imagining an annotation like @Arg(idx = …), where if the idx argument to @Arg is not provided, it can be inferred from the order of the @Arg-annotated fields.

comm example

Modeling comm's CLI above:

import caseapp.{_, ExtraName => Opt}
case class Args(
  @Opt("1") suppressCol1,
  @Opt("2") suppressCol2,
  @Opt("3") suppressCol3,
  @Opt("i") caseInsensitive,
  @Arg file1,
  @Arg file2
)

object Comm extends CaseApp[Args] {
  def run(args: Args): Unit = {
    println(s"Comparing ${args.file1} to ${args.file2}…")
  }
}

This example infers each positional argument's position from its order in the case class: file1 is the first arg, file2 is the second; note also that def run(…) no longer takes a RemainingArgs… that's basically always what I want (no unparsed args allowed, and positional args folded into the Parser machinery), though a version that takes unparsed arguments can be optionally available as well if that's what others want.

The other option (which could also co-exist with the above) is that the positions are explicitly provided:

case class Args(
  @Opt("1") suppressCol1,
  @Opt("2") suppressCol2,
  @Opt("3") suppressCol3,
  @Opt("i") caseInsensitive,
  @Arg(0) file1,
  @Arg(1) file2
)

this seems only useful if you want to be able to do something like:

case class Args(
  @Opt("1") suppressCol1,
  @Opt("2") suppressCol2,
  @Opt("3") suppressCol3,
  @Opt("i") caseInsensitive,
  @Arg(1) file2,
  @Arg(0) file1
)

where the order has been switched.

My gut is that this just shouldn't be allowed and the positions should be inferred from the field ordering as in the first example.

Implementation thoughts

It seems like an extra AnnotationOptions type can be plumbed through the Parser machinery to do this without too much trouble.

Ideally you would get errors if a field has @Opt and @Arg annotations, and I think this approach would let that happen at compile time?

So I've been imagining I could hack this up when I have an hour or two; if there are issues with this approach let me know!

Parsing scaladocs

It would be cool to get some infos from the scaladoc, I'm not sure that's straightforward though (maybe with scalameta?).

Yea this one is just me hand-waving bc I keep hearing things about scalameta parsing scaladocs.

I would tackle the positional args feature first but at some point we should try to loop in the scalameta ppl for help/advice with this bc I think it would be an amazing feature.

My coworkers make https://github.com/hammerlab/ppx_deriving_cmdliner which is a pretty fully-realized ocaml version of what I would love this library to become, and it has this feature and is magical.

Auto-recursing

Thanks for the context on that; I'm imagining that a case class will always be parsed as a product of fields… leaving both options available sounds fine though.

ContextParser

Over the weekend I made a parallel version of the Parser/ArgParser/HListParser stack that takes an arbitrary Context instance which must be implicitly present during each fields arg-parsing: https://github.com/alexarchambault/case-app/compare/e00f40c...hammerlab:ctx

The impetus was that I had some field types where I wanted to parse them / instantiate them in the presence of a hadoop Configuration object.

I got it all working but then ultimately decided to refactor my code to not have these types / to use them differently so that I no longer needed this feature, but just wanted to mention it in case it is interesting. It was my first hands-on project with some of this shapeless magic so it was a valuable learning experience for me 😎.

ryan-williams avatar Jul 06 '17 15:07 ryan-williams

@Arg looks cool! With or without the index as param (Like you commented, I'm not sure the param would be that useful either).

My understanding is that these are likely to be required arguments. Beware that there's currently a small glitch in the handling of mandatory arguments in case-app - the help cannot be printed if the mandatory arguments are not specified (like --help says --mandatory required, and one has to do --help --mandatory foo to actually print the help). I might have a fix for it... I'll push it if it works.

alexarchambault avatar Jul 06 '17 16:07 alexarchambault

Good to know!

Just in case I'm missing something that case-app already handles, what do you mean "mandatory arguments"? I'm assuming you mean fields in the arguments-case-class that have no default value provided and therefore must be specified on the CLI in order for the arguments-case-class to be instantiated?

ryan-williams avatar Jul 06 '17 17:07 ryan-williams

Yes, that's what I mean. It's quite recent, it's been added in 1.2.0-M1.

Also, forget my point about the issue with help and these mandatory args, https://github.com/alexarchambault/case-app/pull/63 fixes it :-)

alexarchambault avatar Jul 07 '17 13:07 alexarchambault

Random other question I asked in the case-app gitter but I'll repost here since that room doesn't seem active yet:

I'm trying to port some args4j-based CLI param objects to case-app and one issue I'm facing is that I have sets of arguments defined in traits that I mix in to multiple different apps.

I'm wondering whether/how i can mimic that with case-app; as long as a shapeless Generic is available for my args type, I'll be fine, right? Any good ways to make a Generic available for a trait, based on its fields? I'll think about this more and ask in the shapeless room if I don't hear/decide otherwise.

ryan-williams avatar Jul 07 '17 21:07 ryan-williams

@ryan-williams A LabelledGeneric should be enough (LabelledGeneric itself requiring Generic and DefaultSymbolicLabelling). Yeah, you should be able to find help on the shapeless room.

alexarchambault avatar Jul 08 '17 12:07 alexarchambault

The Scaladoc parsing could be done using https://github.com/takezoe/runtime-scaladoc-reader I'm using it in a project and it works quite well 👍

nightscape avatar Dec 14 '18 20:12 nightscape

I can't seem to figure out positional args. Is it documented somewhere?

chadselph avatar Apr 28 '20 05:04 chadselph

@chadselph Seems like positional arguments still go to RemainingArgs. No change has been made to support the @Arg proposal right @alexarchambault?

joprice avatar Aug 04 '20 15:08 joprice

I think that's right. I've been using RemainingArgs where relevant but since it only supports String types, I have a mix where validation/type conversion is happening between options and args even though they're doing the same thing.

chadselph avatar Aug 04 '20 16:08 chadselph