/
markdown.go
296 lines (276 loc) · 7.41 KB
/
markdown.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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
// The markdown package implements filesys.Handler for the Markdown format.
//
// Markdown is parsed and rendered with the goldmark package. As such, it
// contains implementations of various goldmark interfaces, which may be used
// for custom parsers and renderers.
package markdown
import (
"errors"
"io"
"io/fs"
"strings"
"github.com/anaminus/drill"
"github.com/anaminus/drill/filesys"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)
// NewHandler returns a filesys.HandlerFunc that handles the Markdown format.
//
// The content of a Markdown file is divided into a tree of sections, delimited
// by headings. A section can be drilled into, with the name corresponding to
// the content of the section's heading.
//
// A section contains all the content that follows a heading, up to the next
// heading of the same level. Content that follows the section's heading, and
// precedes the end of the section or the first sub-heading, is contained within
// an "orphaned" section. The name of this section is an empty string, and will
// be the first child section.
func NewHandler(options ...goldmark.Option) filesys.HandlerFunc {
options = append(options,
goldmark.WithParserOptions(
parser.WithASTTransformers(
util.Prioritized(NewSectionTransformer(), 2000),
),
),
goldmark.WithRendererOptions(
renderer.WithNodeRenderers(
util.Prioritized(NewSectionRenderer(), 2000),
),
),
)
return func(fsys fs.FS, name string) drill.Node {
b, err := fs.ReadFile(fsys, name)
if err != nil {
return nil
}
md := goldmark.New(options...)
parser := md.Parser()
root := parser.Parse(text.NewReader(b))
node := NewNode(root, b, md.Renderer())
return node
}
}
// Node implements drill.Node.
type Node struct {
root *Node
section ast.Node
source []byte
renderer renderer.Renderer
}
// NewNode returns a Node that wraps the given ast.Node, source, and renderer.
// root is assumed to be an ast.Document or a Section.
//
// The node created by NewNode is treated as the root. Nodes that derive from
// the root will point back to the root.
func NewNode(root ast.Node, source []byte, renderer renderer.Renderer) *Node {
node := &Node{
section: root,
source: source,
renderer: renderer,
}
node.root = node
return node
}
// derive returns a Node that wraps section, using the same source and renderer.
func (n *Node) derive(section ast.Node) *Node {
d := *n
d.section = section
return &d
}
// Root returns the original root Node from which the current node derives, or
// the node itself, if it is the root.
func (n *Node) Root() *Node {
if n.root == nil {
return n
}
return n.root
}
// Node returns the wrapped ast.Node.
func (n *Node) Node() ast.Node {
return n.section
}
// WalkChildSections traverses each child node that is a Section. Stops if walk
// returns true.
func (n *Node) WalkChildSections(walk func(child *Section) bool) {
ast.Walk(n.section, func(child ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}
if child == n.section {
return ast.WalkContinue, nil
}
if section, ok := child.(*Section); ok {
if walk(section) {
return ast.WalkStop, nil
}
}
return ast.WalkSkipChildren, nil
})
}
// Fragment renders the wrapped node and returns the result as a string. Returns
// an empty string if an error occurs, or the node has no renderer.
func (n *Node) Fragment() string {
if n.renderer == nil {
return ""
}
var buf strings.Builder
if err := n.renderer.Render(&buf, n.source, n.section); err != nil {
return ""
}
return buf.String()
}
// render renders n to w.
func render(n *Node, w *io.PipeWriter) {
if err := n.renderer.Render(w, n.source, n.section); err != nil {
w.CloseWithError(err)
}
w.Close()
}
// FragmentReader returns a ReadCloser that renders the wrapped node.
func (n *Node) FragmentReader() (r io.ReadCloser, err error) {
if n.renderer == nil {
return nil, errors.New("no renderer")
}
r, w := io.Pipe()
go render(n, w)
return r, nil
}
// WithRenderer returns a copy of the node that uses the given renderer.
func (n *Node) WithRenderer(r renderer.Renderer) *Node {
d := *n
d.renderer = r
return &d
}
// Len returns the number of child Sections.
func (n *Node) Len() int {
var count int
n.WalkChildSections(func(section *Section) bool {
count++
return false
})
return count
}
// OrderedChild returns a Node that wraps the ordered child Section at index i.
// Returns nil if the index is out of bounds.
func (n *Node) OrderedChild(i int) drill.Node {
if i = drill.Index(i, n.Len()); i < 0 {
return nil
}
var count int
var section ast.Node
n.WalkChildSections(func(child *Section) bool {
if count == i {
section = child
return true
}
count++
return false
})
if section == nil {
return nil
}
return n.derive(section)
}
// OrderedChildren returns a list of Nodes that wrap each ordered child Section.
func (n *Node) OrderedChildren() []drill.Node {
var sections []drill.Node
n.WalkChildSections(func(child *Section) bool {
sections = append(sections, n.derive(child))
return false
})
return sections
}
// UnorderedChild returns a Node that wraps the unordered child Section whose
// Name is equal to name.
func (n *Node) UnorderedChild(name string) drill.Node {
var section ast.Node
n.WalkChildSections(func(child *Section) bool {
if child.Name == name {
section = child
return true
}
return false
})
if section == nil {
return nil
}
return n.derive(section)
}
// UnorderedChildren returns a map of names to Nodes that wrap each unordered
// child Section.
func (n *Node) UnorderedChildren() map[string]drill.Node {
sections := map[string]drill.Node{}
n.WalkChildSections(func(child *Section) bool {
sections[child.Name] = n.derive(child)
return false
})
return sections
}
// Descend recursively descends into the unordered child sections matching each
// given name. Returns nil if a child could not be found at any point.
func (n *Node) Descend(names ...string) drill.Node {
for _, name := range names {
var ok bool
n.WalkChildSections(func(section *Section) bool {
if section.Name != name {
return false
}
ok = true
n = n.derive(section)
return true
})
if !ok {
return nil
}
}
return n
}
// Query recursively descends into the child nodes that match the given queries.
// A query is either a string or an int. If an int, then the next node is
// acquired using the OrderedChild method of the current node. If a string, then
// the next node is acquired using the UnorderedChild method of the current
// node. Returns nil if a child could not be found at any point.
func (n *Node) Query(queries ...interface{}) drill.Node {
for _, query := range queries {
switch q := query.(type) {
case string:
var section ast.Node
n.WalkChildSections(func(child *Section) bool {
if child.Name == q {
section = child
return true
}
return false
})
if section == nil {
return nil
}
n = n.derive(section)
case int:
if q = drill.Index(q, n.Len()); q < 0 {
return nil
}
var count int
var section ast.Node
n.WalkChildSections(func(child *Section) bool {
if count == q {
section = child
return true
}
count++
return false
})
if section == nil {
return nil
}
n = n.derive(section)
default:
return nil
}
}
return n
}