Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions extended/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package main

import (
"bytes"
"fmt"
"testing"

"github.com/Kunde21/markdownfmt/v2/markdownfmt"
meta "github.com/yuin/goldmark-meta"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/util"
)

type metaRender struct{}

// RegisterFuncs ...
func (m metaRender) RegisterFuncs(r renderer.NodeRendererFuncRegisterer) {
r.Register(meta.KindMetadata, renderMeta)
}

func renderMeta(w util.BufWriter, src []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}
m, ok := n.(*meta.Metadata)
if !ok {
fmt.Fprintf(w, "%v", n)
}
fmt.Fprintln(w, "---------------------")
for _, v := range m.Items {
fmt.Fprintf(w, "%s: %s\n", v.Key, v.Value)
}
fmt.Fprintln(w, "---------------------")
return ast.WalkContinue, nil
}

func TestMeta(t *testing.T) {
mdfmt := markdownfmt.NewGoldmark()
meta.New().Extend(mdfmt)
mdfmt.Renderer().AddOptions(
renderer.WithNodeRenderers(
util.Prioritized(metaRender{}, 500),
),
)
source := `---
Title: goldmark-meta
Summary: Add YAML metadata to the document
Tags:
- markdown
- goldmark
---

# Hello goldmark-meta
`

var buf bytes.Buffer
if err := mdfmt.Convert([]byte(source), &buf); err != nil {
panic(err)
}
fmt.Print(buf.String())
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ require (
github.com/mattn/go-runewidth v0.0.9
github.com/pkg/errors v0.9.1
github.com/yuin/goldmark v1.3.1
github.com/yuin/goldmark-meta v1.0.0
)

replace github.com/yuin/goldmark-meta => github.com/13rac1/goldmark-meta v1.0.1-0.20201214084408-d2487db1f3f5

go 1.13
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
github.com/13rac1/goldmark-meta v1.0.1-0.20201214084408-d2487db1f3f5 h1:mGs3zaTiNwPYyUpHfSdv2SOzQBXWHQNws7FtHrgeyrI=
github.com/13rac1/goldmark-meta v1.0.1-0.20201214084408-d2487db1f3f5/go.mod h1:zsNNOrZ4nLuyHAJeLQEZcQat8dm70SmB2kHbls092Gc=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.1 h1:eVwehsLsZlCJCwXyGLgg+Q4iFWE/eTIMG0e8waCmm/I=
github.com/yuin/goldmark v1.3.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark-meta v1.0.0 h1:ScsatUIT2gFS6azqzLGUjgOnELsBOxMXerM3ogdJhAM=
github.com/yuin/goldmark-meta v1.0.0/go.mod h1:zsNNOrZ4nLuyHAJeLQEZcQat8dm70SmB2kHbls092Gc=
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=
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

"github.com/Kunde21/markdownfmt/v2/markdown"
"github.com/Kunde21/markdownfmt/v2/markdownfmt"
"github.com/yuin/goldmark/renderer"
)

