Remove unused parser resource plumbing

This commit is contained in:
2026-11-07 15:51:19 +00:00
parent f2822f055c
commit 51a147a6e5
10 changed files with 32 additions and 73 deletions

View File

@@ -76,6 +76,8 @@ rendering. A session owns the mutable backend tree for one invocation; parsing
and presentation reuse that session. The adapter returns raw values and neutral and presentation reuse that session. The adapter returns raw values and neutral
syntax failures to core. It does not own the registry, typed input preparation, syntax failures to core. It does not own the registry, typed input preparation,
integration declarations, provider acquisition, or native report rendering. integration declarations, provider acquisition, or native report rendering.
Parser options carry flag-alias selection; invocation context and streams stay
with core, while backend output destinations discard and help uses a local buffer.
Cobra and pflag imports remain inside this package. Cobra and pflag imports remain inside this package.
## Optional modules ## Optional modules

View File

@@ -1,6 +1,6 @@
# Consolidated audit catalog # Consolidated audit catalog
Status: **14 distinct open findings: 14 P3**, consolidated from 30 Status: **13 distinct open findings: 13 P3**, consolidated from 30
numbered entries in seven audit documents. Reviewed audit state: numbered entries in seven audit documents. Reviewed audit state:
`db732e3a1de55c844f77870e1c6db683c2685d5d`. `db732e3a1de55c844f77870e1c6db683c2685d5d`.
@@ -257,6 +257,10 @@ consumer compatibility or migration work is needed.
## F12 — P3: Parser sessions carry unused execution resources ## F12 — P3: Parser sessions carry unused execution resources
**Complete.** Unused execution and parser resource plumbing was removed; the
backend retains discard destinations and buffered help. Focused routing, help,
context-forwarding and session-isolation tests passed.
Core and the Cobra adapter duplicate stream normalization and retain context and Core and the Cobra adapter duplicate stream normalization and retain context and
streams through parser options and backend nodes, although the adapter parses streams through parser options and backend nodes, although the adapter parses
and renders buffered help rather than executing callbacks or reading application and renders buffered help rather than executing callbacks or reading application

View File

@@ -1,6 +1,6 @@
# Audit remediation implementation plan # Audit remediation implementation plan
Status: in progress; Stages 1–12 complete. Status: in progress; Stages 1–13 complete.
Baseline: `d492c7747175c01a53261206c2b4356e53453621`. Before this sprint, Baseline: `d492c7747175c01a53261206c2b4356e53453621`. Before this sprint,
intervening changes recorded decisions and retired the completed older intervening changes recorded decisions and retired the completed older
@@ -442,6 +442,12 @@ Complete F21 when both callers share one adapter and preserve their context.
## Stage 13 — Remove unused parser resource plumbing ## Stage 13 — Remove unused parser resource plumbing
**Complete.** Parser options carry only alias selection, backend output and
error destinations explicitly discard, and core retains invocation resources.
The test-only App.parse wrapper was removed. Focused routed parsing, help,
context, backend parsing and session-isolation tests passed in the offline aligned
Go 1.27.0 workspace with `-mod=readonly`. F24 remains open for binding wrappers.
Findings: F12 and parsing portion of F24. Sources: `execute.go`, `parser.go`, Findings: F12 and parsing portion of F24. Sources: `execute.go`, `parser.go`,
`internal/commandmeta/metadata.go`, and `internal/cobra/parse.go`. `internal/commandmeta/metadata.go`, and `internal/cobra/parse.go`.

View File

