Retain discovery prefixes in usage diagnostics
This commit is contained in:
@@ -98,7 +98,7 @@ invocation metadata were added.
|
||||
|
||||
## R-2: Retain discovery-prefix context in usage diagnostics
|
||||
|
||||
Status: open. Priority: P3. Category: presentation consistency.
|
||||
Status: complete. Priority: P3. Category: presentation consistency.
|
||||
|
||||
**Evidence:** Discovery parsing uses the backend root with only trailing flag
|
||||
tokens, so parser failures have an empty canonical path.
|
||||
@@ -130,6 +130,14 @@ identify `demo list` in usage. Repeat through a composed group shortcut after
|
||||
R-1. Check identifying fragments rather than exact formatting, and confirm that
|
||||
ordinary root and canonical command diagnostics retain their existing behavior.
|
||||
|
||||
**Completed:** Usage presentation now uses the selected discovery prefix rather
|
||||
than asking the canonical backend for its usage. Regressions first failed for
|
||||
all three error forms at both direct and chained prefixes, and now retain the
|
||||
entered prefix and flag spelling without inventing a canonical identity.
|
||||
Usage status 2, completed diagnostic delivery, metadata-only failures, and
|
||||
ordinary root/canonical usage remain covered. The complete offline root suite
|
||||
passed on Go 1.26.8.
|
||||
|
||||
## R-3: Preserve empty and dash operands before child-name tokens
|
||||
|
||||
Status: open. Priority: P2. Category: inherited parser/routing inconsistency.
|
||||
|
||||
@@ -194,7 +194,13 @@ func (current *execution) usageLine(path []string) string {
|
||||
|
||||
func (current *execution) present(outcome Outcome, path []string) Outcome {
|
||||
usageLine := func(path []string) string { return current.usageLine(path) }
|
||||
if current.route.kind == routeCanonical && !current.route.enteredPath.Equal(current.route.canonicalPath) {
|
||||
switch {
|
||||
case current.route.kind == routeDiscovery:
|
||||
path = clonePath(current.route.enteredPath)
|
||||
usageLine = func([]string) string {
|
||||
return fmt.Sprintf("Usage: %s %s [command]", current.app.identity.Name, current.route.enteredPath.String())
|
||||
}
|
||||
case current.route.kind == routeCanonical && !current.route.enteredPath.Equal(current.route.canonicalPath):
|
||||
path = clonePath(current.route.enteredPath)
|
||||
usageLine = func([]string) string { return current.routeUsageLine() }
|
||||
}
|
||||
|
||||
@@ -11,6 +11,57 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDiscoveryUsageRetainsEnteredPrefix(t *testing.T) {
|
||||
initialized := false
|
||||
inspect := NewCommand(CommandSpec[struct{}]{
|
||||
Name: "inspect", NewInput: func() struct{} { initialized = true; return struct{}{} },
|
||||
Run: func(Invocation, struct{}) Outcome { t.Fatal("usage failure executed a handler"); return Success() },
|
||||
})
|
||||
app, err := New(Identity{Name: "Demo"}, inspect, Group("box", "Box"),
|
||||
Alias("list things", "inspect"), Alias("p", "q"), Alias("q", "box"), Alias("q admin pick", "inspect"),
|
||||
FlagAlias("--hlep", "--help"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, prefix := range []string{"list", "p admin"} {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
tail []string
|
||||
want string
|
||||
}{
|
||||
{name: "bare", want: "incomplete command path"},
|
||||
{name: "unknown flag", tail: []string{"--missing"}, want: "--missing"},
|
||||
{name: "invalid help", tail: []string{"--hlep=bad"}, want: "for --hlep"},
|
||||
} {
|
||||
t.Run(prefix+"/"+test.name, func(t *testing.T) {
|
||||
var output, diagnostics bytes.Buffer
|
||||
outcome := app.Execute(context.Background(), append(strings.Fields(prefix), test.tail...), Streams{Out: &output, Err: &diagnostics})
|
||||
if outcome.Kind != OutcomeUsage || outcome.Status != 2 || outcome.DiagnosticDelivery != DeliveryComplete || output.Len() != 0 {
|
||||
t.Fatalf("outcome = %#v, output %q", outcome, output.String())
|
||||
}
|
||||
for _, fragment := range []string{test.want, "Usage: Demo " + prefix} {
|
||||
if !strings.Contains(diagnostics.String(), fragment) {
|
||||
t.Errorf("diagnostics %q omit %q", diagnostics.String(), fragment)
|
||||
}
|
||||
}
|
||||
if strings.Contains(diagnostics.String(), "Alias for:") {
|
||||
t.Errorf("discovery was assigned a canonical identity: %q", diagnostics.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, prefix := range []string{"", "inspect"} {
|
||||
var diagnostics bytes.Buffer
|
||||
outcome := app.Execute(context.Background(), append(strings.Fields(prefix), "--missing"), Streams{Err: &diagnostics})
|
||||
if outcome.Kind != OutcomeUsage || !strings.Contains(diagnostics.String(), "Usage: Demo "+prefix) || strings.Contains(diagnostics.String(), "Alias for:") {
|
||||
t.Errorf("ordinary usage for %q = %#v, diagnostics %q", prefix, outcome, diagnostics.String())
|
||||
}
|
||||
}
|
||||
if initialized {
|
||||
t.Fatal("usage initialized command input")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserFailuresRetainAliasUsageAndFlagSpellings(t *testing.T) {
|
||||
built := 0
|
||||
command := NewCommand(CommandSpec[struct{}]{
|
||||
|
||||
Reference in New Issue
Block a user