google / subcommands

Go subcommand library.
Apache License 2.0
749 stars 48 forks source link

Feature: Allow to run default command without showing usage #41

Open Zyigh opened 2 years ago

Zyigh commented 2 years ago

Hi,

I found this tiny library that fits my needs much more than a bigger library which I wouldn't use more than 1%. So first of all, thanks for your great work.

I happened to find myself in a situation where I wanted a default command to be executed if no subcommand was provided. The easiest wat I found is to check that the subcommands.Execute(context.Context) func returned a subcommands.ExitUsageError.

This works well, except that it displays the list of all available commands due to this part of the code

if cdr.topFlags.NArg() < 1 {
    cdr.topFlags.Usage()
    return ExitUsageError
}

What I need is a default command that does what it does, and subcommands that does other things too. If I have a default command, I dont want subcommands to prompt commands usage.

The main problems I found are

An easy to implement solution that may impact user (but guarentee a differentiation between no subcommand and invalid subcommand) would be

const (
    ExitSuccess ExitStatus = iota
    ExitFailure
    ExitUsageError
    ExitNoArgs
)

// MustShowUsageWhenNoArg when set to true (default) display command usage
// when no argument is set. This allows to execute a command by default if
// no argument is provided when calling the cli
var MustShowUsageWhenNoArg = true

func (cdr *Commander) Execute(ctx context.Context, args ...interface{}) ExitStatus {
    if cdr.topFlags.NArg() < 1 {
        if MustShowUsageWhenNoArg {
            cdr.topFlags.Usage()
        }
        return ExitNoArgs
    }
    // ...
}

These changes would allow to use subcommands like

// disable default behavior
subcommands.MustShowUsageWhenNoArg = false
exitCode := subcommands.Execute(ctx)

if exitCode == subcommands.ExitNoArgs {
    defaultCmd.Execute(ctx, flag.NewFlagSet(serverCmd.Name(), flag.ContinueOnError))
}

Another approach could be to store directly the command to execute in case no subcommand is set by the user

// DefaultCommand is the command to execute by default if no subcommand was provided
var DefaultCommand Command

func (cdr *Commander) Execute(ctx context.Context, args ...interface{}) ExitStatus {
    if cdr.topFlags.NArg() < 1 {
        if nil != DefaultCommand {
            DefaultCommand.Execute(ctx, flag.NewFlagSet(DefaultCommand.Name(), flag.ContinueOnError), args...)
        } else {
            cdr.topFlags.Usage()
        }
        return ExitUsageError
    }
    // ...
}

This approach has the less impact on the already existing code using this lib.

The last option would be to check on my code the number of arguments provided. If none, I execute my command mannualy, otherwise, I let subcommands.Execute handle the input.

I may miss some subtilities of the subcommands library, so please let me know if there is a another way to delegate the "No args" case to a part of my code