feat: 新增 disk usage 内置命令(跨平台 du 等价)
- 注册表新增 Variant.Builtin 内置动作,与 Argv 互斥并参与平台匹配 - disk.usage 支持位置参数与 --path/--path=,输出按大小降序并带合计 - Unix 用设备号实现 -x 不跨文件系统,Windows 降级为不跟随 reparse point - list/help/explain 支持展示内置动作,补充单元与 CLI 测试、README
This commit is contained in:
@@ -44,6 +44,32 @@ func Builtin() *Registry {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "disk.usage",
|
||||
Aliases: []string{"disk.du"},
|
||||
Group: "disk",
|
||||
Summary: "列出目录下各直接子项的占用空间,按大小降序",
|
||||
Action: "等价于 du -x --max-depth=1 -h <路径> | sort -hr:统计每个直接子项(文件或目录递归)的占用," +
|
||||
"不跨越文件系统边界,末尾输出该路径的合计。路径必填,可作位置参数或用 --path 指定;" +
|
||||
"由于 Windows 没有等价的 du/sort,这里使用 kopsc 内置实现以保证三平台输出一致",
|
||||
Variants: []Variant{
|
||||
{
|
||||
Match: Match{OS: "linux"},
|
||||
Builtin: "disk.usage",
|
||||
Note: "Linux 内置实现(等价 du -x -d 1 -h | sort -hr)",
|
||||
},
|
||||
{
|
||||
Match: Match{OS: "darwin"},
|
||||
Builtin: "disk.usage",
|
||||
Note: "macOS 内置实现(BSD du 不支持 --max-depth)",
|
||||
},
|
||||
{
|
||||
Match: Match{OS: "windows"},
|
||||
Builtin: "disk.usage",
|
||||
Note: "Windows 内置实现(系统没有 du/sort)",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "net.ports",
|
||||
Group: "net",
|
||||
|
||||
@@ -22,7 +22,11 @@ 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`
|
||||
// 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".
|
||||
@@ -73,7 +77,13 @@ func (r *Registry) Validate() error {
|
||||
}
|
||||
|
||||
for j, variant := range cmd.Variants {
|
||||
if len(variant.Argv) == 0 || strings.TrimSpace(variant.Argv[0]) == "" {
|
||||
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 {
|
||||
|
||||
@@ -15,7 +15,7 @@ func TestBuiltinIsValid(t *testing.T) {
|
||||
|
||||
func TestBuiltinIDsStable(t *testing.T) {
|
||||
reg := Builtin()
|
||||
want := []string{"docker.clearsafe", "net.ports", "pkg.refreshindex"}
|
||||
want := []string{"docker.clearsafe", "disk.usage", "net.ports", "pkg.refreshindex"}
|
||||
if got := reg.IDs(); !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("IDs() = %v, want %v", got, want)
|
||||
}
|
||||
@@ -132,6 +132,45 @@ func TestValidateErrors(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateBuiltinVariants(t *testing.T) {
|
||||
builtinOnly := plainCommand("a")
|
||||
builtinOnly.Variants = []Variant{{Match: Match{OS: "linux"}, Builtin: "demo.action"}}
|
||||
|
||||
if err := (&Registry{Commands: []Command{builtinOnly}}).Validate(); err != nil {
|
||||
t.Fatalf("builtin-only variant should be valid: %v", err)
|
||||
}
|
||||
|
||||
both := plainCommand("a")
|
||||
both.Variants = []Variant{{Match: Match{OS: "linux"}, Argv: []string{"x"}, Builtin: "demo.action"}}
|
||||
err := (&Registry{Commands: []Command{both}}).Validate()
|
||||
if err == nil || !strings.Contains(err.Error(), "mutually exclusive") {
|
||||
t.Fatalf("err = %v, want mutual-exclusion error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiskUsageIsBuiltinEverywhere(t *testing.T) {
|
||||
reg := Builtin()
|
||||
cmd, ok := reg.Lookup("disk.usage")
|
||||
if !ok {
|
||||
t.Fatal("disk.usage not found")
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, variant := range cmd.Variants {
|
||||
if variant.Builtin != "disk.usage" {
|
||||
t.Errorf("variant for %s should use the disk.usage builtin, got %q", variant.Match.OS, variant.Builtin)
|
||||
}
|
||||
if len(variant.Argv) != 0 {
|
||||
t.Errorf("builtin variant must not carry argv: %v", variant.Argv)
|
||||
}
|
||||
seen[variant.Match.OS] = true
|
||||
}
|
||||
for _, osName := range []string{"linux", "darwin", "windows"} {
|
||||
if !seen[osName] {
|
||||
t.Errorf("disk.usage lacks a %s variant", osName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchIsComparable(t *testing.T) {
|
||||
a := Match{OS: "linux", Distro: "centos", DistroLike: []string{"rhel"}, Runtime: "docker"}
|
||||
b := Match{OS: "linux", Distro: "centos", DistroLike: []string{"rhel"}, Runtime: "docker"}
|
||||
|
||||
Reference in New Issue
Block a user