Skip to content

Commit 7339f4d

Browse files
authored
Merge pull request #161 from machow/interlinks
Enable Interlinks
2 parents 1a048cd + 184eb50 commit 7339f4d

File tree

9 files changed

+99
-22
lines changed

9 files changed

+99
-22
lines changed

.flake8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[flake8]
2-
exclude = docs, test_*, examples
2+
exclude = docs, test_*, .flake8, examples
33
max-line-length = 90
44
ignore =
55
# line too long

docs/_quarto.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ quartodoc:
8080
style: pkgdown
8181
dir: api
8282
package: quartodoc
83+
render_interlinks: true
8384
sidebar: "api/_sidebar.yml"
8485
sections:
8586
- title: Preperation Functions

docs/get-started/interlinks.qmd

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,20 @@ and attempts to parse them much faster.
7171
Be sure to install the latest version of the interlinks filter, using `quarto add machow/quartodoc`.
7272
:::
7373

74+
### Rendering interlinks in API docs
75+
76+
quartodoc can convert type annotations in function signatures to interlinks.
77+
78+
In order to enable this behavior, set `render_interlinks: true` in the quartodoc config.
79+
80+
81+
```yaml
82+
quartodoc:
83+
render_interlinks: true
84+
```
85+
86+
87+
7488
## Running the interlinks filter
7589

7690
First, build the reference for your own site, which includes an objects.json inventory:

quartodoc/__main__.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,23 @@ def build(config, filter, dry_run, watch, verbose):
209209
doc_build()
210210

211211

212-
@click.command()
212+
@click.command(
213+
short_help="Generate inventory files that the Quarto "
214+
"`interlink` extension can use to auto-link to other docs."
215+
)
213216
@click.argument("config", default="_quarto.yml")
214217
@click.option("--dry-run", is_flag=True, default=False)
215218
@click.option("--fast", is_flag=True, default=False)
216219
def interlinks(config, dry_run, fast):
220+
"""
221+
Generate inventory files that the Quarto `interlink` extension can use to
222+
auto-link to other docs.
223+
224+
The files are stored in a cache directory, which defaults to _inv.
225+
The Quarto extension `interlinks` will look for these files in the cache
226+
and add links to your docs accordingly.
227+
"""
228+
217229
# config loading ----
218230
cfg = yaml.safe_load(open(config))
219231
interlinks = cfg.get("interlinks", {})

quartodoc/autosummary.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,9 @@ class Builder:
397397
dynamic:
398398
Whether to dynamically load all python objects. By default, objects are
399399
loaded using static analysis.
400+
render_interlinks:
401+
Whether to render interlinks syntax inside documented objects. Note that the
402+
interlinks filter is required to generate the links in quarto.
400403
parser:
401404
Docstring parser to use. This correspond to different docstring styles,
402405
and can be one of "google", "sphinx", and "numpy". Defaults to "numpy".
@@ -446,6 +449,7 @@ def __init__(
446449
source_dir: "str | None" = None,
447450
dynamic: bool | None = None,
448451
parser="numpy",
452+
render_interlinks: bool = False,
449453
_fast_inventory=False,
450454
):
451455
self.layout = self.load_layout(
@@ -460,6 +464,10 @@ def __init__(
460464
self.parser = parser
461465

462466
self.renderer = Renderer.from_config(renderer)
467+
if render_interlinks:
468+
# this is a top-level option, but lives on the renderer
469+
# so we just manually set it there for now.
470+
self.renderer.render_interlinks = render_interlinks
463471

464472
if out_index is not None:
465473
self.out_index = out_index

quartodoc/renderers/md_renderer.py

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,14 @@ def __init__(
6666
show_signature_annotations: bool = False,
6767
display_name: str = "relative",
6868
hook_pre=None,
69-
use_interlinks=False,
69+
render_interlinks=False,
7070
):
7171
self.header_level = header_level
7272
self.show_signature = show_signature
7373
self.show_signature_annotations = show_signature_annotations
7474
self.display_name = display_name
7575
self.hook_pre = hook_pre
76-
self.use_interlinks = use_interlinks
76+
self.render_interlinks = render_interlinks
7777

7878
self.crnt_header_level = self.header_level
7979

@@ -105,27 +105,34 @@ def _render_table(self, rows, headers):
105105

106106
return table
107107

108-
def render_annotation(self, el: "str | expr.Name | expr.Expression | None"):
109-
"""Special hook for rendering a type annotation.
108+
# render_annotation method --------------------------------------------------------
110109

110+
@dispatch
111+
def render_annotation(self, el: str) -> str:
112+
"""Special hook for rendering a type annotation.
111113
Parameters
112114
----------
113115
el:
114116
An object representing a type annotation.
115-
116117
"""
118+
return sanitize(el)
117119

118-
if isinstance(el, type(None)):
119-
return el
120-
elif isinstance(el, str):
121-
return sanitize(el)
120+
@dispatch
121+
def render_annotation(self, el: None) -> str:
122+
return ""
122123

124+
@dispatch
125+
def render_annotation(self, el: expr.Name) -> str:
123126
# TODO: maybe there is a way to get tabulate to handle this?
124127
# unescaped pipes screw up table formatting
125-
if isinstance(el, expr.Name):
126-
return sanitize(el.source)
128+
if self.render_interlinks:
129+
return f"[{sanitize(el.source)}](`{el.full}`)"
130+
131+
return sanitize(el.source)
127132

128-
return sanitize(el.full)
133+
@dispatch
134+
def render_annotation(self, el: expr.Expression) -> str:
135+
return "".join(map(self.render_annotation, el))
129136

130137
# signature method --------------------------------------------------------
131138

@@ -148,7 +155,6 @@ def signature(self, el: layout.Doc):
148155
@dispatch
149156
def signature(self, el: dc.Alias, source: Optional[dc.Alias] = None):
150157
"""Return a string representation of an object's signature."""
151-
152158
return self.signature(el.target, el)
153159

154160
@dispatch
@@ -425,17 +431,19 @@ def render(self, el: dc.Parameter):
425431
glob = ""
426432

427433
annotation = self.render_annotation(el.annotation)
434+
name = sanitize(el.name)
435+
428436
if self.show_signature_annotations:
429437
if annotation and has_default:
430-
res = f"{glob}{el.name}: {el.annotation} = {el.default}"
438+
res = f"{glob}{name}: {annotation} = {el.default}"
431439
elif annotation:
432-
res = f"{glob}{el.name}: {el.annotation}"
440+
res = f"{glob}{name}: {annotation}"
433441
elif has_default:
434-
res = f"{glob}{el.name}={el.default}"
442+
res = f"{glob}{name}={el.default}"
435443
else:
436-
res = f"{glob}{el.name}"
444+
res = f"{glob}{name}"
437445

438-
return sanitize(res)
446+
return res
439447

440448
# docstring parts -------------------------------------------------------------
441449

@@ -457,7 +465,6 @@ def render(self, el: ds.DocstringSectionText):
457465
def render(self, el: ds.DocstringSectionParameters):
458466
rows = list(map(self.render, el.value))
459467
header = ["Name", "Type", "Description", "Default"]
460-
461468
return self._render_table(rows, header)
462469

463470
@dispatch

quartodoc/tests/__snapshots__/test_renderers.ambr

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,17 @@
11
# serializer version: 1
2+
# name: test_render_annotations_complex
3+
'''
4+
# quartodoc.tests.example_signature.a_complex_signature { #quartodoc.tests.example_signature.a_complex_signature }
5+
6+
`tests.example_signature.a_complex_signature(x: [list](`list`)\[[C](`quartodoc.tests.example_signature.C`) \| [int](`int`) \| None\])`
7+
8+
## Parameters
9+
10+
| Name | Type | Description | Default |
11+
|--------|--------------------------------------------------------------------------------------|-----------------|------------|
12+
| `x` | [list](`list`)\[[C](`quartodoc.tests.example_signature.C`) \| [int](`int`) \| None\] | The x parameter | _required_ |
13+
'''
14+
# ---
215
# name: test_render_doc_class[embedded]
316
'''
417
# quartodoc.tests.example_class.C { #quartodoc.tests.example_class.C }

quartodoc/tests/example_signature.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,16 @@ def early_args(x, *args, a, b=2, **kwargs):
2222

2323
def late_args(x, a, b=2, *args, **kwargs):
2424
...
25+
26+
27+
class C:
28+
...
29+
30+
31+
def a_complex_signature(x: "list[C | int | None]"):
32+
"""
33+
Parameters
34+
----------
35+
x:
36+
The x parameter
37+
"""

quartodoc/tests/test_renderers.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def test_render_param_kwargs_annotated():
2626

2727
assert (
2828
res
29-
== "a: int, b: int = 1, *args: list\\[str\\], c: int, d: int, **kwargs: dict\\[str, str\\]"
29+
== "a: int, b: int = 1, *args: list\[str\], c: int, d: int, **kwargs: dict\[str, str\]"
3030
)
3131

3232

@@ -99,6 +99,7 @@ def test_render_doc_attribute(renderer):
9999
)
100100

101101
res = renderer.render(attr)
102+
print(res)
102103

103104
assert res == ["abc", r"Optional\[\]", "xyz"]
104105

@@ -111,6 +112,14 @@ def test_render_doc_module(snapshot, renderer, children):
111112
assert res == snapshot
112113

113114

115+
def test_render_annotations_complex(snapshot):
116+
renderer = MdRenderer(render_interlinks=True, show_signature_annotations=True)
117+
bp = blueprint(Auto(name="quartodoc.tests.example_signature.a_complex_signature"))
118+
res = renderer.render(bp)
119+
120+
assert res == snapshot
121+
122+
114123
@pytest.mark.parametrize("children", ["embedded", "flat"])
115124
def test_render_doc_class(snapshot, renderer, children):
116125
bp = blueprint(Auto(name="quartodoc.tests.example_class.C", children=children))

0 commit comments

Comments
 (0)