hroptatyr / yuck

Your Umbrella Command Kit, a bog-standard command line option parser for C with all the knickknackery and whatnots.
Other
62 stars 7 forks source link

Nested subcommands #13

Open vertexmachina opened 6 years ago

vertexmachina commented 6 years ago

Is it possible to have subcommands within subcommands?

I'd like the ability to do something like remote channel change 5, or remote channel read, where channel is the main subcommand and change and read are subcommands that are part of the channel subcommand.

I've attempted to do it with a yuck file like this:

Usage: remote
Interact with a television.

Usage: remote power on
Power on the television

Usage: remote power off
Power off the television

Usage: remote channel change [CHANNEL]...
Change channel to CHANNEL

Usage: remote channel read
Read current channel

Which generates the following:

$ ./remote --help

Usage: remote [OPTION]... COMMAND

Interact with a television.

COMMAND may be one of:
  power       Power off the television
  channel     Read current channel

Options accepted by all commands:
  -h, --help            display this help and exit
  -V, --version         output version information and exit
$ ./remote power --help

Usage: remote power off

Power off the television

Common options:
  -h, --help            display this help and exit
  -V, --version         output version information and exit
$ ./remote channel --help
Usage: remote channel read

Read current channel

Common options:
  -h, --help            display this help and exit
  -V, --version         output version information and exit

As you can see, it seems to only register the last sub-subcommand (e.g., power off and channel read) in the yuck file.

Is this functionality already present and I'm doing it incorrectly? If it isn't present, would the current architecture allow for it to be easily added? I would be happy to contribute.

hroptatyr commented 6 years ago

Hey Austin, unfortunately this isn't possible in yuck as it stands. The way it registers the last sub-subcommand is a leaky abstraction over m4 which happens to allow to redefine macros.

As for a possible implementation: yuck lives on a union of common options and structs of subcommands. Adding another nesting this should read a union of unions of structs of sub-subcommands, structs of sub-commands and common options.

I was thinking of providing means to nest different yuck files somehow and in a meaningful way. For instance in the above case you'd have 3 yuck files, remote.yuck, power yuck and channel.yuck each specifying just their immediate subcommands. Then in the C file with main() you'd have something like

#include "remote.yucc"
#include "power.yucc"
#include "channel.yucc"

int main(int argc, char *argv[])
{
        yuck_t remote[1];
        yuck_parse(remote, argc, argv);

        switch (remote->cmd) {
                power_yuck_t power[1];
        case REMOTE_CMD_POWER:
                yuck_parse(power, argc, argv);
                ...
        }
        ...
}

I imagine yuck gen would be called with a --nest or something to prefix the types with its own name to avoid symbol name clashes.

However, in either case it's currently unclear to me what to do about common options, the term unfolds into global common options and subcommand common options. In your example there's only nesting depth 0 and nesting depth 2 commands, but what if there was a

Usage: remote channel
Display current channel name.

Its options would/should be treated as common options to all remote channel invocations. Actually that's more of a question than a statement.

Now, for a completely different idea: Observe how yuck gendsl on your original file actually does see all subsubcommands. Instead of changing the current m4 script (which generates the C output from that), a second m4 script tailored to your use case could be implemented and you simply do a yuck gen --script=MY.m4 remote.yuck. (Actually using multiple m4 scripts was the plan anyway to support different languages and stuff, I just happen to not use different languages a lot.)

Anyway, tell me what you think.

vertexmachina commented 6 years ago

The tailored m4 script sounds like the best idea for now, or I may keep doing it how I have been which is remote power_on, remote power_off, remote channel_change, etc.

I'd like to contribute to the project for use by others but I don't have any spare time in the foreseeable future.