Open mdedetrich opened 3 years ago
So how exactly is one meant to manually parse args to a CommandApp without using the deprecated main method?
The idea, at least, is that main
is expected to be called by the JVM and not by user code. This lets us make choices that are useful for an entry point but bad for regular code. (For example, the CommandIOApp
version will sys.exit(1)
when passed invalid args.)
Concretely, if your code is intended to just be used from Ammonite, I'd skip the CommandApp
and just use a command (and command.parse) directly. For cases where a command is used in multiple places (as an app and in tests, for example) folks will often store it in a constant on an object somewhere. I don't have much to recommend in the context of Ammonite specifically... this is the first I've heard of decline being used with Ammonite in this way.
So the reason I am using Decline in Ammonite is that even though Ammonite has its own functionality for command line parsing which works great for more trivial use cases, Decline is much better for complex cases (i.e. creating Argument
for custom types) and the script that I was working on in Ammonite ended up being more complex than I thought.
I guess the thing is that Ammonite does actually expose a main
(as you can obviously see in my code) and more generally I don't think a case that in all cases the main
is only the standard JVM main (i.e. Scala becoming more portable to other platforms such as Scala.js/native and even other cases as well).
I understand that you can use Command
instead of CommandApp
but that sought of defeats the main point of CommandApp
which is meant to be used as a single global main
which is my use case here, its just that the main
is being provided by Ammonite's repl session rather than a standard JVM main.
Do you have a strong objection to removing the deprecation and just documenting clearly how its meant to be used?
I guess the thing is that Ammonite does actually expose a main (as you can obviously see in my code) and more generally I don't think a case that in all cases the main is only the standard JVM main (i.e. Scala becoming more portable to other platforms such as Scala.js/native and even other cases as well).
So JS is actually a great example, because that is supported by CommandApp
, but in a fairly surprising way. ScalaJS will always pass an empty array of arguments, so we ignore the input array and go rummaging around in the nodejs stdlib to find the "real" arguments. This would be terrible behaviour for a library function, but we can get away with it since we know exactly in which context main will be called.
Do you have a strong objection to removing the deprecation and just documenting clearly how its meant to be used?
Unfortunately, yes! In part because we used to do this and it was a significant maintenance burden, and in part because we need to make strong assumptions about the calling context of main
so we can do stuff that would otherwise be inappropriate.
Turning this the other way... I would imagine that the way to do this without *App in ammonite would look something like:
val app = Command(...) {
...
}
@main
def entrypoint(args: String*) = {
command.parse(args) match {
case Left(help) => exit(help)
case Right(_) =>
}
}
Does that work in your case, or is there some important functionality that's not available outside of CommandApp
?
Sorry for the late response but I came across this problem in another context which alludes to your point
Unfortunately, yes! In part because we used to do this and it was a significant maintenance burden, and in part because we need to make strong assumptions about the calling context of main so we can do stuff that would otherwise be inappropriate.
I have this situation which is that I actually want to test a Main extends CommandApp
application and by testing Main
I don't mean only testing the Command
part of the App
(i.e. the parsing) but also the initialization of App
along with the Command
part.
I understand there may some deliberation when it comes to the ideal way to test an the Main
entry point but In my case I just want to do a very quick litmus/"smoke" test where I pass in some arguments into CommandApp.main
and see that the App
properly initializes along with catching any exception that happens to be thrown and at least from what I can tell by far the simplest and easiest way to test this is by using CommandApp.main
.
So I am trying to use decline within Ammonite and there doesn't seem to be a good uesr guide on how to do this. From the docs on Ammonite, i.e. https://ammonite.io/#ScriptArguments you can access the command line arguments by doing the following
Assuming you have a an app
MyApp
as follows (taken from example)You would do something like this
The problem is that the
main
method inCommandApp
is apparently deprecated, i.e.Ontop of this the
command
variable inside theCommandApp
class is not publicly visible outside the class (its missing theval
modifier in the constructor) so its not like you can manually doMyApp.command.parse
(should this deprecated methodmain
be removed in the future). So how exactly is one meant to manually parseargs
to aCommandApp
without using the deprecatedmain
method? For example https://ben.kirw.in/decline/scalajs.html#ambient-arguments details how to get theargs
on various platforms but never how to pass it into aCommandApp
and the provided link at https://ben.kirw.in/decline/usage.html#defining-an-application details how you can manually useargs
when you have aCommand
but not when you have aCommandApp
(this goes to the previous problem where thecommand
inside theCommandApp
is not publicly visible).Conclusively I guess I also don't understand why the
CommandApp.main
method is deprecated, if you are using something like Ammonite it is by far the easiest way to make the repl/scripts work with an existingCommandApp
(which is the ideal way to use decline). This would also be the case for any other kind of dynamic repl/interactive session whereargs
are passed in a different way.