Skip to content

Commit 5a5441c

Browse files
committed
Add attribute checks
1 parent 3eb53a3 commit 5a5441c

File tree

3 files changed

+272
-0
lines changed

3 files changed

+272
-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: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
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 == 'Inlines' then
163+
warn_conversion('Blocks', pt, shift)
164+
return setmetatable({pandoc.Plain(obj)}, BlocksMT)
165+
else
166+
warn_conversion('Blocks', pt, shift)
167+
return pandoc.Blocks(obj)
168+
end
169+
end,
170+
171+
Caption = function (obj)
172+
local tp = ptype(obj)
173+
if tp == 'table' and obj.short and obj.long then
174+
obj.short = ensure_type['Inlines'](obj.short)
175+
obj.long = ensure_type['Blocks'](obj.long)
176+
return obj
177+
else
178+
warn_conversion('Caption', tp)
179+
return {
180+
long = ensure_type['Blocks'](obj),
181+
}
182+
end
183+
end,
184+
185+
DefinitionItem = function (obj, shift)
186+
shift = shift or 0
187+
-- warn_conversion('DefinitionItem', type(obj))
188+
obj[1] = ensure_type['Inlines'](obj[1], shift + 1)
189+
obj[2] = ensure_type[{ sequence = 'Blocks'}](obj[2], shift + 1)
190+
return obj
191+
end,
192+
193+
Inlines = function (obj, shift)
194+
shift = shift or 0
195+
local pt = ptype(obj)
196+
if pt == 'Inlines' then
197+
return obj
198+
elseif pt == 'List' or pt == 'table' then
199+
warn_conversion('Inlines', pt, shift)
200+
return setmetatable(obj, InlinesMT)
201+
else
202+
warn_conversion('Inlines', pt, shift)
203+
return pandoc.Inlines(obj)
204+
end
205+
end,
206+
207+
integer = function (obj)
208+
if type(obj) ~= 'number' or math.floor(obj) ~= obj then
209+
warn_conversion('integer', type(obj))
210+
return math.floor(tonumber(obj))
211+
end
212+
return obj
213+
end,
214+
215+
string = function (obj)
216+
if type(obj) ~= 'string' then
217+
warn_conversion('string', type(obj))
218+
return tostring(obj)
219+
end
220+
return obj
221+
end,
222+
}
223+
local ensure_type_metatable = {
224+
__index = function (tbl, key)
225+
if type(key) == 'table' then
226+
if key.sequence then
227+
return function (obj)
228+
local pt = ptype(obj)
229+
if pt == 'table' or pt == 'List' then
230+
return pandoc.List.map(obj, tbl[key.sequence])
231+
end
232+
end
233+
end
234+
end
235+
return function (obj)
236+
warn_conversion(key, ptype(obj))
237+
return obj
238+
end
239+
end
240+
}
241+
setmetatable(ensure_type, ensure_type_metatable)
242+
243+
local InlineMT = debug.getmetatable(pandoc.Space())
244+
local BlockMT = debug.getmetatable(pandoc.HorizontalRule())
245+
local default_Inline_setter = InlineMT.setters.content
246+
local default_Block_setter = BlockMT.setters.content
247+
248+
local function enable_attribute_checks()
249+
InlineMT.setters.content = function (obj, key, value)
250+
local expected_type = InlineTypes[obj.tag][key]
251+
if expected_type then
252+
default_Inline_setter(obj, key, ensure_type[expected_type](value))
253+
else
254+
default_Block_setter(obj, key, value)
255+
end
256+
end
257+
BlockMT.setters.content = function (obj, key, value)
258+
local expected_type = BlockTypes[obj.tag][key]
259+
if expected_type then
260+
default_Block_setter(obj, key, ensure_type[expected_type](value))
261+
else
262+
default_Block_setter(obj, key, value)
263+
end
264+
end
265+
end
266+
267+
return {
268+
enable_attribute_checks = enable_attribute_checks
269+
}

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)