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