// SPDX-FileCopyrightText: (C) Copyright 2026 Pim van Pelt // SPDX-License-Identifier: Apache-2.0 package cli import "context" // Builder constructs Node[C] values without repeating the [C] type parameter at // every node. Obtain one with For[C], then use Root/Dir/Cmd/SlotDir/Slot: // // b := cli.For[Client]() // root := b.Root( // b.Dir("show", "show state", // b.Cmd("version", "print version", runVersion), // b.Cmd("server", "list servers (add for one)", runServers, // b.Slot("", "show one", dynServers, runServer))), // b.Cmd("quit", "exit", runQuit), // ) // // The naming is symmetric: Dir/Cmd are fixed keyword nodes (without/with an // action), SlotDir/Slot are dynamic argument nodes (without/with an action). // The builder is a zero-size value, so constructing many nodes from one is free. // // It is purely a convenience: every method returns a plain *Node[C], so builder // and struct-literal construction interoperate freely — you can mix the two, // and a node built either way can still be mutated afterward (e.g. to wire a // circular slot: opt := b.Slot(...); opt.Children = []*cli.Node[C]{opt}). type Builder[C any] struct{} // For returns a Builder bound to client type C. func For[C any]() Builder[C] { return Builder[C]{} } // Root returns the (wordless) root node holding the given top-level children. func (Builder[C]) Root(children ...*Node[C]) *Node[C] { return &Node[C]{Children: children} } // Dir is a fixed keyword node with no action of its own — a grouping of // subcommands (e.g. "show", "create"). func (Builder[C]) Dir(word, help string, children ...*Node[C]) *Node[C] { return &Node[C]{Word: word, Help: help, Children: children} } // Cmd is a fixed keyword node that runs an action. It may also carry children: // a node that both does something and has subcommands (e.g. "show instance", // which lists members and also accepts ""). func (Builder[C]) Cmd(word, help string, run func(context.Context, C, []string) error, children ...*Node[C]) *Node[C] { return &Node[C]{Word: word, Help: help, Run: run, Children: children} } // SlotDir is a dynamic slot that captures any token as an argument but has no // action of its own — a navigational placeholder (e.g. "" in // "instance evpn ..."). dyn yields the live completion candidates, given // the args captured by earlier slots on the path. func (Builder[C]) SlotDir(word, help string, dyn func(context.Context, C, []string) []string, children ...*Node[C]) *Node[C] { return &Node[C]{Word: word, Help: help, Dynamic: dyn, Children: children} } // Slot is a dynamic slot that both captures a token argument and runs an action // (e.g. "delete group "). It may also carry children. dyn yields the live // completion candidates for the slot, given the args captured by earlier slots. func (Builder[C]) Slot(word, help string, dyn func(context.Context, C, []string) []string, run func(context.Context, C, []string) error, children ...*Node[C]) *Node[C] { return &Node[C]{Word: word, Help: help, Dynamic: dyn, Run: run, Children: children} }