Document application-owned alias contracts

This commit is contained in:
2026-10-19 19:59:41 +00:00
parent 666c006a89
commit 333a630cf1
15 changed files with 270 additions and 64 deletions

View File

@@ -10,21 +10,32 @@ import (
"gitea.maximumdirect.net/eric/CommandKit/internal/commandpath"
)
// Command is an opaque, reusable declaration captured by NewCommand, Group,
// RootGroup, or an integration. Capture copies declaration slices but retains
// callback and caller-owned object references. Installation validates the
// declaration before changing an App registry.
// Command is an opaque, reusable declaration of canonical commands and groups.
// NewCommand, Group, RootGroup, and integration command factories return
// Command values. Capture copies declaration slices but retains callback and
// caller-owned object references. Installation validates the declaration
// before changing an App registry.
type Command struct{ record *commandRecord }
// Declaration is a captured command or application routing rule accepted by
// New and App.Register. Only CommandKit declaration types implement it.
// Declaration is a captured canonical command or application routing rule
// accepted by New and App.Register. Use []Declaration when collecting both
// kinds. Group and RootGroup children remain Commands; install routing rules
// alongside the completed command tree through New or Register. Only
// CommandKit declaration types implement Declaration.
//
// A zero Command, zero RoutingRule, nil declaration, or typed nil declaration
// pointer is invalid. Use NoCommand to represent an intentionally absent
// optional command.
type Declaration interface {
commandkitDeclaration()
}
func (Command) commandkitDeclaration() {}
// RoutingRule is an opaque, reusable command or flag alias declaration.
// RoutingRule is an opaque, reusable command or flag alias declaration. Alias,
// FlagAlias, and FlagAliasFor capture routing rules eagerly and retain any
// construction issues for New or App.Register to report. Identical duplicate
// mappings are accepted; competing targets for the same source are errors.
type RoutingRule struct{ record *routingRuleRecord }
func (RoutingRule) commandkitDeclaration() {}
@@ -236,9 +247,11 @@ func (c typedCommand[I]) prepare(flagValues, flagSpellings [][]string, operands
}, nil
}
// NewCommand captures a typed command declaration without constructing its
// input or running callbacks. Construction issues are retained and reported
// when the declaration is installed. The returned declaration can be reused.
// NewCommand captures a typed canonical command declaration without
// constructing its input or running callbacks. It copies the spec's flags,
// arguments, and children slices. Construction issues are retained and
// reported when the declaration is installed; NewCommand itself does not
// return an error. The returned declaration can be reused across Apps.
func NewCommand[I any](spec CommandSpec[I]) Command {
return captureCommand(spec, nil)
}
@@ -260,25 +273,29 @@ func captureCommand[I any](spec CommandSpec[I], privateRun func(commandExecution
return Command{record: record}
}
// Group captures a non-runnable command group and a copy of its relative child
// slice. Child declarations remain reusable and are not recursively copied.
// Group captures a non-runnable canonical command group and a copy of its
// relative Command child slice. Child declarations remain reusable and are not
// recursively copied. Routing rules cannot be children; install them alongside
// the root command tree through New or App.Register.
func Group(name, summary string, children ...Command) Command {
record := &commandRecord{name: name, summary: summary, children: append([]Command(nil), children...)}
captureComposition(record)
return Command{record: record}
}
// RootGroup captures a group that may only be installed at the application root.
// Installation rejects it below any other command, including through wrappers.
// RootGroup captures a group that may only be installed at the application
// root. Installation rejects it below any other command, including through
// wrappers. Its children are canonical Commands; install routing rules at the
// application root separately.
func RootGroup(name, summary string, children ...Command) Command {
command := Group(name, summary, children...)
command.record.rootOnly = true
return command
}
// WithError returns a new declaration that also retains a non-nil err for
// installation. Earlier declaration and child errors remain available, and
// wrapped error identity is preserved. A nil err returns command unchanged.
// WithError returns a new Command declaration that also retains a non-nil err
// for installation. Earlier declaration and child errors remain available,
// and wrapped error identity is preserved. A nil err returns command unchanged.
func WithError(command Command, err error) Command {
if err == nil {
return command
@@ -294,14 +311,17 @@ func WithError(command Command, err error) Command {
return Command{record: &record}
}
// NoCommand explicitly represents an absent optional declaration. Installation
// skips it, but reports errors attached to it; a zero Command is invalid and is
// not an absence marker.
// NoCommand explicitly represents an absent optional command declaration.
// Installation skips it, but reports errors attached to it; a zero Command is
// invalid and is not an absence marker. Routing rules have no absence marker.
func NoCommand() Command { return Command{record: &commandRecord{absent: true}} }
// Alias captures a command-path alias from path to target. Paths are
// whitespace-separated sequences of command names; construction issues are
// retained for New or App.Register to report.
// whitespace-separated sequences of command names relative to the application
// root. The target must resolve to a canonical command or group; aliases do not
// add operands or transform flag values. Construction issues are retained for
// New or App.Register to report. Identical mappings are accepted, while
// different targets for the same source path are installation errors.
func Alias(path, target string) RoutingRule {
record := &routingRuleRecord{
kind: commandAliasRule,
@@ -314,7 +334,10 @@ func Alias(path, target string) RoutingRule {
}
// FlagAlias captures an application-wide flag spelling alias. Spellings must
// be one long flag name or one individual short spelling.
// be one long flag name or one individual short spelling. The mapping applies
// wherever its canonical target flag is available and contributes nothing on
// commands without that flag. Identical mappings are accepted; different
// targets for the same source spelling in this scope are installation errors.
func FlagAlias(spelling, target string) RoutingRule {
record := &routingRuleRecord{
kind: globalFlagAliasRule,
@@ -327,7 +350,10 @@ func FlagAlias(spelling, target string) RoutingRule {
}
// FlagAliasFor captures a flag spelling alias scoped to a canonical command
// path.
// path, not an alias path. The command and canonical target flag must be
// available in the complete installation. Scoped mappings take precedence over
// application-wide mappings with the same source spelling. Identical mappings
// are accepted; competing targets in the same scope are installation errors.
func FlagAliasFor(commandPath, spelling, target string) RoutingRule {
record := &routingRuleRecord{
kind: scopedFlagAliasRule,