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
|
## 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
|
**Evidence:** Discovery parsing uses the backend root with only trailing flag
|
||||||
tokens, so parser failures have an empty canonical path.
|
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
|
R-1. Check identifying fragments rather than exact formatting, and confirm that
|
||||||
ordinary root and canonical command diagnostics retain their existing behavior.
|
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
|
## R-3: Preserve empty and dash operands before child-name tokens
|
||||||
|
|
||||||
Status: open. Priority: P2. Category: inherited parser/routing inconsistency.
|
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 {
|
func (current *execution) present(outcome Outcome, path []string) Outcome {
|
||||||
usageLine := func(path []string) string { return current.usageLine(path) }
|
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)
|
path = clonePath(current.route.enteredPath)
|
||||||
usageLine = func([]string) string { return current.routeUsageLine() }
|
usageLine = func([]string) string { return current.routeUsageLine() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,57 @@ import (
|
|||||||
"testing"
|
"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) {
|
func TestParserFailuresRetainAliasUsageAndFlagSpellings(t *testing.T) {
|
||||||
built := 0
|
built := 0
|
||||||
command := NewCommand(CommandSpec[struct{}]{
|
command := NewCommand(CommandSpec[struct{}]{
|
||||||
|
|||||||
Reference in New Issue
Block a user