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