Skip to content

Commit 3f48d84

Browse files
committed
add max_depth arg to preview, draft most of renderer doc
1 parent b25cd28 commit 3f48d84

File tree

7 files changed

+175
-60
lines changed

7 files changed

+175
-60
lines changed

docs/_quarto.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ website:
8383
contents:
8484
- get-started/architecture.qmd
8585
- get-started/docstrings.qmd
86+
- get-started/renderers.qmd
8687
- get-started/interlinks.qmd
8788
- id: dummy
8889

docs/examples/index.qmd

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,11 @@
1-
* [pkgdown](pkgdown/reference/)
2-
* [single-page](single-page/reference/)
1+
| style | source | layout |
2+
| ----- | ------ | ------ |
3+
| [pkgdown] | [github][pkgdown-code] | Index page with a title and short description for functions listed in each section. Each function gets its own documentation page. |
4+
| [single-page] | [github][sp-code] | Index page has function documentation embedded on it. |
5+
6+
: {tbl-colwidths="[20, 20, 60]"}
7+
8+
[pkgdown]: /examples/pkgdown/reference
9+
[pkgdown-code]: https://github.com/machow/quartodoc/tree/main/examples/pkgdown
10+
[single-page]: /examples/single-page/reference
11+
[sp-code]: https://github.com/machow/quartodoc/tree/main/examples/single-page

docs/get-started/basic-docs.qmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ quartodoc:
2424
- preview
2525
```
2626
27-
## Options
27+
## Top-level options
2828
2929
The `quartodoc` section takes a `style` field, specifying which [](`quartodoc.Builder`)
3030
to use (currently "pkgdown" or "single-page"; see [Examples](/examples/)).

docs/get-started/docstrings.qmd

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -92,57 +92,10 @@ print(f_obj.docstring.value)
9292
preview(f_obj.docstring.parsed)
9393
```
9494

95-
## Rendering docstrings
96-
97-
quartodoc uses tree visitors to render parsed docstrings to formats like markdown and HTML.
98-
Tree visitors define how each type of object in the parse tree should be handled.
99-
100-
```{python}
101-
import griffe.dataclasses as dc
102-
import griffe.docstrings.dataclasses as ds
103-
104-
from plum import dispatch
105-
from typing import Union
106-
107-
108-
class SomeRenderer:
109-
def __init__(self, header_level: int = 1):
110-
self.header_level = header_level
111-
112-
@dispatch
113-
def visit(self, el):
114-
raise NotImplementedError(f"Unsupported type: {type(el)}")
115-
116-
@dispatch
117-
def visit(self, el: Union[dc.Alias, dc.Object]):
118-
header = "#" * self.header_level
119-
str_header = f"{header} {el.name}"
120-
str_params = f"N PARAMETERS: {len(el.parameters)}"
121-
str_sections = "SECTIONS: " + self.visit(el.docstring)
122-
123-
# return something pretty
124-
return "\n".join([str_header, str_params, str_sections])
125-
126-
@dispatch
127-
def visit(self, el: dc.Docstring):
128-
return "A docstring with {len(el.parsed)} pieces"
129-
130-
print(SomeRenderer(header_level=2).visit(f_obj))
131-
```
132-
133-
Note 3 big pieces:
134-
135-
* **Generic dispatch**: The plum `dispatch` function decorates each `visit` method. The type annotations
136-
specify the types of data each version of visit should dispatch on.
137-
* **Default behavior**: The first `visit` method ensures a `NotImplementedError` is raised by default.
138-
* **Tree walking**: `visit` methods often call `visit` again on sub elements.
139-
140-
14195
## Parsing other docstring formats
14296

14397
Currently, quartodoc expects docstrings in the numpydoc format.
14498
However, the tool it uses under the hood (griffe) is easy to customize, and supports multiple formats.
14599