var (
Expand Down Expand Up @@ -58,7 +59,7 @@ func processFile(filename string, in io.Reader, out io.Writer) error {
return err
}

var opts []markdown.Option
var opts []renderer.Option
if *underlineHeadings {
opts = append(opts, markdown.WithUnderlineHeadings())
}
Expand Down
71 changes: 55 additions & 16 deletions markdown/renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,56 +33,85 @@ var _ renderer.Renderer = &Renderer{}
// Renderer allows to render markdown AST into markdown bytes in consistent format.
// Render is reusable across Renders, it holds configuration only.
type Renderer struct {
conf *renderer.Config
underlineHeadings bool
}

func (mr *Renderer) AddOptions(...renderer.Option) {
// goldmark weirdness, just ignore (called with just HTML options...)
}

func (mr *Renderer) AddMarkdownOptions(opts ...Option) {
func (mr *Renderer) AddOptions(opts ...renderer.Option) {
for _, o := range opts {
o(mr)
o.SetConfig(mr.conf)
}
}

type Option func(r *Renderer)
type Option func(r *renderer.Config)

func WithUnderlineHeadings() Option {
return func(r *Renderer) {
r.underlineHeadings = true
}
func (o Option) SetConfig(r *renderer.Config) { o(r) }

func WithUnderlineHeadings() renderer.Option {
return Option(func(r *renderer.Config) {
if r.Options == nil {
r.Options = map[renderer.OptionName]interface{}{}
}
r.Options["markdownfmt.underlineHeadings"] = true
})
}

func NewRenderer() *Renderer {
return &Renderer{}
return &Renderer{conf: &renderer.Config{}}
}

// render represents a single markdown rendering operation.
type render struct {
mr *Renderer
mr *Renderer
overrides map[ast.NodeKind]renderer.NodeRendererFunc

// TODO(bwplotka): Wrap it with something that catch errors.
w *lineIndentWriter
source []byte
}

// Register override method
func (r *render) Register(k ast.NodeKind, f renderer.NodeRendererFunc) {
if r.overrides == nil {
r.overrides = map[ast.NodeKind]renderer.NodeRendererFunc{}
}
r.overrides[k] = f
}

func (mr *Renderer) newRender(w io.Writer, source []byte) *render {
return &render{
if op, ok := mr.conf.Options["markdownfmt.underlineHeadings"]; ok {
mr.underlineHeadings = op.(bool)
}
mr.conf.NodeRenderers.Sort()
rdr := &render{
mr: mr,
w: wrapWithLineIndentWriter(w),
source: source,
}

for _, nr := range mr.conf.NodeRenderers {
if nr.Value == nil {
continue
}
if r, ok := nr.Value.(renderer.NodeRenderer); ok {
r.RegisterFuncs(rdr)
}
}

return rdr
}

// Render renders the given AST node to the given buffer with the given Renderer.
// NOTE: This is the entry point used by Goldmark.
func (mr *Renderer) Render(w io.Writer, source []byte, node ast.Node) error {
// Perform DFS.
return ast.Walk(node, mr.newRender(w, source).renderNode)
rdr := mr.newRender(w, source)
defer rdr.w.Flush()
return ast.Walk(node, rdr.renderNode)
}

func (r *render) renderNode(node ast.Node, entering bool) (ast.WalkStatus, error) {
defer r.w.Flush()
if entering && node.PreviousSibling() != nil {
switch node.(type) {
// All Block types (except few) usually have 2x new lines before itself when they are non-first siblings.
Expand All @@ -105,6 +134,12 @@ func (r *render) renderNode(node ast.Node, entering bool) (ast.WalkStatus, error
}
}

// NOTE: checking override here risks picking up render options from extensions
// like GFM, which registers all of its HTML render functions.
// if ovr, ok := r.overrides[node.Kind()]; ok {
// return ovr(r.w, r.source, node, entering)
// }

switch tnode := node.(type) {
case *ast.Document:
if entering {
Expand Down Expand Up @@ -303,7 +338,11 @@ func (r *render) renderNode(node ast.Node, entering bool) (ast.WalkStatus, error
case *extAST.TableRow, *extAST.TableHeader:
return ast.WalkStop, errors.Errorf("%v element detected, but table should be rendered in renderTable instead", tnode.Kind().String())
default:
return ast.WalkStop, errors.Errorf("detected unexpected tree type %s", tnode.Kind().String())
if ovr, ok := r.overrides[node.Kind()]; ok {
fmt.Println("extended kind:", node.Kind())
return ovr(r.w, r.source, node, entering)
}
return ast.WalkStop, errors.Errorf("detected unexpected node %s", tnode.Kind().String())
}
return ast.WalkContinue, nil
}
Expand Down
5 changes: 3 additions & 2 deletions markdown/writer_indent.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package markdown

import (
"bufio"
"bytes"
"io"

Expand All @@ -9,7 +10,7 @@ import (

// lineIndentWriter wraps io.Writer and adds given indent everytime new line is created .
type lineIndentWriter struct {
io.Writer
*bufio.Writer

indent []byte
whitespace []byte
Expand All @@ -19,7 +20,7 @@ type lineIndentWriter struct {
}

func wrapWithLineIndentWriter(w io.Writer) *lineIndentWriter {
return &lineIndentWriter{Writer: w, previousCharWasNewLine: true}
return &lineIndentWriter{Writer: bufio.NewWriter(w), previousCharWasNewLine: true}
}

func (l *lineIndentWriter) UpdateIndent(node ast.Node, entering bool) {
Expand Down
16 changes: 7 additions & 9 deletions markdownfmt/markdownfmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,11 @@ import (
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
)

// TODO(karel): unused, can we delete?
func NewParser() parser.Parser {
return NewGoldmark().Parser()
}

func NewGoldmark(opts ...markdown.Option) goldmark.Markdown {
func NewGoldmark(opts ...renderer.Option) goldmark.Markdown {
mr := markdown.NewRenderer()
mr.AddMarkdownOptions(opts...)
extensions := []goldmark.Extender{
extension.GFM,
}
Expand All @@ -28,14 +23,17 @@ func NewGoldmark(opts ...markdown.Option) goldmark.Markdown {
gm := goldmark.New(
goldmark.WithExtensions(extensions...),
goldmark.WithParserOptions(parserOptions...),
goldmark.WithRenderer(mr),
)
// Set renderer outside constructor to reset the
// html render functions registered by GFM.
gm.SetRenderer(mr)
gm.Renderer().AddOptions(opts...)

return gm
}

// Process formats given Markdown.
func Process(filename string, src []byte, opts ...markdown.Option) ([]byte, error) {
func Process(filename string, src []byte, opts ...renderer.Option) ([]byte, error) {
text, err := readSource(filename, src)
if err != nil {
return nil, err
Expand Down