defmain icon indicating copy to clipboard operation
defmain copied to clipboard

CLI flags/switches and subcommands

Open alessiostalla opened this issue 3 years ago • 3 comments

We can easily use subcommands to support something like ./frob create thing, where "create" is the subcommand and "thing" is a rest argument. We can add flags/switches as well like this: ./frob create --beautiful thing. However, there's no built-in way (apparently) to process the following without analyzing the arguments manually (which defeats the purpose of the library): ./frob create thing --value 42.

I think a (hackish?) way of supporting this use case would be to always insert a terminating subcommand. So, the above would actually be processed as if the user had written ./frob create thing --value 42 now-please-do-it and "now-please-do-it" were a subcommand that inherited all the arguments of the "create" subcommand (inheritance which, by the way, is already possible, I don't know if by design or by accident, but I'm using it). And, if we use "--" instead of "now-please-do-it", and enable it for all subcommands, we also obtain the common pattern of using "--" to mean "please process everything after as REST arguments", e.g. in ./frob delete this that --gently -- --also-delete-this --this-is-not-a-flag this-is-not-a-subcommand.

If you're interested in the use case, and in the proposed solution design, I'll gladly provide a PR.

alessiostalla avatar Jul 24 '21 10:07 alessiostalla

@alessiostalla I don't understand what are you proposing here?

Do you want to implement a -- support to pass rest flags unparsed?

svetlyak40wt avatar Jul 26 '21 10:07 svetlyak40wt

Suppose I have this "frob" utility that connects to some server and issues commands. So I may have a main function that knows how to process parameters to connect to the server:

(defmain (frob) ((host "the host" :default "localhost") #| etc. |# &subcommand))

Then I define a subcommand to do some stuff, e.g.

(defcommand (frob create) (&rest args)
  "Creates a beautiful FROB"
  (frob:create (car args) :host host))

In the subcommand I can redefine the host, etc. args so that I may put them before or after the subcommand, but that's not shown here for simplicity.

Now I can invoke my utility with

./frob --host foo.com create my-frob

And with

./frob create --host foo.com my-frob

But not with

./frob create my-frob --host foo.com

Because in the latter case "--host foo.com" ends up in the &rest args and is not processed.

That is the issue. The rest is the solution I was proposing, but I should have not mixed the two, sorry, as it is too much information.

alessiostalla avatar Jul 27 '21 12:07 alessiostalla

The possible solution I propose is the following. We can make

./frob create my-frob --host foo.com

work without changing the cli args processing logic if we add a dummy subcommand:

(defcommand (create do-it) () "Same as create")

However, the user would have to write

./frob create my-frob --host foo.com do-it

which defeats the purpose we had (i.e. being able to add args at the end). However, we could preprocess the arg list returned by the Lisp implementation to always insert a "do-it" at the end, so effectively the user can type

./frob create my-frob --host foo.com

and the application interprets it as if they had typed

./frob create my-frob --host foo.com do-it

Also, defmain could automatically add this do-it subcommand to every command, so that the user does not have to do so manually. Then, if we name this special terminating subcommand "--" instead of do-it, we obtain the "-- means stop parsing" convention automatically – without altering how defmain or clon do the rest of their work.

alessiostalla avatar Jul 27 '21 12:07 alessiostalla