mirror of
https://github.com/rwinkhart/glamour-temp-MUTN.git
synced 2026-09-06 00:47:15 -04:00
Switch to goldmark parser
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
package gold
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CheckedItemElement struct {
|
||||||
|
Checked bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *CheckedItemElement) Render(w io.Writer, ctx RenderContext) error {
|
||||||
|
var el *BaseElement
|
||||||
|
|
||||||
|
pre := "✗ "
|
||||||
|
if e.Checked {
|
||||||
|
pre = "✓ "
|
||||||
|
}
|
||||||
|
|
||||||
|
el = &BaseElement{
|
||||||
|
Prefix: pre,
|
||||||
|
Style: ctx.style[CheckedItem],
|
||||||
|
}
|
||||||
|
|
||||||
|
return el.Render(w, ctx)
|
||||||
|
}
|
||||||
+199
-127
@@ -1,9 +1,12 @@
|
|||||||
package gold
|
package gold
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
bf "gopkg.in/russross/blackfriday.v2"
|
"github.com/yuin/goldmark/ast"
|
||||||
|
astext "github.com/yuin/goldmark/extension/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ElementRenderer interface {
|
type ElementRenderer interface {
|
||||||
@@ -21,210 +24,279 @@ type Element struct {
|
|||||||
Finisher ElementFinisher
|
Finisher ElementFinisher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tr *TermRenderer) NewElement(node *bf.Node) Element {
|
func (tr *TermRenderer) NewElement(node ast.Node, source []byte) Element {
|
||||||
ctx := tr.context
|
ctx := tr.context
|
||||||
|
// fmt.Print(strings.Repeat(" ", ctx.blockStack.Len()), node.Type(), node.Kind())
|
||||||
|
// defer fmt.Println()
|
||||||
|
|
||||||
switch node.Type {
|
switch node.Kind() {
|
||||||
case bf.Document:
|
// Document
|
||||||
de := &DocumentElement{}
|
case ast.KindDocument:
|
||||||
return Element{
|
return Element{
|
||||||
Renderer: de,
|
Renderer: &DocumentElement{},
|
||||||
Finisher: de,
|
Finisher: &DocumentElement{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heading
|
||||||
|
case ast.KindHeading:
|
||||||
|
n := node.(*ast.Heading)
|
||||||
|
he := &HeadingElement{
|
||||||
|
Level: n.Level,
|
||||||
|
First: node.PreviousSibling() == nil,
|
||||||
|
}
|
||||||
|
return Element{
|
||||||
|
Exiting: "",
|
||||||
|
Renderer: he,
|
||||||
|
Finisher: he,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paragraph
|
||||||
|
case ast.KindParagraph:
|
||||||
|
return Element{
|
||||||
|
Renderer: &ParagraphElement{},
|
||||||
|
Finisher: &ParagraphElement{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blockquote
|
||||||
|
case ast.KindBlockquote:
|
||||||
|
e := &BlockElement{
|
||||||
|
Block: &bytes.Buffer{},
|
||||||
|
Style: cascadeStyle(ctx.blockStack.Current().Style, ctx.style[BlockQuote], true),
|
||||||
|
Margin: true,
|
||||||
}
|
}
|
||||||
case bf.BlockQuote:
|
|
||||||
return Element{
|
return Element{
|
||||||
Entering: "\n",
|
Entering: "\n",
|
||||||
Exiting: "\n",
|
Renderer: e,
|
||||||
Renderer: &BaseElement{
|
Finisher: e,
|
||||||
Token: string(node.Literal),
|
|
||||||
Style: ctx.style[BlockQuote],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
case bf.List:
|
|
||||||
le := &ListElement{
|
// Lists
|
||||||
Nested: node.Parent.Type == bf.Item,
|
case ast.KindList:
|
||||||
|
e := &BlockElement{
|
||||||
|
Block: &bytes.Buffer{},
|
||||||
|
Style: cascadeStyle(ctx.blockStack.Current().Style, ctx.style[List], true),
|
||||||
|
Margin: true,
|
||||||
}
|
}
|
||||||
return Element{
|
return Element{
|
||||||
Renderer: le,
|
Entering: "\n",
|
||||||
Finisher: le,
|
Renderer: e,
|
||||||
|
Finisher: e,
|
||||||
}
|
}
|
||||||
case bf.Item:
|
|
||||||
|
case ast.KindListItem:
|
||||||
var l uint
|
var l uint
|
||||||
if node.ListData.ListFlags&bf.ListTypeOrdered > 0 {
|
var e uint
|
||||||
l = 1
|
l = 1
|
||||||
n := node
|
n := node
|
||||||
for n.Prev != nil && (n.Prev.Type == bf.Item) {
|
for n.PreviousSibling() != nil && (n.PreviousSibling().Kind() == ast.KindListItem) {
|
||||||
l++
|
l++
|
||||||
n = n.Prev
|
n = n.PreviousSibling()
|
||||||
|
}
|
||||||
|
if node.Parent().(*ast.List).IsOrdered() {
|
||||||
|
e = l
|
||||||
|
}
|
||||||
|
|
||||||
|
post := "\n"
|
||||||
|
if node.LastChild().Kind() == ast.KindList || node.NextSibling() == nil {
|
||||||
|
post = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.FirstChild().FirstChild().Kind() == astext.KindTaskCheckBox {
|
||||||
|
nc := node.FirstChild().FirstChild().(*astext.TaskCheckBox)
|
||||||
|
|
||||||
|
return Element{
|
||||||
|
Exiting: post,
|
||||||
|
Renderer: &CheckedItemElement{
|
||||||
|
Checked: nc.IsChecked,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Element{
|
return Element{
|
||||||
|
Exiting: post,
|
||||||
Renderer: &ItemElement{
|
Renderer: &ItemElement{
|
||||||
Text: string(node.Literal),
|
Enumeration: e,
|
||||||
Enumeration: l,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case bf.Paragraph:
|
|
||||||
pe := &ParagraphElement{
|
// Text Elements
|
||||||
InsideList: node.Parent != nil && node.Parent.Type == bf.Item,
|
case ast.KindText:
|
||||||
|
n := node.(*ast.Text)
|
||||||
|
s := string(n.Segment.Value(source))
|
||||||
|
|
||||||
|
if n.HardLineBreak() || (n.SoftLineBreak()) {
|
||||||
|
s += "\n"
|
||||||
}
|
}
|
||||||
return Element{
|
return Element{
|
||||||
Renderer: pe,
|
Renderer: &BaseElement{
|
||||||
Finisher: pe,
|
Token: ctx.SanitizeHTML(s, false),
|
||||||
}
|
Style: ctx.style[Text],
|
||||||
case bf.Heading:
|
|
||||||
return Element{
|
|
||||||
Exiting: "\n",
|
|
||||||
Renderer: &HeadingElement{
|
|
||||||
Text: string(node.FirstChild.Literal),
|
|
||||||
Level: node.HeadingData.Level,
|
|
||||||
First: node.Prev == nil,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case bf.HorizontalRule:
|
|
||||||
|
case ast.KindEmphasis:
|
||||||
|
n := node.(*ast.Emphasis)
|
||||||
|
s := string(n.Text(source))
|
||||||
|
style := ctx.style[Emph]
|
||||||
|
if n.Level > 1 {
|
||||||
|
style = ctx.style[Strong]
|
||||||
|
}
|
||||||
|
|
||||||
|
return Element{
|
||||||
|
Renderer: &BaseElement{
|
||||||
|
Token: ctx.SanitizeHTML(s, false),
|
||||||
|
Style: style,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
case ast.KindThematicBreak:
|
||||||
return Element{
|
return Element{
|
||||||
Entering: "",
|
Entering: "",
|
||||||
Exiting: "\n",
|
Exiting: "",
|
||||||
Renderer: &BaseElement{
|
Renderer: &BaseElement{
|
||||||
Style: ctx.style[HorizontalRule],
|
Style: ctx.style[HorizontalRule],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case bf.Emph:
|
|
||||||
return Element{
|
// Links
|
||||||
Renderer: &BaseElement{
|
case ast.KindLink:
|
||||||
Token: string(node.FirstChild.Literal),
|
n := node.(*ast.Link)
|
||||||
Style: ctx.style[Emph],
|
text := string(n.Text(source))
|
||||||
},
|
|
||||||
}
|
|
||||||
case bf.Strong:
|
|
||||||
return Element{
|
|
||||||
Renderer: &BaseElement{
|
|
||||||
Token: string(node.FirstChild.Literal),
|
|
||||||
Style: ctx.style[Strong],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case bf.Del:
|
|
||||||
return Element{
|
|
||||||
Renderer: &BaseElement{
|
|
||||||
Token: string(node.Literal),
|
|
||||||
Style: ctx.style[Del],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case bf.Link:
|
|
||||||
var text string
|
|
||||||
if node.LastChild != nil {
|
|
||||||
text = string(node.LastChild.Literal)
|
|
||||||
}
|
|
||||||
return Element{
|
return Element{
|
||||||
Renderer: &LinkElement{
|
Renderer: &LinkElement{
|
||||||
Text: text,
|
Text: text,
|
||||||
BaseURL: tr.context.options.BaseURL,
|
BaseURL: ctx.options.BaseURL,
|
||||||
URL: string(node.LinkData.Destination),
|
URL: string(n.Destination),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case bf.Image:
|
case ast.KindAutoLink:
|
||||||
var text string
|
n := node.(*ast.AutoLink)
|
||||||
if node.LastChild != nil {
|
text := string(n.Text(source))
|
||||||
text = string(node.LastChild.Literal)
|
return Element{
|
||||||
|
Renderer: &LinkElement{
|
||||||
|
Text: text,
|
||||||
|
BaseURL: ctx.options.BaseURL,
|
||||||
|
URL: string(n.URL(source)),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Images
|
||||||
|
case ast.KindImage:
|
||||||
|
n := node.(*ast.Image)
|
||||||
|
text := string(n.Text(source))
|
||||||
return Element{
|
return Element{
|
||||||
Renderer: &ImageElement{
|
Renderer: &ImageElement{
|
||||||
Text: text,
|
Text: text,
|
||||||
BaseURL: tr.context.options.BaseURL,
|
BaseURL: ctx.options.BaseURL,
|
||||||
URL: string(node.LinkData.Destination),
|
URL: string(n.Destination),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case bf.Text:
|
|
||||||
return Element{
|
// Code
|
||||||
Renderer: &BaseElement{
|
case ast.KindFencedCodeBlock:
|
||||||
Token: tr.context.SanitizeHTML(string(node.Literal), false),
|
n := node.(*ast.FencedCodeBlock)
|
||||||
Style: ctx.style[Text],
|
l := n.Lines().Len()
|
||||||
},
|
s := ""
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
line := n.Lines().At(i)
|
||||||
|
s += string(line.Value(source))
|
||||||
}
|
}
|
||||||
case bf.HTMLBlock:
|
|
||||||
return Element{
|
|
||||||
Renderer: &BaseElement{
|
|
||||||
Token: tr.context.SanitizeHTML(string(node.Literal), true) + "\n",
|
|
||||||
Style: ctx.style[HTMLBlock],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case bf.CodeBlock:
|
|
||||||
return Element{
|
return Element{
|
||||||
Entering: "\n",
|
Entering: "\n",
|
||||||
Renderer: &CodeBlockElement{
|
Renderer: &CodeBlockElement{
|
||||||
Code: string(node.Literal),
|
Code: s,
|
||||||
Language: string(node.CodeBlockData.Info),
|
Language: string(n.Language(source)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case bf.Softbreak:
|
|
||||||
|
case ast.KindCodeBlock:
|
||||||
|
n := node.(*ast.CodeBlock)
|
||||||
|
l := n.Lines().Len()
|
||||||
|
s := ""
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
line := n.Lines().At(i)
|
||||||
|
s += string(line.Value(source))
|
||||||
|
}
|
||||||
return Element{
|
return Element{
|
||||||
Exiting: "\n",
|
Entering: "\n",
|
||||||
Renderer: &BaseElement{
|
Renderer: &CodeBlockElement{
|
||||||
Token: string(node.Literal),
|
Code: s,
|
||||||
Style: ctx.style[Softbreak],
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case bf.Hardbreak:
|
|
||||||
|
case ast.KindCodeSpan:
|
||||||
|
// n := node.(*ast.CodeSpan)
|
||||||
|
e := &BlockElement{
|
||||||
|
Block: &bytes.Buffer{},
|
||||||
|
Style: cascadeStyle(ctx.blockStack.Current().Style, ctx.style[Code], true),
|
||||||
|
}
|
||||||
return Element{
|
return Element{
|
||||||
Exiting: "\n",
|
Renderer: e,
|
||||||
Renderer: &BaseElement{
|
Finisher: e,
|
||||||
Token: string(node.Literal),
|
|
||||||
Style: ctx.style[Hardbreak],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
case bf.Code:
|
|
||||||
return Element{
|
// Tables
|
||||||
Renderer: &BaseElement{
|
case astext.KindTable:
|
||||||
Token: string(node.Literal),
|
|
||||||
Style: ctx.style[Code],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case bf.HTMLSpan:
|
|
||||||
return Element{
|
|
||||||
Renderer: &BaseElement{
|
|
||||||
Token: tr.context.SanitizeHTML(string(node.Literal), true) + "\n",
|
|
||||||
Style: ctx.style[HTMLSpan],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case bf.Table:
|
|
||||||
te := &TableElement{}
|
te := &TableElement{}
|
||||||
return Element{
|
return Element{
|
||||||
Entering: "\n",
|
Entering: "\n",
|
||||||
Renderer: te,
|
Renderer: te,
|
||||||
Finisher: te,
|
Finisher: te,
|
||||||
}
|
}
|
||||||
case bf.TableCell:
|
|
||||||
|
case astext.KindTableCell:
|
||||||
s := ""
|
s := ""
|
||||||
n := node.FirstChild
|
n := node.FirstChild()
|
||||||
for n != nil {
|
for n != nil {
|
||||||
s += string(n.Literal)
|
s += string(n.Text(source))
|
||||||
s += string(n.LinkData.Destination)
|
// s += string(n.LinkData.Destination)
|
||||||
n = n.Next
|
n = n.NextSibling()
|
||||||
}
|
}
|
||||||
|
|
||||||
return Element{
|
return Element{
|
||||||
Renderer: &TableCellElement{
|
Renderer: &TableCellElement{
|
||||||
Text: s,
|
Text: s,
|
||||||
Head: node.Parent.Parent.Type == bf.TableHead,
|
Head: node.Parent().Kind() == astext.KindTableHeader,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case bf.TableHead:
|
|
||||||
|
case astext.KindTableHeader:
|
||||||
return Element{
|
return Element{
|
||||||
Finisher: &TableHeadElement{},
|
Finisher: &TableHeadElement{},
|
||||||
}
|
}
|
||||||
case bf.TableBody:
|
case astext.KindTableRow:
|
||||||
return Element{}
|
|
||||||
case bf.TableRow:
|
|
||||||
return Element{
|
return Element{
|
||||||
Finisher: &TableRowElement{},
|
Finisher: &TableRowElement{},
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
// HTML Elements
|
||||||
|
case ast.KindHTMLBlock:
|
||||||
|
n := node.(*ast.HTMLBlock)
|
||||||
return Element{
|
return Element{
|
||||||
Renderer: &BaseElement{
|
Renderer: &BaseElement{
|
||||||
Token: string(node.Literal),
|
Token: ctx.SanitizeHTML(string(n.Text(source)), true) + "\n",
|
||||||
|
Style: ctx.style[HTMLBlock],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
case ast.KindRawHTML:
|
||||||
|
n := node.(*ast.RawHTML)
|
||||||
|
return Element{
|
||||||
|
Renderer: &BaseElement{
|
||||||
|
Token: ctx.SanitizeHTML(string(n.Text(source)), true) + "\n",
|
||||||
|
Style: ctx.style[HTMLSpan],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handled by parents
|
||||||
|
case astext.KindTaskCheckBox:
|
||||||
|
// handled by KindListItem
|
||||||
|
return Element{}
|
||||||
|
case ast.KindTextBlock:
|
||||||
|
return Element{}
|
||||||
|
|
||||||
|
// Unknown case
|
||||||
|
default:
|
||||||
|
fmt.Println("Warning: unhandled element", node.Kind().String())
|
||||||
|
return Element{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,5 +14,6 @@ require (
|
|||||||
github.com/rakyll/statik v0.1.6
|
github.com/rakyll/statik v0.1.6
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||||
github.com/spf13/cobra v0.0.5
|
github.com/spf13/cobra v0.0.5
|
||||||
|
github.com/yuin/goldmark v1.1.14
|
||||||
gopkg.in/russross/blackfriday.v2 v2.0.0
|
gopkg.in/russross/blackfriday.v2 v2.0.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
|
|||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
|
github.com/yuin/goldmark v1.1.14 h1:9/OvYI+gdtQ5EAZY0y4kuVnuKjlE03BRqTw/njWYRNo=
|
||||||
|
github.com/yuin/goldmark v1.1.14/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
|
||||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package gold
|
package gold
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -8,6 +9,13 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
"github.com/yuin/goldmark/ast"
|
||||||
|
"github.com/yuin/goldmark/extension"
|
||||||
|
astext "github.com/yuin/goldmark/extension/ast"
|
||||||
|
"github.com/yuin/goldmark/parser"
|
||||||
|
"github.com/yuin/goldmark/renderer"
|
||||||
|
"github.com/yuin/goldmark/util"
|
||||||
bf "gopkg.in/russross/blackfriday.v2"
|
bf "gopkg.in/russross/blackfriday.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -83,21 +91,83 @@ func (tr *TermRenderer) Render(in string) string {
|
|||||||
|
|
||||||
// RenderBytes returns the markdown rendered into a byte slice.
|
// RenderBytes returns the markdown rendered into a byte slice.
|
||||||
func (tr *TermRenderer) RenderBytes(in []byte) []byte {
|
func (tr *TermRenderer) RenderBytes(in []byte) []byte {
|
||||||
return bf.Run(in, bf.WithRenderer(tr))
|
md := goldmark.New(
|
||||||
|
goldmark.WithExtensions(extension.GFM),
|
||||||
|
goldmark.WithParserOptions(
|
||||||
|
parser.WithAutoHeadingID(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
md.SetRenderer(
|
||||||
|
renderer.NewRenderer(
|
||||||
|
renderer.WithNodeRenderers(util.Prioritized(tr, 1000))))
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := md.Convert(in, &buf); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderNode renders a single markdown node.
|
// RegisterFuncs implements NodeRenderer.RegisterFuncs .
|
||||||
func (tr *TermRenderer) RenderNode(w io.Writer, node *bf.Node, entering bool) bf.WalkStatus {
|
func (r *TermRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||||
|
// blocks
|
||||||
|
reg.Register(ast.KindDocument, r.renderNode)
|
||||||
|
reg.Register(ast.KindHeading, r.renderNode)
|
||||||
|
reg.Register(ast.KindBlockquote, r.renderNode)
|
||||||
|
reg.Register(ast.KindCodeBlock, r.renderNode)
|
||||||
|
reg.Register(ast.KindFencedCodeBlock, r.renderNode)
|
||||||
|
reg.Register(ast.KindHTMLBlock, r.renderNode)
|
||||||
|
reg.Register(ast.KindList, r.renderNode)
|
||||||
|
reg.Register(ast.KindListItem, r.renderNode)
|
||||||
|
reg.Register(ast.KindParagraph, r.renderNode)
|
||||||
|
reg.Register(ast.KindTextBlock, r.renderNode)
|
||||||
|
reg.Register(ast.KindThematicBreak, r.renderNode)
|
||||||
|
|
||||||
|
// inlines
|
||||||
|
reg.Register(ast.KindAutoLink, r.renderNode)
|
||||||
|
reg.Register(ast.KindCodeSpan, r.renderNode)
|
||||||
|
reg.Register(ast.KindEmphasis, r.renderNode)
|
||||||
|
reg.Register(ast.KindImage, r.renderNode)
|
||||||
|
reg.Register(ast.KindLink, r.renderNode)
|
||||||
|
reg.Register(ast.KindRawHTML, r.renderNode)
|
||||||
|
reg.Register(ast.KindText, r.renderNode)
|
||||||
|
reg.Register(ast.KindString, r.renderNode)
|
||||||
|
|
||||||
|
// tables
|
||||||
|
reg.Register(astext.KindTable, r.renderNode)
|
||||||
|
reg.Register(astext.KindTableHeader, r.renderNode)
|
||||||
|
reg.Register(astext.KindTableRow, r.renderNode)
|
||||||
|
reg.Register(astext.KindTableCell, r.renderNode)
|
||||||
|
|
||||||
|
// definitions
|
||||||
|
reg.Register(astext.KindDefinitionList, r.renderNode)
|
||||||
|
reg.Register(astext.KindDefinitionTerm, r.renderNode)
|
||||||
|
reg.Register(astext.KindDefinitionDescription, r.renderNode)
|
||||||
|
|
||||||
|
// footnotes
|
||||||
|
reg.Register(astext.KindFootnote, r.renderNode)
|
||||||
|
reg.Register(astext.KindFootnoteList, r.renderNode)
|
||||||
|
reg.Register(astext.KindFootnoteLink, r.renderNode)
|
||||||
|
reg.Register(astext.KindFootnoteBackLink, r.renderNode)
|
||||||
|
|
||||||
|
// checkboxes
|
||||||
|
reg.Register(astext.KindTaskCheckBox, r.renderNode)
|
||||||
|
|
||||||
|
// strikethrough
|
||||||
|
reg.Register(astext.KindStrikethrough, r.renderNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *TermRenderer) renderNode(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
// _, _ = w.Write([]byte(node.Type.String()))
|
// _, _ = w.Write([]byte(node.Type.String()))
|
||||||
writeTo := w
|
writeTo := io.Writer(w)
|
||||||
bs := tr.context.blockStack
|
bs := tr.context.blockStack
|
||||||
|
|
||||||
// children get rendered by their parent
|
// children get rendered by their parent
|
||||||
if isChild(node) {
|
if isChild(node) {
|
||||||
return bf.GoToNext
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
e := tr.NewElement(node)
|
e := tr.NewElement(node, source)
|
||||||
if entering {
|
if entering {
|
||||||
// everything below the Document element gets rendered into a block buffer
|
// everything below the Document element gets rendered into a block buffer
|
||||||
if bs.Len() > 0 {
|
if bs.Len() > 0 {
|
||||||
@@ -108,8 +178,7 @@ func (tr *TermRenderer) RenderNode(w io.Writer, node *bf.Node, entering bool) bf
|
|||||||
if e.Renderer != nil {
|
if e.Renderer != nil {
|
||||||
err := e.Renderer.Render(writeTo, tr.context)
|
err := e.Renderer.Render(writeTo, tr.context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
return ast.WalkStop, err
|
||||||
return bf.Terminate
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -120,21 +189,20 @@ func (tr *TermRenderer) RenderNode(w io.Writer, node *bf.Node, entering bool) bf
|
|||||||
|
|
||||||
// if we're finished rendering the entire document,
|
// if we're finished rendering the entire document,
|
||||||
// flush to the real writer
|
// flush to the real writer
|
||||||
if node.Type == bf.Document {
|
if node.Type() == ast.TypeDocument {
|
||||||
writeTo = w
|
writeTo = w
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Finisher != nil {
|
if e.Finisher != nil {
|
||||||
err := e.Finisher.Finish(writeTo, tr.context)
|
err := e.Finisher.Finish(writeTo, tr.context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
return ast.WalkStop, err
|
||||||
return bf.Terminate
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, _ = writeTo.Write([]byte(e.Exiting))
|
_, _ = bs.Current().Block.Write([]byte(e.Exiting))
|
||||||
}
|
}
|
||||||
|
|
||||||
return bf.GoToNext
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderHeader renders the markdown's header.
|
// RenderHeader renders the markdown's header.
|
||||||
@@ -145,14 +213,14 @@ func (tr *TermRenderer) RenderHeader(w io.Writer, ast *bf.Node) {
|
|||||||
func (tr *TermRenderer) RenderFooter(w io.Writer, ast *bf.Node) {
|
func (tr *TermRenderer) RenderFooter(w io.Writer, ast *bf.Node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func isChild(node *bf.Node) bool {
|
func isChild(node ast.Node) bool {
|
||||||
if node.Parent == nil {
|
if node.Parent() == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// These types are already rendered by their parent
|
// These types are already rendered by their parent
|
||||||
switch node.Parent.Type {
|
switch node.Parent().Kind() {
|
||||||
case bf.Heading, bf.Link, bf.Image, bf.TableCell, bf.Emph, bf.Strong:
|
case ast.KindLink, ast.KindImage, ast.KindEmphasis, ast.KindBlockquote, astext.KindTableCell:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|||||||
+33
-15
@@ -1,13 +1,13 @@
|
|||||||
package gold
|
package gold
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/muesli/reflow"
|
"github.com/muesli/reflow"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HeadingElement struct {
|
type HeadingElement struct {
|
||||||
Text string
|
|
||||||
Level int
|
Level int
|
||||||
First bool
|
First bool
|
||||||
}
|
}
|
||||||
@@ -31,6 +31,25 @@ func (e *HeadingElement) Render(w io.Writer, ctx RenderContext) error {
|
|||||||
rules = cascadeStyles(false, rules, ctx.style[H6])
|
rules = cascadeStyles(false, rules, ctx.style[H6])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !e.First {
|
||||||
|
renderText(w, bs.Current().Style, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
be := BlockElement{
|
||||||
|
Block: &bytes.Buffer{},
|
||||||
|
Style: cascadeStyle(bs.Current().Style, rules, true),
|
||||||
|
}
|
||||||
|
bs.Push(be)
|
||||||
|
|
||||||
|
renderText(w, bs.Parent().Style, rules.Prefix)
|
||||||
|
renderText(w, bs.Current().Style, rules.StyledPrefix)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *HeadingElement) Finish(w io.Writer, ctx RenderContext) error {
|
||||||
|
bs := ctx.blockStack
|
||||||
|
rules := bs.Current().Style
|
||||||
|
|
||||||
var indent uint
|
var indent uint
|
||||||
var margin uint
|
var margin uint
|
||||||
if rules.Indent != nil {
|
if rules.Indent != nil {
|
||||||
@@ -43,7 +62,7 @@ func (e *HeadingElement) Render(w io.Writer, ctx RenderContext) error {
|
|||||||
iw := &IndentWriter{
|
iw := &IndentWriter{
|
||||||
Indent: indent + margin,
|
Indent: indent + margin,
|
||||||
IndentFunc: func(wr io.Writer) {
|
IndentFunc: func(wr io.Writer) {
|
||||||
renderText(w, bs.Current().Style, " ")
|
renderText(w, bs.Parent().Style, " ")
|
||||||
},
|
},
|
||||||
Forward: &AnsiWriter{
|
Forward: &AnsiWriter{
|
||||||
Forward: w,
|
Forward: w,
|
||||||
@@ -51,22 +70,21 @@ func (e *HeadingElement) Render(w io.Writer, ctx RenderContext) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
flow := reflow.NewReflow(int(bs.Width(ctx) - indent - margin*2))
|
flow := reflow.NewReflow(int(bs.Width(ctx) - indent - margin*2))
|
||||||
|
_, err := flow.Write(bs.Current().Block.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
flow.Close()
|
||||||
|
|
||||||
var pre string
|
_, err = iw.Write(flow.Bytes())
|
||||||
if !e.First {
|
|
||||||
pre = "\n"
|
|
||||||
}
|
|
||||||
el := &BaseElement{
|
|
||||||
Prefix: pre,
|
|
||||||
Token: e.Text,
|
|
||||||
Style: rules,
|
|
||||||
}
|
|
||||||
err := el.Render(flow, ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
flow.Close()
|
renderText(w, bs.Current().Style, rules.StyledSuffix)
|
||||||
_, err = iw.Write(flow.Bytes())
|
renderText(w, bs.Parent().Style, rules.Suffix)
|
||||||
return err
|
|
||||||
|
bs.Current().Block.Reset()
|
||||||
|
bs.Pop()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
package gold
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/muesli/reflow"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ListElement struct {
|
|
||||||
Nested bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ListElement) Render(w io.Writer, ctx RenderContext) error {
|
|
||||||
bs := ctx.blockStack
|
|
||||||
rules := ctx.style[List]
|
|
||||||
|
|
||||||
if !e.Nested {
|
|
||||||
_, _ = w.Write([]byte("\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
be := BlockElement{
|
|
||||||
Block: &bytes.Buffer{},
|
|
||||||
Style: cascadeStyle(bs.Current().Style, rules, true),
|
|
||||||
}
|
|
||||||
bs.Push(be)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ListElement) Finish(w io.Writer, ctx RenderContext) error {
|
|
||||||
bs := ctx.blockStack
|
|
||||||
rules := bs.Current().Style
|
|
||||||
|
|
||||||
renderText(bs.Current().Block, rules, rules.Suffix)
|
|
||||||
|
|
||||||
_, err := NewMarginWriter(ctx, w, rules).Write(
|
|
||||||
reflow.Bytes(bs.Current().Block.Bytes(), int(bs.Width(ctx))))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
bs.Current().Block.Reset()
|
|
||||||
bs.Pop()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -9,17 +9,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ParagraphElement struct {
|
type ParagraphElement struct {
|
||||||
InsideList bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ParagraphElement) Render(w io.Writer, ctx RenderContext) error {
|
func (e *ParagraphElement) Render(w io.Writer, ctx RenderContext) error {
|
||||||
bs := ctx.blockStack
|
bs := ctx.blockStack
|
||||||
var rules ElementStyle
|
var rules ElementStyle
|
||||||
|
|
||||||
if e.InsideList {
|
|
||||||
// list item
|
|
||||||
rules = ctx.style[List]
|
|
||||||
} else {
|
|
||||||
rules = ctx.style[Paragraph]
|
rules = ctx.style[Paragraph]
|
||||||
_, _ = w.Write([]byte("\n"))
|
_, _ = w.Write([]byte("\n"))
|
||||||
be := BlockElement{
|
be := BlockElement{
|
||||||
@@ -27,7 +22,6 @@ func (e *ParagraphElement) Render(w io.Writer, ctx RenderContext) error {
|
|||||||
Style: cascadeStyle(bs.Current().Style, rules, true),
|
Style: cascadeStyle(bs.Current().Style, rules, true),
|
||||||
}
|
}
|
||||||
bs.Push(be)
|
bs.Push(be)
|
||||||
}
|
|
||||||
|
|
||||||
renderText(w, bs.Current().Style, rules.Prefix)
|
renderText(w, bs.Current().Style, rules.Prefix)
|
||||||
return nil
|
return nil
|
||||||
@@ -38,11 +32,6 @@ func (e *ParagraphElement) Finish(w io.Writer, ctx RenderContext) error {
|
|||||||
rules := bs.Current().Style
|
rules := bs.Current().Style
|
||||||
|
|
||||||
keepNewlines := false
|
keepNewlines := false
|
||||||
if e.InsideList {
|
|
||||||
// remove indent & margin for list items
|
|
||||||
rules = bs.Current().Style
|
|
||||||
keepNewlines = true
|
|
||||||
}
|
|
||||||
|
|
||||||
renderText(bs.Current().Block, rules, rules.Suffix)
|
renderText(bs.Current().Block, rules, rules.Suffix)
|
||||||
|
|
||||||
@@ -61,8 +50,6 @@ func (e *ParagraphElement) Finish(w io.Writer, ctx RenderContext) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bs.Current().Block.Reset()
|
bs.Current().Block.Reset()
|
||||||
if !e.InsideList {
|
|
||||||
bs.Pop()
|
bs.Pop()
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,38 +16,54 @@ type StyleType int
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
Document StyleType = iota
|
Document StyleType = iota
|
||||||
|
|
||||||
|
// Block elements
|
||||||
|
Heading
|
||||||
|
Paragraph
|
||||||
BlockQuote
|
BlockQuote
|
||||||
List
|
List
|
||||||
|
|
||||||
|
// Text elements
|
||||||
|
Text
|
||||||
|
Emph
|
||||||
|
Strong
|
||||||
|
HorizontalRule
|
||||||
|
|
||||||
|
// ListItem styles
|
||||||
Item
|
Item
|
||||||
|
CheckedItem
|
||||||
Enumeration
|
Enumeration
|
||||||
Paragraph
|
|
||||||
Heading
|
// Links
|
||||||
|
Link
|
||||||
|
LinkText
|
||||||
|
|
||||||
|
// Images
|
||||||
|
Image
|
||||||
|
ImageText
|
||||||
|
|
||||||
|
// Code styles
|
||||||
|
CodeBlock
|
||||||
|
Code
|
||||||
|
|
||||||
|
// Table styles
|
||||||
|
Table
|
||||||
|
TableCell
|
||||||
|
TableHead
|
||||||
|
TableBody
|
||||||
|
TableRow
|
||||||
|
|
||||||
|
// HTML styles
|
||||||
|
HTMLBlock
|
||||||
|
HTMLSpan
|
||||||
|
|
||||||
|
// Style definitions for individual heading levels
|
||||||
H1
|
H1
|
||||||
H2
|
H2
|
||||||
H3
|
H3
|
||||||
H4
|
H4
|
||||||
H5
|
H5
|
||||||
H6
|
H6
|
||||||
HorizontalRule
|
|
||||||
Emph
|
|
||||||
Strong
|
|
||||||
Del
|
|
||||||
Link
|
|
||||||
LinkText
|
|
||||||
Image
|
|
||||||
ImageText
|
|
||||||
Text
|
|
||||||
HTMLBlock
|
|
||||||
CodeBlock
|
|
||||||
Softbreak
|
|
||||||
Hardbreak
|
|
||||||
Code
|
|
||||||
HTMLSpan
|
|
||||||
Table
|
|
||||||
TableCell
|
|
||||||
TableHead
|
|
||||||
TableBody
|
|
||||||
TableRow
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ElementStyle struct {
|
type ElementStyle struct {
|
||||||
@@ -67,6 +83,8 @@ type ElementStyle struct {
|
|||||||
Theme string `json:"theme"`
|
Theme string `json:"theme"`
|
||||||
Prefix string `json:"prefix"`
|
Prefix string `json:"prefix"`
|
||||||
Suffix string `json:"suffix"`
|
Suffix string `json:"suffix"`
|
||||||
|
StyledPrefix string `json:"styled_prefix"`
|
||||||
|
StyledSuffix string `json:"styled_suffix"`
|
||||||
Format string `json:"format"`
|
Format string `json:"format"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,6 +253,8 @@ func keyToType(key string) (StyleType, error) {
|
|||||||
return List, nil
|
return List, nil
|
||||||
case "item":
|
case "item":
|
||||||
return Item, nil
|
return Item, nil
|
||||||
|
case "checked_item":
|
||||||
|
return CheckedItem, nil
|
||||||
case "enumeration":
|
case "enumeration":
|
||||||
return Enumeration, nil
|
return Enumeration, nil
|
||||||
case "paragraph":
|
case "paragraph":
|
||||||
@@ -259,8 +279,6 @@ func keyToType(key string) (StyleType, error) {
|
|||||||
return Emph, nil
|
return Emph, nil
|
||||||
case "strong":
|
case "strong":
|
||||||
return Strong, nil
|
return Strong, nil
|
||||||
case "del":
|
|
||||||
return Del, nil
|
|
||||||
case "link":
|
case "link":
|
||||||
return Link, nil
|
return Link, nil
|
||||||
case "link_text":
|
case "link_text":
|
||||||
@@ -275,10 +293,6 @@ func keyToType(key string) (StyleType, error) {
|
|||||||
return HTMLBlock, nil
|
return HTMLBlock, nil
|
||||||
case "code_block":
|
case "code_block":
|
||||||
return CodeBlock, nil
|
return CodeBlock, nil
|
||||||
case "softbreak":
|
|
||||||
return Softbreak, nil
|
|
||||||
case "hardbreak":
|
|
||||||
return Hardbreak, nil
|
|
||||||
case "code":
|
case "code":
|
||||||
return Code, nil
|
return Code, nil
|
||||||
case "html_span":
|
case "html_span":
|
||||||
|
|||||||
Reference in New Issue
Block a user