defmain
defmain copied to clipboard
CLI flags/switches and subcommands
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 I don't understand what are you proposing here?
Do you want to implement a --
support to pass rest flags unparsed?
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.
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.