diff --git a/command/builder.go b/command/builder.go index c0782c66..d3b0c3f5 100644 --- a/command/builder.go +++ b/command/builder.go @@ -13,6 +13,7 @@ func New(output io.Writer, logf LogFunc, name string, early HandlerFunc) Command return c } +// newNode initialises a subcommand tree and returns its address. func newNode(output io.Writer, logf LogFunc, name, usage string) *node { n := &node{ name: name, usage: usage, diff --git a/command/builder_test.go b/command/builder_test.go index 7b9a1701..05e29358 100644 --- a/command/builder_test.go +++ b/command/builder_test.go @@ -18,8 +18,14 @@ func TestBuild(t *testing.T) { t.Run("direct zero length", func(t *testing.T) { wantPanic := "invalid subcommand" - t.Run("zero length name", func(t *testing.T) { defer checkRecover(t, "Command", wantPanic); c.Command("", "usage", stubHandler) }) - t.Run("zero length usage", func(t *testing.T) { defer checkRecover(t, "Command", wantPanic); c.Command("name", "", stubHandler) }) + t.Run("zero length name", func(t *testing.T) { + defer checkRecover(t, "Command", wantPanic) + c.Command("", "usage", stubHandler) + }) + t.Run("zero length usage", func(t *testing.T) { + defer checkRecover(t, "Command", wantPanic) + c.Command("name", "", stubHandler) + }) }) t.Run("direct adopt unique names", func(t *testing.T) { @@ -34,8 +40,14 @@ func TestBuild(t *testing.T) { t.Run("zero length", func(t *testing.T) { wantPanic := "invalid subcommand tree" - t.Run("zero length name", func(t *testing.T) { defer checkRecover(t, "New", wantPanic); c.New("", "usage") }) - t.Run("zero length usage", func(t *testing.T) { defer checkRecover(t, "New", wantPanic); c.New("name", "") }) + t.Run("zero length name", func(t *testing.T) { + defer checkRecover(t, "New", wantPanic) + c.New("", "usage") + }) + t.Run("zero length usage", func(t *testing.T) { + defer checkRecover(t, "New", wantPanic) + c.New("name", "") + }) }) t.Run("direct adopt unique names", func(t *testing.T) { diff --git a/command/command.go b/command/command.go index 5533c4ef..f3b699bc 100644 --- a/command/command.go +++ b/command/command.go @@ -6,36 +6,43 @@ import ( "strings" ) -// UsageInternal causes the command to be hidden from help text when set as the usage string. -const UsageInternal = "internal" +// UsageInternal is a special usage string that hides the command from the +// generated help message. +const UsageInternal = "\x00" type ( // HandlerFunc is called when matching a directly handled subcommand tree. HandlerFunc = func(args []string) error - // LogFunc is the function signature of a printf function. + // LogFunc is the function signature of a printf function. The zero value + // implies [log.Printf]. LogFunc = func(format string, a ...any) - // FlagDefiner is a deferred flag definer value, usually encapsulating the default value. + // FlagDefiner is a deferred flag definer value, usually encapsulating the + // default value. FlagDefiner interface { // Define defines the flag in set. Define(b *strings.Builder, set *flag.FlagSet, p any, name, usage string) } + // A Flag is satisfied by command objects capable of receiving flags. Flag[T any] interface { // Flag defines a generic flag type in Node's flag set. Flag(p any, name string, value FlagDefiner, usage string) T } + // A Command is the root of a command tree. Command interface { Parse(arguments []string) error - // MustParse determines exit outcomes for Parse errors - // and calls handleError if [HandlerFunc] returns a non-nil error. + // MustParse determines exit outcomes for Parse errors and calls + // handleError if [HandlerFunc] returns a non-nil error. MustParse(arguments []string, handleError func(error)) baseNode[Command] } + + // A Node is a subcommand under a [Command]. Node baseNode[Node] baseNode[T any] interface { @@ -53,3 +60,16 @@ type ( Flag[T] } ) + +// rootNode satisfies baseNode for [Command]. +type rootNode struct{ *node } + +func (r rootNode) Command(name, usage string, f HandlerFunc) Command { + r.node.Command(name, usage, f) + return r +} + +func (r rootNode) Flag(p any, name string, value FlagDefiner, usage string) Command { + r.node.Flag(p, name, value, usage) + return r +} diff --git a/command/unreachable_test.go b/command/command_test.go similarity index 100% rename from command/unreachable_test.go rename to command/command_test.go diff --git a/command/node.go b/command/node.go index 4d09c975..b6f84bf8 100644 --- a/command/node.go +++ b/command/node.go @@ -6,6 +6,7 @@ import ( "strings" ) +// A node represents a command. type node struct { child, next *node name, usage string @@ -13,13 +14,16 @@ type node struct { out io.Writer logf LogFunc + // Names of commands preceding node. prefix []string + // Short user-facing representations of flags received by node. suffix strings.Builder f HandlerFunc set *flag.FlagSet } +// adopt adds v as the last child of n. func (n *node) adopt(v *node) bool { if n.child != nil { return n.child.append(v) @@ -28,6 +32,7 @@ func (n *node) adopt(v *node) bool { return true } +// append adds v as the last sibling of n. func (n *node) append(v *node) bool { if n.name == v.name { return false diff --git a/command/wrap.go b/command/wrap.go deleted file mode 100644 index ee08a61a..00000000 --- a/command/wrap.go +++ /dev/null @@ -1,14 +0,0 @@ -package command - -// the top level node wants [Command] returned for its builder methods -type rootNode struct{ *node } - -func (r rootNode) Command(name, usage string, f HandlerFunc) Command { - r.node.Command(name, usage, f) - return r -} - -func (r rootNode) Flag(p any, name string, value FlagDefiner, usage string) Command { - r.node.Flag(p, name, value, usage) - return r -}