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:
parent
0be85fc7f9
commit
e5d791165d
3 changed files with 99 additions and 14 deletions
6
go.mod
6
go.mod
|
@ -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
16
go.sum
|
@ -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=
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue