Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[flake8]
exclude = docs, test_*, examples
exclude = docs, test_*, .flake8, examples
max-line-length = 90
ignore =
# line too long
Expand Down
1 change: 1 addition & 0 deletions docs/_quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ quartodoc:
style: pkgdown
dir: api
package: quartodoc
render_interlinks: true
sidebar: "api/_sidebar.yml"
sections:
- title: Preperation Functions
Expand Down
14 changes: 14 additions & 0 deletions docs/get-started/interlinks.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,20 @@ and attempts to parse them much faster.
Be sure to install the latest version of the interlinks filter, using `quarto add machow/quartodoc`.
:::

### Rendering interlinks in API docs

quartodoc can convert type annotations in function signatures to interlinks.

In order to enable this behavior, set `render_interlinks: true` in the quartodoc config.


```yaml
quartodoc:
render_interlinks: true
```



## Running the interlinks filter

First, build the reference for your own site, which includes an objects.json inventory:
Expand Down
14 changes: 13 additions & 1 deletion quartodoc/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,23 @@ def build(config, filter, dry_run, watch, verbose):
doc_build()


@click.command()
@click.command(
short_help="Generate inventory files that the Quarto "
"`interlink` extension can use to auto-link to other docs."
)
@click.argument("config", default="_quarto.yml")
@click.option("--dry-run", is_flag=True, default=False)
@click.option("--fast", is_flag=True, default=False)
def interlinks(config, dry_run, fast):
"""
Generate inventory files that the Quarto `interlink` extension can use to
auto-link to other docs.

The files are stored in a cache directory, which defaults to _inv.
The Quarto extension `interlinks` will look for these files in the cache
and add links to your docs accordingly.
"""

# config loading ----
cfg = yaml.safe_load(open(config))
interlinks = cfg.get("interlinks", {})
Expand Down
8 changes: 8 additions & 0 deletions quartodoc/autosummary.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,9 @@ class Builder:
dynamic:
Whether to dynamically load all python objects. By default, objects are
loaded using static analysis.
render_interlinks:
Whether to render interlinks syntax inside documented objects. Note that the
interlinks filter is required to generate the links in quarto.
parser:
Docstring parser to use. This correspond to different docstring styles,
and can be one of "google", "sphinx", and "numpy". Defaults to "numpy".
Expand Down Expand Up @@ -446,6 +449,7 @@ def __init__(
source_dir: "str | None" = None,
dynamic: bool | None = None,
parser="numpy",
render_interlinks: bool = False,
_fast_inventory=False,
):
self.layout = self.load_layout(
Expand All @@ -460,6 +464,10 @@ def __init__(
self.parser = parser

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

if out_index is not None:
self.out_index = out_index
Expand Down
45 changes: 26 additions & 19 deletions quartodoc/renderers/md_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ def __init__(
show_signature_annotations: bool = False,
display_name: str = "relative",
hook_pre=None,
use_interlinks=False,
render_interlinks=False,
):
self.header_level = header_level
self.show_signature = show_signature
self.show_signature_annotations = show_signature_annotations
self.display_name = display_name
self.hook_pre = hook_pre
self.use_interlinks = use_interlinks
self.render_interlinks = render_interlinks

self.crnt_header_level = self.header_level

Expand Down Expand Up @@ -104,34 +104,40 @@ def _render_table(self, rows, headers):

return table

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

@dispatch
def render_annotation(self, el: str) -> str:
"""Special hook for rendering a type annotation.
Parameters
----------
el:
An object representing a type annotation.

"""
return sanitize(el)

if isinstance(el, type(None)):
return el
elif isinstance(el, str):
return sanitize(el)
@dispatch
def render_annotation(self, el: None) -> str:
return ""

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

return sanitize(el.source)

return sanitize(el.full)
@dispatch
def render_annotation(self, el: expr.Expression) -> str:
return "".join(map(self.render_annotation, el))

# signature method --------------------------------------------------------

@dispatch
def signature(self, el: dc.Alias, source: Optional[dc.Alias] = None):
"""Return a string representation of an object's signature."""

return self.signature(el.target, el)

@dispatch
Expand Down Expand Up @@ -409,17 +415,19 @@ def render(self, el: dc.Parameter):
glob = ""

annotation = self.render_annotation(el.annotation)
name = sanitize(el.name)

if self.show_signature_annotations:
if annotation and has_default:
res = f"{glob}{el.name}: {el.annotation} = {el.default}"
res = f"{glob}{name}: {annotation} = {el.default}"
elif annotation:
res = f"{glob}{el.name}: {el.annotation}"
res = f"{glob}{name}: {annotation}"
elif has_default:
res = f"{glob}{el.name}={el.default}"
res = f"{glob}{name}={el.default}"
else:
res = f"{glob}{el.name}"
res = f"{glob}{name}"

return sanitize(res)
return res

# docstring parts -------------------------------------------------------------

Expand All @@ -441,7 +449,6 @@ def render(self, el: ds.DocstringSectionText):
def render(self, el: ds.DocstringSectionParameters):
rows = list(map(self.render, el.value))
header = ["Name", "Type", "Description", "Default"]

return self._render_table(rows, header)

@dispatch
Expand Down
13 changes: 13 additions & 0 deletions quartodoc/tests/__snapshots__/test_renderers.ambr
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
# serializer version: 1
# name: test_render_annotations_complex
'''
# quartodoc.tests.example_signature.a_complex_signature { #quartodoc.tests.example_signature.a_complex_signature }

`tests.example_signature.a_complex_signature(x: [list](`list`)\[[C](`quartodoc.tests.example_signature.C`) \| [int](`int`) \| None\])`

## Parameters

| Name | Type | Description | Default |
|--------|--------------------------------------------------------------------------------------|-----------------|------------|
| `x` | [list](`list`)\[[C](`quartodoc.tests.example_signature.C`) \| [int](`int`) \| None\] | The x parameter | _required_ |
'''
# ---
# name: test_render_doc_class[embedded]
'''
# quartodoc.tests.example_class.C { #quartodoc.tests.example_class.C }
Expand Down
13 changes: 13 additions & 0 deletions quartodoc/tests/example_signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,16 @@ def early_args(x, *args, a, b=2, **kwargs):

def late_args(x, a, b=2, *args, **kwargs):
...


class C:
...


def a_complex_signature(x: "list[C | int | None]"):
"""
Parameters
----------
x:
The x parameter
"""
11 changes: 10 additions & 1 deletion quartodoc/tests/test_renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def test_render_param_kwargs_annotated():

assert (
res
== "a: int, b: int = 1, *args: list\\[str\\], c: int, d: int, **kwargs: dict\\[str, str\\]"
== "a: int, b: int = 1, *args: list\[str\], c: int, d: int, **kwargs: dict\[str, str\]"
)


Expand Down Expand Up @@ -99,6 +99,7 @@ def test_render_doc_attribute(renderer):
)

res = renderer.render(attr)
print(res)

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

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


def test_render_annotations_complex(snapshot):
renderer = MdRenderer(render_interlinks=True, show_signature_annotations=True)
bp = blueprint(Auto(name="quartodoc.tests.example_signature.a_complex_signature"))
res = renderer.render(bp)

assert res == snapshot


@pytest.mark.parametrize("children", ["embedded", "flat"])
def test_render_doc_class(snapshot, renderer, children):
bp = blueprint(Auto(name="quartodoc.tests.example_class.C", children=children))
Expand Down