From 4e108ef9cb780b04eb1026df83418121cdea6968 Mon Sep 17 00:00:00 2001 From: Rodion Steshenko Date: Wed, 18 Feb 2026 04:07:31 -0500 Subject: [PATCH] fix: escape angle brackets in man page output (#2330) Angle brackets in command Usage, Short, Long, and flag Usage strings were stripped by md2man which interpreted them as HTML tags. This caused placeholders like [:] to render as [:] in man pages. Fix: escape < and > with backslashes before passing to md2man. Added TestGenManAngleBracketsPreserved to verify the fix. --- doc/man_docs.go | 17 +++++++++++++---- doc/man_docs_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/doc/man_docs.go b/doc/man_docs.go index 560bc20c7..f06e8e757 100644 --- a/doc/man_docs.go +++ b/doc/man_docs.go @@ -140,6 +140,15 @@ func fillHeader(header *GenManHeader, name string, disableAutoGen bool) error { return nil } +// escapeAngleBrackets escapes angle brackets so they are not stripped by md2man +// which interprets them as HTML tags. This is necessary for command usage strings +// that contain placeholders like [:]. +func escapeAngleBrackets(s string) string { + s = strings.ReplaceAll(s, "<", `\<`) + s = strings.ReplaceAll(s, ">", `\>`) + return s +} + func manPreamble(buf io.StringWriter, header *GenManHeader, cmd *cobra.Command, dashedName string) { description := cmd.Long if len(description) == 0 { @@ -149,11 +158,11 @@ func manPreamble(buf io.StringWriter, header *GenManHeader, cmd *cobra.Command, cobra.WriteStringAndCheck(buf, fmt.Sprintf(`%% "%s" "%s" "%s" "%s" "%s" # NAME `, header.Title, header.Section, header.date, header.Source, header.Manual)) - cobra.WriteStringAndCheck(buf, fmt.Sprintf("%s \\- %s\n\n", dashedName, cmd.Short)) + cobra.WriteStringAndCheck(buf, fmt.Sprintf("%s \\- %s\n\n", dashedName, escapeAngleBrackets(cmd.Short))) cobra.WriteStringAndCheck(buf, "# SYNOPSIS\n") - cobra.WriteStringAndCheck(buf, fmt.Sprintf("**%s**\n\n", cmd.UseLine())) + cobra.WriteStringAndCheck(buf, fmt.Sprintf("**%s**\n\n", escapeAngleBrackets(cmd.UseLine()))) cobra.WriteStringAndCheck(buf, "# DESCRIPTION\n") - cobra.WriteStringAndCheck(buf, description+"\n\n") + cobra.WriteStringAndCheck(buf, escapeAngleBrackets(description)+"\n\n") } func manPrintFlags(buf io.StringWriter, flags *pflag.FlagSet) { @@ -180,7 +189,7 @@ func manPrintFlags(buf io.StringWriter, flags *pflag.FlagSet) { format += "]" } format += "\n\t%s\n\n" - cobra.WriteStringAndCheck(buf, fmt.Sprintf(format, flag.DefValue, flag.Usage)) + cobra.WriteStringAndCheck(buf, fmt.Sprintf(format, flag.DefValue, escapeAngleBrackets(flag.Usage))) }) } diff --git a/doc/man_docs_test.go b/doc/man_docs_test.go index ae6c8e533..5c126b1b8 100644 --- a/doc/man_docs_test.go +++ b/doc/man_docs_test.go @@ -214,6 +214,31 @@ func assertNextLineEquals(scanner *bufio.Scanner, expectedLine string) error { return fmt.Errorf("hit EOF before finding %v", expectedLine) } +func TestGenManAngleBracketsPreserved(t *testing.T) { + // Regression test for https://github.com/spf13/cobra/issues/2330 + // Angle brackets in Usage strings were stripped by md2man because + // it interpreted them as HTML tags. + cmd := &cobra.Command{ + Use: "transfer [:] [:]", + Short: "Transfer between remotes", + Long: "Transfer from to across remotes.", + } + + buf := new(bytes.Buffer) + if err := GenMan(cmd, nil, buf); err != nil { + t.Fatal(err) + } + output := buf.String() + + // The rendered man page must contain the angle-bracketed placeholders. + // Before the fix, md2man stripped everything between < and > (treating + // them as HTML tags), leaving only "transfer [:] [:]" in the output. + checkStringContains(t, output, "") + checkStringContains(t, output, "") + checkStringContains(t, output, "") + checkStringContains(t, output, "") +} + func BenchmarkGenManToFile(b *testing.B) { file, err := os.CreateTemp("", "") if err != nil {