First stab at maglevc
This commit is contained in:
127
cmd/maglevc/tree.go
Normal file
127
cmd/maglevc/tree.go
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright (c) 2026, Pim van Pelt <pim@ipng.ch>
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"git.ipng.ch/ipng/vpp-maglev/internal/grpcapi"
|
||||
)
|
||||
|
||||
// Node is one word in the command tree. Leaf nodes have a Run function.
|
||||
// Slot nodes have Dynamic set (and no fixed Word to match against); they
|
||||
// accept any single token as an argument and may have further Children.
|
||||
type Node struct {
|
||||
Word string
|
||||
Help string
|
||||
Dynamic func(context.Context, grpcapi.MaglevClient) []string // non-nil → slot node
|
||||
Children []*Node
|
||||
Run func(context.Context, grpcapi.MaglevClient, []string) error
|
||||
}
|
||||
|
||||
// Walk descends the tree following tokens. At each step it tries fixed
|
||||
// children first (exact then prefix), then falls back to a slot child
|
||||
// (Dynamic != nil). Tokens consumed by slot children are collected as args.
|
||||
// Returns the deepest node reached and the args collected from slot nodes.
|
||||
func Walk(root *Node, tokens []string) (*Node, []string) {
|
||||
node := root
|
||||
var args []string
|
||||
for len(tokens) > 0 {
|
||||
tok := tokens[0]
|
||||
|
||||
// Try fixed children (exact, then unique prefix).
|
||||
next := matchFixedChild(node.Children, tok)
|
||||
if next != nil {
|
||||
node = next
|
||||
tokens = tokens[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
// Try a slot child.
|
||||
slot := findSlotChild(node.Children)
|
||||
if slot != nil {
|
||||
args = append(args, tok)
|
||||
tokens = tokens[1:]
|
||||
node = slot
|
||||
continue
|
||||
}
|
||||
|
||||
// Dead end — no match.
|
||||
break
|
||||
}
|
||||
return node, args
|
||||
}
|
||||
|
||||
// matchFixedChild returns the child matching tok by exact then unique prefix,
|
||||
// considering only non-slot children.
|
||||
func matchFixedChild(children []*Node, tok string) *Node {
|
||||
var fixed []*Node
|
||||
for _, c := range children {
|
||||
if c.Dynamic == nil {
|
||||
fixed = append(fixed, c)
|
||||
}
|
||||
}
|
||||
// Exact match.
|
||||
for _, c := range fixed {
|
||||
if c.Word == tok {
|
||||
return c
|
||||
}
|
||||
}
|
||||
// Unique prefix match.
|
||||
var matches []*Node
|
||||
for _, c := range fixed {
|
||||
if strings.HasPrefix(c.Word, tok) {
|
||||
matches = append(matches, c)
|
||||
}
|
||||
}
|
||||
if len(matches) == 1 {
|
||||
return matches[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// findSlotChild returns the first child that is a slot node (Dynamic != nil).
|
||||
func findSlotChild(children []*Node) *Node {
|
||||
for _, c := range children {
|
||||
if c.Dynamic != nil {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Candidates returns the completable children at the current position given
|
||||
// the already-typed tokens and the partial token being completed.
|
||||
func Candidates(root *Node, tokens []string, partial string, ctx context.Context, client grpcapi.MaglevClient) []*Node {
|
||||
// Walk the already-confirmed tokens.
|
||||
node, _ := Walk(root, tokens)
|
||||
|
||||
// Now look at what could follow at this node.
|
||||
// Check fixed children filtered by partial.
|
||||
fixedMatches := filterFixedChildren(node.Children, partial)
|
||||
|
||||
// Check dynamic slot child if present.
|
||||
var dynMatches []*Node
|
||||
slot := findSlotChild(node.Children)
|
||||
if slot != nil && slot.Dynamic != nil {
|
||||
vals := slot.Dynamic(ctx, client)
|
||||
for _, v := range vals {
|
||||
if strings.HasPrefix(v, partial) {
|
||||
dynMatches = append(dynMatches, &Node{Word: v, Help: slot.Help})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return append(fixedMatches, dynMatches...)
|
||||
}
|
||||
|
||||
func filterFixedChildren(children []*Node, prefix string) []*Node {
|
||||
var out []*Node
|
||||
for _, c := range children {
|
||||
if c.Dynamic == nil && strings.HasPrefix(c.Word, prefix) {
|
||||
out = append(out, c)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user