command-line-api icon indicating copy to clipboard operation
command-line-api copied to clipboard

Should Commands have Ancestors

Open shaggygi opened this issue 3 years ago • 4 comments

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

shaggygi avatar Feb 26 '22 17:02 shaggygi

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.

jonsequitur avatar Feb 26 '22 17:02 jonsequitur

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.

shaggygi avatar Feb 26 '22 18:02 shaggygi

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.

kriswuollett avatar Apr 27 '22 01:04 kriswuollett

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

jonsequitur avatar Apr 29 '22 20:04 jonsequitur