From 0f2f6ebbe9a579a87b7b57d2235ceb21e1ed97c2 Mon Sep 17 00:00:00 2001 From: Cory Snider Date: Fri, 9 Aug 2024 14:02:03 -0400 Subject: [PATCH 1/2] Suppress tag on first paragraph after header Paragraph tags following headers are not necessary and confuse semantic parsers such as lexgrog. Signed-off-by: Cory Snider --- md2man/roff.go | 2 +- md2man/roff_test.go | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/md2man/roff.go b/md2man/roff.go index 8a290f1..042ca41 100644 --- a/md2man/roff.go +++ b/md2man/roff.go @@ -145,7 +145,7 @@ func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering if r.listDepth > 0 { return blackfriday.GoToNext } - if entering { + if entering && (node.Prev == nil || node.Prev.Type != blackfriday.Heading) { out(w, paraTag) } else { out(w, crTag) diff --git a/md2man/roff_test.go b/md2man/roff_test.go index 5bda3ac..7b8cdac 100644 --- a/md2man/roff_test.go +++ b/md2man/roff_test.go @@ -421,6 +421,14 @@ func TestComments(t *testing.T) { doTestsInlineParam(t, inlineTests, TestParams{}) } +func TestHeadings(t *testing.T) { + tests := []string{ + "# title\n\n# NAME\ncommand - description\n\n# SYNOPSIS\nA short description\n\nWhich spans multiple paragraphs\n", + ".nh\n.TH title\n\n.SH NAME\ncommand - description\n\n\n.SH SYNOPSIS\nA short description\n\n.PP\nWhich spans multiple paragraphs\n", + } + doTestsInline(t, tests) +} + func execRecoverableTestSuite(t *testing.T, tests []string, params TestParams, suite func(candidate *string)) { // Catch and report panics. This is useful when running 'go test -v' on // the integration server. When developing, though, crash dump is often From 426206cccccc8bddf41b690d9abec3eece500e94 Mon Sep 17 00:00:00 2001 From: Cory Snider Date: Fri, 9 Aug 2024 14:42:43 -0400 Subject: [PATCH 2/2] Backslash-escape first hyphen in NAME section This is the required format for man-page parsers across the ecosystem to successfuly parse out whatis information. Signed-off-by: Cory Snider --- md2man/roff.go | 18 +++++++++++++++++- md2man/roff_test.go | 8 +++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/md2man/roff.go b/md2man/roff.go index 042ca41..671d74d 100644 --- a/md2man/roff.go +++ b/md2man/roff.go @@ -103,7 +103,23 @@ func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering switch node.Type { case blackfriday.Text: - escapeSpecialChars(w, node.Literal) + // Special case: format the NAME section as required for proper whatis parsing. + // Refer to the lexgrog(1) and groff_man(7) manual pages for details. + if node.Parent != nil && + node.Parent.Type == blackfriday.Paragraph && + node.Parent.Prev != nil && + node.Parent.Prev.Type == blackfriday.Heading && + node.Parent.Prev.FirstChild != nil && + bytes.EqualFold(node.Parent.Prev.FirstChild.Literal, []byte("NAME")) { + before, after, found := bytes.Cut(node.Literal, []byte(" - ")) + escapeSpecialChars(w, before) + if found { + out(w, ` \- `) + escapeSpecialChars(w, after) + } + } else { + escapeSpecialChars(w, node.Literal) + } case blackfriday.Softbreak: out(w, crTag) case blackfriday.Hardbreak: diff --git a/md2man/roff_test.go b/md2man/roff_test.go index 7b8cdac..4bf0a5c 100644 --- a/md2man/roff_test.go +++ b/md2man/roff_test.go @@ -424,7 +424,13 @@ func TestComments(t *testing.T) { func TestHeadings(t *testing.T) { tests := []string{ "# title\n\n# NAME\ncommand - description\n\n# SYNOPSIS\nA short description\n\nWhich spans multiple paragraphs\n", - ".nh\n.TH title\n\n.SH NAME\ncommand - description\n\n\n.SH SYNOPSIS\nA short description\n\n.PP\nWhich spans multiple paragraphs\n", + ".nh\n.TH title\n\n.SH NAME\ncommand \\- description\n\n\n.SH SYNOPSIS\nA short description\n\n.PP\nWhich spans multiple paragraphs\n", + + "# title\n\n# Name\nmy-command, other - description - with - hyphens\n", + ".nh\n.TH title\n\n.SH Name\nmy-command, other \\- description - with - hyphens\n", + + "# title\n\n# Not NAME\nsome - other - text\n", + ".nh\n.TH title\n\n.SH Not NAME\nsome - other - text\n", } doTestsInline(t, tests) }