Skip to content

Commit 81a0fda

Browse files
inhereclaude
andcommitted
feat(docgen): man Examples 多行保留
man EXAMPLES 改用 .nf/.fi(no-fill)区块逐行输出, 不再用 cleanLine 折叠换行, 多行示例按原样保留。抽出 protectLeading/roffRawLine 辅助函数复用行首控制字符保护。 对比文档移除已解决的 man Examples 单行折叠差距项。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e571b2a commit 81a0fda

3 files changed

Lines changed: 30 additions & 9 deletions

File tree

docgen/docgen_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ func newTestApp() *gcli.App {
2828
Name: "demo",
2929
Desc: "this is a demo command for test",
3030
Help: "long help message for demo command",
31-
Examples: "<cyan>{$fullCmd} --name tom arg0 arg1</> # run the demo",
31+
Examples: "<cyan>{$fullCmd} --name tom arg0 arg1</> # run the demo\n" +
32+
"<cyan>{$fullCmd} --name tom -v arg0</> # verbose run",
3233
Subs: []*gcli.Command{sub},
3334
Config: func(c *gcli.Command) {
3435
var name string
@@ -66,6 +67,8 @@ func TestCmdMarkdown(t *testing.T) {
6667
assert.NotContains(t, md, "<cyan>")
6768
assert.NotContains(t, md, "{$fullCmd}")
6869
assert.StrContains(t, md, "demo --name tom arg0 arg1")
70+
// Examples: 多行保留(代码块内两行示例均在)
71+
assert.StrContains(t, md, "demo --name tom -v arg0")
6972
assert.StrContains(t, md, "## SubCommands")
7073
// 子命令链接文件名应与 tree 命名规则一致
7174
assert.StrContains(t, md, "demo_child.md")
@@ -100,6 +103,11 @@ func TestCmdMan(t *testing.T) {
100103
assert.StrContains(t, man, ".SH EXAMPLES")
101104
// --name 需被转义为 \-\-name
102105
assert.StrContains(t, man, "\\-\\-name")
106+
// Examples 多行保留: 用 .nf/.fi 区块逐行输出, 两行示例均在且未被折叠成一行
107+
assert.StrContains(t, man, ".nf")
108+
assert.StrContains(t, man, ".fi")
109+
assert.StrContains(t, man, "demo \\-\\-name tom arg0 arg1 # run the demo")
110+
assert.StrContains(t, man, "demo \\-\\-name tom \\-v arg0 # verbose run")
103111
}
104112

105113
func TestManTree(t *testing.T) {

docgen/manpage.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,25 @@ func escapeRoff(s string) string {
1717
return s
1818
}
1919

20-
// roffLine 转义并输出一行文本。
21-
// roff 中行首的 `.` 与 `'` 是控制字符, 需用 `\&` 零宽前缀保护。
22-
func roffLine(s string) string {
23-
s = escapeRoff(cleanLine(s))
20+
// protectLeading 保护行首控制字符: roff 中行首的 `.` 与 `'` 是控制字符,
21+
// 需用 `\&` 零宽前缀保护, 否则该行会被当作宏调用。
22+
func protectLeading(s string) string {
2423
if s != "" && (s[0] == '.' || s[0] == '\'') {
25-
s = "\\&" + s
24+
return "\\&" + s
2625
}
2726
return s
2827
}
2928

29+
// roffLine 转义并输出一行文本(折叠内部换行为空格, 用于单行场景)。
30+
func roffLine(s string) string {
31+
return protectLeading(escapeRoff(cleanLine(s)))
32+
}
33+
34+
// roffRawLine 转义单行文本但不折叠换行, 用于 .nf/.fi 区块内逐行输出以保留原始换行。
35+
func roffRawLine(s string) string {
36+
return protectLeading(escapeRoff(strings.TrimRight(s, "\r")))
37+
}
38+
3039
// CmdMan 渲染单个命令为 man page(roff 格式)。
3140
func CmdMan(c *gcli.Command) string {
3241
var buf strings.Builder
@@ -79,10 +88,14 @@ func CmdMan(c *gcli.Command) string {
7988
}
8089
}
8190

82-
// EXAMPLES
91+
// EXAMPLES(用 .nf/.fi 关闭填充并逐行输出, 保留多行原样)
8392
if c.Examples != "" {
8493
buf.WriteString(".SH EXAMPLES\n")
85-
buf.WriteString(roffLine(renderText(c, c.Examples)) + "\n")
94+
buf.WriteString(".nf\n")
95+
for _, line := range strings.Split(strings.Trim(renderText(c, c.Examples), "\n"), "\n") {
96+
buf.WriteString(roffRawLine(line) + "\n")
97+
}
98+
buf.WriteString(".fi\n")
8699
}
87100

88101
return buf.String()

docs/compare-with-others.zh-CN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
1. **采用度/生态**(主要差距):cobra 体量碾压,插件、教程、招聘熟悉度都更高——这是短期难追的项。
7878
2. **POSIX 默认性**:cobra+pflag 的 GNU 行为「默认即标准」;gcli 不少 POSIX 特性是 opt-in(EnhanceShort)。
7979
3. **补全 shell 覆盖**:gcli 支持 bash/zsh/pwsh(含动态),暂无 fish(cobra/urfave 有)。
80-
4. **细节**man 文档的 Examples 暂折叠为单行;kong 的自定义类型映射器机制更完备。
80+
4. **细节**:kong 的自定义类型映射器机制更完备。
8181

8282
**不适合**:深度依赖社区生态/标准 POSIX 默认行为、或需要最大社区背书的项目 → cobra 更稳。强类型纯声明式解析(无需周边)→ kong 更轻。
8383

0 commit comments

Comments
 (0)