Switch to goldmark parser

This commit is contained in:
Christian Muehlhaeuser
2019-12-13 03:00:45 +01:00
parent 30ed5cc5ef
commit 07f52dd42a
9 changed files with 397 additions and 256 deletions
+25
View File
@@ -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)
}
+202 -130
View File
@@ -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{}
} }
} }
+1
View File
@@ -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
) )
+2
View File
@@ -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=
+85 -17
View File
@@ -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
View File
@@ -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
} }
-46
View File
@@ -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
}
+7 -20
View File
@@ -9,25 +9,19 @@ 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 { rules = ctx.style[Paragraph]
// list item _, _ = w.Write([]byte("\n"))
rules = ctx.style[List] be := BlockElement{
} else { Block: &bytes.Buffer{},
rules = ctx.style[Paragraph] Style: cascadeStyle(bs.Current().Style, rules, true),
_, _ = w.Write([]byte("\n"))
be := BlockElement{
Block: &bytes.Buffer{},
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
} }
+42 -28
View File
@@ -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":