/
diagutil.go
117 lines (107 loc) · 3.03 KB
/
diagutil.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 diagrams
import (
"bytes"
"compress/zlib"
"fmt"
"io"
"net/http"
"os"
"path"
"strings"
"github.com/pkg/errors"
"github.com/spf13/afero"
)
func OutputPlantuml(output, plantuml, umlInput string, fs afero.Fs) error {
mode := path.Ext(output)
mode = strings.Replace(mode, ".", "", 1)
var encoded string
var out []byte
var err error
encoded, err = DeflateAndEncode([]byte(umlInput))
if err != nil {
return err
}
switch mode {
case "png", "svg":
plantuml = fmt.Sprintf("%s/%s/~1%s", plantuml, mode, encoded)
out, err = sendHTTPRequest(plantuml)
if err != nil {
return err
}
case "html":
plantuml = fmt.Sprintf("%s/%s/%s", plantuml, "svg", encoded)
out = []byte(fmt.Sprintf(`<img src="%s" alt="plantuml">`, plantuml))
case "link":
plantuml = fmt.Sprintf("%s/%s/%s", plantuml, "svg", encoded)
out = []byte(plantuml)
case "puml", "uml", "plantuml":
out = []byte(umlInput)
default:
return fmt.Errorf("extension must be .svg, .png, .uml, .puml, .plantuml, .html or .link, not %#v", mode)
}
return errors.Wrapf(afero.WriteFile(fs, output, append(out, byte('\n')), os.ModePerm), "writing %q", output)
}
func sendHTTPRequest(url string) ([]byte, error) {
resp, err := http.Get(url) //nolint:gosec
if err != nil {
return nil, errors.Errorf("Unable to create http request to %s, Error:%s", url, err.Error())
}
defer resp.Body.Close()
out, err := io.ReadAll(resp.Body)
if err != nil {
return nil, errors.Errorf("unable to read body")
}
return out, nil
}
// The functions below ported from https://github.com/dougn/python-plantuml/blob/master/plantuml.py
func DeflateAndEncode(text []byte) (string, error) {
var buf bytes.Buffer
zw, err := zlib.NewWriterLevel(&buf, zlib.BestCompression)
if err != nil {
return "", err
}
if _, err := zw.Write(text); err != nil {
return "", err
}
if err := zw.Close(); err != nil {
return "", err
}
return encode(buf.Bytes()), nil
}
func encode(data []byte) string {
var buf bytes.Buffer
i := 0
for wholeTripleBytes := len(data) / 3 * 3; i < wholeTripleBytes; i += 3 {
encode3bytes(&buf, data[i], data[i+1], data[i+2])
}
switch len(data) - i {
case 1:
encode3bytes(&buf, data[i], 0, 0)
case 2:
encode3bytes(&buf, data[i], data[i+1], 0)
}
return buf.String()
}
// 3 bytes takes 24 bits. This splits 24 bits into 4 bytes of which lower 6-bit takes account.
func encode3bytes(w io.ByteWriter, b1, b2, b3 byte) {
if err := w.WriteByte(encode6bit(0x3F & (b1 >> 2))); err != nil {
panic(err)
}
if err := w.WriteByte(encode6bit(0x3F & (((b1 & 0x3) << 4) | (b2 >> 4)))); err != nil {
panic(err)
}
if err := w.WriteByte(encode6bit(0x3F & (((b2 & 0xF) << 2) | (b3 >> 6)))); err != nil {
panic(err)
}
if err := w.WriteByte(encode6bit(0x3F & b3)); err != nil {
panic(err)
}
}
func encode6bit(b byte) byte {
// 6 bit makes value 0 to 63. The func maps 0-63 to characters
// '0'-'9', 'A'-'Z', 'a'-'z', '-', '_'. '?' should never be reached.
if b > 63 {
panic("unexpected character!")
}
return "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_"[b]
}