diff --git a/md2man/roff.go b/md2man/roff.go index 02af759..e65dc59 100644 --- a/md2man/roff.go +++ b/md2man/roff.go @@ -96,7 +96,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: @@ -138,7 +154,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..4bf0a5c 100644 --- a/md2man/roff_test.go +++ b/md2man/roff_test.go @@ -421,6 +421,20 @@ 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", + + "# 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) +} + 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