146100
See the griffe [loading docs](https://mkdocstrings.github.io/griffe/loading/) for instructions.
147101
Specifically, the [GriffeLoader](https://mkdocstrings.github.io/griffe/reference/griffe/loader/#griffe.loader.GriffeLoader) takes options for customizing docstring parsing.
148-

docs/get-started/overview.qmd

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,14 @@ quarto preview
7070

7171
* Load docstrings (with [griffe](https://github.com/mkdocstrings/griffe))
7272
* Render docstrings (e.g. with [MdRenderer](/api/#sec-MdRenderer))
73-
* Enable cross references to function documentation.
73+
* Enable cross references to function documentation (with interlinks filter).
7474
- Link to functions within a doc.
7575
- Link to functions in other docs.
7676
- Generate an inventory file for other docs to link to yours.
77-
* Generate high-level summaries.
77+
* Generate high-level summaries (with [Builder](/api/#api-builders)).
7878
- First line of docstring used as description.
7979
- Class doc pages have table of class attributes.
80-
- Tables of function names and descriptions.
80+
- Index pages list function names and descriptions.
8181

8282
## Example sites
8383

docs/get-started/renderers.qmd

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
---
2+
title: Rendering docstrings
3+
jupyter:
4+
kernelspec:
5+
display_name: Python 3 (ipykernel)
6+
language: python
7+
name: python3
8+
---
9+
10+
The previous section covered how to read and preview parsed docstrings.
11+
In this section, we'll look at how to render a parsed docstring into a format
12+
that can be used in documentation, like markdown or HTML.
13+
14+
## Setting up problem
15+
16+
Suppose that we wanted to take a function like `get_object()` and render a summary, with:
17+
18+
* The number of parameters it takes.
19+
* The number of sections in its parsed docstring.
20+
21+
For `get_object()` it might look like the following:
22+
23+
```
24+
## get_object
25+
N PARAMETERS: 3
26+
SECTIONS: A docstring with 4 pieces
27+
```
28+
29+
## Inspecting a function
30+
31+
As covered in the previous section, we can preview information about `get_object()`.
32+
33+
```{python}
34+
from quartodoc import get_object, preview
35+
36+
f_obj = get_object("quartodoc", "get_object")
37+
38+
preview(f_obj, max_depth=3)
39+
```
40+
41+
Note the following pieces:
42+
43+
* `preview()` takes a max_depth argument, that limits how much information it shows.
44+
* `get_object()` takes 3 parameters.
45+
* `get_object()` has a docstring with 4 sections.
46+
47+
Importantly, the nodes (``) in the tree mention the name class of the python objects
48+
being previewed (e.g. `Alias`, `Expression`, `Parameters`).
49+
We'll need these to specify how to render objects of each class.
50+
51+
## Generic dispatch
52+
53+
Generic dispatch is the main programming technique used by quartodoc renderers.
54+
It let's you define how a function (like `render()`) should operate on different
55+
types of objects.
56+
57+
```{python}
58+
from plum import dispatch
59+
60+
import griffe.dataclasses as dc
61+
import griffe.docstrings.dataclasses as ds
62+
63+
64+
@dispatch
65+
def render(el: object):
66+
print(f"Default rendering: {type(el)}")
67+
68+
@dispatch
69+
def render(el: dc.Alias):
70+
print("Alias rendering")
71+
render(el.parameters)
72+
73+
@dispatch
74+
def render(el: list):
75+
print("List rendering")
76+
[render(entry) for entry in el]
77+
78+
79+
render(f_obj)
80+
```
81+
82+
## Defining a Renderer
83+
84+
quartodoc uses tree visitors to render parsed docstrings to formats like markdown and HTML.
85+
Tree visitors define how each type of object in the parse tree should be handled.
86+
87+
```{python}
88+
import griffe.dataclasses as dc
89+
import griffe.docstrings.dataclasses as ds
90+
91+
from quartodoc import get_object
92+
from plum import dispatch
93+
from typing import Union
94+
95+
96+
class SomeRenderer:
97+
def __init__(self, header_level: int = 1):
98+
self.header_level = header_level
99+
100+
@dispatch
101+
def visit(self, el):
102+
raise NotImplementedError(f"Unsupported type: {type(el)}")
103+
104+
@dispatch
105+
def visit(self, el: Union[dc.Alias, dc.Object]):
106+
header = "#" * self.header_level
107+
str_header = f"{header} {el.name}"
108+
str_params = f"N PARAMETERS: {len(el.parameters)}"
109+
str_sections = "SECTIONS: " + self.visit(el.docstring)
110+
111+
# return something pretty
112+
return "\n".join([str_header, str_params, str_sections])
113+
114+
@dispatch
115+
def visit(self, el: dc.Docstring):
116+
return f"A docstring with {len(el.parsed)} pieces"
117+
118+
119+
f_obj = get_object("quartodoc", "get_object")
120+
121+
print(SomeRenderer(header_level=2).visit(f_obj))
122+
```
123+
124+
Note 3 big pieces:
125+
126+
* **Generic dispatch**: The plum `dispatch` function decorates each `visit` method. The type annotations
127+
specify the types of data each version of visit should dispatch on.
128+
* **Default behavior**: The first `visit` method ensures a `NotImplementedError` is raised by default.
129+
* **Tree walking**: `visit` methods often call `visit` again on sub elements.
130+
131+

quartodoc/ast.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,24 @@
1313

1414

1515
@dispatch
16-
def fields(el: Union[dc.Object, dc.ObjectAliasMixin]):
17-
options = ["name", "canonical_path", "classes", "members", "functions", "docstring"]
16+
def fields(el: dc.Object):
17+
options = [
18+
"name",
19+
"canonical_path",
20+
"classes",
21+
"parameters",
22+
"members",
23+
"functions",
24+
"docstring",
25+
]
1826
return [opt for opt in options if hasattr(el, opt)]
1927

2028

29+
@dispatch
30+
def fields(el: dc.ObjectAliasMixin):
31+
return fields(el.target)
32+
33+
2134
@dispatch
2235
def fields(el: dc.Function):
2336
return ["name", "annotation", "parameters", "docstring"]
@@ -86,10 +99,11 @@ class Formatter:
8699
icon_connector = "│ "
87100
string_truncate_mark = " ..."
88101

89-
def __init__(self, string_max_length: int = 50):
102+
def __init__(self, string_max_length: int = 50, max_depth=999):
90103
self.string_max_length = string_max_length
104+
self.max_depth = max_depth
91105

92-
def format(self, call, pad=0):
106+
def format(self, call, depth=0, pad=0):
93107
"""Return a Symbolic or Call back as a nice tree, with boxes for nodes."""
94108

95109
call = self.transform(call)
@@ -105,10 +119,17 @@ def format(self, call, pad=0):
105119

106120
call_str = self.icon_block + call.__class__.__name__
107121

122+
# short-circuit for max depth ----
123+
if depth >= self.max_depth:
124+
return call_str + self.string_truncate_mark
125+
126+
# format arguments ----
108127
fields_str = []
109128
for name in crnt_fields:
110129
val = self.get_field(call, name)
111-
formatted_val = self.format(val, pad=len(str(name)) + self.n_spaces)
130+
formatted_val = self.format(
131+
val, depth + 1, pad=len(str(name)) + self.n_spaces
132+
)
112133
fields_str.append(f"{name} = {formatted_val}")
113134

114135
padded = []
@@ -154,7 +175,7 @@ def fmt_pipe(self, x, is_final=False, pad=0):
154175
return prefix + connector.join(x.splitlines())
155176

156177

157-
def preview(ast: "dc.Object | ds.Docstring | object"):
178+
def preview(ast: "dc.Object | ds.Docstring | object", max_depth=999):
158179
"""Print a friendly representation of a griffe object (e.g. function, docstring)
159180
160181
Examples
@@ -170,4 +191,4 @@ def preview(ast: "dc.Object | ds.Docstring | object"):
170191
...
171192
172193
"""
173-
print(Formatter().format(ast))
194+
print(Formatter(max_depth=max_depth).format(ast))

0 commit comments

Comments
 (0)