vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go
changeset 256 6d9efbef00a9
child 260 445e01aede7e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go	Sun Jul 11 10:35:56 2021 +0200
@@ -0,0 +1,345 @@
+package md2man
+
+import (
+	"fmt"
+	"io"
+	"os"
+	"strings"
+
+	"github.com/russross/blackfriday/v2"
+)
+
+// roffRenderer implements the blackfriday.Renderer interface for creating
+// roff format (manpages) from markdown text
+type roffRenderer struct {
+	extensions   blackfriday.Extensions
+	listCounters []int
+	firstHeader  bool
+	defineTerm   bool
+	listDepth    int
+}
+
+const (
+	titleHeader      = ".TH "
+	topLevelHeader   = "\n\n.SH "
+	secondLevelHdr   = "\n.SH "
+	otherHeader      = "\n.SS "
+	crTag            = "\n"
+	emphTag          = "\\fI"
+	emphCloseTag     = "\\fP"
+	strongTag        = "\\fB"
+	strongCloseTag   = "\\fP"
+	breakTag         = "\n.br\n"
+	paraTag          = "\n.PP\n"
+	hruleTag         = "\n.ti 0\n\\l'\\n(.lu'\n"
+	linkTag          = "\n\\[la]"
+	linkCloseTag     = "\\[ra]"
+	codespanTag      = "\\fB\\fC"
+	codespanCloseTag = "\\fR"
+	codeTag          = "\n.PP\n.RS\n\n.nf\n"
+	codeCloseTag     = "\n.fi\n.RE\n"
+	quoteTag         = "\n.PP\n.RS\n"
+	quoteCloseTag    = "\n.RE\n"
+	listTag          = "\n.RS\n"
+	listCloseTag     = "\n.RE\n"
+	arglistTag       = "\n.TP\n"
+	tableStart       = "\n.TS\nallbox;\n"
+	tableEnd         = ".TE\n"
+	tableCellStart   = "T{\n"
+	tableCellEnd     = "\nT}\n"
+)
+
+// NewRoffRenderer creates a new blackfriday Renderer for generating roff documents
+// from markdown
+func NewRoffRenderer() *roffRenderer { // nolint: golint
+	var extensions blackfriday.Extensions
+
+	extensions |= blackfriday.NoIntraEmphasis
+	extensions |= blackfriday.Tables
+	extensions |= blackfriday.FencedCode
+	extensions |= blackfriday.SpaceHeadings
+	extensions |= blackfriday.Footnotes
+	extensions |= blackfriday.Titleblock
+	extensions |= blackfriday.DefinitionLists
+	return &roffRenderer{
+		extensions: extensions,
+	}
+}
+
+// GetExtensions returns the list of extensions used by this renderer implementation
+func (r *roffRenderer) GetExtensions() blackfriday.Extensions {
+	return r.extensions
+}
+
+// RenderHeader handles outputting the header at document start
+func (r *roffRenderer) RenderHeader(w io.Writer, ast *blackfriday.Node) {
+	// disable hyphenation
+	out(w, ".nh\n")
+}
+
+// RenderFooter handles outputting the footer at the document end; the roff
+// renderer has no footer information
+func (r *roffRenderer) RenderFooter(w io.Writer, ast *blackfriday.Node) {
+}
+
+// RenderNode is called for each node in a markdown document; based on the node
+// type the equivalent roff output is sent to the writer
+func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
+
+	var walkAction = blackfriday.GoToNext
+
+	switch node.Type {
+	case blackfriday.Text:
+		r.handleText(w, node, entering)
+	case blackfriday.Softbreak:
+		out(w, crTag)
+	case blackfriday.Hardbreak:
+		out(w, breakTag)
+	case blackfriday.Emph:
+		if entering {
+			out(w, emphTag)
+		} else {
+			out(w, emphCloseTag)
+		}
+	case blackfriday.Strong:
+		if entering {
+			out(w, strongTag)
+		} else {
+			out(w, strongCloseTag)
+		}
+	case blackfriday.Link:
+		if !entering {
+			out(w, linkTag+string(node.LinkData.Destination)+linkCloseTag)
+		}
+	case blackfriday.Image:
+		// ignore images
+		walkAction = blackfriday.SkipChildren
+	case blackfriday.Code:
+		out(w, codespanTag)
+		escapeSpecialChars(w, node.Literal)
+		out(w, codespanCloseTag)
+	case blackfriday.Document:
+		break
+	case blackfriday.Paragraph:
+		// roff .PP markers break lists
+		if r.listDepth > 0 {
+			return blackfriday.GoToNext
+		}
+		if entering {
+			out(w, paraTag)
+		} else {
+			out(w, crTag)
+		}
+	case blackfriday.BlockQuote:
+		if entering {
+			out(w, quoteTag)
+		} else {
+			out(w, quoteCloseTag)
+		}
+	case blackfriday.Heading:
+		r.handleHeading(w, node, entering)
+	case blackfriday.HorizontalRule:
+		out(w, hruleTag)
+	case blackfriday.List:
+		r.handleList(w, node, entering)
+	case blackfriday.Item:
+		r.handleItem(w, node, entering)
+	case blackfriday.CodeBlock:
+		out(w, codeTag)
+		escapeSpecialChars(w, node.Literal)
+		out(w, codeCloseTag)
+	case blackfriday.Table:
+		r.handleTable(w, node, entering)
+	case blackfriday.TableCell:
+		r.handleTableCell(w, node, entering)
+	case blackfriday.TableHead:
+	case blackfriday.TableBody:
+	case blackfriday.TableRow:
+		// no action as cell entries do all the nroff formatting
+		return blackfriday.GoToNext
+	default:
+		fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
+	}
+	return walkAction
+}
+
+func (r *roffRenderer) handleText(w io.Writer, node *blackfriday.Node, entering bool) {
+	var (
+		start, end string
+	)
+	// handle special roff table cell text encapsulation
+	if node.Parent.Type == blackfriday.TableCell {
+		if len(node.Literal) > 30 {
+			start = tableCellStart
+			end = tableCellEnd
+		} else {
+			// end rows that aren't terminated by "tableCellEnd" with a cr if end of row
+			if node.Parent.Next == nil && !node.Parent.IsHeader {
+				end = crTag
+			}
+		}
+	}
+	out(w, start)
+	escapeSpecialChars(w, node.Literal)
+	out(w, end)
+}
+
+func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, entering bool) {
+	if entering {
+		switch node.Level {
+		case 1:
+			if !r.firstHeader {
+				out(w, titleHeader)
+				r.firstHeader = true
+				break
+			}
+			out(w, topLevelHeader)
+		case 2:
+			out(w, secondLevelHdr)
+		default:
+			out(w, otherHeader)
+		}
+	}
+}
+
+func (r *roffRenderer) handleList(w io.Writer, node *blackfriday.Node, entering bool) {
+	openTag := listTag
+	closeTag := listCloseTag
+	if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
+		// tags for definition lists handled within Item node
+		openTag = ""
+		closeTag = ""
+	}
+	if entering {
+		r.listDepth++
+		if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
+			r.listCounters = append(r.listCounters, 1)
+		}
+		out(w, openTag)
+	} else {
+		if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
+			r.listCounters = r.listCounters[:len(r.listCounters)-1]
+		}
+		out(w, closeTag)
+		r.listDepth--
+	}
+}
+
+func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering bool) {
+	if entering {
+		if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
+			out(w, fmt.Sprintf(".IP \"%3d.\" 5\n", r.listCounters[len(r.listCounters)-1]))
+			r.listCounters[len(r.listCounters)-1]++
+		} else if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
+			// state machine for handling terms and following definitions
+			// since blackfriday does not distinguish them properly, nor
+			// does it seperate them into separate lists as it should
+			if !r.defineTerm {
+				out(w, arglistTag)
+				r.defineTerm = true
+			} else {
+				r.defineTerm = false
+			}
+		} else {
+			out(w, ".IP \\(bu 2\n")
+		}
+	} else {
+		out(w, "\n")
+	}
+}
+
+func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering bool) {
+	if entering {
+		out(w, tableStart)
+		//call walker to count cells (and rows?) so format section can be produced
+		columns := countColumns(node)
+		out(w, strings.Repeat("l ", columns)+"\n")
+		out(w, strings.Repeat("l ", columns)+".\n")
+	} else {
+		out(w, tableEnd)
+	}
+}
+
+func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, entering bool) {
+	var (
+		start, end string
+	)
+	if node.IsHeader {
+		start = codespanTag
+		end = codespanCloseTag
+	}
+	if entering {
+		if node.Prev != nil && node.Prev.Type == blackfriday.TableCell {
+			out(w, "\t"+start)
+		} else {
+			out(w, start)
+		}
+	} else {
+		// need to carriage return if we are at the end of the header row
+		if node.IsHeader && node.Next == nil {
+			end = end + crTag
+		}
+		out(w, end)
+	}
+}
+
+// because roff format requires knowing the column count before outputting any table
+// data we need to walk a table tree and count the columns
+func countColumns(node *blackfriday.Node) int {
+	var columns int
+
+	node.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
+		switch node.Type {
+		case blackfriday.TableRow:
+			if !entering {
+				return blackfriday.Terminate
+			}
+		case blackfriday.TableCell:
+			if entering {
+				columns++
+			}
+		default:
+		}
+		return blackfriday.GoToNext
+	})
+	return columns
+}
+
+func out(w io.Writer, output string) {
+	io.WriteString(w, output) // nolint: errcheck
+}
+
+func needsBackslash(c byte) bool {
+	for _, r := range []byte("-_&\\~") {
+		if c == r {
+			return true
+		}
+	}
+	return false
+}
+
+func escapeSpecialChars(w io.Writer, text []byte) {
+	for i := 0; i < len(text); i++ {
+		// escape initial apostrophe or period
+		if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') {
+			out(w, "\\&")
+		}
+
+		// directly copy normal characters
+		org := i
+
+		for i < len(text) && !needsBackslash(text[i]) {
+			i++
+		}
+		if i > org {
+			w.Write(text[org:i]) // nolint: errcheck
+		}
+
+		// escape a character
+		if i >= len(text) {
+			break
+		}
+
+		w.Write([]byte{'\\', text[i]}) // nolint: errcheck
+	}
+}