Implement tables rendering

This adds support for Markdown table rendering with
github.com/olekukonko/tablewriter. Tables are rendered as ASCII text
in a preformatted text block. Cells are hard-aligned with spaces.
tablewriter options are not yet configurable, although they should be.
For now, extra formatting inside tables is omitted.

Fixes #2.
This commit is contained in:
Timur Demin 2021-08-09 18:39:01 +05:00 committed by Timur Demin
parent 0be85fc7f9
commit e5d791165d
3 changed files with 99 additions and 14 deletions

6
go.mod
View file

@ -3,6 +3,8 @@ module github.com/tdemin/gmnhg
go 1.16
require (
github.com/gomarkdown/markdown v0.0.0-20201024011455-45c732cc8a6b
gopkg.in/yaml.v2 v2.3.0
github.com/gomarkdown/markdown v0.0.0-20210514010506-3b9f47219fe7
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/olekukonko/tablewriter v0.0.5
gopkg.in/yaml.v2 v2.4.0
)

16
go.sum
View file

@ -1,6 +1,14 @@
github.com/gomarkdown/markdown v0.0.0-20201024011455-45c732cc8a6b h1:Om9FdD4lzIJELyJxwr9EWSjaG6GMUNS3iebnhrGevhI=
github.com/gomarkdown/markdown v0.0.0-20201024011455-45c732cc8a6b/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
github.com/gomarkdown/markdown v0.0.0-20210514010506-3b9f47219fe7 h1:oKYOfNR7Hp6XpZ4JqolL5u642Js5Z0n7psPVl+S5heo=
github.com/gomarkdown/markdown v0.0.0-20210514010506-3b9f47219fe7/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View file

@ -25,15 +25,17 @@ import (
"time"
"github.com/gomarkdown/markdown/ast"
"github.com/olekukonko/tablewriter"
)
var (
lineBreak = []byte{'\n'}
space = []byte{' '}
linkPrefix = []byte("=> ")
quotePrefix = []byte("> ")
itemPrefix = []byte("* ")
itemIndent = []byte{'\t'}
lineBreak = []byte{'\n'}
space = []byte{' '}
linkPrefix = []byte("=> ")
quotePrefix = []byte("> ")
itemPrefix = []byte("* ")
itemIndent = []byte{'\t'}
preformattedToggle = []byte("```\n")
)
var meaningfulCharsRegex = regexp.MustCompile(`\A[\s]+\z`)
@ -193,9 +195,9 @@ func (r Renderer) paragraph(w io.Writer, node *ast.Paragraph, entering bool) (no
}
func (r Renderer) code(w io.Writer, node *ast.CodeBlock) {
w.Write([]byte("```\n"))
w.Write(preformattedToggle)
w.Write(node.Literal)
w.Write([]byte("```\n"))
w.Write(preformattedToggle)
}
func (r Renderer) list(w io.Writer, node *ast.List, level int) {
@ -249,6 +251,76 @@ func (r Renderer) text(w io.Writer, node ast.Node) {
}
}
func extractText(node ast.Node) string {
if node := node.AsLeaf(); node != nil {
return strings.ReplaceAll(string(node.Literal), "\n", " ")
}
if node := node.AsContainer(); node != nil {
b := strings.Builder{}
for _, child := range node.Children {
b.WriteString(extractText(child))
}
return b.String()
}
panic("encountered a non-leaf & non-container node")
}
func (r Renderer) tableHead(t *tablewriter.Table, node *ast.TableHeader) {
if node := node.AsContainer(); node != nil {
// should always have a single row consisting of at least one
// cell but worth checking nonetheless; tablewriter only
// supports a single header row as of now therefore ignore
// second row and the rest
if len(node.Children) > 0 {
if row := node.Children[0].AsContainer(); row != nil {
cells := make([]string, len(row.Children))
for i, cell := range row.Children {
cells[i] = extractText(cell)
}
t.SetHeader(cells)
}
}
}
}
func (r Renderer) tableBody(t *tablewriter.Table, node *ast.TableBody) {
if node := node.AsContainer(); node != nil {
for _, row := range node.Children {
if row := row.AsContainer(); row != nil {
cells := make([]string, len(row.Children))
for i, cell := range row.Children {
cells[i] = extractText(cell)
}
t.Append(cells)
}
}
}
}
func (r Renderer) table(w io.Writer, node *ast.Table, entering bool) {
if entering {
w.Write(preformattedToggle)
// gomarkdown appears to only parse headings consisting of a
// single line and always have a TableBody preceded by a single
// TableHeader but we're better off not relying on it
t := tablewriter.NewWriter(w)
t.SetAutoFormatHeaders(false) // TODO: tablewriter options should probably be configurable
if node := node.AsContainer(); node != nil {
for _, child := range node.Children {
switch child := child.(type) {
case *ast.TableHeader:
r.tableHead(t, child)
case *ast.TableBody:
r.tableBody(t, child)
}
}
}
t.Render()
} else {
w.Write(preformattedToggle)
}
}
// RenderNode implements Renderer.RenderNode().
func (r Renderer) RenderNode(w io.Writer, node ast.Node, entering bool) ast.WalkStatus {
// despite most of the subroutines here accepting entering, most of
@ -279,6 +351,9 @@ func (r Renderer) RenderNode(w io.Writer, node ast.Node, entering bool) ast.Walk
r.list(w, node, 0)
noNewLine = false
}
case *ast.Table:
r.table(w, node, entering)
noNewLine = false
}
if !noNewLine && !entering {
w.Write(lineBreak)