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" {
|
if binding.meta.Name == "help" {
|
||||||
return fmt.Errorf("flag name %q is reserved for help", binding.meta.Name)
|
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)
|
return fmt.Errorf("flag name %q is reserved for reporting", binding.meta.Name)
|
||||||
}
|
}
|
||||||
if _, exists := flagNames[binding.meta.Name]; exists {
|
if _, exists := flagNames[binding.meta.Name]; exists {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Shared debug binding implementation plan
|
# Shared debug binding implementation plan
|
||||||
|
|
||||||
Status: in progress; Stage 1 complete.
|
Status: in progress; Stages 1–2 complete.
|
||||||
Baseline: `6727d22ce6e1ac668b56a3e6d8fa5f7a06e54d88`.
|
Baseline: `6727d22ce6e1ac668b56a3e6d8fa5f7a06e54d88`.
|
||||||
Execution audience: Luna coding agent.
|
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
|
## 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),
|
Primary files: [command.go](../../command.go), [binding.go](../../binding.go),
|
||||||
[parser.go](../../parser.go), and
|
[parser.go](../../parser.go), and
|
||||||
[neutral metadata](../../internal/commandmeta/metadata.go).
|
[neutral metadata](../../internal/commandmeta/metadata.go).
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package commandkit
|
package commandkit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"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.
|
// its command-line backend.
|
||||||
package commandmeta
|
package commandmeta
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
// Key identifies one command inside one routing snapshot.
|
// Key identifies one command inside one routing snapshot.
|
||||||
type Key uint64
|
type Key uint64
|
||||||
|
|
||||||
@@ -100,6 +102,35 @@ func ReportingFlags() []Flag {
|
|||||||
return flags
|
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.
|
// IsReportingFlag reports whether name is reserved for a shared reporting flag.
|
||||||
func IsReportingFlag(name string) bool {
|
func IsReportingFlag(name string) bool {
|
||||||
for _, flag := range reportingFlags {
|
for _, flag := range reportingFlags {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package commandmeta
|
package commandmeta
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestReportingFlagsAreBooleanAndReturnedIndependently(t *testing.T) {
|
func TestReportingFlagsAreBooleanAndReturnedIndependently(t *testing.T) {
|
||||||
flags := ReportingFlags()
|
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