Browse Source

initial commit

main
Nicolas Massé 5 years ago
commit
111683b59e
  1. 16
      README.md
  2. 8
      go.mod
  3. 5
      go.sum
  4. 191
      main.go
  5. 154
      test/reference.md

16
README.md

@ -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

8
go.mod

@ -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
)

5
go.sum

@ -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=

191
main.go

@ -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
}

154
test/reference.md

@ -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)
![Hmm](http://example.org/image.png)
![Alt text](/path/to/img.jpg "Optional title") ![Alt text](/path/to/img.jpg "Hello \" 世界")
~~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…
Cancel
Save