l3nz / cli-matic

Compact, hands-free [sub]command line parsing library for Clojure.
Eclipse Public License 2.0
365 stars 29 forks source link

Nested sub-commmands #69

Closed l3nz closed 4 years ago

l3nz commented 5 years ago

At the moment we can have

./cmd.clj --gv 1 run --cv 2

but it would be cool if you could nest sub-commands,

./cmd.clj --gv 1 run --cv 2 alpha --scv 3

So that calling "run" would print out the subcommands for "run" and "run alpha" or "run beta" would be actual sub-commmands.

This means that either you have a definition for :runs as a fn (for a terminal command) or you would have another :commands containing a list of command definitions, like in:

{:command     "run"
                  :description "Runs something - subcommand area"
                  :opts        [{:option "cv" :as "Addendum 1" :type :int}]              
                  :commands [
                      {:command     "alpha"
                          :description "Runs  alpha"
                          :opts        [{:option "scv" :as "Something" :type :int}]              
                          :runs        alpha_fn}]}
l3nz commented 5 years ago

The trick may be changing the subcommmand from nil or a string, to a vector of 0 or more strings.

See https://github.com/l3nz/cli-matic/blob/23e52bbcc14695bc74bff73bc07624d119004694/src/cli_matic/utils.cljc#L240

And making the main parser recursive: https://github.com/l3nz/cli-matic/blob/23e52bbcc14695bc74bff73bc07624d119004694/src/cli_matic/core.cljc#L356

l3nz commented 5 years ago

Actually, we could get away with :global-options and :commands, nesting them into a new recursive structure that contains options and commands:

{:command     "toycalc"
 :description "A command-line toy calculator"
 :version     "0.0.1"
 :opts [{:option  "base"
         :as      "The number base for output"
         :type    :int
         :default 10}]
 :commands [
    {:command     "add"
    :description "Adds two numbers together"
    :opts        [{:option "a" :as "Addendum 1" :type :int}
                  {:option "b" :as "Addendum 2" :type :int :default 0}]              
    :commands [{:command "plain"
                 :description "Adds two numbers"
                 :runs add_plain}
               {:command "double"
                 :description "Adds two numbers twice"
                 :runs add_twice}]}
     {:command     "sub"
      :description "Subtracts parameter B from A"
      :opts        [{:option "a" :as "Parameter A" :type :int :default 0}
                    {:option "b" :as "Parameter B" :type :int :default 0}]
      :runs        subtract_numbers}
     ]})
}  

It would be relatively simple to transform today's globals and commands into this new data structure, where you can run:

cmd -?
cmd  add -?
cmd add -a 1 -b 2 plain
cmd add -a 1 -b 2 double
cmd sub -?

All attributes going UP the tree are globals for your command; leaf trees have a :runs function, while branches have a :commands list

ieugen commented 4 years ago

Hi,

Do you have an ETA for this feature? I'm new to clojure and wanted to port a CLI app to clojure and compile it to native. Found cli-matic and I like it, however my CLI makes use of sub-commands. For reference, it's written in TypeScript with https://oclif.io/ .

Thanks, Eugen

ieugen commented 4 years ago

@l3nz : let me know if I can help out with testing and/or maybe developing. If you are open to a PR let me know if you have any other constrains besides the ones in the CONTRIBUTING guide.

l3nz commented 4 years ago

Making some progress - see https://github.com/l3nz/cli-matic/blob/b69_nested/TODO.md

l3nz commented 4 years ago

First snapshot out - most likely not working, but in theory is should work with current configuration. https://clojars.org/cli-matic/versions/0.4.0.1-SNAPSHOT

l3nz commented 4 years ago

ATM, as I had no feedback and the first version was basically working for me, I'm publishing v0.4.0. I have not yet documented the new format, but the old one will continue to be supported unless you want to use the nested format. So you got a new engine under the same old hood.

There are surely some issues, but until this goes in front of real people they will not be found, so let's click on "I'm feeling lucky".

ieugen commented 4 years ago

Awesome. I'm still catching up on things but I will give it a try.