Index backend commands for path lookup

This commit is contained in:
2026-10-09 01:28:19 +00:00
parent 628dc78083
commit ae656d8fa9
3 changed files with 101 additions and 40 deletions

View File

@@ -71,8 +71,9 @@ func RenderHelp(root *Command, path []string) (string, *Failure) {
if err != nil {
return "", &Failure{Message: err.Error()}
}
declaration := declarationFor(nodes, selected)
if declaration == nil || len(remaining) != 0 || !equalPath(path, pathFor(root, declaration)) {
declaration := nodes[selected]
selectedPath, pathOK := pathFor(selected, nodes)
if declaration == nil || !pathOK || len(remaining) != 0 || !equalPath(path, selectedPath) {
return "", &Failure{Message: "help path is not registered"}
}
selected.SetOut(buffer)
@@ -91,8 +92,9 @@ func UsageLine(root *Command, path []string) (string, *Failure) {
options := normalizeOptions(ParseOptions{Context: context.Background(), In: strings.NewReader(""), Out: io.Discard, Err: io.Discard})
backendRoot, nodes := buildTree(root, options)
selected, remaining, err := backendRoot.Find(path)
declaration := declarationFor(nodes, selected)
if err != nil || declaration == nil || len(remaining) != 0 || !equalPath(path, pathFor(root, declaration)) {
declaration := nodes[selected]
selectedPath, pathOK := pathFor(selected, nodes)
if err != nil || declaration == nil || !pathOK || len(remaining) != 0 || !equalPath(path, selectedPath) {
return "", &Failure{Message: "usage path is not registered"}
}
line := selected.UseLine()
@@ -128,11 +130,15 @@ func Parse(root *Command, argv []string, options ParseOptions) Result {
if selected == nil {
return failure(result, "no command was selected")
}
selectedNode := declarationFor(nodes, selected)
selectedNode := nodes[selected]
if selectedNode == nil {
return failure(result, "selected command is not part of the declared tree")
}
result.Path = pathFor(root, selectedNode)
var pathOK bool
result.Path, pathOK = pathFor(selected, nodes)
if !pathOK {
return failure(result, "selected command is not part of the declared tree")
}
consumed := len(leading) - len(unconsumed)
if consumed != len(result.Path) || !equalPath(leading[:consumed], result.Path) {
return failure(result, "command path must match a registered command exactly")
@@ -180,8 +186,8 @@ func normalizeOptions(options ParseOptions) ParseOptions {
return options
}
func buildTree(root *Command, options ParseOptions) (*spfcobra.Command, map[*Command]*spfcobra.Command) {
nodes := make(map[*Command]*spfcobra.Command)
func buildTree(root *Command, options ParseOptions) (*spfcobra.Command, map[*spfcobra.Command]*Command) {
nodes := make(map[*spfcobra.Command]*Command)
var build func(*Command) *spfcobra.Command
build = func(declaration *Command) *spfcobra.Command {
backend := &spfcobra.Command{
@@ -223,7 +229,7 @@ func buildTree(root *Command, options ParseOptions) (*spfcobra.Command, map[*Com
// Cobra's automatic help flag is suppressed because CommandKit declares
// and interprets the local help flag itself.
backend.SetHelpCommand(nil)
nodes[declaration] = backend
nodes[backend] = declaration
for _, child := range declaration.Children {
backend.AddCommand(build(child))
}
@@ -320,15 +326,6 @@ func equalPath(left, right []string) bool {
return true
}
func declarationFor(nodes map[*Command]*spfcobra.Command, selected *spfcobra.Command) *Command {
for declaration, backend := range nodes {
if backend == selected {
return declaration
}
}
return nil
}
func splitRoutingPrefix(argv []string) ([]string, []string) {
for index, argument := range argv {
if argument == "--" || (strings.HasPrefix(argument, "-") && argument != "-") {
@@ -338,29 +335,21 @@ func splitRoutingPrefix(argv []string) ([]string, []string) {
return append([]string(nil), argv...), nil
}
func pathFor(root, selected *Command) []string {
func pathFor(selected *spfcobra.Command, declarations map[*spfcobra.Command]*Command) ([]string, bool) {
var reverse []string
for current := selected; current != nil && current != root; {
reverse = append(reverse, current.Name)
current = parentOf(root, current)
}
path := make([]string, len(reverse))
for index := range reverse {
path[len(reverse)-index-1] = reverse[index]
}
return path
}
func parentOf(root, target *Command) *Command {
for _, child := range root.Children {
if child == target {
return root
for current := selected; current != nil; current = current.Parent() {
declaration := declarations[current]
if declaration == nil {
return nil, false
}
if parent := parentOf(child, target); parent != nil {
return parent
if current.Parent() != nil {
reverse = append(reverse, declaration.Name)
}
}
return nil
for left, right := 0, len(reverse)-1; left < right; left, right = left+1, right-1 {
reverse[left], reverse[right] = reverse[right], reverse[left]
}
return reverse, true
}
func failure(result Result, message string) Result {

View File

@@ -2,6 +2,7 @@ package cobra
import (
"context"
"fmt"
"io"
"reflect"
"strings"
@@ -130,3 +131,51 @@ func TestParseReturnsNeutralFailuresAndFreshState(t *testing.T) {
t.Fatalf("unknown flag failure = %#v", failed.Failure)
}
}
func TestSelectedPathsResolveThroughBackendParents(t *testing.T) {
root := &Command{Name: "app"}
for index := 0; index < 128; index++ {
root.Children = append(root.Children, &Command{Name: fmt.Sprintf("wide%03d", index), Runnable: true})
}
path := make([]string, 24)
current := root
for index := range path {
path[index] = fmt.Sprintf("deep%02d", index)
child := &Command{Name: path[index]}
if index == len(path)-1 {
child.Runnable = true
}
current.Children = append(current.Children, child)
current = child
}
result := Parse(root, path, ParseOptions{})
if result.Failure != nil || !reflect.DeepEqual(result.Path, path) {
t.Fatalf("deep parse path = %#v", result)
}
rootResult := Parse(root, nil, ParseOptions{})
if rootResult.Failure != nil || len(rootResult.Path) != 0 {
t.Fatalf("root parse path = %#v", rootResult)
}
help, failure := RenderHelp(root, path)
if failure != nil || !strings.Contains(help, path[len(path)-1]) {
t.Fatalf("deep help = %q, %v", help, failure)
}
if _, failure := RenderHelp(root, []string{"wide128"}); failure == nil {
t.Fatal("help accepted an unknown sibling")
}
line, failure := UsageLine(root, path)
if failure != nil || !strings.Contains(line, path[len(path)-1]) {
t.Fatalf("deep usage line = %q, %v", line, failure)
}
if _, failure := UsageLine(root, []string{"wide128"}); failure == nil {
t.Fatal("usage accepted an unknown sibling")
}
if _, failure := RenderHelp(root, nil); failure != nil {
t.Fatalf("root help failed: %v", failure)
}
if _, failure := UsageLine(root, nil); failure != nil {
t.Fatalf("root usage failed: %v", failure)
}
}