Open ryan-williams opened 7 years ago
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.
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
exampleModeling 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.
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!
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.
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 😎.
@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.
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?
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 :-)
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 A LabelledGeneric
should be enough (LabelledGeneric
itself requiring Generic
and DefaultSymbolicLabelling
). Yeah, you should be able to find help on the shapeless room.
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 👍
I can't seem to figure out positional args. Is it documented somewhere?
@chadselph Seems like positional arguments still go to RemainingArgs. No change has been made to support the @Arg proposal right @alexarchambault?
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.
I love this library, thank you!
Some things I would like to see that afaict it doesn't have now:
case class
for the help-string@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