dotnet / command-line-api

Command line parsing, invocation, and rendering of terminal output.
https://github.com/dotnet/command-line-api/wiki
MIT License
3.42k stars 383 forks source link

Should Commands have Ancestors #1659

Open shaggygi opened 2 years ago

shaggygi commented 2 years ago

Similar to Children, it seems like Commands could also have Ancestors. Unless there is a better approach, I have to scan each Command's Parents (and each one of their Parents and so on) until I find the one that includes a GlobalOption needed.

Thoughts?

Thx

jonsequitur commented 2 years ago

Could you say a bit more about what you're wanting to do?

One hint that might or might not be relevant: If you keep a reference to the Option from the outset, you can use ParseResult.FindResultFor and ParseResult.GetValueOrDefault. It's more performant than traversing the hierarchy.

shaggygi commented 2 years ago

Let's say I have the following:

rootCommand subCommand1 subCommand2 subCommand3

subCommand1 has a GlobalOption. When executing subCommand3, I need to find out what the GlobalOption is within subCommand1.

There is probably a better way, but below is similar to what I'm trying...

public static Command? FindAncestorCommand(Command command, string commandName)
    {
        foreach (Command parentCommand in command.Parents)
        {
            if (parentCommand.Name == commandName)
            {
                return parentCommand;
            }
            else
            {
                Command? nextParent = FindAncestorCommand(parentCommand, commandName);

                if (nextParent is not null)
                {
                    return nextParent;
                }
            }
        }

        return null;
    }

Then call within my SetHandler...

if (CommandHelper.FindAncestorCommand(command, "subCommand1") is not SubCommand1 subCommand1)
{
    throw new Exception($"Invalid command. {command}");
}

string optionValue = context.ParseResult.GetValueForOption(subCommand1.GetMyOption());

Also note, I have all my Commands and some Options separated into own classes. Not one big setup with Program.cs.

kriswuollett commented 2 years ago

Instead of I'm here since I may want to convert some python utilities that use Click, specifically Click's Nested Handling and Contexts, to dotnet, and happened to find this issue. I'm following the example in #1537 to use custom bindings --- shouldn't I be I be able to get a command's ancestor's custom bound options, and not re-process the individual options and arguments as suggested above? In Click's world, I'd be setting a context object that gets passed down to the subcommand.

jonsequitur commented 2 years ago

We don't currently have APIs to find ancestors or descendants directly, only parents and children. But it's a simple enough thing to do with a helper method so I'm not sure we'd add this kind of convenience method to the surface area of the library.

Here's an example of how we traverse all ancestors using an internal method FlattenBreadthFirst:

https://github.com/dotnet/command-line-api/blob/3cb430c773e946bc8500416d2cc68a745fafd7fc/src/System.CommandLine/Command.cs#L207-L221