diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 88cb53b..5306df8 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -90,9 +90,11 @@ quartodoc: dir: api package: quartodoc render_interlinks: true + repo_url: https://github.com/machow/quartodoc renderer: style: markdown table_style: description-list + show_source_link: true sidebar: "api/_sidebar.yml" css: "api/_styles-quartodoc.css" sections: diff --git a/quartodoc/autosummary.py b/quartodoc/autosummary.py index c6bea4e..aa749be 100644 --- a/quartodoc/autosummary.py +++ b/quartodoc/autosummary.py @@ -18,6 +18,7 @@ from . import layout from .parsers import get_parser_defaults from .renderers import Renderer +from .repo_info import RepoInfo from .validation import fmt_all from ._pydantic_compat import ValidationError from .pandoc.blocks import Blocks, Header @@ -441,6 +442,9 @@ class Builder: render_interlinks: Whether to render interlinks syntax inside documented objects. Note that the interlinks filter is required to generate the links in quarto. + repo_url: + URL for the source repository. This is used to generate links from documentation + to source code. parser: Docstring parser to use. This correspond to different docstring styles, and can be one of "google", "sphinx", and "numpy". Defaults to "numpy". @@ -494,6 +498,7 @@ def __init__( dynamic: bool | None = None, parser="numpy", render_interlinks: bool = False, + repo_url: str | None = None, _fast_inventory=False, ): self.layout = self.load_layout( @@ -507,12 +512,17 @@ def __init__( self.sidebar = sidebar self.css = css self.parser = parser + self.repo_url = repo_url 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 repo_url: + # also a top-level option set on renderer + print("SETTING REPOINFO") + self.renderer.repo_info = RepoInfo.from_link(repo_url) if out_index is not None: self.out_index = out_index diff --git a/quartodoc/layout.py b/quartodoc/layout.py index 43cdf8e..12d8fb7 100644 --- a/quartodoc/layout.py +++ b/quartodoc/layout.py @@ -267,6 +267,8 @@ class Auto(AutoOptions): (Not implemented). A list of members to include. exclude: (Not implemented). A list of members to exclude. + show_source_link: + Whether to show a link to item source code. dynamic: Whether to dynamically load docstring. By default docstrings are loaded using static analysis. dynamic may be a string pointing to another object, diff --git a/quartodoc/renderers/md_renderer.py b/quartodoc/renderers/md_renderer.py index 583071e..2461faa 100644 --- a/quartodoc/renderers/md_renderer.py +++ b/quartodoc/renderers/md_renderer.py @@ -15,6 +15,7 @@ from quartodoc import layout from quartodoc.pandoc.blocks import DefinitionList from quartodoc.pandoc.inlines import Span, Strong, Attr, Code, Inlines +from quartodoc.repo_info import RepoInfo from .base import Renderer, escape, sanitize, convert_rst_link_to_md @@ -124,19 +125,22 @@ def __init__( header_level: int = 1, show_signature: bool = True, show_signature_annotations: bool = False, + show_source_link: bool = False, display_name: str = "relative", hook_pre=None, render_interlinks=False, - # table_style="description-list", table_style="table", + repo_info: "RepoInfo | None" = None, ): self.header_level = header_level self.show_signature = show_signature self.show_signature_annotations = show_signature_annotations + self.show_source_link = show_source_link self.display_name = display_name self.hook_pre = hook_pre self.render_interlinks = render_interlinks self.table_style = table_style + self.repo_info = repo_info self.crnt_header_level = self.header_level @@ -424,8 +428,13 @@ def render(self, el: Union[layout.DocClass, layout.DocModule]): [self.render(x) for x in raw_meths if isinstance(x, layout.Doc)] ) - str_sig = self.signature(el) - sig_part = [str_sig] if self.show_signature else [] + sig_part: list[str] = [] + + if self.show_signature: + sig_part.append(self.signature(el)) + + if self.show_source_link: + sig_part.append(self.source_link(el.obj)) with self._increment_header(): body = self.render(el.obj) @@ -439,6 +448,7 @@ def render(self, el: Union[layout.DocFunction, layout.DocAttribute]): title = self.render_header(el) str_sig = self.signature(el) + str_source = el.obj sig_part = [str_sig] if self.show_signature else [] with self._increment_header(): @@ -682,6 +692,22 @@ def render(self, el: ds.DocstringRaise) -> ParamRow: def render(self, el): raise NotImplementedError(f"{type(el)}") + # Source links ============================================================ + + def source_link(self, el: "dc.Alias | dc.Object"): + + if self.repo_info is not None: + fpath = str(el.relative_package_filepath) + url = self.repo_info.source_link(fpath) + + return f'
' + + raise ValueError( + "Unable to produce a link to source file without repo info. " + "Either set repo_info= in the renderer, or provide a repo_url in the " + "`quartodoc:` section of your _quarto.yml." + ) + # Summarize =============================================================== # this method returns a summary description, such as a table summarizing a # layout.Section, or a row in the table for layout.Page or layout.DocFunction. diff --git a/quartodoc/repo_info.py b/quartodoc/repo_info.py new file mode 100644 index 0000000..ad8d9b6 --- /dev/null +++ b/quartodoc/repo_info.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing_extensions import Self + + +@dataclass +class GithubLink: + host: str + owner: str + repo: str + + @classmethod + def parse(cls, link) -> Self: + import re + + # gitlab supports names of the form my/repo/name + supports_subgroups = re.search(r"^https?://gitlab\.", link) is not None + subgroup_token = "/" if not supports_subgroups else "" + rx = ( + r"^(?P