djot 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,557 +0,0 @@
1
- local ast = require("djot.ast")
2
- local unpack = unpack or table.unpack
3
- local insert_attribute, copy_attributes =
4
- ast.insert_attribute, ast.copy_attributes
5
- local emoji -- require this later, only if emoji encountered
6
- local format = string.format
7
- local find, gsub = string.find, string.gsub
8
-
9
- -- Produce a copy of a table.
10
- local function copy(tbl)
11
- local result = {}
12
- if tbl then
13
- for k,v in pairs(tbl) do
14
- local newv = v
15
- if type(v) == "table" then
16
- newv = copy(v)
17
- end
18
- result[k] = newv
19
- end
20
- end
21
- return result
22
- end
23
-
24
- local function to_text(node)
25
- local buffer = {}
26
- if node[1] == "str" then
27
- buffer[#buffer + 1] = node[2]
28
- elseif node[1] == "softbreak" then
29
- buffer[#buffer + 1] = " "
30
- elseif #node > 1 then
31
- for i=2,#node do
32
- buffer[#buffer + 1] = to_text(node[i])
33
- end
34
- end
35
- return table.concat(buffer)
36
- end
37
-
38
- local Renderer = {}
39
-
40
- function Renderer:new()
41
- local state = {
42
- out = function(s)
43
- io.stdout:write(s)
44
- end,
45
- tight = false,
46
- footnote_index = {},
47
- next_footnote_index = 1,
48
- references = nil,
49
- footnotes = nil }
50
- setmetatable(state, self)
51
- self.__index = self
52
- return state
53
- end
54
-
55
- Renderer.html_escapes =
56
- { ["<"] = "&lt;",
57
- [">"] = "&gt;",
58
- ["&"] = "&amp;",
59
- ['"'] = "&quot;" }
60
-
61
- function Renderer:escape_html(s)
62
- if find(s, '[<>&]') then
63
- return (gsub(s, '[<>&]', self.html_escapes))
64
- else
65
- return s
66
- end
67
- end
68
-
69
- function Renderer:escape_html_attribute(s)
70
- if find(s, '[<>&"]') then
71
- return (gsub(s, '[<>&"]', self.html_escapes))
72
- else
73
- return s
74
- end
75
- end
76
-
77
- function Renderer:render(doc, handle)
78
- self.references = doc.references
79
- self.footnotes = doc.footnotes
80
- if handle then
81
- self.out = function(s)
82
- handle:write(s)
83
- end
84
- end
85
- self[doc[1]](self, doc)
86
- end
87
-
88
-
89
- function Renderer:render_children(node)
90
- if #node > 1 then
91
- local oldtight
92
- if node.tight ~= nil then
93
- oldtight = self.tight
94
- self.tight = node.tight
95
- end
96
- for i=2,#node do
97
- self[node[i][1]](self, node[i])
98
- end
99
- if node.tight ~= nil then
100
- self.tight = oldtight
101
- end
102
- end
103
- end
104
-
105
- function Renderer:render_attrs(node)
106
- if node.attr then
107
- local keys = node.attr._keys or {}
108
- for i=1,#keys do
109
- local k = keys[i]
110
- if k == nil then
111
- break
112
- end
113
- self.out(" " .. k .. "=" .. '"' ..
114
- self:escape_html_attribute(node.attr[k]) .. '"')
115
- end
116
- end
117
- if node.pos then
118
- local sp, ep = unpack(node.pos)
119
- self.out(' data-startpos="' .. tostring(sp) ..
120
- '" data-endpos="' .. tostring(ep) .. '"')
121
- end
122
- end
123
-
124
- function Renderer:render_tag(tag, node)
125
- self.out("<" .. tag)
126
- self:render_attrs(node)
127
- self.out(">")
128
- end
129
-
130
- function Renderer:add_backlink(nodes, i)
131
- local backlink = {"link", {"str","↩︎︎"}}
132
- backlink.destination = "#fnref" .. tostring(i)
133
- backlink.attr = {role = "doc-backlink", _keys = {"role"}}
134
- if nodes[#nodes][1] == "para" then
135
- nodes[#nodes][#(nodes[#nodes]) + 1] = backlink
136
- else
137
- nodes[#nodes + 1] = {"para", backlink}
138
- end
139
- end
140
-
141
- function Renderer:doc(node)
142
- self:render_children(node)
143
- -- render notes
144
- if self.next_footnote_index > 1 then
145
- local ordered_footnotes = {}
146
- for k,v in pairs(self.footnotes) do
147
- if self.footnote_index[k] then
148
- ordered_footnotes[self.footnote_index[k]] = v
149
- end
150
- end
151
- self.out('<section role="doc-endnotes">\n<hr>\n<ol>\n')
152
- for i=1,#ordered_footnotes do
153
- self.out(format('<li id="fn%d">\n', i))
154
- self:add_backlink(ordered_footnotes[i],i)
155
- self:render_children(ordered_footnotes[i])
156
- self.out('</li>\n')
157
- end
158
- self.out('</ol>\n</section>\n')
159
- end
160
- end
161
-
162
- function Renderer:raw_block(node)
163
- if node.format == "html" then
164
- self.out(node[2]) -- no escaping
165
- end
166
- end
167
-
168
- function Renderer:para(node)
169
- if not self.tight then
170
- self:render_tag("p", node)
171
- end
172
- self:render_children(node)
173
- if not self.tight then
174
- self.out("</p>")
175
- end
176
- self.out("\n")
177
- end
178
-
179
- function Renderer:blockquote(node)
180
- self:render_tag("blockquote", node)
181
- self.out("\n")
182
- self:render_children(node)
183
- self.out("</blockquote>\n")
184
- end
185
-
186
- function Renderer:div(node)
187
- self:render_tag("div", node)
188
- self.out("\n")
189
- self:render_children(node)
190
- self.out("</div>\n")
191
- end
192
-
193
- function Renderer:heading(node)
194
- self:render_tag("h" .. node.level , node)
195
- self:render_children(node)
196
- self.out("</h" .. node.level .. ">\n")
197
- end
198
-
199
- function Renderer:thematic_break(node)
200
- self:render_tag("hr", node)
201
- self.out("\n")
202
- end
203
-
204
- function Renderer:code_block(node)
205
- self:render_tag("pre", node)
206
- self.out("<code")
207
- if node.lang and #node.lang > 0 then
208
- self.out(" class=\"language-" .. node.lang .. "\"")
209
- end
210
- self.out(">")
211
- self:render_children(node)
212
- self.out("</code></pre>\n")
213
- end
214
-
215
- function Renderer:table(node)
216
- self:render_tag("table", node)
217
- self.out("\n")
218
- self:render_children(node)
219
- self.out("</table>\n")
220
- end
221
-
222
- function Renderer:row(node)
223
- self:render_tag("tr", node)
224
- self.out("\n")
225
- self:render_children(node)
226
- self.out("</tr>\n")
227
- end
228
-
229
- function Renderer:cell(node)
230
- local tag
231
- if node.head then
232
- tag = "th"
233
- else
234
- tag = "td"
235
- end
236
- local attr = copy(node.attr)
237
- if node.align then
238
- insert_attribute(attr, "style", "text-align: " .. node.align .. ";")
239
- end
240
- self:render_tag(tag, {attr = attr})
241
- self:render_children(node)
242
- self.out("</" .. tag .. ">\n")
243
- end
244
-
245
- function Renderer:caption(node)
246
- self:render_tag("caption", node)
247
- self:render_children(node)
248
- self.out("</caption>\n")
249
- end
250
-
251
- function Renderer:list(node)
252
- local sty = node.list_style
253
- if sty == "*" or sty == "+" or sty == "-" then
254
- self:render_tag("ul", node)
255
- self.out("\n")
256
- self:render_children(node)
257
- self.out("</ul>\n")
258
- elseif sty == "X" then
259
- local attr = copy(node.attr)
260
- if attr.class then
261
- attr.class = "task-list " .. attr.class
262
- else
263
- insert_attribute(attr, "class", "task-list")
264
- end
265
- self:render_tag("ul", {attr = attr})
266
- self.out("\n")
267
- self:render_children(node)
268
- self.out("</ul>\n")
269
- elseif sty == ":" then
270
- self:render_tag("dl", node)
271
- self.out("\n")
272
- self:render_children(node)
273
- self.out("</dl>\n")
274
- else
275
- self.out("<ol")
276
- if node.start and node.start > 1 then
277
- self.out(" start=\"" .. node.start .. "\"")
278
- end
279
- local list_type = gsub(node.list_style, "%p", "")
280
- if list_type ~= "1" then
281
- self.out(" type=\"" .. list_type .. "\"")
282
- end
283
- self:render_attrs(node)
284
- self.out(">\n")
285
- self:render_children(node)
286
- self.out("</ol>\n")
287
- end
288
- end
289
-
290
- function Renderer:list_item(node)
291
- if node.checkbox then
292
- if node.checkbox == "checked" then
293
- self.out('<li class="checked">')
294
- elseif node.checkbox == "unchecked" then
295
- self.out('<li class="unchecked">')
296
- end
297
- else
298
- self:render_tag("li", node)
299
- end
300
- self.out("\n")
301
- self:render_children(node)
302
- self.out("</li>\n")
303
- end
304
-
305
- function Renderer:term(node)
306
- self:render_tag("dt", node)
307
- self:render_children(node)
308
- self.out("</dt>\n")
309
- end
310
-
311
- function Renderer:definition(node)
312
- self:render_tag("dd", node)
313
- self.out("\n")
314
- self:render_children(node)
315
- self.out("</dd>\n")
316
- end
317
-
318
- function Renderer:definition_list_item(node)
319
- self:render_children(node)
320
- end
321
-
322
- function Renderer:reference_definition()
323
- end
324
-
325
- function Renderer:footnote_reference(node)
326
- local label = node[2]
327
- local index = self.footnote_index[label]
328
- if not index then
329
- index = self.next_footnote_index
330
- self.footnote_index[label] = index
331
- self.next_footnote_index = self.next_footnote_index + 1
332
- end
333
- self.out(format('<a href="#fn%d" role="doc-noteref"><sup>%d</sup></a>',
334
- index, index))
335
- end
336
-
337
- function Renderer:raw_inline(node)
338
- if node.format == "html" then
339
- self.out(node[2]) -- no escaping
340
- end
341
- end
342
-
343
- function Renderer:str(node)
344
- -- add a span, if needed, to contain attribute on a bare string:
345
- if node.attr then
346
- self:render_tag("span", node)
347
- self.out(self:escape_html(node[2]))
348
- self.out("</span>")
349
- else
350
- self.out(self:escape_html(node[2]))
351
- end
352
- end
353
-
354
- function Renderer:softbreak()
355
- self.out("\n")
356
- end
357
-
358
- function Renderer:hardbreak()
359
- self.out("<br>\n")
360
- end
361
-
362
- function Renderer:nbsp()
363
- self.out("&nbsp;")
364
- end
365
-
366
- function Renderer:verbatim(node)
367
- self:render_tag("code", node)
368
- self:render_children(node)
369
- self.out("</code>")
370
- end
371
-
372
- function Renderer:link(node)
373
- local attrs = {}
374
- if node.reference then
375
- local ref = self.references[node.reference]
376
- if ref then
377
- if ref.attributes then
378
- attrs = copy(ref.attributes)
379
- end
380
- insert_attribute(attrs, "href", ref.destination)
381
- end
382
- elseif node.destination then
383
- insert_attribute(attrs, "href", node.destination)
384
- end
385
- -- link's attributes override reference's:
386
- copy_attributes(attrs, node.attr)
387
- self:render_tag("a", {attr = attrs})
388
- self:render_children(node)
389
- self.out("</a>")
390
- end
391
-
392
- function Renderer:image(node)
393
- local attrs = {}
394
- local alt_text = to_text(node)
395
- if #alt_text > 0 then
396
- insert_attribute(attrs, "alt", to_text(node))
397
- end
398
- if node.reference then
399
- local ref = self.references[node.reference]
400
- if ref then
401
- if ref.attributes then
402
- attrs = copy(ref.attributes)
403
- end
404
- insert_attribute(attrs, "src", ref.destination)
405
- end
406
- elseif node.destination then
407
- insert_attribute(attrs, "src", node.destination)
408
- end
409
- -- image's attributes override reference's:
410
- copy_attributes(attrs, node.attr)
411
- self:render_tag("img", {attr = attrs})
412
- end
413
-
414
- function Renderer:span(node)
415
- self:render_tag("span", node)
416
- self:render_children(node)
417
- self.out("</span>")
418
- end
419
-
420
- function Renderer:mark(node)
421
- self:render_tag("mark", node)
422
- self:render_children(node)
423
- self.out("</mark>")
424
- end
425
-
426
- function Renderer:insert(node)
427
- self:render_tag("ins", node)
428
- self:render_children(node)
429
- self.out("</ins>")
430
- end
431
-
432
- function Renderer:delete(node)
433
- self:render_tag("del", node)
434
- self:render_children(node)
435
- self.out("</del>")
436
- end
437
-
438
- function Renderer:subscript(node)
439
- self:render_tag("sub", node)
440
- self:render_children(node)
441
- self.out("</sub>")
442
- end
443
-
444
- function Renderer:superscript(node)
445
- self:render_tag("sup", node)
446
- self:render_children(node)
447
- self.out("</sup>")
448
- end
449
-
450
- function Renderer:emph(node)
451
- self:render_tag("em", node)
452
- self:render_children(node)
453
- self.out("</em>")
454
- end
455
-
456
- function Renderer:strong(node)
457
- self:render_tag("strong", node)
458
- self:render_children(node)
459
- self.out("</strong>")
460
- end
461
-
462
- function Renderer:double_quoted(node)
463
- self.out("&ldquo;")
464
- self:render_children(node)
465
- self.out("&rdquo;")
466
- end
467
-
468
- function Renderer:single_quoted(node)
469
- self.out("&lsquo;")
470
- self:render_children(node)
471
- self.out("&rsquo;")
472
- end
473
-
474
- function Renderer:left_double_quote()
475
- self.out("&ldquo;")
476
- end
477
-
478
- function Renderer:right_double_quote()
479
- self.out("&rdquo;")
480
- end
481
-
482
- function Renderer:left_single_quote()
483
- self.out("&lsquo;")
484
- end
485
-
486
- function Renderer:right_single_quote()
487
- self.out("&rsquo;")
488
- end
489
-
490
- function Renderer:ellipses()
491
- self.out("&hellip;")
492
- end
493
-
494
- function Renderer:em_dash()
495
- self.out("&mdash;")
496
- end
497
-
498
- function Renderer:en_dash()
499
- self.out("&ndash;")
500
- end
501
-
502
- function Renderer:emoji(node)
503
- emoji = require("djot.emoji")
504
- local found = emoji[node[2]:sub(2,-2)]
505
- if found then
506
- self.out(found)
507
- else
508
- self.out(node[2])
509
- end
510
- end
511
-
512
- function Renderer:math(node)
513
- local math_type = "inline"
514
- if find(node.attr.class, "display") then
515
- math_type = "display"
516
- end
517
- self:render_tag("span", node)
518
- if math_type == "inline" then
519
- self.out("\\(")
520
- else
521
- self.out("\\[")
522
- end
523
- self:render_children(node)
524
- if math_type == "inline" then
525
- self.out("\\)")
526
- else
527
- self.out("\\]")
528
- end
529
- self.out("</span>")
530
- end
531
-
532
- return { Renderer = Renderer }
533
-
534
-
535
- --[[
536
- Copyright (C) 2022 John MacFarlane
537
-
538
- Permission is hereby granted, free of charge, to any person obtaining
539
- a copy of this software and associated documentation files (the
540
- "Software"), to deal in the Software without restriction, including
541
- without limitation the rights to use, copy, modify, merge, publish,
542
- distribute, sublicense, and/or sell copies of the Software, and to
543
- permit persons to whom the Software is furnished to do so, subject to
544
- the following conditions:
545
-
546
- The above copyright notice and this permission notice shall be included
547
- in all copies or substantial portions of the Software.
548
-
549
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
550
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
551
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
552
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
553
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
554
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
555
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
556
-
557
- ]]