Skip to content

Commit b594330

Browse files
committed
feat: prepare quartodoc for doc site
1 parent 5b1375c commit b594330

File tree

3 files changed

+214
-22
lines changed

3 files changed

+214
-22
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,4 @@ dmypy.json
134134

135135
# Pyre type checker
136136
.pyre/
137+
.Rproj.user

quartodoc.py

Lines changed: 211 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import click
1+
import sphobjinv as soi
22

33
from griffe.loader import GriffeLoader
44
from griffe.docstrings.parsers import Parser, parse
@@ -10,23 +10,129 @@
1010

1111
from plum import dispatch
1212

13-
from typing import Tuple, Union
13+
from typing import Tuple, Union, Callable
1414

1515

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")
2321

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
2460
from pathlib import Path
2561

2662
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+
"""
28122

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."""
30136

31137
obj = inv.json_dict()
32138

@@ -36,7 +142,40 @@ def convert_inventory(in_name, out_name=None):
36142
out = dict(meta)
37143
out["items"] = entries
38144

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 =================================================
40179

41180

42181
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
75214
return f_data
76215

77216

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 -----------------------------------------------------------------------
79251
# these largely re-format the output of griffe
80252

81253

@@ -106,7 +278,7 @@ def escape(val: str):
106278
return f"`{val}`"
107279

108280

109-
# to_md =======================================================================
281+
# to_md -----------------------------------------------------------------------
110282
# griffe function dataclass structure:
111283
# Object:
112284
# kind: Kind {"module", "class", "function", "attribute"}
@@ -129,8 +301,29 @@ def escape(val: str):
129301

130302

131303
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+
132325
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
134327
):
135328
self.header_level = header_level
136329
self.show_signature = show_signature
@@ -146,7 +339,9 @@ def to_md(self, el: Union[dc.Alias, dc.Object]):
146339

147340
_str_pars = self.to_md(el.parameters)
148341
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}"
150345

151346
str_body = []
152347
if el.docstring is None:
@@ -259,7 +454,3 @@ def to_md(self, el: ExampleText):
259454
)
260455
def to_md(self, el):
261456
raise NotImplementedError(f"{type(el)}")
262-
263-
264-
if __name__ == "__main__":
265-
convert_inventory()

setup.cfg

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ url = https://github.com/machow/quartodoc
88
author = Michael Chow
99
author_email = [email protected]
1010
license = MIT
11-
keywords = template, packaging, documentation
11+
keywords = documentation, quarto
1212
classifiers =
1313
Programming Language :: Python :: 3.8
1414
Programming Language :: Python :: 3.9
@@ -24,13 +24,13 @@ python_requires = >3.8
2424
install_requires =
2525
griffe
2626
plum-dispatch
27+
sphobjinv
2728
tabulate
2829

2930

3031
[options.extras_require]
3132
dev =
3233
pytest
33-
pytest-dotenv
3434

3535

3636
[bdist_wheel]

0 commit comments

Comments
 (0)