1
+ local inventory = {} -- sphinx inventories
2
+ local autolink -- set in Meta
3
+ local autolink_ignore_token = " qd-no-link"
4
+
5
+ local function _debug_log (text , debug )
6
+ if debug then
7
+ quarto .log .warning (text )
8
+ end
9
+ end
10
+
1
11
local function read_inv_text (filename )
2
12
-- read file
3
13
local file = io.open (filename , " r" )
@@ -11,16 +21,16 @@ local function read_inv_text(filename)
11
21
local project = str :match (" # Project: (%S+)" )
12
22
local version = str :match (" # Version: (%S+)" )
13
23
14
- local data = {project = project , version = version , items = {}}
24
+ local data = { project = project , version = version , items = {} }
15
25
16
26
local ptn_data =
17
27
" ^" ..
18
- " (.-)%s+" .. -- name
19
- " ([%S:]-):" .. -- domain
20
- " ([%S]+)%s+" .. -- role
21
- " (%-?%d+)%s+" .. -- priority
22
- " (%S*)%s+" .. -- uri
23
- " (.-)\r ?$" -- dispname
28
+ " (.-)%s+" .. -- name
29
+ " ([%S:]-):" .. -- domain
30
+ " ([%S]+)%s+" .. -- role
31
+ " (%-?%d+)%s+" .. -- priority
32
+ " (%S*)%s+" .. -- uri
33
+ " (.-)\r ?$" -- dispname
24
34
25
35
26
36
-- Iterate through each line in the file content
@@ -48,7 +58,6 @@ local function read_inv_text(filename)
48
58
end
49
59
50
60
local function read_json (filename )
51
-
52
61
local file = io.open (filename , " r" )
53
62
if file == nil then
54
63
return nil
@@ -66,18 +75,15 @@ local function read_inv_text_or_json(base_name)
66
75
-- TODO: refactors so we don't just close the file immediately
67
76
io.close (file )
68
77
json = read_inv_text (base_name .. " .txt" )
69
-
70
78
else
71
79
json = read_json (base_name .. " .json" )
72
80
end
73
81
74
82
return json
75
83
end
76
84
77
- local inventory = {}
78
-
79
- local function lookup (search_object )
80
-
85
+ -- each inventory has entries: project, version, items
86
+ local function lookup (search_object , debug )
81
87
local results = {}
82
88
for _ , inv in ipairs (inventory ) do
83
89
for _ , item in ipairs (inv .items ) do
@@ -98,7 +104,7 @@ local function lookup(search_object)
98
104
goto continue
99
105
else
100
106
if search_object .domain or item .domain == " py" then
101
- table.insert (results , item )
107
+ table.insert (results , item )
102
108
end
103
109
104
110
goto continue
@@ -112,23 +118,24 @@ local function lookup(search_object)
112
118
return results [1 ]
113
119
end
114
120
if # results > 1 then
115
- quarto . log . warning (" Found multiple matches for " .. search_object .name .. " , using the first match." )
121
+ _debug_log (" Found multiple matches for " .. search_object .name .. " , using the first match." , debug )
116
122
return results [1 ]
117
123
end
118
124
if # results == 0 then
119
- quarto .log .warning (" Found no matches for object:\n " , search_object )
125
+ _debug_log (" Found no matches for object:\n " , debug )
126
+ _debug_log (search_object , debug )
120
127
end
121
128
122
129
return nil
123
130
end
124
131
125
- local function mysplit (inputstr , sep )
132
+ local function mysplit (inputstr , sep )
126
133
if sep == nil then
127
- sep = " %s"
134
+ sep = " %s"
128
135
end
129
- local t = {}
130
- for str in string.gmatch (inputstr , " ([^" .. sep .. " ]+)" ) do
131
- table.insert (t , str )
136
+ local t = {}
137
+ for str in string.gmatch (inputstr , " ([^" .. sep .. " ]+)" ) do
138
+ table.insert (t , str )
132
139
end
133
140
return t
134
141
end
@@ -140,7 +147,84 @@ local function normalize_role(role)
140
147
return role
141
148
end
142
149
143
- local function build_search_object (str )
150
+ local function copy_replace (original , key , new_value )
151
+ -- First create a copy of the table
152
+ local copy = {}
153
+ for k , v in pairs (original ) do
154
+ copy [k ] = v
155
+ end
156
+
157
+ -- Then replace the specific value
158
+ copy [key ] = new_value
159
+
160
+ return copy
161
+ end
162
+
163
+ local function contains (list , value )
164
+ -- check if list contains a value
165
+ for i , v in ipairs (list ) do
166
+ if v == value then
167
+ return true
168
+ end
169
+ end
170
+ return false
171
+ end
172
+
173
+ local function flatten_alias_list (list )
174
+ -- flatten a list of lists into a single list,
175
+ -- where each entry has the form {key, subvalue}}
176
+ -- e.g.
177
+ -- input: {key1 = {subval1, subval2}, key2 = subval3}
178
+ -- output: {{key1, subval1}, {key1, subval2}, {key2, subval3}}
179
+ local flat = {}
180
+ for key , sublist in pairs (list ) do
181
+ if type (sublist ) == " table" then
182
+ for _ , subvalue in ipairs (sublist ) do
183
+ table.insert (flat , { key , subvalue })
184
+ end
185
+ else
186
+ table.insert (flat , { key , sublist })
187
+ end
188
+ end
189
+ return flat
190
+ end
191
+
192
+ local function prepend_aliases (flat_aliases )
193
+ -- if str up to first period starts with an alias, then
194
+ -- replace it with the full name.
195
+ -- For example, suppose we have the alias quartodoc -> qd
196
+ -- e.g. qd.Auto -> quartodoc.Auto
197
+ -- e.g. qda.Auto -> qda.Auto
198
+
199
+ local new_inv = {}
200
+ new_inv [" project" ] = " aliases"
201
+ new_inv [" version" ] = " 0.0.9999" -- I have not begun to think about version...
202
+ new_inv [" items" ] = {}
203
+
204
+ for _ , name_pair in pairs (flat_aliases ) do
205
+ local full = name_pair [1 ]
206
+ local alias = name_pair [2 ]
207
+ for _ , inv in ipairs (inventory ) do
208
+ for _ , item in ipairs (inv .items ) do
209
+ if string.sub (item .name , 1 , string.len (full ) + 1 ) == (full .. " ." ) then
210
+ -- replace full .. "." with alias .. "."
211
+ local prefix
212
+ if not alias or pandoc .utils .stringify (alias ) == " " then
213
+ prefix = " "
214
+ else
215
+ -- TODO: ensure alias doesn't end with period
216
+ prefix = pandoc .utils .stringify (alias ) .. " ."
217
+ end
218
+ local new_name = prefix .. string.sub (item .name , string.len (full ) + 2 )
219
+ table.insert (new_inv .items , copy_replace (item , " name" , new_name ))
220
+ end
221
+ end
222
+ end
223
+ end
224
+ table.insert (inventory , new_inv )
225
+ end
226
+
227
+ local function build_search_object (str , debug )
144
228
local starts_with_colon = str :sub (1 , 1 ) == " :"
145
229
local search = {}
146
230
if starts_with_colon then
@@ -163,15 +247,15 @@ local function build_search_object(str)
163
247
search .role = normalize_role (t [3 ])
164
248
search .name = t [4 ]:match (" %%60(.*)%%60" )
165
249
else
166
- quarto . log . warning (" couldn't parse this link: " .. str )
250
+ _debug_log (" couldn't parse this link: " .. str , debug )
167
251
return {}
168
252
end
169
253
else
170
254
search .name = str :match (" %%60(.*)%%60" )
171
255
end
172
256
173
257
if search .name == nil then
174
- quarto . log . warning (" couldn't parse this link: " .. str )
258
+ _debug_log (" couldn't parse this link: " .. str , debug )
175
259
return {}
176
260
end
177
261
@@ -220,7 +304,60 @@ function Link(link)
220
304
return link
221
305
end
222
306
223
- local function fixup_json (json , prefix )
307
+ function Code (code )
308
+ if (not autolink ) or contains (code .classes , autolink_ignore_token ) then
309
+ return code
310
+ end
311
+
312
+ -- allow text for lookup to be simple function call
313
+ -- and also support shortened syntax (~~ prefix)
314
+ -- e.g. my_func() -> my_func
315
+ -- e.g. a.b.call() -> a.b.call
316
+ -- e.g. ~~my_func() -> my_func
317
+ local text
318
+
319
+ -- detect and remove shortening syntax (~~ prefix)
320
+ local is_shortened = code .text :sub (1 , 2 ) == " ~~"
321
+ local is_short_dot = code .text :sub (1 , 3 ) == " ~~."
322
+ local unprefixed = code .text :gsub (" ^~~%.?" , " " )
323
+ if unprefixed :match (" %(%s*%)" ) then
324
+ text = unprefixed :gsub (" %(%s*%)" , " " )
325
+ else
326
+ text = unprefixed
327
+ end
328
+
329
+
330
+ -- return code.attr
331
+ local search = build_search_object (" %60" .. text .. " %60" )
332
+ local item = lookup (search )
333
+
334
+ -- determine replacement, used if no link text specified ----
335
+ if item == nil then
336
+ code .text = unprefixed
337
+ return code
338
+ end
339
+
340
+ -- shorten text if shortening syntax used
341
+ if is_shortened then
342
+ -- keep text after last period (.)
343
+ local split = mysplit (unprefixed , " ." )
344
+ if # split > 0 then
345
+ local new_name = split [# split ]
346
+ if is_short_dot then
347
+ -- if shortened with dot, keep the dot
348
+ new_name = " ." .. new_name
349
+ end
350
+ code .text = new_name
351
+ else
352
+ code .text = unprefixed
353
+ end
354
+ end
355
+
356
+
357
+ return pandoc .Link (code , item .uri :gsub (" %$$" , search .name ))
358
+ end
359
+
360
+ local function fixup_json (json , prefix , attach )
224
361
for _ , item in ipairs (json .items ) do
225
362
item .uri = prefix .. item .uri
226
363
end
@@ -232,6 +369,23 @@ return {
232
369
Meta = function (meta )
233
370
local json
234
371
local prefix
372
+ local aliases
373
+
374
+ -- set globals from config
375
+ if meta .interlinks and meta .interlinks .autolink then
376
+ autolink = true
377
+ else
378
+ autolink = false
379
+ end
380
+
381
+ local aliases
382
+ if meta .interlinks and meta .interlinks .aliases then
383
+ aliases = meta .interlinks .aliases
384
+ else
385
+ aliases = {}
386
+ end
387
+
388
+ -- process sources
235
389
if meta .interlinks and meta .interlinks .sources then
236
390
for k , v in pairs (meta .interlinks .sources ) do
237
391
local base_name = quarto .project .offset .. " /_inv/" .. k .. " _objects"
@@ -246,9 +400,12 @@ return {
246
400
if json ~= nil then
247
401
fixup_json (json , " /" )
248
402
end
403
+
404
+ prepend_aliases (flatten_alias_list (aliases ))
249
405
end
250
406
},
251
407
{
252
- Link = Link
408
+ Link = Link ,
409
+ Code = Code
253
410
}
254
411
}
0 commit comments