feat: 跨平台命令助手 kopsc 初始实现
- 意图命令按 OS/架构、Linux 发行版与容器运行时解析并执行对应底层命令 - 内置 docker.clearsafe、net.ports、pkg.refreshindex - --dry-run 预览、list/explain/platform/version 辅助命令 - 纯 Go 跨平台构建脚本 scripts/build
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
// 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]
|
||||
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 || 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
|
||||
}
|
||||
Reference in New Issue
Block a user