Accept shared debug bindings
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user