Skip to content

Commit f3b90a3

Browse files
committed
Add attribute checks
1 parent a4b9edb commit f3b90a3

File tree

3 files changed

+336
-0
lines changed

3 files changed

+336
-0
lines changed

src/resources/filters/main.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ import("./quarto-init/metainit.lua")
196196

197197
-- [/import]
198198

199+
_quarto.modules.attribcheck.enable_attribute_checks()
200+
199201
initCrossrefIndex()
200202

201203
initShortcodeHandlers()
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
local io = require 'io'
2+
local pandoc = require 'pandoc'
3+
local utils = require 'pandoc.utils'
4+
local ptype = utils.type
5+
6+
local InlinesMT = debug.getmetatable(pandoc.Inlines{})
7+
local BlocksMT = debug.getmetatable(pandoc.Blocks{})
8+
9+
local InlineTypes = {
10+
Cite = {
11+
citation = 'List',
12+
content = 'Inlines',
13+
},
14+
Code = {
15+
attr = 'Attr',
16+
text = 'string',
17+
},
18+
Emph = {
19+
content = 'Inlines',
20+
},
21+
Image = {
22+
attr = 'Attr',
23+
caption = 'Inlines',
24+
src = 'string',
25+
title = 'string',
26+
},
27+
LineBreak = {
28+
},
29+
Link = {
30+
attr = 'Attr',
31+
content = 'Inlines',
32+
target = 'string',
33+
title = 'string',
34+
},
35+
Math = {
36+
mathtype = 'string',
37+
text = 'string',
38+
},
39+
Note = {
40+
content = 'Blocks',
41+
},
42+
Quoted = {
43+
content = 'Inlines',
44+
quotetype = 'string',
45+
},
46+
RawInline = {
47+
format = 'string',
48+
text = 'string',
49+
},
50+
SmallCaps = {
51+
content = 'Inlines',
52+
},
53+
SoftBreak = {
54+
},
55+
Space = {
56+
},
57+
Str = {
58+
text = 'string',
59+
},
60+
Span = {
61+
attr = 'Attr',
62+
content = 'Inlines',
63+
},
64+
Strikeout = {
65+
content = 'Inlines',
66+
},
67+
Strong = {
68+
content = 'Inlines',
69+
},
70+
Subscript = {
71+
content = 'Inlines',
72+
},
73+
Superscript = {
74+
content = 'Inlines',
75+
},
76+
Underline = {
77+
content = 'Inlines',
78+
},
79+
}
80+
81+
local BlockTypes = {
82+
BlockQuote = {
83+
content = 'Blocks',
84+
},
85+
BulletList = {
86+
content = {sequence = 'Blocks'}
87+
},
88+
CodeBlock = {
89+
attr = 'Attr',
90+
text = 'string',
91+
},
92+
DefinitionList = {
93+
content = {sequence = 'DefinitionItem'},
94+
},
95+
Div = {
96+
attr = 'Attr',
97+
content = 'Blocks',
98+
},
99+
Figure = {
100+
attr = 'Attr',
101+
caption = 'Caption',
102+
content = 'Blocks',
103+
},
104+
Header = {
105+
attr = 'Attr',
106+
content = 'Inlines',
107+
level = 'integer',
108+
},
109+
HorizontalRule = {
110+
content = 'Inlines',
111+
},
112+
LineBlock = {
113+
content = 'Inlines',
114+
},
115+
OrderedList = {
116+
content = {sequence = 'Blocks'},
117+
},
118+
Para = {
119+
content = 'Inlines',
120+
},
121+
Plain = {
122+
content = 'Inlines',
123+
},
124+
RawBlock = {
125+
content = 'Inlines',
126+
},
127+
Table = {
128+
attr = 'Attr',
129+
caption = 'Caption',
130+
colspecs = {sequence = 'ColSpec'},
131+
bodies = {sequence = 'TableBody'},
132+
head = 'TableHead',
133+
foot = 'TableFoot',
134+
},
135+
}
136+
137+
local function warn_conversion (expected, actual, shift)
138+
local dbginfo = debug.getinfo(5 + (shift or 0), 'Sln')
139+
warn(actual .. ' instead of ' .. expected .. ': ' ..
140+
(dbginfo.name or '<no name>') .. ' in ' .. dbginfo.source .. ':' ..
141+
dbginfo.currentline)
142+
return
143+
end
144+
145+
local ensure_type
146+
ensure_type = {
147+
Attr = function (obj)
148+
local pt = ptype(obj)
149+
return pt == 'Attr'
150+
and obj
151+
or pandoc.Attr(obj)
152+
end,
153+
154+
Blocks = function (obj, shift)
155+
shift = shift or 0
156+
local pt = ptype(obj)
157+
if pt == 'Blocks' then
158+
return obj
159+
elseif pt == 'List' or pt == 'table' then
160+
warn_conversion('Blocks', pt, shift)
161+
return setmetatable(obj, BlocksMT)
162+
elseif pt == 'Inline' then
163+
warn_conversion('Blocks', pt, shift)
164+
return setmetatable({pandoc.Plain{obj}}, BlocksMT)
165+
elseif pt == 'Inlines' then
166+
warn_conversion('Blocks', pt, shift)
167+
return setmetatable({pandoc.Plain(obj)}, BlocksMT)
168+
else
169+
warn_conversion('Blocks', pt, shift)
170+
return pandoc.Blocks(obj)
171+
end
172+
end,
173+
174+
Caption = function (obj, shift)
175+
local tp = ptype(obj)
176+
if tp == 'table' and obj.long then
177+
obj.long = ensure_type['Blocks'](obj.long, shift + 1)
178+
if obj.short then
179+
obj.short = ensure_type['Inlines'](obj.short, shift + 1)
180+
end
181+
return obj
182+
else
183+
warn_conversion('Caption', tp)
184+
local blocks = ensure_type['Blocks'](obj)
185+
for i = 1, #obj do
186+
obj[i] = nil
187+
end
188+
obj.long = blocks
189+
return obj
190+
end
191+
end,
192+
193+
DefinitionItem = function (obj, shift)
194+
shift = shift or 0
195+
-- warn_conversion('DefinitionItem', type(obj))
196+
obj[1] = ensure_type['Inlines'](obj[1], shift + 1)
197+
obj[2] = ensure_type[{ sequence = 'Blocks'}](obj[2], shift + 1)
198+
return obj
199+
end,
200+
201+
Inlines = function (obj, shift)
202+
shift = shift or 0
203+
local pt = ptype(obj)
204+
if pt == 'Inlines' then
205+
return obj
206+
elseif pt == 'List' or pt == 'table' then
207+
warn_conversion('Inlines', pt, shift)
208+
return setmetatable(obj, InlinesMT)
209+
else
210+
warn_conversion('Inlines', pt, shift)
211+
return pandoc.Inlines(obj)
212+
end
213+
end,
214+
215+
TableBody = function (obj, shift)
216+
local pt = ptype(obj)
217+
if pt ~= 'table' then
218+
warn_conversion('table (TableBody)', pt, shift + 1)
219+
end
220+
return obj
221+
end,
222+
223+
TableFoot = function (obj)
224+
local pt = ptype(obj)
225+
if pt ~= 'pandoc TableFoot' then
226+
error('Cannot auto-convert to TableFoot, got ' .. pt)
227+
end
228+
return obj
229+
end,
230+
231+
TableHead = function (obj)
232+
local pt = ptype(obj)
233+
if pt ~= 'pandoc TableHead' then
234+
error('Cannot auto-convert to TableHead, got ' .. pt)
235+
end
236+
return obj
237+
end,
238+
239+
integer = function (obj)
240+
if type(obj) ~= 'number' or math.floor(obj) ~= obj then
241+
warn_conversion('integer', type(obj))
242+
return math.floor(tonumber(obj))
243+
end
244+
return obj
245+
end,
246+
247+
string = function (obj)
248+
if type(obj) ~= 'string' then
249+
warn_conversion('string', type(obj))
250+
return tostring(obj)
251+
end
252+
return obj
253+
end,
254+
}
255+
local ensure_type_metatable = {
256+
__index = function (tbl, key)
257+
if type(key) == 'table' then
258+
if key.sequence then
259+
return function (obj)
260+
local pt = ptype(obj)
261+
if pt == 'table' or pt == 'List' then
262+
return pandoc.List.map(obj, tbl[key.sequence])
263+
end
264+
end
265+
end
266+
end
267+
return function (obj)
268+
warn_conversion(key, ptype(obj))
269+
return obj
270+
end
271+
end
272+
}
273+
setmetatable(ensure_type, ensure_type_metatable)
274+
275+
local InlineMT = debug.getmetatable(pandoc.Space())
276+
local BlockMT = debug.getmetatable(pandoc.HorizontalRule())
277+
local PandocMT = debug.getmetatable(pandoc.Pandoc{})
278+
local default_Inline_setter = InlineMT.setters.content
279+
local default_Block_setter = BlockMT.setters.content
280+
281+
local function enable_attribute_checks()
282+
for fieldname, setter in pairs(InlineMT.setters) do
283+
InlineMT.setters[fieldname] = function (obj, key, value)
284+
local expected_type = InlineTypes[obj.tag][key]
285+
value = expected_type
286+
and ensure_type[expected_type](value, 0)
287+
or value
288+
setter(obj, key, value)
289+
end
290+
end
291+
for fieldname, setter in pairs(BlockMT.setters) do
292+
BlockMT.setters[fieldname] = function (obj, key, value)
293+
local expected_type = BlockTypes[obj.tag][fieldname]
294+
value = expected_type
295+
and ensure_type[expected_type](value, 0)
296+
or value
297+
setter(obj, key, value)
298+
end
299+
end
300+
PandocMT.setters.blocks = function (obj, key, value)
301+
default_Block_setter(obj, key, ensure_type['Blocks'](value))
302+
end
303+
end
304+
305+
return {
306+
ensure_blocks = function (obj)
307+
local pt = ptype(obj)
308+
if pt == 'Blocks' then
309+
return obj
310+
elseif pt == 'List' or pt == 'table' then
311+
return setmetatable(obj, BlocksMT)
312+
elseif pt == 'Inline' then
313+
return setmetatable({pandoc.Plain{obj}}, BlocksMT)
314+
elseif pt == 'Inlines' then
315+
return setmetatable({pandoc.Plain(obj)}, BlocksMT)
316+
else
317+
return pandoc.Blocks(obj)
318+
end
319+
end,
320+
321+
ensure_inlines = function (obj)
322+
local pt = ptype(obj)
323+
if pt == 'Inlines' then
324+
return obj
325+
elseif pt == 'List' or pt == 'table' then
326+
return setmetatable(obj, InlinesMT)
327+
else
328+
return pandoc.Inlines(obj)
329+
end
330+
end,
331+
332+
enable_attribute_checks = enable_attribute_checks
333+
}

src/resources/filters/modules/import_all.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
_quarto.modules = {
55
astshortcode = require("modules/astshortcode"),
6+
attribcheck = require("modules/attribcheck"),
67
authors = require("modules/authors"),
78
brand = require("modules/brand/brand"),
89
callouts = require("modules/callouts"),

0 commit comments

Comments
 (0)