-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathrenderer.go
131 lines (115 loc) · 3.08 KB
/
renderer.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
package md
import (
"io"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/renderer"
"go4.org/bytereplacer"
)
var unescaper = bytereplacer.New("\\\\", "\\", "\\", "")
// var doubleBackslash = []byte{'\\', '\\'}
// Unescape handles escape characters. This is a helper function for renderers.
func Unescape(src []byte) []byte {
return unescaper.Replace(src)
}
type unescapeWriter struct {
w io.Writer
}
func (w unescapeWriter) Write(b []byte) (int, error) {
return w.w.Write(Unescape(b))
}
func UnescapeWriter(w io.Writer) io.Writer {
return unescapeWriter{w}
}
// BasicRenderer renders the package's ast.Nodes into simple unformatted
// plain text. It serves as an implementation reference. However, this
// implementation does not have a state, which is required for Inline and
// Blockquote.
type BasicRenderer struct{}
var DefaultRenderer renderer.Renderer = &BasicRenderer{}
func (r *BasicRenderer) AddOptions(...renderer.Option) {}
func (r *BasicRenderer) Render(w io.Writer, source []byte, n ast.Node) error {
// Wrap the current writer behind an unescaper.
w = UnescapeWriter(w)
return ast.Walk(n, func(node ast.Node, enter bool) (ast.WalkStatus, error) {
return r.walker(w, source, node, enter), nil
})
}
func (r *BasicRenderer) walker(w io.Writer, source []byte, n ast.Node, enter bool) ast.WalkStatus {
switch n := n.(type) {
case *ast.Document:
// noop
case *ast.Blockquote:
if enter {
// A blockquote contains a paragraph each line. Because Discord.
for child := n.FirstChild(); child != nil; child = child.NextSibling() {
write(w, "> ")
ast.Walk(child, func(node ast.Node, enter bool) (ast.WalkStatus, error) {
// We only call when entering, since we don't want to trigger a
// hard new line after each paragraph.
if enter {
return r.walker(w, source, node, enter), nil
}
return ast.WalkContinue, nil
})
}
}
// We've already walked over children ourselves.
return ast.WalkSkipChildren
case *ast.Paragraph:
if !enter {
write(w, "\n")
}
case *ast.FencedCodeBlock:
if enter {
// Write the body
for i := 0; i < n.Lines().Len(); i++ {
line := n.Lines().At(i)
write(w, "▏▕ "+string(line.Value(source)))
}
}
case *ast.Link:
if enter {
write(w, string(n.Title)+" ("+string(n.Destination)+")")
}
case *ast.AutoLink:
if enter {
write(w, string(n.URL(source)))
}
case *Inline:
// n.Attr should be used, but since we're in plaintext mode, there is no
// formatting.
case *Emoji:
if enter {
write(w, ":"+string(n.Name)+":")
}
case *Mention:
if enter {
switch {
case n.Channel != nil:
write(w, "#"+n.Channel.Name)
case n.GuildUser != nil:
write(w, "@"+n.GuildUser.Username)
case n.GuildRole != nil:
write(w, "@"+n.GuildRole.Name)
}
}
case *ast.String:
if enter {
w.Write(n.Value)
}
case *ast.Text:
if enter {
w.Write(n.Segment.Value(source))
switch {
case n.HardLineBreak():
write(w, "\n\n")
case n.SoftLineBreak():
write(w, "\n")
}
}
}
return ast.WalkContinue
}
func write(w io.Writer, str string) {
w.Write([]byte(str))
}