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+
111local function read_inv_text (filename )
212 -- read file
313 local file = io.open (filename , " r" )
@@ -11,16 +21,16 @@ local function read_inv_text(filename)
1121 local project = str :match (" # Project: (%S+)" )
1222 local version = str :match (" # Version: (%S+)" )
1323
14- local data = {project = project , version = version , items = {}}
24+ local data = { project = project , version = version , items = {} }
1525
1626 local ptn_data =
1727 " ^" ..
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
2434
2535
2636 -- Iterate through each line in the file content
@@ -48,7 +58,6 @@ local function read_inv_text(filename)
4858end
4959
5060local function read_json (filename )
51-
5261 local file = io.open (filename , " r" )
5362 if file == nil then
5463 return nil
@@ -66,18 +75,15 @@ local function read_inv_text_or_json(base_name)
6675 -- TODO: refactors so we don't just close the file immediately
6776 io.close (file )
6877 json = read_inv_text (base_name .. " .txt" )
69-
7078 else
7179 json = read_json (base_name .. " .json" )
7280 end
7381
7482 return json
7583end
7684
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 )
8187 local results = {}
8288 for _ , inv in ipairs (inventory ) do
8389 for _ , item in ipairs (inv .items ) do
@@ -98,7 +104,7 @@ local function lookup(search_object)
98104 goto continue
99105 else
100106 if search_object .domain or item .domain == " py" then
101- table.insert (results , item )
107+ table.insert (results , item )
102108 end
103109
104110 goto continue
@@ -112,23 +118,24 @@ local function lookup(search_object)
112118 return results [1 ]
113119 end
114120 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 )
116122 return results [1 ]
117123 end
118124 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 )
120127 end
121128
122129 return nil
123130end
124131
125- local function mysplit (inputstr , sep )
132+ local function mysplit (inputstr , sep )
126133 if sep == nil then
127- sep = " %s"
134+ sep = " %s"
128135 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 )
132139 end
133140 return t
134141end
@@ -140,7 +147,84 @@ local function normalize_role(role)
140147 return role
141148end
142149
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 )
144228 local starts_with_colon = str :sub (1 , 1 ) == " :"
145229 local search = {}
146230 if starts_with_colon then
@@ -163,15 +247,15 @@ local function build_search_object(str)
163247 search .role = normalize_role (t [3 ])
164248 search .name = t [4 ]:match (" %%60(.*)%%60" )
165249 else
166- quarto . log . warning (" couldn't parse this link: " .. str )
250+ _debug_log (" couldn't parse this link: " .. str , debug )
167251 return {}
168252 end
169253 else
170254 search .name = str :match (" %%60(.*)%%60" )
171255 end
172256
173257 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 )
175259 return {}
176260 end
177261
@@ -220,7 +304,60 @@ function Link(link)
220304 return link
221305end
222306
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 )
224361 for _ , item in ipairs (json .items ) do
225362 item .uri = prefix .. item .uri
226363 end
@@ -232,6 +369,23 @@ return {
232369 Meta = function (meta )
233370 local json
234371 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
235389 if meta .interlinks and meta .interlinks .sources then
236390 for k , v in pairs (meta .interlinks .sources ) do
237391 local base_name = quarto .project .offset .. " /_inv/" .. k .. " _objects"
@@ -246,9 +400,12 @@ return {
246400 if json ~= nil then
247401 fixup_json (json , " /" )
248402 end
403+
404+ prepend_aliases (flatten_alias_list (aliases ))
249405 end
250406 },
251407 {
252- Link = Link
408+ Link = Link ,
409+ Code = Code
253410 }
254411}
0 commit comments