djot 0.0.5 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.mdl_style.rb +4 -0
- data/.mdlrc +1 -0
- data/.rubocop.yml +14 -3
- data/.rubocop_todo.yml +7 -10
- data/CHANGELOG.md +45 -0
- data/Dockerfile +10 -0
- data/Gemfile +2 -0
- data/README.md +20 -5
- data/Rakefile +18 -17
- data/Steepfile +2 -0
- data/djot.gemspec +3 -3
- data/lib/djot/javascript.rb +96 -25
- data/lib/djot/pure.rb +4 -0
- data/lib/djot/version.rb +1 -1
- data/lib/djot.rb +8 -39
- data/manifest.scm +2 -0
- data/sig/djot.gen.rbs +32 -0
- data/sig/djot.rbs +6 -4
- metadata +19 -34
- data/lib/lua/djot/ast.lua +0 -642
- data/lib/lua/djot/attributes.lua +0 -273
- data/lib/lua/djot/block.lua +0 -807
- data/lib/lua/djot/emoji.lua +0 -1880
- data/lib/lua/djot/html.lua +0 -557
- data/lib/lua/djot/inline.lua +0 -641
- data/lib/lua/djot/match.lua +0 -75
- data/lib/lua/djot.lua +0 -107
data/lib/lua/djot/ast.lua
DELETED
@@ -1,642 +0,0 @@
|
|
1
|
-
local match = require("djot.match")
|
2
|
-
local unpack = unpack or table.unpack
|
3
|
-
|
4
|
-
local find, lower, sub, gsub, rep, format =
|
5
|
-
string.find, string.lower, string.sub, string.gsub, string.rep, string.format
|
6
|
-
|
7
|
-
local unpack_match, get_length, matches_pattern =
|
8
|
-
match.unpack_match, match.get_length, match.matches_pattern
|
9
|
-
|
10
|
-
local function get_string_content(node)
|
11
|
-
local buffer = {}
|
12
|
-
for i=2,#node do
|
13
|
-
local n = node[i]
|
14
|
-
if type(n) ~= "table" then
|
15
|
-
break
|
16
|
-
elseif n[1] == "str" or n[1] == "nbsp" then
|
17
|
-
buffer[#buffer + 1] = n[2]
|
18
|
-
elseif n[1] == "softbreak" then
|
19
|
-
buffer[#buffer + 1] = "\n"
|
20
|
-
else
|
21
|
-
buffer[#buffer + 1] = get_string_content(n)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
return table.concat(buffer)
|
25
|
-
end
|
26
|
-
|
27
|
-
local roman_digits = {
|
28
|
-
i = 1,
|
29
|
-
v = 5,
|
30
|
-
x = 10,
|
31
|
-
l = 50,
|
32
|
-
c = 100,
|
33
|
-
d = 500,
|
34
|
-
m = 1000 }
|
35
|
-
|
36
|
-
local function roman_to_number(s)
|
37
|
-
-- go backwards through the digits
|
38
|
-
local total = 0
|
39
|
-
local prevdigit = 0
|
40
|
-
local i=#s
|
41
|
-
while i > 0 do
|
42
|
-
local c = lower(sub(s,i,i))
|
43
|
-
local n = roman_digits[c]
|
44
|
-
if n < prevdigit then -- e.g. ix
|
45
|
-
total = total - n
|
46
|
-
else
|
47
|
-
total = total + n
|
48
|
-
end
|
49
|
-
assert(n ~= nil, "Encountered bad character in roman numeral " .. s)
|
50
|
-
prevdigit = n
|
51
|
-
i = i - 1
|
52
|
-
end
|
53
|
-
return total
|
54
|
-
end
|
55
|
-
|
56
|
-
local function get_list_start(marker, style)
|
57
|
-
local numtype = string.gsub(style, "%p", "")
|
58
|
-
local s = string.gsub(marker, "%p", "")
|
59
|
-
if numtype == "1" then
|
60
|
-
return tonumber(s)
|
61
|
-
elseif numtype == "A" then
|
62
|
-
return (string.byte(s) - string.byte("A") + 1)
|
63
|
-
elseif numtype == "a" then
|
64
|
-
return (string.byte(s) - string.byte("a") + 1)
|
65
|
-
elseif numtype == "I" then
|
66
|
-
return roman_to_number(s)
|
67
|
-
elseif numtype == "i" then
|
68
|
-
return roman_to_number(s)
|
69
|
-
elseif numtype == "" then
|
70
|
-
return nil
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
local ignorable = {
|
75
|
-
image_marker = true,
|
76
|
-
escape = true,
|
77
|
-
blankline = true,
|
78
|
-
checkbox_checked = true,
|
79
|
-
checkbox_unchecked = true
|
80
|
-
}
|
81
|
-
|
82
|
-
local function is_tight(matches, startidx, endidx, is_last_item)
|
83
|
-
-- see if there are any blank lines between blocks in a list item.
|
84
|
-
local blanklines = 0
|
85
|
-
-- we don't care about blank lines at very end of list
|
86
|
-
for i=startidx, endidx do
|
87
|
-
local _, _, x = unpack_match(matches[i])
|
88
|
-
if x == "blankline" then
|
89
|
-
if matches_pattern(matches[i+1], "%+list_item") then
|
90
|
-
-- don't count blank lines before list starts
|
91
|
-
elseif matches_pattern(matches[i+1], "%-list_item") and
|
92
|
-
(is_last_item or matches_pattern(matches[i+2], "%-list_item")) then
|
93
|
-
-- don't count blank lines at end of nested lists
|
94
|
-
-- or end of last item
|
95
|
-
else
|
96
|
-
blanklines = blanklines + 1
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
return (blanklines == 0)
|
101
|
-
end
|
102
|
-
|
103
|
-
local function insert_attribute(attr, key, val)
|
104
|
-
if not attr._keys then
|
105
|
-
attr._keys = {}
|
106
|
-
end
|
107
|
-
local function add_key(k)
|
108
|
-
local keys = attr._keys
|
109
|
-
for i=1,#keys do
|
110
|
-
if keys[i] == k then
|
111
|
-
return
|
112
|
-
end
|
113
|
-
end
|
114
|
-
keys[#keys + 1] = k
|
115
|
-
end
|
116
|
-
-- _keys records order of key insertion for deterministic output
|
117
|
-
if key == "id" then
|
118
|
-
attr.id = val
|
119
|
-
add_key("id")
|
120
|
-
elseif key == "class" then
|
121
|
-
if attr.class then
|
122
|
-
attr.class =
|
123
|
-
attr.class .. " " .. val
|
124
|
-
else
|
125
|
-
attr.class = val
|
126
|
-
add_key("class")
|
127
|
-
end
|
128
|
-
else
|
129
|
-
attr[key] = val
|
130
|
-
add_key(key)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
local function copy_attributes(target, source)
|
135
|
-
if source then
|
136
|
-
for k,v in pairs(source) do
|
137
|
-
if k ~= "_keys" then
|
138
|
-
insert_attribute(target, k, v)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
local function insert_attributes(targetnode, attrnode)
|
145
|
-
targetnode.attr = targetnode.attr or {_keys = {}}
|
146
|
-
local i=2
|
147
|
-
while i <= #attrnode do
|
148
|
-
local x,y = unpack(attrnode[i])
|
149
|
-
if x == "id" or x == "class" then
|
150
|
-
insert_attribute(targetnode.attr, x, y)
|
151
|
-
elseif x == "key" then
|
152
|
-
local valnode = attrnode[i + 1]
|
153
|
-
if valnode[1] == "value" then
|
154
|
-
-- resolve backslash escapes
|
155
|
-
insert_attribute(targetnode.attr, y, valnode[2]:gsub("\\(%p)", "%1"))
|
156
|
-
end
|
157
|
-
i = i + 1
|
158
|
-
end
|
159
|
-
i = i + 1
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
local function make_definition_list_item(result)
|
164
|
-
assert(result[1] and result[1][1] ~= "list_item", "sanity check")
|
165
|
-
result[1] = "definition_list_item"
|
166
|
-
if result[2] and result[2][1] == "para" then
|
167
|
-
result[2][1] = "term"
|
168
|
-
else
|
169
|
-
table.insert(result, 2, {"term"})
|
170
|
-
end
|
171
|
-
if result[3] then
|
172
|
-
local defn = {"definition"}
|
173
|
-
for i=3,#result do
|
174
|
-
defn[#defn + 1] = result[i]
|
175
|
-
result[i] = nil
|
176
|
-
end
|
177
|
-
result[3] = defn
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
-- create an abstract syntax tree based on an event
|
182
|
-
-- stream and references
|
183
|
-
local function to_ast(subject, matches, options)
|
184
|
-
if not options then
|
185
|
-
options = {}
|
186
|
-
end
|
187
|
-
local idx = 1
|
188
|
-
local matcheslen = #matches
|
189
|
-
local sourcepos = options.sourcepos
|
190
|
-
local references = {}
|
191
|
-
local footnotes = {}
|
192
|
-
local identifiers = {} -- identifiers used (to ensure uniqueness)
|
193
|
-
|
194
|
-
-- generate auto identifier for heading
|
195
|
-
local function get_identifier(s)
|
196
|
-
local base = s:gsub("[][~!@#$%^&*(){}`,.<>\\|=+/?]","")
|
197
|
-
:gsub("^%s+",""):gsub("%s+$","")
|
198
|
-
:gsub("%s+","-")
|
199
|
-
local suffix = ""
|
200
|
-
local i = 0
|
201
|
-
local ident = base
|
202
|
-
-- generate unique id
|
203
|
-
while identifiers[ident] do
|
204
|
-
i = i + 1
|
205
|
-
ident = base .. tostring(i)
|
206
|
-
end
|
207
|
-
identifiers[ident] = true
|
208
|
-
return ident
|
209
|
-
end
|
210
|
-
|
211
|
-
local function set_checkbox(node, startidx)
|
212
|
-
-- determine if checked or unchecked
|
213
|
-
local _,_,ann = unpack_match(matches[startidx + 1])
|
214
|
-
if ann == "checkbox_checked" then
|
215
|
-
node.checkbox = "checked"
|
216
|
-
elseif ann == "checkbox_unchecked" then
|
217
|
-
node.checkbox = "unchecked"
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
local function get_node(maintag)
|
222
|
-
local nodes = {maintag}
|
223
|
-
local stopper
|
224
|
-
local block_attributes = nil
|
225
|
-
if maintag then
|
226
|
-
-- strip off data (e.g. for list_items)
|
227
|
-
stopper = "^%-" .. gsub(maintag, "%[.*$", "")
|
228
|
-
end
|
229
|
-
while idx <= matcheslen do
|
230
|
-
local match = matches[idx]
|
231
|
-
local startpos, endpos, annot = unpack_match(match)
|
232
|
-
if stopper and find(annot, stopper) then
|
233
|
-
idx = idx + 1
|
234
|
-
return nodes
|
235
|
-
else
|
236
|
-
local mod, tag = string.match(annot, "^([-+]?)(.*)")
|
237
|
-
if ignorable[tag] then
|
238
|
-
idx = idx + 1 -- skip
|
239
|
-
elseif mod == "+" then -- open
|
240
|
-
local startidx = idx
|
241
|
-
idx = idx + 1
|
242
|
-
local result = get_node(tag)
|
243
|
-
if tag == "list_item[X]" then
|
244
|
-
set_checkbox(result, startidx)
|
245
|
-
end
|
246
|
-
local _, finalpos = unpack_match(matches[idx - 1])
|
247
|
-
if sourcepos then
|
248
|
-
result.pos = {startpos, finalpos}
|
249
|
-
end
|
250
|
-
if block_attributes and tag ~= "block_attributes" then
|
251
|
-
for i=1,#block_attributes do
|
252
|
-
insert_attributes(result, block_attributes[i])
|
253
|
-
end
|
254
|
-
if result.attr and result.attr.id then
|
255
|
-
identifiers[result.attr.id] = true
|
256
|
-
end
|
257
|
-
block_attributes = nil
|
258
|
-
end
|
259
|
-
if tag == "verbatim" then
|
260
|
-
local s = get_string_content(result)
|
261
|
-
-- trim space next to ` at beginning or end
|
262
|
-
if find(s, "^ +`") then
|
263
|
-
s = s:sub(2)
|
264
|
-
end
|
265
|
-
if find(s, "` +$") then
|
266
|
-
s = s:sub(1, #s - 1)
|
267
|
-
end
|
268
|
-
result = {"verbatim", {"str", s}}
|
269
|
-
-- check for raw_format, which makes this a raw node
|
270
|
-
local sp,ep,ann = unpack_match(matches[idx])
|
271
|
-
if ann == "raw_format" then
|
272
|
-
local s = get_string_content(result)
|
273
|
-
result = {"raw_inline", s}
|
274
|
-
result.format = sub(subject, sp + 2, ep - 1)
|
275
|
-
idx = idx + 1 -- skip the raw_format
|
276
|
-
end
|
277
|
-
elseif tag == "caption" then
|
278
|
-
if nodes[#nodes][1] == "table" then
|
279
|
-
-- move caption in table node
|
280
|
-
table.insert(nodes[#nodes], 2, result)
|
281
|
-
result = nil
|
282
|
-
end
|
283
|
-
elseif tag == "reference_definition" then
|
284
|
-
local dest = ""
|
285
|
-
local key
|
286
|
-
for i=2,#result do
|
287
|
-
if result[i][1] == "reference_key" then
|
288
|
-
key = result[i][2]
|
289
|
-
end
|
290
|
-
if result[i][1] == "reference_value" then
|
291
|
-
dest = dest .. result[i][2]
|
292
|
-
end
|
293
|
-
end
|
294
|
-
references[key] = { destination = dest,
|
295
|
-
attributes = result.attr }
|
296
|
-
elseif tag == "footnote" then
|
297
|
-
local label
|
298
|
-
if result[2][1] == "note_label" then
|
299
|
-
label = result[2][2]
|
300
|
-
end
|
301
|
-
if label then
|
302
|
-
table.remove(result,2)
|
303
|
-
footnotes[label] = result
|
304
|
-
end
|
305
|
-
result = nil
|
306
|
-
elseif tag == "inline_math" then
|
307
|
-
result[1] = "math"
|
308
|
-
result.attr = {class = "math inline", _keys={"class"}}
|
309
|
-
elseif tag == "display_math" then
|
310
|
-
result[1] = "math"
|
311
|
-
result.attr = {class = "math display", _keys={"class"}}
|
312
|
-
elseif tag == "url" then
|
313
|
-
result[1] = "link"
|
314
|
-
result.destination = get_string_content(result)
|
315
|
-
elseif tag == "email" then
|
316
|
-
result[1] = "link"
|
317
|
-
result.destination = "mailto:" .. get_string_content(result)
|
318
|
-
elseif tag == "imagetext" or tag == "linktext" then
|
319
|
-
-- gobble destination or reference
|
320
|
-
local nextmatch = matches[idx]
|
321
|
-
local _, _, nextannot = unpack_match(nextmatch)
|
322
|
-
if nextannot == "+destination" then
|
323
|
-
idx = idx + 1
|
324
|
-
local dest = get_node("destination")
|
325
|
-
result.destination = get_string_content(dest):gsub("\r?\n", "")
|
326
|
-
elseif nextannot == "+reference" then
|
327
|
-
idx = idx + 1
|
328
|
-
local ref = get_node("reference")
|
329
|
-
if #ref == 1 then -- []
|
330
|
-
result.reference = get_string_content(result):gsub("\r?\n", " ")
|
331
|
-
else
|
332
|
-
result.reference = get_string_content(ref):gsub("\r?\n", " ")
|
333
|
-
end
|
334
|
-
end
|
335
|
-
result[1] = result[1]:gsub("text","")
|
336
|
-
elseif tag == "heading" then
|
337
|
-
result.level = get_length(match)
|
338
|
-
local heading_str = get_string_content(result)
|
339
|
-
:gsub("^%s+",""):gsub("%s+$","")
|
340
|
-
if not (result.attr and result.attr.id) then
|
341
|
-
local ident = get_identifier(heading_str)
|
342
|
-
insert_attributes(result, {nil,{"id", ident}})
|
343
|
-
end
|
344
|
-
-- insert into references unless there's a same-named one already:
|
345
|
-
if not references[heading_str] then
|
346
|
-
references[heading_str] =
|
347
|
-
{destination = "#" .. result.attr.id, attributes = {_keys={}}}
|
348
|
-
end
|
349
|
-
elseif tag == "table" then
|
350
|
-
-- look for a separator line
|
351
|
-
-- if found, make the preceding rows headings
|
352
|
-
-- and set attributes for column alignments on the table
|
353
|
-
local i=2
|
354
|
-
local aligns = {}
|
355
|
-
while i <= #result do
|
356
|
-
local found, align
|
357
|
-
if result[i][1] == "row" then
|
358
|
-
local row = result[i]
|
359
|
-
for j=2,#row do
|
360
|
-
found, _, align = find(row[j][1], "^separator_(.*)")
|
361
|
-
if not found then
|
362
|
-
break
|
363
|
-
end
|
364
|
-
aligns[j - 1] = align
|
365
|
-
end
|
366
|
-
if found and #aligns > 0 then
|
367
|
-
-- set previous row to head and adjust aligns
|
368
|
-
local prevrow = result[i - 1]
|
369
|
-
if prevrow[1] == "row" then
|
370
|
-
prevrow.head = true
|
371
|
-
for k=2,#prevrow do
|
372
|
-
-- set head on cells too
|
373
|
-
prevrow[k].head = true
|
374
|
-
if aligns[k - 1] ~= "default" then
|
375
|
-
prevrow[k].align = aligns[k - 1]
|
376
|
-
end
|
377
|
-
end
|
378
|
-
end
|
379
|
-
table.remove(result,i) -- remove sep line
|
380
|
-
-- we don't need to increment i because we removed ith elt
|
381
|
-
else
|
382
|
-
if #aligns > 0 then
|
383
|
-
for l=2,#result[i] do
|
384
|
-
if aligns[l - 1] ~= "default" then
|
385
|
-
result[i][l].align = aligns[l - 1]
|
386
|
-
end
|
387
|
-
end
|
388
|
-
end
|
389
|
-
i = i + 1
|
390
|
-
end
|
391
|
-
end
|
392
|
-
end
|
393
|
-
result.level = get_length(match)
|
394
|
-
elseif tag == "div" then
|
395
|
-
if result[2] and result[2][1] == "class" then
|
396
|
-
result.attr = result.attr or {_keys = {}}
|
397
|
-
insert_attribute(result.attr, "class", result[2][2])
|
398
|
-
table.remove(result, 2)
|
399
|
-
end
|
400
|
-
elseif tag == "code_block" then
|
401
|
-
if result[2] then
|
402
|
-
if result[2][1] == "code_language" then
|
403
|
-
result.lang = result[2][2]
|
404
|
-
table.remove(result, 2)
|
405
|
-
elseif result[2][1] == "raw_format" then
|
406
|
-
local fmt = result[2][2]:sub(2)
|
407
|
-
local s = get_string_content(result)
|
408
|
-
result = {"raw_block", s}
|
409
|
-
result.format = fmt
|
410
|
-
end
|
411
|
-
end
|
412
|
-
elseif tag == "block_attributes" then
|
413
|
-
if block_attributes then
|
414
|
-
block_attributes[#block_attributes + 1] = result
|
415
|
-
else
|
416
|
-
block_attributes = {result}
|
417
|
-
end
|
418
|
-
result = nil
|
419
|
-
elseif tag == "attributes" then
|
420
|
-
-- parse attributes, add to last node
|
421
|
-
local prevnode = nodes[#nodes]
|
422
|
-
local endswithspace = false
|
423
|
-
if type(prevnode) == "table" then
|
424
|
-
if prevnode[1] == "str" then
|
425
|
-
-- split off last consecutive word of string
|
426
|
-
-- to which to attach attributes
|
427
|
-
local lastwordpos = string.find(prevnode[2], "%w+$")
|
428
|
-
if not lastwordpos then
|
429
|
-
endswithspace = true
|
430
|
-
elseif lastwordpos > 1 then
|
431
|
-
local newnode = {"str", sub(prevnode[2], lastwordpos, -1)}
|
432
|
-
prevnode[2] = sub(prevnode[2], 1, lastwordpos - 1)
|
433
|
-
nodes[#nodes + 1] = newnode
|
434
|
-
prevnode = newnode
|
435
|
-
end
|
436
|
-
end
|
437
|
-
if not endswithspace then
|
438
|
-
insert_attributes(prevnode, result)
|
439
|
-
end
|
440
|
-
end
|
441
|
-
result = nil
|
442
|
-
elseif find(tag, "^list_item") then
|
443
|
-
local marker = string.match(subject, "^%S+", startpos)
|
444
|
-
local styles = {}
|
445
|
-
gsub(tag, "%[([^]]*)%]", function(x) styles[#styles + 1] = x end)
|
446
|
-
-- create a list node with the consecutive list items
|
447
|
-
-- of the same kind
|
448
|
-
local list = {"list", result}
|
449
|
-
-- put the attributes from the first item on the list itself:
|
450
|
-
list.attr = result.attr
|
451
|
-
result.attr = nil
|
452
|
-
result[1] = "list_item"
|
453
|
-
if marker == ":" then
|
454
|
-
make_definition_list_item(result)
|
455
|
-
end
|
456
|
-
if sourcepos then
|
457
|
-
list.pos = {result.pos[1], result.pos[2]}
|
458
|
-
end
|
459
|
-
-- now get remaining items
|
460
|
-
local nextitem = matches[idx]
|
461
|
-
while nextitem do
|
462
|
-
local sp, _, ann = unpack_match(nextitem)
|
463
|
-
if not find(ann, "^%+list_item") then
|
464
|
-
break
|
465
|
-
end
|
466
|
-
-- check which of the styles this item matches
|
467
|
-
local newstyles = {}
|
468
|
-
gsub(ann, "%[([^]]*)%]",
|
469
|
-
function(x) newstyles[x] = true end)
|
470
|
-
local matched_styles = {}
|
471
|
-
for _,x in ipairs(styles) do
|
472
|
-
if newstyles[x] then
|
473
|
-
matched_styles[#matched_styles + 1] = x
|
474
|
-
end
|
475
|
-
end
|
476
|
-
if #styles > 0 and #matched_styles == 0 then
|
477
|
-
break -- does not match any styles
|
478
|
-
end
|
479
|
-
styles = matched_styles
|
480
|
-
-- at this point styles contains the styles that match all items
|
481
|
-
-- in the list so far...
|
482
|
-
|
483
|
-
list[#list].tight = is_tight(matches, startidx, idx - 1, false)
|
484
|
-
startidx = idx
|
485
|
-
idx = idx + 1
|
486
|
-
local item = get_node(tag)
|
487
|
-
if tag == "list_item[X]" then
|
488
|
-
set_checkbox(item, startidx)
|
489
|
-
end
|
490
|
-
item[1] = "list_item"
|
491
|
-
if sourcepos then
|
492
|
-
item.pos = {sp, finalpos}
|
493
|
-
list.pos[2] = item.pos[2]
|
494
|
-
end
|
495
|
-
if marker == ":" then
|
496
|
-
make_definition_list_item(item)
|
497
|
-
end
|
498
|
-
list[#list + 1] = item
|
499
|
-
nextitem = matches[idx]
|
500
|
-
end
|
501
|
-
list[#list].tight = is_tight(matches, startidx, idx - 1, true)
|
502
|
-
local tight = true
|
503
|
-
for i=2,#list do
|
504
|
-
tight = tight and list[i].tight
|
505
|
-
list[i].tight = nil
|
506
|
-
end
|
507
|
-
list.list_style = styles[1] -- resolve, if still ambiguous
|
508
|
-
list.tight = tight
|
509
|
-
list.start = get_list_start(marker, list.list_style)
|
510
|
-
result = list
|
511
|
-
end
|
512
|
-
nodes[#nodes + 1] = result
|
513
|
-
elseif mod == "-" then -- close
|
514
|
-
assert(false, "unmatched " .. annot .. " encountered at byte " ..
|
515
|
-
startpos)
|
516
|
-
idx = idx + 1
|
517
|
-
return nil
|
518
|
-
elseif tag == "reference_key" then
|
519
|
-
local key = sub(subject, startpos + 1, endpos - 1)
|
520
|
-
local result = {tag, key}
|
521
|
-
idx = idx + 1
|
522
|
-
nodes[#nodes + 1] = result
|
523
|
-
elseif tag == "reference_value" then
|
524
|
-
local val = sub(subject, startpos, endpos)
|
525
|
-
local result = {tag, val}
|
526
|
-
idx = idx + 1
|
527
|
-
nodes[#nodes + 1] = result
|
528
|
-
else -- leaf
|
529
|
-
local result
|
530
|
-
if tag == "softbreak" then
|
531
|
-
result = {tag}
|
532
|
-
elseif tag == "footnote_reference" then
|
533
|
-
result = {tag, sub(subject, startpos + 2, endpos - 1)}
|
534
|
-
else
|
535
|
-
result = {tag, sub(subject, startpos, endpos)}
|
536
|
-
end
|
537
|
-
if sourcepos then
|
538
|
-
result.pos = {startpos, endpos}
|
539
|
-
end
|
540
|
-
if block_attributes then
|
541
|
-
for i=1,#block_attributes do
|
542
|
-
insert_attributes(result, block_attributes[i])
|
543
|
-
end
|
544
|
-
block_attributes = nil
|
545
|
-
end
|
546
|
-
idx = idx + 1
|
547
|
-
if result then
|
548
|
-
nodes[#nodes + 1] = result
|
549
|
-
end
|
550
|
-
end
|
551
|
-
end
|
552
|
-
end
|
553
|
-
return nodes
|
554
|
-
end
|
555
|
-
|
556
|
-
local doc = get_node("doc")
|
557
|
-
doc.references = references
|
558
|
-
doc.footnotes = footnotes
|
559
|
-
return doc
|
560
|
-
end
|
561
|
-
|
562
|
-
local function render_nodes(nodes, handle, init, indent)
|
563
|
-
indent = indent or 0
|
564
|
-
init = init or 1
|
565
|
-
for i=init,#nodes do
|
566
|
-
local node = nodes[i]
|
567
|
-
handle:write(rep(" ", indent))
|
568
|
-
if type(node) == "string" then
|
569
|
-
handle:write(format("%q",node))
|
570
|
-
else
|
571
|
-
handle:write(node[1])
|
572
|
-
if node.pos then
|
573
|
-
handle:write(format(" (%d-%d)", node.pos[1], node.pos[2]))
|
574
|
-
end
|
575
|
-
for k,v in pairs(node) do
|
576
|
-
if type(k) == "string" and k ~= "pos" and k ~= "attr" then
|
577
|
-
handle:write(format(" %s=%q", k, tostring(v)))
|
578
|
-
end
|
579
|
-
end
|
580
|
-
if node.attr then
|
581
|
-
local keys = node.attr._keys
|
582
|
-
for j=1,#keys do
|
583
|
-
local k = keys[j]
|
584
|
-
handle:write(format(" %s=%q", k, node.attr[k]))
|
585
|
-
end
|
586
|
-
end
|
587
|
-
end
|
588
|
-
handle:write("\n")
|
589
|
-
if node[2] then -- children
|
590
|
-
render_nodes(node, handle, 2, indent + 2)
|
591
|
-
end
|
592
|
-
end
|
593
|
-
end
|
594
|
-
|
595
|
-
local function render(doc, handle)
|
596
|
-
render_nodes(doc, handle, 2, 0)
|
597
|
-
if doc.references then
|
598
|
-
handle:write("references = {\n")
|
599
|
-
for k,v in pairs(doc.references) do
|
600
|
-
handle:write(format(" [%q] = %q,\n", k, v.destination))
|
601
|
-
end
|
602
|
-
handle:write("}\n")
|
603
|
-
end
|
604
|
-
if doc.footnotes then
|
605
|
-
handle:write("footnotes = {\n")
|
606
|
-
for k,v in pairs(doc.footnotes) do
|
607
|
-
handle:write(format(" [%q] =\n", k))
|
608
|
-
render_nodes(v, handle, 2, 4)
|
609
|
-
end
|
610
|
-
handle:write("}\n")
|
611
|
-
end
|
612
|
-
end
|
613
|
-
|
614
|
-
return { to_ast = to_ast,
|
615
|
-
render = render,
|
616
|
-
insert_attribute = insert_attribute,
|
617
|
-
copy_attributes = copy_attributes }
|
618
|
-
|
619
|
-
|
620
|
-
--[[
|
621
|
-
Copyright (C) 2022 John MacFarlane
|
622
|
-
|
623
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
624
|
-
a copy of this software and associated documentation files (the
|
625
|
-
"Software"), to deal in the Software without restriction, including
|
626
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
627
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
628
|
-
permit persons to whom the Software is furnished to do so, subject to
|
629
|
-
the following conditions:
|
630
|
-
|
631
|
-
The above copyright notice and this permission notice shall be included
|
632
|
-
in all copies or substantial portions of the Software.
|
633
|
-
|
634
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
635
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
636
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
637
|
-
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
638
|
-
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
639
|
-
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
640
|
-
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
641
|
-
|
642
|
-
]]
|