commit
111683b59e
5 changed files with 374 additions and 0 deletions
@ -0,0 +1,16 @@ |
|||||
|
# A Markdown Renderer for the BlackFriday library |
||||
|
|
||||
|
## Use case |
||||
|
|
||||
|
This renderer enables "Markdown to Markdown" processing. |
||||
|
|
||||
|
## Remaining work |
||||
|
|
||||
|
* Markdown tables |
||||
|
* HTML elements |
||||
|
* Definition lists |
||||
|
* Fenced code blocks, quotes, and paragraph when part of a list |
||||
|
|
||||
|
## License |
||||
|
|
||||
|
Licensed under the MIT License |
||||
@ -0,0 +1,8 @@ |
|||||
|
module github.com/nmasse-itix/blackfriday-markdown-renderer |
||||
|
|
||||
|
go 1.14 |
||||
|
|
||||
|
require ( |
||||
|
github.com/russross/blackfriday/v2 v2.0.1 |
||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect |
||||
|
) |
||||
@ -0,0 +1,5 @@ |
|||||
|
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= |
||||
|
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= |
||||
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= |
||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= |
||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= |
||||
@ -0,0 +1,191 @@ |
|||||
|
package bfmdrenderer |
||||
|
|
||||
|
import ( |
||||
|
"io" |
||||
|
"log" |
||||
|
"strconv" |
||||
|
|
||||
|
bf "github.com/russross/blackfriday/v2" |
||||
|
) |
||||
|
|
||||
|
// Option defines the functional option type
|
||||
|
type Option func(r *Renderer) |
||||
|
|
||||
|
// NewRenderer will return a new renderer with sane defaults
|
||||
|
func NewRenderer(options ...Option) *Renderer { |
||||
|
r := &Renderer{} |
||||
|
for _, option := range options { |
||||
|
option(r) |
||||
|
} |
||||
|
return r |
||||
|
} |
||||
|
|
||||
|
// Renderer is a custom Blackfriday renderer
|
||||
|
type Renderer struct { |
||||
|
paragraphDecoration []byte |
||||
|
nestedListLevel int |
||||
|
nestedListDecoration []byte |
||||
|
orderedListCounters []int |
||||
|
} |
||||
|
|
||||
|
// Taken from the black friday HTML renderer
|
||||
|
func skipParagraphTags(node *bf.Node) bool { |
||||
|
parent := node.Parent |
||||
|
if parent != nil && parent.Type == bf.BlockQuote { |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
grandparent := node.Parent.Parent |
||||
|
if grandparent == nil || grandparent.Type != bf.List { |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
return grandparent.Type == bf.List && grandparent.Tight |
||||
|
} |
||||
|
|
||||
|
// RenderNode satisfies the Renderer interface
|
||||
|
func (r *Renderer) RenderNode(w io.Writer, node *bf.Node, entering bool) bf.WalkStatus { |
||||
|
switch node.Type { |
||||
|
case bf.Document: |
||||
|
return bf.GoToNext |
||||
|
case bf.BlockQuote: |
||||
|
if entering { |
||||
|
r.paragraphDecoration = append(r.paragraphDecoration, byte('>'), byte(' ')) |
||||
|
} else { |
||||
|
r.paragraphDecoration = r.paragraphDecoration[:len(r.paragraphDecoration)-2] |
||||
|
} |
||||
|
return bf.GoToNext |
||||
|
case bf.List: |
||||
|
if entering { |
||||
|
r.orderedListCounters = append(r.orderedListCounters, 0) |
||||
|
r.nestedListLevel++ |
||||
|
if r.nestedListLevel > 1 { |
||||
|
r.nestedListDecoration = append(r.nestedListDecoration, byte(' '), byte(' ')) |
||||
|
} |
||||
|
} else { |
||||
|
if r.nestedListLevel > 1 { |
||||
|
r.nestedListDecoration = r.nestedListDecoration[:len(r.nestedListDecoration)-2] |
||||
|
} else { |
||||
|
w.Write([]byte("\n")) |
||||
|
} |
||||
|
r.nestedListLevel-- |
||||
|
r.orderedListCounters = r.orderedListCounters[:len(r.orderedListCounters)-1] |
||||
|
} |
||||
|
|
||||
|
return bf.GoToNext |
||||
|
case bf.Item: |
||||
|
if entering { |
||||
|
w.Write(r.nestedListDecoration) |
||||
|
if node.Parent.ListFlags&bf.ListTypeOrdered != 0 { |
||||
|
r.orderedListCounters[len(r.orderedListCounters)-1]++ |
||||
|
w.Write([]byte(strconv.Itoa(r.orderedListCounters[len(r.orderedListCounters)-1]))) |
||||
|
w.Write([]byte{node.ListData.Delimiter}) |
||||
|
w.Write([]byte(" ")) |
||||
|
} else if node.Parent.ListFlags&bf.ListTypeTerm != 0 { |
||||
|
log.Println("Definition lists not implemented by Renderer") |
||||
|
} else { |
||||
|
w.Write([]byte{node.ListData.BulletChar}) |
||||
|
w.Write([]byte(" ")) |
||||
|
} |
||||
|
} |
||||
|
return bf.GoToNext |
||||
|
case bf.Paragraph: |
||||
|
if entering { |
||||
|
w.Write(r.paragraphDecoration) |
||||
|
} else { |
||||
|
w.Write([]byte("\n")) |
||||
|
if !skipParagraphTags(node) { |
||||
|
w.Write([]byte("\n")) |
||||
|
} |
||||
|
} |
||||
|
return bf.GoToNext |
||||
|
case bf.Heading: |
||||
|
if entering { |
||||
|
for i := 0; i < node.Level; i++ { |
||||
|
w.Write([]byte("#")) |
||||
|
} |
||||
|
w.Write([]byte(" ")) |
||||
|
} else { |
||||
|
w.Write([]byte("\n\n")) |
||||
|
} |
||||
|
return bf.GoToNext |
||||
|
case bf.HorizontalRule: |
||||
|
w.Write([]byte("---\n\n")) |
||||
|
return bf.GoToNext |
||||
|
case bf.Emph: |
||||
|
w.Write([]byte("*")) |
||||
|
return bf.GoToNext |
||||
|
case bf.Strong: |
||||
|
w.Write([]byte("**")) |
||||
|
return bf.GoToNext |
||||
|
case bf.Del: |
||||
|
w.Write([]byte("~~")) |
||||
|
return bf.GoToNext |
||||
|
case bf.Link: |
||||
|
if entering { |
||||
|
w.Write([]byte("[")) |
||||
|
} else { |
||||
|
w.Write([]byte("](")) |
||||
|
w.Write(node.LinkData.Destination) |
||||
|
w.Write([]byte(")")) |
||||
|
} |
||||
|
return bf.GoToNext |
||||
|
case bf.Image: |
||||
|
if entering { |
||||
|
w.Write([]byte("![")) |
||||
|
} else { |
||||
|
w.Write([]byte("](")) |
||||
|
w.Write(node.LinkData.Destination) |
||||
|
w.Write([]byte(")")) |
||||
|
} |
||||
|
return bf.GoToNext |
||||
|
case bf.Code: |
||||
|
w.Write([]byte("`")) |
||||
|
w.Write(node.Literal) |
||||
|
w.Write([]byte("`")) |
||||
|
return bf.GoToNext |
||||
|
case bf.Text: |
||||
|
w.Write(node.Literal) |
||||
|
return bf.GoToNext |
||||
|
case bf.CodeBlock: |
||||
|
w.Write([]byte("```")) |
||||
|
w.Write(node.CodeBlockData.Info) |
||||
|
w.Write([]byte("\n")) |
||||
|
w.Write(node.Literal) |
||||
|
w.Write([]byte("```\n\n")) |
||||
|
return bf.GoToNext |
||||
|
case bf.Softbreak: |
||||
|
log.Println("Soft breaks not implemented by renderer") |
||||
|
case bf.Hardbreak: |
||||
|
w.Write([]byte(" \n")) |
||||
|
return bf.GoToNext |
||||
|
case bf.HTMLBlock: |
||||
|
fallthrough |
||||
|
case bf.HTMLSpan: |
||||
|
log.Println("HTML elements not implemented by renderer") |
||||
|
case bf.Table: |
||||
|
fallthrough |
||||
|
case bf.TableCell: |
||||
|
fallthrough |
||||
|
case bf.TableHead: |
||||
|
fallthrough |
||||
|
case bf.TableBody: |
||||
|
fallthrough |
||||
|
case bf.TableRow: |
||||
|
log.Println("Markdown tables not implemented by renderer") |
||||
|
default: |
||||
|
log.Printf("Unknown BlackFriday Node type '%s'\n", node.Type) |
||||
|
} |
||||
|
|
||||
|
return bf.SkipChildren |
||||
|
} |
||||
|
|
||||
|
// RenderHeader satisfies the Renderer interface
|
||||
|
func (r *Renderer) RenderHeader(w io.Writer, ast *bf.Node) { |
||||
|
// Nothing required here
|
||||
|
} |
||||
|
|
||||
|
// RenderFooter satisfies the Renderer interface
|
||||
|
func (r *Renderer) RenderFooter(w io.Writer, ast *bf.Node) { |
||||
|
// Nothing required here
|
||||
|
} |
||||
@ -0,0 +1,154 @@ |
|||||
|
# An h1 header |
||||
|
|
||||
|
Paragraphs are separated by a blank line. |
||||
|
|
||||
|
2nd paragraph. *Italic*, **bold**, `monospace`. Itemized lists look like: |
||||
|
|
||||
|
- this one |
||||
|
- that one |
||||
|
- the other one |
||||
|
|
||||
|
Nothing to note here. |
||||
|
|
||||
|
> Block quotes are written like so. |
||||
|
> |
||||
|
> > They can be nested. |
||||
|
> |
||||
|
> They can span multiple paragraphs, if you like. |
||||
|
|
||||
|
- Item 1 |
||||
|
- Item 2 |
||||
|
- Item 2a |
||||
|
- Item 2a |
||||
|
- Item 2b |
||||
|
- Item 3 |
||||
|
|
||||
|
Hmm. |
||||
|
|
||||
|
1. Item 1 |
||||
|
2. Item 2 |
||||
|
1. Blah. |
||||
|
2. Blah. |
||||
|
3. Item 3 |
||||
|
- Item 3a |
||||
|
- Item 3b |
||||
|
|
||||
|
Large spacing... |
||||
|
|
||||
|
1. An entire paragraph is written here, and bigger spacing between list items is desired. This is supported too. |
||||
|
|
||||
|
2. Item 2 |
||||
|
|
||||
|
1. Blah. |
||||
|
|
||||
|
2. Blah. |
||||
|
|
||||
|
3. Item 3 |
||||
|
|
||||
|
- Item 3a |
||||
|
|
||||
|
- Item 3b |
||||
|
|
||||
|
Last paragraph here. |
||||
|
|
||||
|
## An h2 header |
||||
|
|
||||
|
- Paragraph right away. |
||||
|
- **Big item**: Right away after header. |
||||
|
|
||||
|
[Visit GitHub!](www.github.com) |
||||
|
|
||||
|
 |
||||
|
|
||||
|
  |
||||
|
|
||||
|
~~Mistaken text.~~ |
||||
|
|
||||
|
This (**should** be *fine*). |
||||
|
|
||||
|
A \> B. |
||||
|
|
||||
|
It's possible to backslash escape \<html\> tags and \`backticks\`. They are treated as text. |
||||
|
|
||||
|
1986\. What a great season. |
||||
|
|
||||
|
The year was 1986. What a great season. |
||||
|
|
||||
|
\*literal asterisks\*. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
http://example.com |
||||
|
|
||||
|
Now a [link](www.github.com) in a paragraph. End with [link_underscore.go](www.github.com). |
||||
|
|
||||
|
- [Link](www.example.com) |
||||
|
|
||||
|
### An h3 header |
||||
|
|
||||
|
Here's a numbered list: |
||||
|
|
||||
|
1. first item |
||||
|
2. second item |
||||
|
3. third item |
||||
|
|
||||
|
By the way, instead of indenting the block, you can use delimited blocks, if you like: |
||||
|
|
||||
|
``` |
||||
|
define foobar() { |
||||
|
print "Welcome to flavor country!"; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
(which makes copying & pasting easier). You can optionally mark the delimited block for Pandoc to syntax highlight it: |
||||
|
|
||||
|
```Go |
||||
|
func main() { |
||||
|
println("Hi.") |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Nested Lists |
||||
|
|
||||
|
### Codeblock within list |
||||
|
|
||||
|
- list1 |
||||
|
|
||||
|
```C |
||||
|
if (i == 5) |
||||
|
break; |
||||
|
``` |
||||
|
|
||||
|
### Blockquote within list |
||||
|
|
||||
|
- list1 |
||||
|
|
||||
|
> This a quote within a list. |
||||
|
|
||||
|
### Multi-level nested |
||||
|
|
||||
|
- Item 1 |
||||
|
|
||||
|
Another paragraph inside this list item is indented just like the previous paragraph. |
||||
|
|
||||
|
- Item 2 |
||||
|
|
||||
|
- Item 2a |
||||
|
|
||||
|
Things go here. |
||||
|
|
||||
|
> This a quote within a list. |
||||
|
|
||||
|
And they stay here. |
||||
|
|
||||
|
- Item 2b |
||||
|
|
||||
|
- Item 3 |
||||
|
|
||||
|
## Line Breaks |
||||
|
|
||||
|
Some text with two trailing spaces for linebreak. |
||||
|
More text immediately after. |
||||
|
Useful for writing poems. |
||||
|
|
||||
|
Done. |
||||
Loading…
Reference in new issue