Require exact resolved paths for explicit help
This commit is contained in:
@@ -111,7 +111,7 @@ additional discovery check passed on Go 1.26.8.
|
|||||||
|
|
||||||
## R-3: Preserve exact target lookup for the explicit help command
|
## R-3: Preserve exact target lookup for the explicit help command
|
||||||
|
|
||||||
Status: open. Priority: P2. Category: help grammar regression.
|
Status: complete. Priority: P2. Category: help grammar regression.
|
||||||
|
|
||||||
**Evidence:** [renderHelpTargets](../../execute.go#L155) reuses execution lookup
|
**Evidence:** [renderHelpTargets](../../execute.go#L155) reuses execution lookup
|
||||||
and accepts `routeCanonical` results even when remaining target tokens are
|
and accepts `routeCanonical` results even when remaining target tokens are
|
||||||
@@ -137,6 +137,13 @@ requires operands.
|
|||||||
targets fail with usage status 2; exact aliases, group shortcuts, and discovery
|
targets fail with usage status 2; exact aliases, group shortcuts, and discovery
|
||||||
prefixes still render help without application initialization.
|
prefixes still render help without application initialization.
|
||||||
|
|
||||||
|
**Completed:** Explicit help now requires all resolved target tokens to form
|
||||||
|
the selected path; execution operands no longer disappear into a help view.
|
||||||
|
New public-boundary regressions failed before the fix and now reject canonical
|
||||||
|
and aliased operand-bearing targets while preserving exact command/group aliases,
|
||||||
|
discovery help, and ordinary execution-form help without input initialization.
|
||||||
|
The complete offline root suite passed on Go 1.26.8.
|
||||||
|
|
||||||
## R-4: Resolve discovery-help contexts through group shortcuts
|
## R-4: Resolve discovery-help contexts through group shortcuts
|
||||||
|
|
||||||
Status: open. Priority: P2. Category: incomplete alias composition.
|
Status: open. Priority: P2. Category: incomplete alias composition.
|
||||||
|
|||||||
@@ -161,7 +161,9 @@ func (current *execution) renderHelpTargets(targets []string) Outcome {
|
|||||||
return Failure(errors.New("cannot render help without command metadata"))
|
return Failure(errors.New("cannot render help without command metadata"))
|
||||||
}
|
}
|
||||||
route := resolveEnteredRoute(targets, snapshot)
|
route := resolveEnteredRoute(targets, snapshot)
|
||||||
if route.kind == routeUnknown {
|
// Explicit help targets are paths, not command invocations with operands.
|
||||||
|
// Discovery routes have no canonical path and retain only trailing options.
|
||||||
|
if route.kind == routeUnknown || len(route.args) != len(route.canonicalPath) {
|
||||||
session := internalcobra.Prepare(snapshot.metadata, current.options)
|
session := internalcobra.Prepare(snapshot.metadata, current.options)
|
||||||
return current.app.renderHelpOutcomeWithSession(targets, current.streams, session)
|
return current.app.renderHelpOutcomeWithSession(targets, current.streams, session)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,44 @@ func TestRunnableCommandCanHaveAliasOnlyDiscoveryPrefixes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExplicitHelpRequiresAnExactResolvedTarget(t *testing.T) {
|
||||||
|
initialized := false
|
||||||
|
run := NewCommand(CommandSpec[struct{}]{
|
||||||
|
Name: "run", NewInput: func() struct{} { initialized = true; return struct{}{} },
|
||||||
|
Arguments: []Argument[struct{}]{RepeatArgument(ArgumentMeta{Name: "ARG"}, 2, 2,
|
||||||
|
func(value string) (string, error) { return value, nil }, func(*struct{}, Supplied[[]string]) {})},
|
||||||
|
Run: func(Invocation, struct{}) Outcome { t.Error("help ran the command"); return Success() },
|
||||||
|
})
|
||||||
|
app, err := New(Identity{Name: "Demo"}, run, Group("box", "Box", simpleRunnable("show")),
|
||||||
|
Alias("r", "run"), Alias("b", "box"), Alias("list show", "box show"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, args := range [][]string{
|
||||||
|
{"help", "run", "first", "second"}, {"help", "r", "first"},
|
||||||
|
{"help", "r", "first", "second"}, {"help", "r", "first", "second", "third"},
|
||||||
|
} {
|
||||||
|
var output, diagnostics bytes.Buffer
|
||||||
|
outcome := app.Execute(context.Background(), args, Streams{Out: &output, Err: &diagnostics})
|
||||||
|
if outcome.Kind != OutcomeUsage || output.Len() != 0 {
|
||||||
|
t.Errorf("Execute(%q) = %v, output %q, diagnostics %q", args, outcome.Kind, output.String(), diagnostics.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, args := range [][]string{
|
||||||
|
{"help", "run"}, {"help", "r"}, {"help", "box"}, {"help", "b"},
|
||||||
|
{"help", "list"}, {"r", "first", "second", "--help"},
|
||||||
|
} {
|
||||||
|
var output, diagnostics bytes.Buffer
|
||||||
|
outcome := app.Execute(context.Background(), args, Streams{Out: &output, Err: &diagnostics})
|
||||||
|
if outcome.Kind != OutcomeSuccess || !strings.Contains(output.String(), "Usage:") {
|
||||||
|
t.Errorf("Execute(%q) = %v, output %q, diagnostics %q", args, outcome.Kind, output.String(), diagnostics.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if initialized {
|
||||||
|
t.Fatal("help initialized command input")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func routeTestApp(t *testing.T, extra ...Declaration) *App {
|
func routeTestApp(t *testing.T, extra ...Declaration) *App {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
collections := simpleRunnable("collections")
|
collections := simpleRunnable("collections")
|
||||||
|
|||||||
Reference in New Issue
Block a user