-
Notifications
You must be signed in to change notification settings - Fork 2
/
gomarkdown.go
117 lines (99 loc) · 3.13 KB
/
gomarkdown.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package main
import (
"bytes"
"fmt"
"io"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/ast"
mdhtml "github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
"github.com/alecthomas/chroma"
"github.com/alecthomas/chroma/formatters/html"
"github.com/alecthomas/chroma/lexers"
"github.com/alecthomas/chroma/styles"
)
const TOC = "[TOC]"
var extensions = parser.CommonExtensions |
parser.Attributes |
parser.AutoHeadingIDs |
parser.Mmark |
parser.NoEmptyLineBeforeBlock |
parser.OrderedListStart |
parser.SuperSubscript
func makeRenderer(formatter *html.Formatter, style *chroma.Style) *mdhtml.Renderer {
htmlFlags := mdhtml.RendererOptions{
Flags: mdhtml.CommonFlags |
mdhtml.Smartypants |
mdhtml.SmartypantsDashes |
mdhtml.SmartypantsFractions |
mdhtml.SmartypantsLatexDashes,
RenderNodeHook: myRenderHook(formatter, style),
}
return mdhtml.NewRenderer(htmlFlags)
}
// https://github.com/alecthomas/chroma#supported-languages
func makeChromaStyle() (*chroma.Style, error) {
styleName := "monokailight"
style := styles.Get(styleName)
if style == nil {
return nil, fmt.Errorf("Failed to get style=%s", styleName)
}
return style, nil
}
// https://github.com/alecthomas/chroma/blob/master/quick/quick.go
func htmlHighlight(w io.Writer, formatter *html.Formatter, style *chroma.Style, source, lang, defaultLang string) error {
if lang == "" {
lang = defaultLang
}
l := lexers.Get(lang)
if l == nil {
l = lexers.Analyse(source)
}
if l == nil {
l = lexers.Fallback
}
l = chroma.Coalesce(l)
it, err := l.Tokenise(nil, source)
if err != nil {
return err
}
return formatter.Format(w, style, it)
}
// an actual rendering of Paragraph is more complicated
func renderCode(w io.Writer, formatter *html.Formatter, style *chroma.Style, codeBlock *ast.CodeBlock, entering bool) {
defaultLang := ""
lang := string(codeBlock.Info)
htmlHighlight(w, formatter, style, string(codeBlock.Literal), lang, defaultLang)
}
func myRenderHook(formatter *html.Formatter, style *chroma.Style) mdhtml.RenderNodeFunc {
return func(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
if code, ok := node.(*ast.CodeBlock); ok {
renderCode(w, formatter, style, code, entering)
return ast.GoToNext, true
}
return ast.GoToNext, false
}
}
func renderMarkdown(m []byte) ([]byte, []byte, error) {
// Parse markdown (Note: bits are not thread safe, so create each time)
myFormatter := html.New(html.WithClasses(true), html.TabWidth(2))
myParser := parser.NewWithExtensions(extensions).Parse(m)
// Extract chroma style
style, err := makeChromaStyle()
if err != nil {
return nil, nil, err
}
// Render body
body := bytes.NewBufferString("")
body.Write(markdown.Render(myParser, makeRenderer(myFormatter, style)))
// Render CSS
css := bytes.NewBufferString("")
myFormatter.WriteCSS(css, style)
// Render TOC
toc := bytes.NewBufferString("")
mdhtml.NewRenderer(mdhtml.RendererOptions{Flags: mdhtml.TOC}).RenderHeader(toc, myParser)
// Inject the TOC into the body
b := bytes.Replace(body.Bytes(), []byte(TOC), toc.Bytes(), 1)
// Render css and body
return css.Bytes(), b, nil
}