Accept shared debug bindings

This commit is contained in:
2026-11-08 15:00:34 +00:00
parent 7dd30f7df3
commit 3271c18b77
5 changed files with 72 additions and 3 deletions

View File

@@ -498,7 +498,7 @@ func validateBindings[I any](flags []Flag[I], arguments []Argument[I]) error {
if binding.meta.Name == "help" {
return fmt.Errorf("flag name %q is reserved for help", binding.meta.Name)
}
if commandmeta.IsReportingFlag(binding.meta.Name) {
if commandmeta.IsReportingFlag(binding.meta.Name) && !(binding.meta.Name == "debug" && binding.boolean) {
return fmt.Errorf("flag name %q is reserved for reporting", binding.meta.Name)
}
if _, exists := flagNames[binding.meta.Name]; exists {

View File

@@ -1,6 +1,6 @@
# Shared debug binding implementation plan
Status: in progress; Stage 1 complete.
Status: in progress; Stages 1–2 complete.
Baseline: `6727d22ce6e1ac668b56a3e6d8fa5f7a06e54d88`.
Execution audience: Luna coding agent.
@@ -106,6 +106,11 @@ and concurrent use and retained invocations preserve the selected result.
## Stage 2 — Recognize shared boolean bindings and compose metadata
**Complete.** Declaration validation accepts only boolean debug bindings and
retains other reservation and wiring checks. Effective metadata combines the
shared option without removing consumer entries. Focused validation and metadata
tests passed offline with Go 1.27.0; backend adoption follows in Stage 3.
Primary files: [command.go](../../command.go), [binding.go](../../binding.go),
[parser.go](../../parser.go), and
[neutral metadata](../../internal/commandmeta/metadata.go).

View File

@@ -1,6 +1,7 @@
package commandkit
import (
"strconv"
"strings"
"testing"
@@ -313,3 +314,20 @@ func TestFlagAliasSharedSuffixAndUnavailableDestination(t *testing.T) {
}
}
}
func TestSharedDebugDeclarationValidation(t *testing.T) {
valid := BoolFlag(FlagMeta{Name: "debug", Short: "d"}, true, func(*struct{}, Supplied[bool]) {})
if _, err := New(Identity{Name: "Example"}, flagAliasCommand("run", valid)); err != nil {
t.Fatalf("valid debug: %v", err)
}
for _, flags := range [][]Flag[struct{}]{
{valid, valid},
{BoolFlag[struct{}](FlagMeta{Name: "debug"}, false, nil)},
{BoolFlag(FlagMeta{Name: "debug", Short: "h"}, false, func(*struct{}, Supplied[bool]) {})},
{ValueFlag(FlagMeta{Name: "debug"}, (*bool)(nil), strconv.ParseBool, func(*struct{}, Supplied[bool]) {})},
} {
if _, err := New(Identity{Name: "Example"}, flagAliasCommand("bad", flags...)); err == nil {
t.Fatalf("invalid binding accepted: %#v", flags)
}
}
}

View File

@@ -2,6 +2,8 @@
// its command-line backend.
package commandmeta
import "strings"
// Key identifies one command inside one routing snapshot.
type Key uint64
@@ -100,6 +102,35 @@ func ReportingFlags() []Flag {
return flags
}
// EffectiveFlags combines the shared debug option while preserving consumer order.
func EffectiveFlags(flags, core []Flag) []Flag {
result := append([]Flag(nil), flags...)
shared := -1
for i, flag := range result {
if flag.Name == "debug" && flag.Boolean {
shared = i
break
}
}
for _, flag := range core {
if flag.Name == "debug" && shared >= 0 {
app := result[shared]
usage := flag.Usage
if app.Usage != "" {
usage += "; " + app.Usage
}
if strings.EqualFold(app.Default, "true") {
usage += "; application input defaults to true (reporting defaults to ordinary)"
}
flag.Usage, flag.Short, flag.Required, flag.Completion = usage, app.Short, app.Required, app.Completion
result[shared] = flag
continue
}
result = append(result, flag)
}
return result
}
// IsReportingFlag reports whether name is reserved for a shared reporting flag.
func IsReportingFlag(name string) bool {
for _, flag := range reportingFlags {

View File

@@ -1,6 +1,9 @@
package commandmeta
import "testing"
import (
"strings"
"testing"
)
func TestReportingFlagsAreBooleanAndReturnedIndependently(t *testing.T) {
flags := ReportingFlags()
@@ -33,3 +36,15 @@ func TestReportingFlagsAreBooleanAndReturnedIndependently(t *testing.T) {
}
}
}
func TestEffectiveFlagsCombinesDebugMetadata(t *testing.T) {
flags := []Flag{{Name: "before"}, {Name: "debug", Short: "d", Boolean: true, Default: "true", Required: true, Usage: "application diagnostics", Completion: CompletionHint{Kind: CompletionFiles}}, {Name: "after"}}
core := ReportingFlags()
got := EffectiveFlags(flags, core)
if len(got) != len(flags)+len(core)-1 || got[1].Name != "debug" || got[1].Default != "false" || got[1].Short != "d" || !got[1].Required || got[1].Completion.Kind != CompletionFiles || !strings.Contains(got[1].Usage, "application diagnostics") || !strings.Contains(got[1].Usage, "application input defaults to true") {
t.Fatalf("effective debug = %#v", got)
}
if flags[1].Default != "true" || core[2].Short != "" {
t.Fatal("input metadata changed")
}
}