djot 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- ]]