forked from abhinav/goldmark-mermaid
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver_render.go
148 lines (128 loc) · 3.32 KB
/
server_render.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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package mermaid
import (
"bytes"
"fmt"
"os"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/util"
)
// ServerRenderer renders Mermaid diagrams into images server-side.
//
// It operates by replacing mermaid code blocks in your document
// with SVGs.
type ServerRenderer struct {
// MMDC is the MermaidJS CLI that we'll use
// to render Mermaid diagrams server-side.
//
// Uses CLI by default.
MMDC MMDC
// Theme for mermaid diagrams.
//
// Values include "dark", "default", "forest", and "neutral".
// See MermaidJS documentation for a full list.
Theme string
}
// RegisterFuncs registers the renderer for Mermaid blocks with the provided
// Goldmark Registerer.
func (r *ServerRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(Kind, r.Render)
// Normally, we won't hit this
// because Transformer won't add ScriptBlocks for ServerRenderer.
//
// Guard against the possibility that the document used a different
// transformer.
reg.Register(ScriptKind, func(util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) {
return ast.WalkContinue, nil // no-op
})
}
// Render renders [Block] nodes.
func (r *ServerRenderer) Render(w util.BufWriter, src []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
var mmdc MMDC = DefaultMMDC
if r.MMDC != nil {
mmdc = r.MMDC
}
n := node.(*Block)
if !entering {
w.WriteString("</div>")
return ast.WalkContinue, nil
}
w.WriteString(`<div class="mermaid">`)
var buff bytes.Buffer
lines := n.Lines()
for i := 0; i < lines.Len(); i++ {
line := lines.At(i)
buff.Write(line.Value(src))
}
if buff.Len() == 0 {
return ast.WalkContinue, nil
}
svgout, err := (&mermaidGenerator{
MMDC: mmdc,
Theme: r.Theme,
}).Generate(buff.Bytes())
if err != nil {
return ast.WalkContinue, fmt.Errorf("generate svg: %w", err)
}
_, err = w.Write(svgout)
return ast.WalkContinue, err
}
type mermaidGenerator struct {
MMDC MMDC
Theme string
}
func (d *mermaidGenerator) Generate(src []byte) (_ []byte, err error) {
input, err := os.CreateTemp("", "in.*.mermaid")
if err != nil {
return nil, err
}
defer os.Remove(input.Name()) // ignore error
_, err = input.Write(src)
if err == nil {
err = input.Close()
}
if err != nil {
return nil, fmt.Errorf("write input: %w", err)
}
output, err := os.CreateTemp("", "out.*.svg")
if err != nil {
return nil, err
}
defer os.Remove(output.Name()) // ignore error
if err := output.Close(); err != nil {
return nil, err
}
args := []string{
"--input", input.Name(),
"--output", output.Name(),
"--outputFormat", "svg",
"--quiet",
}
if len(d.Theme) > 0 {
args = append(args, "--theme", d.Theme)
}
cmd := d.MMDC.Command(args...)
// If the user-provided MMDC didn't set Stdout/Stderr,
// capture its output and if anything fails beyond this point,
// include the output in the error.
var cmdout bytes.Buffer
defer func() {
if err != nil && cmdout.Len() > 0 {
err = fmt.Errorf("%w\noutput:\n%s", err, cmdout.String())
}
}()
if cmd.Stdout == nil {
cmd.Stdout = &cmdout
}
if cmd.Stderr == nil {
cmd.Stderr = &cmdout
}
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("mmdc: %w", err)
}
out, err := os.ReadFile(output.Name())
if err != nil {
return nil, fmt.Errorf("read svg: %w", err)
}
return out, nil
}