@@ -80,10 +80,8 @@ func (a *App) Execute(ctx context.Context, args []string, streams Streams) Outco
type execution struct { type execution struct {
app *App app *App
ctx context.Context ctx context.Context
streams Streams
reporter *Reporter reporter *Reporter
debugEvents []string debugEvents []string
options commandmeta.ParseOptions
snapshot *routingSnapshot snapshot *routingSnapshot
session *internalcobra.Session session *internalcobra.Session
route routeResult route routeResult
@@ -116,13 +114,10 @@ func (current commandExecution) renderHelpTargets(targets []string) Outcome {
} }
func newExecution(app *App, ctx context.Context, streams Streams) *execution { func newExecution(app *App, ctx context.Context, streams Streams) *execution {
streams = normalizeStreams(streams)
current := &execution{ current := &execution{
app: app, app: app,
ctx: ctx, ctx: ctx,
streams: streams,
reporter: newReporter(streams), reporter: newReporter(streams),
options: commandmeta.ParseOptions{Context: ctx, Out: streams.Out, Err: streams.Err},
} }
if app != nil { if app != nil {
current.snapshot = app.routeSnapshot() current.snapshot = app.routeSnapshot()
@@ -147,7 +142,7 @@ func (current *execution) prepareSession() {
if current.snapshot == nil { if current.snapshot == nil {
current.snapshot = current.app.routeSnapshot() current.snapshot = current.app.routeSnapshot()
} }
options := current.options options := commandmeta.ParseOptions{}
if current.snapshot != nil { if current.snapshot != nil {
switch current.route.kind { switch current.route.kind {
case routeCanonical: case routeCanonical:
@@ -201,11 +196,11 @@ func (current *execution) renderHelpTargets(targets []string) Outcome {
// Explicit help targets are paths, not command invocations with operands. // Explicit help targets are paths, not command invocations with operands.
// Discovery routes have no canonical path and retain only trailing options. // Discovery routes have no canonical path and retain only trailing options.
if route.kind == routeUnknown || len(route.args) != len(route.canonicalPath) { if route.kind == routeUnknown || len(route.args) != len(route.canonicalPath) {
session := internalcobra.Prepare(snapshot.metadata, current.options) session := internalcobra.Prepare(snapshot.metadata, commandmeta.ParseOptions{})
view := commandmeta.HelpView{EnteredPath: targets, CanonicalPath: targets} view := commandmeta.HelpView{EnteredPath: targets, CanonicalPath: targets}
return current.app.renderHelpViewOutcomeWithSession(view, current.reporter, session) return current.app.renderHelpViewOutcomeWithSession(view, current.reporter, session)
} }
options := current.options options := commandmeta.ParseOptions{}
switch route.kind { switch route.kind {
case routeCanonical: case routeCanonical:
options.FlagAliases = snapshot.flagAliases[route.canonicalPath.Key()].neutralRecords() options.FlagAliases = snapshot.flagAliases[route.canonicalPath.Key()].neutralRecords()

View File

@@ -4,7 +4,6 @@ package cobra
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"io" "io"
"strconv" "strconv"
@@ -35,8 +34,7 @@ func Prepare(root *commandmeta.Command, options commandmeta.ParseOptions) *Sessi
session.metadata = root session.metadata = root
session.flagAliases = append([]commandmeta.FlagAlias(nil), options.FlagAliases...) session.flagAliases = append([]commandmeta.FlagAlias(nil), options.FlagAliases...)
session.flagAliasesForPath = options.FlagAliasesForPath session.flagAliasesForPath = options.FlagAliasesForPath
options = normalizeOptions(options) session.root, session.declarations = buildTree(root)
session.root, session.declarations = buildTree(root, options)
return session return session
} }
@@ -289,23 +287,7 @@ func Parse(root *commandmeta.Command, argv []string, options commandmeta.ParseOp
return Prepare(root, options).Parse(argv) return Prepare(root, options).Parse(argv)
} }
func normalizeOptions(options commandmeta.ParseOptions) commandmeta.ParseOptions { func buildTree(root *commandmeta.Command) (*spfcobra.Command, map[*spfcobra.Command]*commandmeta.Command) {
if options.Context == nil {
options.Context = context.Background()
}
if options.In == nil {
options.In = strings.NewReader("")
}
if options.Out == nil {
options.Out = io.Discard
}
if options.Err == nil {
options.Err = io.Discard
}
return options
}
func buildTree(root *commandmeta.Command, options commandmeta.ParseOptions) (*spfcobra.Command, map[*spfcobra.Command]*commandmeta.Command) {
nodes := make(map[*spfcobra.Command]*commandmeta.Command) nodes := make(map[*spfcobra.Command]*commandmeta.Command)
var build func(*commandmeta.Command) *spfcobra.Command var build func(*commandmeta.Command) *spfcobra.Command
build = func(declaration *commandmeta.Command) *spfcobra.Command { build = func(declaration *commandmeta.Command) *spfcobra.Command {
@@ -321,10 +303,8 @@ func buildTree(root *commandmeta.Command, options commandmeta.ParseOptions) (*sp
if declaration.Runnable { if declaration.Runnable {
backend.Run = func(*spfcobra.Command, []string) {} backend.Run = func(*spfcobra.Command, []string) {}
} }
backend.SetContext(options.Context) backend.SetOut(io.Discard)
backend.SetIn(options.In) backend.SetErr(io.Discard)
backend.SetOut(options.Out)
backend.SetErr(options.Err)
backend.SetHelpFunc(func(*spfcobra.Command, []string) {}) backend.SetHelpFunc(func(*spfcobra.Command, []string) {})
backend.SetFlagErrorFunc(func(_ *spfcobra.Command, err error) error { return err }) backend.SetFlagErrorFunc(func(_ *spfcobra.Command, err error) error { return err })
backend.SetUsageTemplate(usageTemplate) backend.SetUsageTemplate(usageTemplate)

View File

@@ -1,8 +1,6 @@
package cobra package cobra
import ( import (
"bytes"
"context"
"fmt" "fmt"
"io" "io"
"reflect" "reflect"
@@ -182,7 +180,7 @@ func TestParseReturnsNeutralFailuresAndFreshState(t *testing.T) {
tree := &Command{Name: "app", Children: []*Command{ tree := &Command{Name: "app", Children: []*Command{
{Name: "run", Runnable: true, Flags: []Flag{{Name: "value"}}}, {Name: "run", Runnable: true, Flags: []Flag{{Name: "value"}}},
}} }}
options := ParseOptions{Context: context.Background(), In: strings.NewReader("unused"), Out: io.Discard, Err: io.Discard} options := ParseOptions{}
first := Parse(tree, []string{"run", "--value=old"}, options) first := Parse(tree, []string{"run", "--value=old"}, options)
second := Parse(tree, []string{"run"}, options) second := Parse(tree, []string{"run"}, options)
if first.Failure != nil || second.Failure != nil { if first.Failure != nil || second.Failure != nil {
@@ -247,8 +245,7 @@ func TestSelectedPathsResolveThroughBackendParents(t *testing.T) {
func TestSessionReusesTreeForParseHelpAndUsageWithFreshState(t *testing.T) { func TestSessionReusesTreeForParseHelpAndUsageWithFreshState(t *testing.T) {
tree := &Command{Name: "app", Children: []*Command{{Name: "run", Runnable: true, Flags: []Flag{{Name: "value"}}}}} tree := &Command{Name: "app", Children: []*Command{{Name: "run", Runnable: true, Flags: []Flag{{Name: "value"}}}}}
var output, errors bytes.Buffer session := Prepare(tree, ParseOptions{})
session := Prepare(tree, ParseOptions{Context: context.Background(), Out: &output, Err: &errors})
first := session.Parse([]string{"run", "--value=old"}) first := session.Parse([]string{"run", "--value=old"})
if first.Failure != nil || !reflect.DeepEqual(first.FlagValues, [][]string{{"old"}}) { if first.Failure != nil || !reflect.DeepEqual(first.FlagValues, [][]string{{"old"}}) {
t.Fatalf("first session parse = %#v", first) t.Fatalf("first session parse = %#v", first)
@@ -261,19 +258,15 @@ func TestSessionReusesTreeForParseHelpAndUsageWithFreshState(t *testing.T) {
if failure != nil || !strings.Contains(line, "run") { if failure != nil || !strings.Contains(line, "run") {
t.Fatalf("session usage = %q, %v", line, failure) t.Fatalf("session usage = %q, %v", line, failure)
} }
if output.Len() != 0 || errors.Len() != 0 { if session.root.OutOrStdout() != io.Discard || session.root.ErrOrStderr() != io.Discard {
t.Fatalf("help escaped session delivery control: out=%q err=%q", output.String(), errors.String()) t.Fatal("backend destinations are not discarded")
} }
var secondOutput, secondErrors bytes.Buffer second := Prepare(tree, ParseOptions{})
second := Prepare(tree, ParseOptions{Context: context.Background(), Out: &secondOutput, Err: &secondErrors})
secondResult := second.Parse([]string{"run"}) secondResult := second.Parse([]string{"run"})
if secondResult.Failure != nil || !reflect.DeepEqual(secondResult.FlagValues, [][]string{nil}) { if secondResult.Failure != nil || !reflect.DeepEqual(secondResult.FlagValues, [][]string{nil}) {
t.Fatalf("fresh session state = %#v", secondResult) t.Fatalf("fresh session state = %#v", secondResult)
} }
if output.Len() != 0 || errors.Len() != 0 || secondOutput.Len() != 0 || secondErrors.Len() != 0 {
t.Fatal("prepared session streams were unexpectedly used")
}
} }
func TestMissingSessionTreeReturnsNeutralFailures(t *testing.T) { func TestMissingSessionTreeReturnsNeutralFailures(t *testing.T) {

View File

@@ -1,9 +1,7 @@
package cobra package cobra
import ( import (
"context"
"fmt" "fmt"
"io"
"testing" "testing"
) )
@@ -11,7 +9,7 @@ func BenchmarkPerformanceParse(b *testing.B) {
wide := performanceFlatTree(128) wide := performanceFlatTree(128)
wideArgs := []string{"wide127"} wideArgs := []string{"wide127"}
deep, deepArgs := performanceNestedTree(24) deep, deepArgs := performanceNestedTree(24)
options := ParseOptions{Context: context.Background(), Out: io.Discard, Err: io.Discard} options := ParseOptions{}
for _, benchmark := range []struct { for _, benchmark := range []struct {
name string name string
@@ -41,7 +39,7 @@ func BenchmarkPerformanceParse(b *testing.B) {
func BenchmarkPerformanceSessionParseAndHelp(b *testing.B) { func BenchmarkPerformanceSessionParseAndHelp(b *testing.B) {
root := performanceFlatTree(8) root := performanceFlatTree(8)
args := []string{"wide007"} args := []string{"wide007"}
session := Prepare(root, ParseOptions{Context: context.Background(), Out: io.Discard, Err: io.Discard}) session := Prepare(root, ParseOptions{})
result := session.Parse(args) result := session.Parse(args)
if result.Failure != nil { if result.Failure != nil {
b.Fatalf("fixture parse failed: %s", result.Failure.Message) b.Fatalf("fixture parse failed: %s", result.Failure.Message)

View File

@@ -2,11 +2,6 @@
// its command-line backend. // its command-line backend.
package commandmeta package commandmeta
import (
"context"
"io"
)
// Key identifies one command inside one routing snapshot. // Key identifies one command inside one routing snapshot.
type Key uint64 type Key uint64
@@ -181,14 +176,10 @@ type Result struct {
Failure *Failure Failure *Failure
} }
// ParseOptions makes parser context and streams explicit. // ParseOptions selects flag aliases for parser contexts.
type ParseOptions struct { type ParseOptions struct {
// FlagAliasesForPath selects aliases for the actual parser context when // FlagAliasesForPath selects aliases for the actual parser context when
// routing has not already selected one. It overrides FlagAliases when set. // routing has not already selected one. It overrides FlagAliases when set.
FlagAliasesForPath func([]string) []FlagAlias FlagAliasesForPath func([]string) []FlagAlias
Context context.Context
In io.Reader
Out io.Writer
Err io.Writer
FlagAliases []FlagAlias FlagAliases []FlagAlias
} }

View File

@@ -5,19 +5,10 @@ import (
"sort" "sort"
"strconv" "strconv"
internalcobra "go.maximumdirect.net/commandkit/internal/cobra"
commandmeta "go.maximumdirect.net/commandkit/internal/commandmeta" commandmeta "go.maximumdirect.net/commandkit/internal/commandmeta"
"go.maximumdirect.net/commandkit/internal/commandpath" "go.maximumdirect.net/commandkit/internal/commandpath"
) )
func (a *App) parse(args []string, options commandmeta.ParseOptions) commandmeta.Result {
if a == nil {
return commandmeta.Result{Failure: &commandmeta.Failure{Message: "cannot parse commands on a nil app"}}
}
result := internalcobra.Parse(a.routeSnapshot().metadata, args, options)
return reduceReportingOccurrences(result)
}
func reduceReportingOccurrences(result commandmeta.Result) commandmeta.Result { func reduceReportingOccurrences(result commandmeta.Result) commandmeta.Result {
selection := commandmeta.ReportingSelection{} selection := commandmeta.ReportingSelection{}
for index, occurrence := range result.FlagOccurrences { for index, occurrence := range result.FlagOccurrences {

View File

@@ -34,15 +34,14 @@ func TestAppParseUsesRegisteredBindingsWithTheAdapter(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
options := commandmeta.ParseOptions{Context: context.Background()} result := parseReportingArgs(app, []string{"config", "show", "one", "--scope", "app"})
result := app.parse([]string{"config", "show", "one", "--scope", "app"}, options)
if result.Failure != nil { if result.Failure != nil {
t.Fatalf("parse failed: %s", result.Failure.Message) t.Fatalf("parse failed: %s", result.Failure.Message)
} }
if !reflect.DeepEqual(result.Path, []string{"config", "show"}) || !reflect.DeepEqual(result.FlagValues, [][]string{{"app"}}) || !reflect.DeepEqual(result.Operands, []string{"one"}) { if !reflect.DeepEqual(result.Path, []string{"config", "show"}) || !reflect.DeepEqual(result.FlagValues, [][]string{{"app"}}) || !reflect.DeepEqual(result.Operands, []string{"one"}) {
t.Fatalf("parse result = %#v", result) t.Fatalf("parse result = %#v", result)
} }
result = app.parse([]string{"config", "--scope", "app", "show"}, options) result = parseReportingArgs(app, []string{"config", "--scope", "app", "show"})
if result.Failure == nil { if result.Failure == nil {
t.Fatal("accepted a child flag before the complete command path") t.Fatal("accepted a child flag before the complete command path")
} }
@@ -55,7 +54,7 @@ func TestRoutingSnapshotPairsParsedKeyWithItsRunner(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
snapshot := app.routeSnapshot() snapshot := app.routeSnapshot()
session := internalcobra.Prepare(snapshot.metadata, commandmeta.ParseOptions{Context: context.Background()}) session := internalcobra.Prepare(snapshot.metadata, commandmeta.ParseOptions{})
parsed := session.Parse([]string{"run"}) parsed := session.Parse([]string{"run"})
if parsed.Failure != nil { if parsed.Failure != nil {
t.Fatalf("parse failed: %s", parsed.Failure.Message) t.Fatalf("parse failed: %s", parsed.Failure.Message)