1
- import click
1
+ import sphobjinv as soi
2
2
3
3
from griffe .loader import GriffeLoader
4
4
from griffe .docstrings .parsers import Parser , parse
10
10
11
11
from plum import dispatch
12
12
13
- from typing import Tuple , Union
13
+ from typing import Tuple , Union , Callable
14
14
15
15
16
- @click .command ()
17
- @click .option ("--in-name" , help = "Name of input inventory file" )
18
- @click .option ("--out-name" , help = "Name of result (defaults to <input_name>.json)" )
19
- def convert_inventory (in_name , out_name = None ):
20
- """Convert a sphinx inventory file to json."""
21
- import json
22
- import sphobjinv as soi
16
+ # Set version =================================================================
17
+
18
+ from importlib_metadata import version as _v
19
+
20
+ __version__ = _v ("quartodoc" )
23
21
22
+ del _v
23
+
24
+
25
+ # Inventory files =============================================================
26
+ #
27
+ # inventories have this form:
28
+ # {
29
+ # "project": "Siuba", "version": "0.4.2", "count": 2,
30
+ # "items": [
31
+ # {
32
+ # "name": "siuba.dply.verbs.mutate",
33
+ # "domain": "py",
34
+ # "role": "function",
35
+ # "priority": 0,
36
+ # "uri": "api/verbs-mutate-transmute/",
37
+ # "dispname": "-"
38
+ # },
39
+ # ...
40
+ # ]
41
+ # }
42
+
43
+
44
+ # @click.command()
45
+ # @click.option("--in-name", help="Name of input inventory file")
46
+ # @click.option("--out-name", help="Name of result (defaults to <input_name>.json)")
47
+ def convert_inventory (in_name : "Union[str, soi.Inventory]" , out_name = None ):
48
+ """Convert a sphinx inventory file to json.
49
+
50
+ Parameters
51
+ ----------
52
+ in_name: str or sphobjinv.Inventory file
53
+ Name of inventory file.
54
+ out_name: str, optional
55
+ Output file name.
56
+
57
+ """
58
+
59
+ import json
24
60
from pathlib import Path
25
61
26
62
if out_name is None :
27
- out_name = Path (in_name ).with_suffix (".json" )
63
+ if isinstance (in_name , str ):
64
+ out_name = Path (in_name ).with_suffix (".json" )
65
+ else :
66
+ raise TypeError ()
67
+
68
+ if isinstance (in_name , soi .Inventory ):
69
+ inv = in_name
70
+ else :
71
+ inv = soi .Inventory (in_name )
72
+
73
+ out = _to_clean_dict (inv )
74
+
75
+ json .dump (out , open (out_name , "w" ))
76
+
77
+
78
+ def create_inventory (
79
+ project : str ,
80
+ version : str ,
81
+ items : "list[dc.Object | dc.Alias]" ,
82
+ uri : "str | Callable[dc.Object, str]" = lambda s : f"{ s .canonical_path } .html" ,
83
+ dispname : "str | Callable[dc.Object, str]" = "-" ,
84
+ ) -> soi .Inventory ():
85
+ """Return a sphinx inventory file.
86
+
87
+ Parameters
88
+ ----------
89
+ project: str
90
+ Name of the project (often the package name).
91
+ version: str
92
+ Version of the project (often the package version).
93
+ items: str
94
+ A docstring parser to use.
95
+ uri:
96
+ Link relative to the docs where the items documentation lives.
97
+ dispname:
98
+ Name to be shown when a link to the item is made.
99
+
100
+ Examples
101
+ --------
102
+
103
+ >>> f_obj = get_object("quartodoc", "create_inventory")
104
+ >>> inv = create_inventory("example", "0.0", [f_obj])
105
+ >>> inv
106
+ Inventory(project='example', version='0.0', source_type=<SourceTypes.Manual: 'manual'>)
107
+
108
+ To preview the inventory, we can convert it to a dictionary:
109
+
110
+ >>> _to_clean_dict(inv)
111
+ {'project': 'example',
112
+ 'version': '0.0',
113
+ 'count': 1,
114
+ 'items': [{'name': 'quartodoc.create_inventory',
115
+ 'domain': 'py',
116
+ 'role': 'function',
117
+ 'priority': '1',
118
+ 'uri': 'quartodoc.create_inventory.html',
119
+ 'dispname': '-'}]}
120
+
121
+ """
28
122
29
- inv = soi .Inventory (in_name )
123
+ inv = soi .Inventory ()
124
+ inv .project = project
125
+ inv .version = version
126
+
127
+ soi_items = [_create_inventory_item (x , uri , dispname ) for x in items ]
128
+
129
+ inv .objects .extend (soi_items )
130
+
131
+ return inv
132
+
133
+
134
+ def _to_clean_dict (inv : soi .Inventory ):
135
+ """Similar to Inventory.json_dict(), but with a list of items."""
30
136
31
137
obj = inv .json_dict ()
32
138
@@ -36,7 +142,40 @@ def convert_inventory(in_name, out_name=None):
36
142
out = dict (meta )
37
143
out ["items" ] = entries
38
144
39
- json .dump (out , open (out_name , "w" ))
145
+ return out
146
+
147
+
148
+ def _create_inventory_item (
149
+ item : "dc.Object | dc.Alias" ,
150
+ uri : "str | Callable[dc.Object, str]" ,
151
+ dispname : "str | Callable[dc.Object, str]" = "-" ,
152
+ priority = "1" ,
153
+ ) -> soi .DataObjStr :
154
+ if isinstance (item , dc .Alias ):
155
+ target = item .target
156
+ else :
157
+ target = item
158
+
159
+ return soi .DataObjStr (
160
+ name = target .canonical_path ,
161
+ domain = "py" ,
162
+ role = target .kind .value ,
163
+ priority = priority ,
164
+ uri = _maybe_call (uri , target ),
165
+ dispname = _maybe_call (dispname , target ),
166
+ )
167
+
168
+
169
+ def _maybe_call (s : "str | Callable" , obj ):
170
+ if callable (s ):
171
+ return s (obj )
172
+ elif isinstance (s , str ):
173
+ return s
174
+
175
+ raise TypeError (f"Expected string or callable, received: { type (s )} " )
176
+
177
+
178
+ # Docstring loading / parsing =================================================
40
179
41
180
42
181
def parse_function (module : str , func_name : str ):
@@ -75,7 +214,40 @@ def get_function(module: str, func_name: str, parser: str = "numpy") -> dc.Objec
75
214
return f_data
76
215
77
216
78
- # utils =======================================================================
217
+ def get_object (module : str , object_name : str , parser : str = "numpy" ) -> dc .Object :
218
+ """Fetch a griffe object.
219
+
220
+ Parameters
221
+ ----------
222
+ module: str
223
+ A module name.
224
+ object_name: str
225
+ A function name.
226
+ parser: str
227
+ A docstring parser to use.
228
+
229
+ See Also
230
+ --------
231
+ get_function: a deprecated function.
232
+
233
+ Examples
234
+ --------
235
+
236
+ >>> get_function("quartodoc", "get_function")
237
+ <Function('get_function', ...
238
+
239
+ """
240
+ griffe = GriffeLoader (docstring_parser = Parser (parser ))
241
+ mod = griffe .load_module (module )
242
+
243
+ f_data = mod ._modules_collection [f"{ module } .{ object_name } " ]
244
+
245
+ return f_data
246
+
247
+
248
+ # Docstring rendering =========================================================
249
+
250
+ # utils -----------------------------------------------------------------------
79
251
# these largely re-format the output of griffe
80
252
81
253
@@ -106,7 +278,7 @@ def escape(val: str):
106
278
return f"`{ val } `"
107
279
108
280
109
- # to_md =======================================================================
281
+ # to_md -----------------------------------------------------------------------
110
282
# griffe function dataclass structure:
111
283
# Object:
112
284
# kind: Kind {"module", "class", "function", "attribute"}
@@ -129,8 +301,29 @@ def escape(val: str):
129
301
130
302
131
303
class MdRenderer :
304
+ """Render docstrings to markdown.
305
+
306
+ Parameters
307
+ ----------
308
+ header_level: int
309
+ The level of the header (e.g. 1 is the biggest).
310
+ show_signature: bool
311
+ Whether to show the function signature.
312
+
313
+ Examples
314
+ --------
315
+
316
+ >>> from quartodoc import MdRenderer, get_object
317
+ >>> renderer = MdRenderer(header_level=2)
318
+ >>> f = get_object("quartodoc", "get_object")
319
+ >>> print(renderer.to_md(f)[:81])
320
+ ## get_object
321
+ `get_object(module: str, object_name: str, parser: str = 'numpy')`
322
+
323
+ """
324
+
132
325
def __init__ (
133
- self , header_level : int = 2 , show_signature : str = True , hook_pre = None
326
+ self , header_level : int = 2 , show_signature : bool = True , hook_pre = None
134
327
):
135
328
self .header_level = header_level
136
329
self .show_signature = show_signature
@@ -146,7 +339,9 @@ def to_md(self, el: Union[dc.Alias, dc.Object]):
146
339
147
340
_str_pars = self .to_md (el .parameters )
148
341
str_sig = f"`{ el .name } ({ _str_pars } )`"
149
- str_title = f"{ '#' * self .header_level } { el .name } "
342
+
343
+ _anchor = f"{{#sec-{ el .name } }}"
344
+ str_title = f"{ '#' * self .header_level } { el .name } { _anchor } "
150
345
151
346
str_body = []
152
347
if el .docstring is None :
@@ -259,7 +454,3 @@ def to_md(self, el: ExampleText):
259
454
)
260
455
def to_md (self , el ):
261
456
raise NotImplementedError (f"{ type (el )} " )
262
-
263
-
264
- if __name__ == "__main__" :
265
- convert_inventory ()
0 commit comments