feat(commands): add SubCommand type and EffectiveUsage method
Introduce SubCommand struct for declaring sub-commands structurally within a parent command Definition. The EffectiveUsage() method auto-generates usage strings from sub-command names and args, preventing drift between help text and actual handler behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6a6d2c9d47
commit
9f61d50470
2 changed files with 75 additions and 2 deletions
|
|
@ -1,5 +1,18 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SubCommand defines a single sub-command within a parent command.
|
||||||
|
type SubCommand struct {
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
ArgsUsage string // optional, e.g. "<session-id>"
|
||||||
|
Handler Handler
|
||||||
|
}
|
||||||
|
|
||||||
// Definition is the single-source metadata and behavior contract for a slash command.
|
// Definition is the single-source metadata and behavior contract for a slash command.
|
||||||
//
|
//
|
||||||
// Design notes (phase 1):
|
// Design notes (phase 1):
|
||||||
|
|
@ -10,7 +23,26 @@ package commands
|
||||||
type Definition struct {
|
type Definition struct {
|
||||||
Name string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
Usage string
|
Usage string // for simple commands; ignored when SubCommands is set
|
||||||
Aliases []string
|
Aliases []string
|
||||||
Handler Handler
|
SubCommands []SubCommand // optional; when set, Executor routes to sub-command handlers
|
||||||
|
Handler Handler // for simple commands without sub-commands
|
||||||
|
}
|
||||||
|
|
||||||
|
// EffectiveUsage returns the usage string. When SubCommands are present,
|
||||||
|
// it is auto-generated from sub-command names so metadata and behavior
|
||||||
|
// cannot drift.
|
||||||
|
func (d Definition) EffectiveUsage() string {
|
||||||
|
if len(d.SubCommands) == 0 {
|
||||||
|
return d.Usage
|
||||||
|
}
|
||||||
|
names := make([]string, 0, len(d.SubCommands))
|
||||||
|
for _, sc := range d.SubCommands {
|
||||||
|
name := sc.Name
|
||||||
|
if sc.ArgsUsage != "" {
|
||||||
|
name += " " + sc.ArgsUsage
|
||||||
|
}
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("/%s [%s]", d.Name, strings.Join(names, "|"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
41
pkg/commands/definition_test.go
Normal file
41
pkg/commands/definition_test.go
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDefinition_EffectiveUsage_NoSubCommands(t *testing.T) {
|
||||||
|
d := Definition{Name: "start", Usage: "/start"}
|
||||||
|
if got := d.EffectiveUsage(); got != "/start" {
|
||||||
|
t.Fatalf("EffectiveUsage()=%q, want %q", got, "/start")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefinition_EffectiveUsage_WithSubCommands(t *testing.T) {
|
||||||
|
d := Definition{
|
||||||
|
Name: "show",
|
||||||
|
SubCommands: []SubCommand{
|
||||||
|
{Name: "model"},
|
||||||
|
{Name: "channel"},
|
||||||
|
{Name: "agents"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
want := "/show [model|channel|agents]"
|
||||||
|
if got := d.EffectiveUsage(); got != want {
|
||||||
|
t.Fatalf("EffectiveUsage()=%q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefinition_EffectiveUsage_WithArgsUsage(t *testing.T) {
|
||||||
|
d := Definition{
|
||||||
|
Name: "session",
|
||||||
|
SubCommands: []SubCommand{
|
||||||
|
{Name: "list"},
|
||||||
|
{Name: "resume", ArgsUsage: "<id>"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
want := "/session [list|resume <id>]"
|
||||||
|
if got := d.EffectiveUsage(); got != want {
|
||||||
|
t.Fatalf("EffectiveUsage()=%q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue