4
4
import itertools
5
5
import json
6
6
import warnings
7
+ import yaml
7
8
8
9
from pydantic import BaseModel , Field
9
10
from dataclasses import dataclass
@@ -29,10 +30,10 @@ class InvLookupError(Exception):
29
30
30
31
def get_path_to_root ():
31
32
# In lua filters you can use quarto.project.offset
32
- return os .environ [ENV_PROJECT_ROOT ]
33
+ return Path ( os .environ [ENV_PROJECT_ROOT ])
33
34
34
35
35
- def parse_rst_style_ref (full_text ):
36
+ def parse_rst_style_ref (full_text : str ):
36
37
"""
37
38
Returns
38
39
-------
@@ -52,6 +53,19 @@ def parse_rst_style_ref(full_text):
52
53
return ref , text
53
54
54
55
56
+ def parse_md_style_link (full_text : str ):
57
+ import re
58
+
59
+ m = re .match (r"\[(?P<text>.*?)\]\((?P<ref>.*?)\)" , full_text )
60
+
61
+ if m is None :
62
+ raise Exception ()
63
+
64
+ text , ref = m .groups ()
65
+
66
+ return ref , text
67
+
68
+
55
69
# Dataclasses representing pandoc elements ------------------------------------
56
70
# These classes are used to help indicate what elements the Interlinks class
57
71
# would return in a pandoc filter.
@@ -60,15 +74,15 @@ def parse_rst_style_ref(full_text):
60
74
class Link (BaseModel ):
61
75
"""Indicates a pandoc Link element."""
62
76
63
- kind : Literal ["link " ] = "link "
77
+ kind : Literal ["Link " ] = "Link "
64
78
content : str
65
79
url : str
66
80
67
81
68
82
class Code (BaseModel ):
69
83
"""Indicates a pandoc Code element."""
70
84
71
- kind : Literal ["code " ] = "code "
85
+ kind : Literal ["Code " ] = "Code "
72
86
content : str
73
87
74
88
@@ -79,11 +93,12 @@ class Unchanged(BaseModel):
79
93
return the original content element.
80
94
"""
81
95
82
- kind : Literal ["unchanged " ] = "unchanged "
96
+ kind : Literal ["Unchanged " ] = "Unchanged "
83
97
content : str
84
98
85
99
86
100
class TestSpecEntry (BaseModel ):
101
+ input : str
87
102
output_text : Optional [str ] = None
88
103
output_link : Optional [str ] = None
89
104
output_element : Optional [
@@ -93,6 +108,10 @@ class TestSpecEntry(BaseModel):
93
108
warning : Optional [str ] = None
94
109
95
110
111
+ class TestSpec (BaseModel ):
112
+ __root__ : list [TestSpecEntry ]
113
+
114
+
96
115
# Reference syntax ------------------------------------------------------------
97
116
# note that the classes above were made pydantic models so we could serialize
98
117
# them from json. We could make these ones pydantic too, but there is not a
@@ -280,16 +299,16 @@ def ref_to_anchor(self, ref: str | Ref, text: "str | None"):
280
299
is_shortened = ref .target .startswith ("~" )
281
300
282
301
entry = self .lookup_reference (ref )
283
- dst_url = entry [ " full_uri" ]
302
+ dst_url = entry . full_uri
284
303
285
304
if not text :
286
- name = entry [ " name" ] if entry [ " dispname" ] == "-" else entry [ " dispname" ]
305
+ name = entry . name if entry . dispname == "-" else entry . dispname
287
306
if is_shortened :
288
307
# shorten names from module.sub_module.func_name -> func_name
289
308
name = name .split ("." )[- 1 ]
290
- return Link (name , url = dst_url )
309
+ return Link (content = name , url = dst_url )
291
310
292
- return Link (text , url = dst_url )
311
+ return Link (content = text , url = dst_url )
293
312
294
313
def pandoc_ref_to_anchor (self , ref : str , text : str ) -> Link | Code | Unchanged :
295
314
"""Convert a ref to a Link, with special handling for pandoc filters.
@@ -299,27 +318,33 @@ def pandoc_ref_to_anchor(self, ref: str, text: str) -> Link | Code | Unchanged:
299
318
non-ref urls unchanged.
300
319
"""
301
320
302
- if (ref .startswith ("%60" ) or ref .startswith (":" )) and ref .endswith ("%60" ):
321
+ # detect what *might* be an interlink. note that we don't validate
322
+ # that it has a closing `, to allow a RefSyntaxError to bubble up.
323
+ if ref .startswith ("%60" ) or ref .startswith (":" ):
303
324
# Get URL ----
304
325
try :
305
326
return self .ref_to_anchor (ref .replace ("%60" , "`" ), text )
306
327
except InvLookupError as e :
307
- warnings .warn (warnings . warn ( str ( e )) )
328
+ warnings .warn (f" { e . __class__ . __name__ } : { e } " )
308
329
if text :
309
330
# Assuming content is a ListContainer(Str(...))
310
331
body = text
311
332
else :
312
- body = ref .replace ("%60" , "" )
313
- return Code (body )
333
+ body = ref .replace ("%60" , "` " )
334
+ return Code (content = body )
314
335
315
- return Unchanged (ref )
336
+ return Unchanged (content = ref )
316
337
317
338
@staticmethod
318
339
def _filter_by_field (items , field_name : str , value : "str | None" = None ):
319
340
if value is None :
320
341
return items
321
342
322
- return (item for item in items if item [field_name ] == value )
343
+ # TODO: Ref uses invname, while EnhancedItem uses inv_name
344
+ if field_name == "invname" :
345
+ field_name = "inv_name"
346
+
347
+ return (item for item in items if getattr (item , field_name ) == value )
323
348
324
349
@classmethod
325
350
def from_items (cls , items : "list[EnhancedItem]" ):
@@ -331,9 +356,16 @@ def from_items(cls, items: "list[EnhancedItem]"):
331
356
return invs
332
357
333
358
@classmethod
334
- def from_quarto_config (cls , cfg : dict ):
359
+ def from_quarto_config (cls , cfg : str | dict , root_dir : str | None = None ):
360
+
361
+ if isinstance (cfg , str ):
362
+ if root_dir is None :
363
+ root_dir = Path (cfg ).parent
364
+
365
+ cfg = yaml .safe_load (open (cfg ))
366
+
335
367
invs = cls ()
336
- p_root = get_path_to_root ()
368
+ p_root = get_path_to_root () if root_dir is None else Path ( root_dir )
337
369
338
370
interlinks = cfg ["interlinks" ]
339
371
sources = interlinks ["sources" ]
@@ -354,3 +386,5 @@ def from_quarto_config(cls, cfg: dict):
354
386
json_data = json .load (open (inv_path ))
355
387
356
388
invs .load_inventory (json_data , url = cfg ["url" ], invname = doc_name )
389
+
390
+ return invs
0 commit comments