feat: 补全 explain 的说明与输出

- 帮助补齐:解释变体机制、命令名两种写法(点号/空格)、别名与分组名、
  输出各项含义、打分规则、退出码,并给出示例
- 支持 `kopsc explain docker clearsafe` 空格写法;未知命令改为列出全部
  可用命令并提示写法,不再只说「未知命令」
- 单命令输出补上别名、用法、破坏性提示;候选列表分「执行/说明」两行
- 新增分组模式 `kopsc explain net`,表格汇总该组各命令的匹配结果
- list 与分组表格改用显示宽度对齐,修正中文表头错位
This commit is contained in:
2026-11-01 08:48:16 +08:00
parent e244c23b52
commit 905cc6a102
3 changed files with 297 additions and 41 deletions
+106 -3
View File
@@ -166,13 +166,17 @@ func TestExplainUnavailableStillListsCandidates(t *testing.T) {
}
func TestExplainUnknownCommand(t *testing.T) {
app, _, stderr := testApp(centos("docker"))
app, stdout, _ := testApp(centos("docker"))
if code := app.Execute([]string{"explain", "nope"}); code != 1 {
t.Fatalf("exit code = %d, want 1", code)
}
if !strings.Contains(stderr.String(), "未知命令") {
t.Errorf("stderr = %s", stderr.String())
out := stdout.String()
// The error lists what does exist instead of only saying "unknown".
for _, want := range []string{`没有名为 "nope" 的命令`, "可用的命令:", "docker.clearsafe", "docker clearsafe"} {
if !strings.Contains(out, want) {
t.Errorf("explain output missing %q:\n%s", want, out)
}
}
}
@@ -800,3 +804,102 @@ func TestGroupHelpIsChinese(t *testing.T) {
t.Errorf("group help still says \"help for\":\n%s", out)
}
}
func TestExplainAcceptsSpacedAndDottedForms(t *testing.T) {
for _, args := range [][]string{
{"explain", "docker.clearsafe"},
{"explain", "docker", "clearsafe"},
{"explain", "docker.prunesafe"}, // alias
} {
app, stdout, stderr := testApp(centos("docker"))
if code := app.Execute(args); code != 0 {
t.Fatalf("args %v: exit code = %d, stderr=%s", args, code, stderr.String())
}
out := stdout.String()
for _, want := range []string{
"命令: docker.clearsafe",
"别名: docker.prunesafe",
"平台:",
"结果:",
"候选:",
"执行: docker system prune -f",
} {
if !strings.Contains(out, want) {
t.Errorf("args %v: output missing %q:\n%s", args, want, out)
}
}
}
}
func TestExplainShowsUsageOnlyWhenDeclared(t *testing.T) {
// net.pids declares <端口>, so the usage line must appear.
app, stdout, _ := testApp(centos("docker"))
if code := app.Execute([]string{"explain", "net.pids"}); code != 0 {
t.Fatalf("exit code = %d", code)
}
if !strings.Contains(stdout.String(), "用法: kopsc net pids <端口>") {
t.Errorf("usage line missing:\n%s", stdout.String())
}
// docker.clearsafe takes no arguments, so it has no usage line.
app, stdout, _ = testApp(centos("docker"))
if code := app.Execute([]string{"explain", "docker.clearsafe"}); code != 0 {
t.Fatalf("exit code = %d", code)
}
if strings.Contains(stdout.String(), "用法:") {
t.Errorf("unexpected usage line:\n%s", stdout.String())
}
}
func TestExplainMentionsYesForDestructiveCommand(t *testing.T) {
app, stdout, _ := testApp(centos("docker"))
if code := app.Execute([]string{"explain", "docker.clearall"}); code != 0 {
t.Fatalf("exit code = %d", code)
}
if !strings.Contains(stdout.String(), "破坏性命令") {
t.Errorf("destructive hint missing:\n%s", stdout.String())
}
}
func TestExplainGroupSummarisesCommands(t *testing.T) {
app, stdout, _ := testApp(centos("docker"))
if code := app.Execute([]string{"explain", "net"}); code != 0 {
t.Fatalf("exit code = %d", code)
}
out := stdout.String()
for _, want := range []string{`分组 "net"`, "net.pids", "net.ports", "命令", "状态", "将执行", "说明"} {
if !strings.Contains(out, want) {
t.Errorf("group output missing %q:\n%s", want, out)
}
}
}
func TestExplainRequiresCommandName(t *testing.T) {
app, _, stderr := testApp(centos("docker"))
if code := app.Execute([]string{"explain"}); code == 0 {
t.Fatal("expected a non-zero exit code without a command name")
}
if !strings.Contains(stderr.String(), "缺少命令名") {
t.Errorf("stderr = %s", stderr.String())
}
}
func TestExplainHelpDocumentsWaysAndExitCodes(t *testing.T) {
app, stdout, _ := testApp(centos("docker"))
if code := app.Execute([]string{"explain", "--help"}); code != 0 {
t.Fatalf("exit code = %d", code)
}
out := stdout.String()
for _, want := range []string{
"docker.clearsafe", // dotted form
"docker clearsafe", // spaced form
"别名", // aliases are accepted
"退出码", // exit codes documented
"得分", // scoring explained
"kopsc explain docker cle", // an example
} {
if !strings.Contains(out, want) {
t.Errorf("explain --help missing %q:\n%s", want, out)
}
}
}