command: use NUL for magic usage string
Test / Create distribution (push) Successful in 52s
Test / Sandbox (push) Successful in 2m51s
Test / Hakurei (push) Successful in 4m44s
Test / Sandbox (race detector) (push) Successful in 5m46s
Test / Hakurei (race detector) (push) Successful in 7m5s
Test / ShareFS (push) Successful in 7m5s
Test / Flake checks (push) Successful in 1m8s

This change also improves documentation.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
cat
2026-08-07 18:57:01 +09:00
parent 6a7f124deb
commit b37c1d8993
6 changed files with 48 additions and 24 deletions
+1
View File
@@ -13,6 +13,7 @@ func New(output io.Writer, logf LogFunc, name string, early HandlerFunc) Command
return c return c
} }
// newNode initialises a subcommand tree and returns its address.
func newNode(output io.Writer, logf LogFunc, name, usage string) *node { func newNode(output io.Writer, logf LogFunc, name, usage string) *node {
n := &node{ n := &node{
name: name, usage: usage, name: name, usage: usage,
+16 -4
View File
@@ -18,8 +18,14 @@ func TestBuild(t *testing.T) {
t.Run("direct zero length", func(t *testing.T) { t.Run("direct zero length", func(t *testing.T) {
wantPanic := "invalid subcommand" 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 name", func(t *testing.T) {
t.Run("zero length usage", func(t *testing.T) { defer checkRecover(t, "Command", wantPanic); c.Command("name", "", stubHandler) }) 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) { 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) { t.Run("zero length", func(t *testing.T) {
wantPanic := "invalid subcommand tree" 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 name", func(t *testing.T) {
t.Run("zero length usage", func(t *testing.T) { defer checkRecover(t, "New", wantPanic); c.New("name", "") }) 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) { t.Run("direct adopt unique names", func(t *testing.T) {
+26 -6
View File
@@ -6,36 +6,43 @@ import (
"strings" "strings"
) )
// UsageInternal causes the command to be hidden from help text when set as the usage string. // UsageInternal is a special usage string that hides the command from the
const UsageInternal = "internal" // generated help message.
const UsageInternal = "\x00"
type ( type (
// HandlerFunc is called when matching a directly handled subcommand tree. // HandlerFunc is called when matching a directly handled subcommand tree.
HandlerFunc = func(args []string) error 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) 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 { FlagDefiner interface {
// Define defines the flag in set. // Define defines the flag in set.
Define(b *strings.Builder, set *flag.FlagSet, p any, name, usage string) 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[T any] interface {
// Flag defines a generic flag type in Node's flag set. // Flag defines a generic flag type in Node's flag set.
Flag(p any, name string, value FlagDefiner, usage string) T Flag(p any, name string, value FlagDefiner, usage string) T
} }
// A Command is the root of a command tree.
Command interface { Command interface {
Parse(arguments []string) error Parse(arguments []string) error
// MustParse determines exit outcomes for Parse errors // MustParse determines exit outcomes for Parse errors and calls
// and calls handleError if [HandlerFunc] returns a non-nil error. // handleError if [HandlerFunc] returns a non-nil error.
MustParse(arguments []string, handleError func(error)) MustParse(arguments []string, handleError func(error))
baseNode[Command] baseNode[Command]
} }
// A Node is a subcommand under a [Command].
Node baseNode[Node] Node baseNode[Node]
baseNode[T any] interface { baseNode[T any] interface {
@@ -53,3 +60,16 @@ type (
Flag[T] 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
}
+5
View File
@@ -6,6 +6,7 @@ import (
"strings" "strings"
) )
// A node represents a command.
type node struct { type node struct {
child, next *node child, next *node
name, usage string name, usage string
@@ -13,13 +14,16 @@ type node struct {
out io.Writer out io.Writer
logf LogFunc logf LogFunc
// Names of commands preceding node.
prefix []string prefix []string
// Short user-facing representations of flags received by node.
suffix strings.Builder suffix strings.Builder
f HandlerFunc f HandlerFunc
set *flag.FlagSet set *flag.FlagSet
} }
// adopt adds v as the last child of n.
func (n *node) adopt(v *node) bool { func (n *node) adopt(v *node) bool {
if n.child != nil { if n.child != nil {
return n.child.append(v) return n.child.append(v)
@@ -28,6 +32,7 @@ func (n *node) adopt(v *node) bool {
return true return true
} }
// append adds v as the last sibling of n.
func (n *node) append(v *node) bool { func (n *node) append(v *node) bool {
if n.name == v.name { if n.name == v.name {
return false return false
-14
View File
@@ -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
}