Files
kenaito-kopsc/internal/registry/registry.go
T
odboy 84f7361396 feat: 新增 disk usage 内置命令(跨平台 du 等价)
- 注册表新增 Variant.Builtin 内置动作,与 Argv 互斥并参与平台匹配
- disk.usage 支持位置参数与 --path/--path=,输出按大小降序并带合计
- Unix 用设备号实现 -x 不跨文件系统,Windows 降级为不跟随 reparse point
- list/help/explain 支持展示内置动作,补充单元与 CLI 测试、README
2026-10-31 13:11:24 +08:00

131 lines
3.9 KiB
Go

// Package registry holds kopsc's built-in command registry: platform-neutral
// intents, each with one or more platform-specific variants.
package registry
import (
"fmt"
"strings"
)
// Match constrains a Variant to environments. An empty field means "any".
// A Variant with a completely empty Match is a generic fallback.
type Match struct {
OS string // runtime.GOOS
Arch string // runtime.GOARCH
Distro string // exact os-release ID, e.g. "centos"
DistroLike []string // os-release ID_LIKE ancestry, e.g. "rhel"
Runtime string // container runtime required on PATH, e.g. "podman"
}
// Variant is one concrete implementation of a command.
type Variant struct {
Match Match
Argv []string // program followed by arguments
Shell bool // run through the platform shell instead of exec'ing Argv[0]
// Builtin names an in-process implementation registered with the executor.
// It is mutually exclusive with Argv and lets kopsc provide commands that
// have no equivalent system utility (for example on Windows).
Builtin string
Note string // human-readable rationale shown by `kopsc explain`
}
// Command is a platform-neutral intent, e.g. "docker.clearsafe".
type Command struct {
ID string // hierarchical, dot-separated: "docker.clearsafe"
Aliases []string
Summary string
Group string // CLI grouping, e.g. "docker"
Action string // what the command does, including its safety contract
Variants []Variant
}
// Registry is an ordered set of commands.
type Registry struct {
Commands []Command
}
// Validate reports structural problems: empty or duplicate identifiers,
// empty aliases, commands without variants and variants without an argv.
func (r *Registry) Validate() error {
seenIDs := make(map[string]bool)
seenAliases := make(map[string]string)
for i, cmd := range r.Commands {
if strings.TrimSpace(cmd.ID) == "" {
return fmt.Errorf("command #%d: empty ID", i)
}
if seenIDs[cmd.ID] {
return fmt.Errorf("command #%d: duplicate ID %q", i, cmd.ID)
}
seenIDs[cmd.ID] = true
if strings.TrimSpace(cmd.Summary) == "" {
return fmt.Errorf("command %q: empty summary", cmd.ID)
}
if len(cmd.Variants) == 0 {
return fmt.Errorf("command %q: no variants", cmd.ID)
}
for _, alias := range cmd.Aliases {
if strings.TrimSpace(alias) == "" {
return fmt.Errorf("command %q: empty alias", cmd.ID)
}
if owner, ok := seenAliases[alias]; ok {
return fmt.Errorf("command %q: alias %q already used by %q", cmd.ID, alias, owner)
}
seenAliases[alias] = cmd.ID
}
for j, variant := range cmd.Variants {
if len(variant.Argv) == 0 && variant.Builtin == "" {
return fmt.Errorf("command %q variant #%d: empty argv and no builtin", cmd.ID, j)
}
if len(variant.Argv) > 0 && variant.Builtin != "" {
return fmt.Errorf("command %q variant #%d: argv and builtin are mutually exclusive", cmd.ID, j)
}
if len(variant.Argv) > 0 && strings.TrimSpace(variant.Argv[0]) == "" {
return fmt.Errorf("command %q variant #%d: empty argv", cmd.ID, j)
}
if err := variant.Match.validate(); err != nil {
return fmt.Errorf("command %q variant #%d: %w", cmd.ID, j, err)
}
}
}
return nil
}
func (m Match) validate() error {
for _, field := range m.DistroLike {
if strings.TrimSpace(field) == "" {
return fmt.Errorf("empty DistroLike entry")
}
}
return nil
}
// Lookup finds a command by ID or alias.
func (r *Registry) Lookup(idOrAlias string) (*Command, bool) {
for i := range r.Commands {
cmd := &r.Commands[i]
if cmd.ID == idOrAlias {
return cmd, true
}
for _, alias := range cmd.Aliases {
if alias == idOrAlias {
return cmd, true
}
}
}
return nil, false
}
// IDs returns every command ID in registry order.
func (r *Registry) IDs() []string {
ids := make([]string, 0, len(r.Commands))
for _, cmd := range r.Commands {
ids = append(ids, cmd.ID)
}
return ids
}