Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Positional arguments, parsing scaladocs #58

Open
ryan-williams opened this issue Jun 29, 2017 · 11 comments
Open

Positional arguments, parsing scaladocs #58

ryan-williams opened this issue Jun 29, 2017 · 11 comments

Comments

@ryan-williams
Copy link
Contributor

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

@alexarchambault
Copy link
Owner

alexarchambault commented Jul 6, 2017

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.

@ryan-williams
Copy link
Contributor Author

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: 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 😎.

@alexarchambault
Copy link
Owner

@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.

@ryan-williams
Copy link
Contributor Author

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?

@alexarchambault
Copy link
Owner

alexarchambault commented Jul 7, 2017

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, #63 fixes it :-)

@ryan-williams
Copy link
Contributor Author

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.

@alexarchambault
Copy link
Owner

@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.

@nightscape
Copy link
Contributor

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 👍

@chadselph
Copy link

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

@joprice
Copy link

joprice commented Aug 4, 2020

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

@chadselph
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants