From f486e72e6c89eb4315d4746a2d57f8bb62985161 Mon Sep 17 00:00:00 2001 From: Cory Snider Date: Wed, 7 Aug 2024 16:59:43 -0400 Subject: [PATCH 1/2] Support multi-paragraph definition lists The AST hierarchy for a list item is always List > Item > Paragraph. Emit newlines when visiting the Paragraph node instead of the Item and List so that Items with multiple Paragraphs and nested Lists correctly render with just the right number of line breaks. Signed-off-by: Cory Snider --- go-md2man.1.md | 12 ++++++++++++ md2man/roff.go | 25 +++++++++++++++---------- md2man/roff_test.go | 4 +++- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/go-md2man.1.md b/go-md2man.1.md index aa4587e..c7ff868 100644 --- a/go-md2man.1.md +++ b/go-md2man.1.md @@ -13,6 +13,18 @@ written purely in Go so as to reduce dependencies on 3rd party libs. By default, the input is stdin and the output is stdout. +# OPTIONS + +**-in=**_file_ +: Path to markdown file to be processed. + + Defaults to stdin. + +**-out=**_file_ +: Path to output processed file. + + Defaults to stdout. + # EXAMPLES Convert the markdown file *go-md2man.1.md* into a manpage: ``` diff --git a/md2man/roff.go b/md2man/roff.go index e65dc59..8a450cf 100644 --- a/md2man/roff.go +++ b/md2man/roff.go @@ -41,7 +41,7 @@ const ( quoteTag = "\n.PP\n.RS\n" quoteCloseTag = "\n.RE\n" listTag = "\n.RS\n" - listCloseTag = "\n.RE\n" + listCloseTag = ".RE\n" dtTag = "\n.TP\n" dd2Tag = "\n" tableStart = "\n.TS\nallbox;\n" @@ -150,14 +150,21 @@ func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering case blackfriday.Document: break case blackfriday.Paragraph: - // roff .PP markers break lists - if r.listDepth > 0 { - return blackfriday.GoToNext - } - if entering && (node.Prev == nil || node.Prev.Type != blackfriday.Heading) { - out(w, paraTag) + if entering { + if r.listDepth > 0 { + // roff .PP markers break lists + if node.Prev != nil { // continued paragraph + out(w, crTag) + } + } else if node.Prev != nil && node.Prev.Type == blackfriday.Heading { + out(w, crTag) + } else { + out(w, paraTag) + } } else { - out(w, crTag) + if node.Next == nil || node.Next.Type != blackfriday.List { + out(w, crTag) + } } case blackfriday.BlockQuote: if entering { @@ -260,8 +267,6 @@ func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering } else { out(w, ".IP \\(bu 2\n") } - } else { - out(w, "\n") } } diff --git a/md2man/roff_test.go b/md2man/roff_test.go index 4bf0a5c..6a3d7b3 100644 --- a/md2man/roff_test.go +++ b/md2man/roff_test.go @@ -210,9 +210,11 @@ func TestCodeSpan(t *testing.T) { func TestListLists(t *testing.T) { tests := []string{ "\n\n**[grpc]**\n: Section for gRPC socket listener settings. Contains three properties:\n - **address** (Default: \"/run/containerd/containerd.sock\")\n - **uid** (Default: 0)\n - **gid** (Default: 0)", - ".nh\n\n.TP\n\\fB[grpc]\\fP\nSection for gRPC socket listener settings. Contains three properties:\n.RS\n.IP \\(bu 2\n\\fBaddress\\fP (Default: \"/run/containerd/containerd.sock\")\n.IP \\(bu 2\n\\fBuid\\fP (Default: 0)\n.IP \\(bu 2\n\\fBgid\\fP (Default: 0)\n\n.RE\n\n", + ".nh\n\n.TP\n\\fB[grpc]\\fP\nSection for gRPC socket listener settings. Contains three properties:\n.RS\n.IP \\(bu 2\n\\fBaddress\\fP (Default: \"/run/containerd/containerd.sock\")\n.IP \\(bu 2\n\\fBuid\\fP (Default: 0)\n.IP \\(bu 2\n\\fBgid\\fP (Default: 0)\n.RE\n", "Definition title\n: Definition description one\n: And two\n: And three\n", ".nh\n\n.TP\nDefinition title\nDefinition description one\n\nAnd two\n\nAnd three\n", + "Definition\n: description\n\n split\n\n over\n\n multiple\n\n paragraphs\n", + ".nh\n\n.TP\nDefinition\ndescription\n\nsplit\n\nover\n\nmultiple\n\nparagraphs\n", } doTestsParam(t, tests, TestParams{blackfriday.DefinitionLists}) } From 149c3522edfb89f51d0e568250134ca579f9c6cd Mon Sep 17 00:00:00 2001 From: Cory Snider Date: Thu, 8 Aug 2024 19:24:42 -0400 Subject: [PATCH 2/2] Stop over-indenting lists that are not nested List items are already indented paragraphs. There is no need to also increase the relative inset unless the list is nested inside another list. Signed-off-by: Cory Snider --- md2man/roff.go | 19 +++++++++++++++++-- md2man/roff_test.go | 8 ++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/md2man/roff.go b/md2man/roff.go index 8a450cf..9d6c473 100644 --- a/md2man/roff.go +++ b/md2man/roff.go @@ -154,7 +154,11 @@ func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering if r.listDepth > 0 { // roff .PP markers break lists if node.Prev != nil { // continued paragraph - out(w, crTag) + if node.Prev.Type == blackfriday.List && node.Prev.ListFlags&blackfriday.ListTypeDefinition == 0 { + out(w, ".IP\n") + } else { + out(w, crTag) + } } } else if node.Prev != nil && node.Prev.Type == blackfriday.Heading { out(w, crTag) @@ -227,6 +231,10 @@ func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, enteri func (r *roffRenderer) handleList(w io.Writer, node *blackfriday.Node, entering bool) { openTag := listTag closeTag := listCloseTag + if (entering && r.listDepth == 0) || (!entering && r.listDepth == 1) { + openTag = crTag + closeTag = "" + } if node.ListFlags&blackfriday.ListTypeDefinition != 0 { // tags for definition lists handled within Item node openTag = "" @@ -262,7 +270,14 @@ func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering // subsequent ones, as there should be no vertical // whitespace between the DT and the first DD. if node.Prev != nil && node.Prev.ListFlags&(blackfriday.ListTypeTerm|blackfriday.ListTypeDefinition) == blackfriday.ListTypeDefinition { - out(w, dd2Tag) + if node.Prev.Type == blackfriday.Item && + node.Prev.LastChild != nil && + node.Prev.LastChild.Type == blackfriday.List && + node.Prev.LastChild.ListFlags&blackfriday.ListTypeDefinition == 0 { + out(w, ".IP\n") + } else { + out(w, dd2Tag) + } } } else { out(w, ".IP \\(bu 2\n") diff --git a/md2man/roff_test.go b/md2man/roff_test.go index 6a3d7b3..acde994 100644 --- a/md2man/roff_test.go +++ b/md2man/roff_test.go @@ -207,6 +207,14 @@ func TestCodeSpan(t *testing.T) { doTestsInline(t, tests) } +func TestFlatLists(t *testing.T) { + tests := []string{ + "Paragraph\n\n- item one\n- item two\n- item three\n", + ".nh\n\n.PP\nParagraph\n.IP \\(bu 2\nitem one\n.IP \\(bu 2\nitem two\n.IP \\(bu 2\nitem three\n", + } + doTestsInline(t, tests) +} + func TestListLists(t *testing.T) { tests := []string{ "\n\n**[grpc]**\n: Section for gRPC socket listener settings. Contains three properties:\n - **address** (Default: \"/run/containerd/containerd.sock\")\n - **uid** (Default: 0)\n - **gid** (Default: 0)",