-
Notifications
You must be signed in to change notification settings - Fork 4
/
cli.go
135 lines (118 loc) · 3.04 KB
/
cli.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
package mermaid
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
)
// CLI provides access to the MermaidJS CLI.
// Use it with [CLICompiler] to override how the "mmdc" CLI is invoked.
type CLI interface {
// CommandContext builds an exec.Cmd to run the MermaidJS CLI
// with the provided arguments.
//
// The list of arguments DOES NOT include 'mmdc'.
CommandContext(context.Context, ...string) *exec.Cmd
}
type mmdcCLI struct{ Path string }
// DefaultCLI is a [CLI] implementation that invokes the "mmdc" CLI
// by searching $PATH for it.
var DefaultCLI = MMDC("")
// MMDC returns a [CLI] implementation that invokes the "mmdc" CLI
// with the provided path.
//
// If path is empty, $PATH will be searched for "mmdc".
func MMDC(path string) CLI {
return &mmdcCLI{Path: path}
}
func (c *mmdcCLI) CommandContext(ctx context.Context, args ...string) *exec.Cmd {
path := c.Path
if path == "" {
path = "mmdc"
}
return exec.CommandContext(ctx, path, args...)
}
// CLICompiler compiles Mermaid diagrams into images
// by shell-executing the "mmdc" command.
//
// Plug it into [ServerRenderer] to use it.
type CLICompiler struct {
// CLI is the MermaidJS CLI that we'll use
// to compile Mermaid diagrams into images.
//
// If unset, uses DefaultCLI.
CLI CLI
// Theme for rendered diagrams.
//
// Values include "dark", "default", "forest", and "neutral".
// See MermaidJS documentation for a full list.
Theme string
}
var _ Compiler = (*CLICompiler)(nil)
// Compile compiles the provided Mermaid diagram into an SVG.
func (d *CLICompiler) Compile(ctx context.Context, req *CompileRequest) (_ *CompileResponse, err error) {
mmdc := DefaultCLI
if d.CLI != nil {
mmdc = d.CLI
}
input, err := os.CreateTemp("", "in.*.mermaid")
if err != nil {
return nil, err
}
defer func() {
_ = os.Remove(input.Name()) // ignore error
}()
_, err = input.WriteString(req.Source)
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 func() {
_ = 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 := mmdc.CommandContext(ctx, 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 &CompileResponse{
SVG: string(out),
}, nil
}