Edit on GitHub

pdoc.render_helpers

  1from __future__ import annotations
  2
  3import inspect
  4import os
  5import re
  6import warnings
  7from collections.abc import Collection, Iterable, Mapping
  8from contextlib import contextmanager
  9from unittest.mock import patch
 10
 11import pygments.formatters.html
 12import pygments.lexers.python
 13from jinja2 import ext, nodes
 14
 15try:
 16    # Jinja2 >= 3.0
 17    from jinja2 import pass_context  # type: ignore
 18except ImportError:  # pragma: no cover
 19    from jinja2 import contextfilter as pass_context  # type: ignore
 20
 21from jinja2.runtime import Context
 22from markupsafe import Markup
 23
 24import pdoc.markdown2
 25
 26from . import docstrings
 27from ._compat import cache, removesuffix
 28
 29lexer = pygments.lexers.python.PythonLexer()
 30"""
 31The pygments lexer used for pdoc.render_helpers.highlight.
 32Overwrite this to configure pygments lexing.
 33"""
 34
 35formatter = pygments.formatters.html.HtmlFormatter(
 36    cssclass="pdoc-code codehilite",
 37    linenos="inline",
 38    anchorlinenos=True,
 39)
 40"""
 41The pygments formatter used for pdoc.render_helpers.highlight. 
 42Overwrite this to configure pygments highlighting of code blocks.
 43
 44The usage of the `.codehilite` CSS selector in custom templates is deprecated since pdoc 10, use `.pdoc-code` instead.
 45"""
 46
 47signature_formatter = pygments.formatters.html.HtmlFormatter(nowrap=True)
 48"""
 49The pygments formatter used for pdoc.render_helpers.format_signature. 
 50Overwrite this to configure pygments highlighting of signatures.
 51"""
 52
 53markdown_extensions = {
 54    "code-friendly": None,
 55    "cuddled-lists": None,
 56    "fenced-code-blocks": {"cssclass": formatter.cssclass},
 57    "footnotes": None,
 58    "header-ids": None,
 59    "markdown-in-html": None,
 60    "pyshell": None,
 61    "strike": None,
 62    "tables": None,
 63    "task_list": None,
 64    "toc": {"depth": 2},
 65}
 66"""
 67The default extensions loaded for `markdown2`.
 68Overwrite this to configure Markdown rendering.
 69"""
 70
 71
 72@cache
 73def highlight(doc: pdoc.doc.Doc) -> str:
 74    """Highlight the source code of a documentation object using pygments."""
 75    if isinstance(doc, str):  # pragma: no cover
 76        warnings.warn(
 77            "Passing a string to the `highlight` render helper is deprecated, pass a pdoc.doc.Doc object instead.",
 78            DeprecationWarning,
 79        )
 80        return Markup(pygments.highlight(doc, lexer, formatter))
 81
 82    # set up correct line numbers and anchors
 83    formatter.linespans = doc.qualname or "L"
 84    formatter.linenostart = doc.source_lines[0] + 1 if doc.source_lines else 1
 85    return Markup(pygments.highlight(doc.source, lexer, formatter))
 86
 87
 88def format_signature(sig: inspect.Signature, colon: bool) -> str:
 89    """Format and highlight a function signature using pygments. Returns HTML."""
 90    # First get a list with all params as strings.
 91    result = pdoc.doc._PrettySignature._params(sig)  # type: ignore
 92    return_annot = pdoc.doc._PrettySignature._return_annotation_str(sig)  # type: ignore
 93
 94    multiline = (
 95        sum(len(x) + 2 for x in result) + len(return_annot)
 96        > pdoc.doc._PrettySignature.MULTILINE_CUTOFF
 97    )
 98
 99    # Next, individually highlight each parameter using pygments and wrap it in a span.param.
100    # This later allows us to properly control line breaks.
101    pretty_result = []
102    for i, param in enumerate(result):
103        pretty = pygments.highlight(param, lexer, signature_formatter).strip()
104        if multiline:
105            pretty = f"""<span class="param">\t{pretty},</span>"""
106        else:
107            pretty = f"""<span class="param">{pretty}, </span>"""
108        pretty_result.append(pretty)
109
110    # remove last comma.
111    if pretty_result:
112        pretty_result[-1] = pretty_result[-1].rpartition(",")[0] + "</span>"
113
114    # Add return annotation.
115    rendered = "(%s)" % "".join(pretty_result)
116    if return_annot:
117        anno = pygments.highlight(return_annot, lexer, signature_formatter).strip()
118        rendered = (
119            rendered[:-1]
120            + f'<span class="return-annotation">) -> {anno}{":" if colon else ""}</span>'
121        )
122
123    if multiline:
124        rendered = f'<span class="signature pdoc-code multiline">{rendered}</span>'
125    else:
126        rendered = f'<span class="signature pdoc-code condensed">{rendered}</span>'
127
128    return Markup(rendered)
129
130
131@cache
132def to_html(docstring: str) -> str:
133    """
134    Convert `docstring` from Markdown to HTML.
135    """
136    # careful: markdown2 returns a subclass of str with an extra
137    # .toc_html attribute. don't further process the result,
138    # otherwise this attribute will be lost.
139    return pdoc.markdown2.markdown(docstring, extras=markdown_extensions)  # type: ignore
140
141
142@pass_context
143def to_markdown_with_context(context: Context, docstring: str) -> str:
144    """
145    Converts `docstring` from a custom docformat to Markdown (if necessary), and then from Markdown to HTML.
146    """
147    module: pdoc.doc.Module = context["module"]
148    docformat: str = context["docformat"]
149    return to_markdown(docstring, module, docformat)
150
151
152def to_markdown(docstring: str, module: pdoc.doc.Module, default_docformat: str) -> str:
153    docformat = getattr(module.obj, "__docformat__", default_docformat) or ""
154    return docstrings.convert(docstring, docformat, module.source_file)
155
156
157def possible_sources(
158    all_modules: Collection[str], identifier: str
159) -> Iterable[tuple[str, str]]:
160    """
161    For a given identifier, return all possible sources where it could originate from.
162    For example, assume `examplepkg._internal.Foo` with all_modules=["examplepkg"].
163    This could be a Foo class in _internal.py, or a nested `class _internal: class Foo` in examplepkg.
164    We return both candidates as we don't know if _internal.py exists.
165    It may not be in all_modules because it's been excluded by `__all__`.
166    However, if `examplepkg._internal` is in all_modules we know that it can only be that option.
167    """
168    if identifier in all_modules:
169        yield identifier, ""
170        return
171
172    modulename = identifier
173    qualname = None
174    while modulename:
175        modulename, _, add = modulename.rpartition(".")
176        qualname = f"{add}.{qualname}" if qualname else add
177        yield modulename, qualname
178        if modulename in all_modules:
179            return
180    raise ValueError(f"Invalid identifier: {identifier}")
181
182
183def split_identifier(all_modules: Collection[str], fullname: str) -> tuple[str, str]:
184    """
185    Split an identifier into a `(modulename, qualname)` tuple. For example, `pdoc.render_helpers.split_identifier`
186    would be split into `("pdoc.render_helpers","split_identifier")`. This is necessary to generate links to the
187    correct module.
188    """
189    warnings.warn(
190        "pdoc.render_helpers.split_identifier is deprecated and will be removed in a future release. "
191        "Use pdoc.render_helpers.possible_sources instead.",
192        DeprecationWarning,
193    )
194    *_, last = possible_sources(all_modules, fullname)
195    return last
196
197
198def _relative_link(current: list[str], target: list[str]) -> str:
199    if target == current:
200        return f"../{target[-1]}.html"
201    elif target[: len(current)] == current:
202        return "/".join(target[len(current) :]) + ".html"
203    else:
204        return "../" + _relative_link(current[:-1], target)
205
206
207@cache
208def relative_link(current_module: str, target_module: str) -> str:
209    """Compute the relative link to another module's HTML file."""
210    if current_module == target_module:
211        return ""
212    return _relative_link(
213        current_module.split(".")[:-1],
214        target_module.split("."),
215    )
216
217
218def qualname_candidates(identifier: str, context_qualname: str) -> list[str]:
219    """
220    Given an identifier in a current namespace, return all possible qualnames in the current module.
221    For example, if we are in Foo's subclass Bar and `baz()` is the identifier,
222    return `Foo.Bar.baz()`, `Foo.baz()`, and `baz()`.
223    """
224    end = len(context_qualname)
225    ret = []
226    while end > 0:
227        ret.append(f"{context_qualname[:end]}.{identifier}")
228        end = context_qualname.rfind(".", 0, end)
229    ret.append(identifier)
230    return ret
231
232
233@pass_context
234def linkify(context: Context, code: str, namespace: str = "") -> str:
235    """
236    Link all identifiers in a block of text. Identifiers referencing unknown modules or modules that
237    are not rendered at the moment will be ignored.
238    A piece of text is considered to be an identifier if it either contains a `.` or is surrounded by `<code>` tags.
239    """
240
241    def linkify_repl(m: re.Match):
242        text = m.group(0)
243        identifier = removesuffix(text, "()")
244
245        # Check if this is a local reference within this module?
246        mod: pdoc.doc.Module = context["module"]
247        for qualname in qualname_candidates(identifier, namespace):
248            doc = mod.get(qualname)
249            if doc and context["is_public"](doc).strip():
250                return f'<a href="#{qualname}">{text}</a>'
251
252        module = ""
253        qualname = ""
254        try:
255            # Check if the object we are interested in is imported and re-exposed in the current namespace.
256            for module, qualname in possible_sources(
257                context["all_modules"], identifier
258            ):
259                doc = mod.get(qualname)
260                if (
261                    doc
262                    and doc.taken_from == (module, qualname)
263                    and context["is_public"](doc).strip()
264                ):
265                    if text.endswith("()"):
266                        text = f"{doc.fullname}()"
267                    else:
268                        text = doc.fullname
269                    return f'<a href="#{qualname}">{text}</a>'
270        except ValueError:
271            # possible_sources did not find a parent module.
272            return text
273        else:
274            # It's not, but we now know the parent module. Does the target exist?
275            doc = context["all_modules"][module]
276            if qualname:
277                assert isinstance(doc, pdoc.doc.Module)
278                doc = doc.get(qualname)
279            target_exists_and_public = (
280                doc is not None and context["is_public"](doc).strip()
281            )
282            if target_exists_and_public:
283                if qualname:
284                    qualname = f"#{qualname}"
285                return f'<a href="{relative_link(context["module"].modulename, module)}{qualname}">{text}</a>'
286            else:
287                return text
288
289    return Markup(
290        re.sub(
291            r"""
292            # Part 1: foo.bar or foo.bar() (without backticks)
293            (?<![/=?#&])  # heuristic: not part of a URL
294            \b
295                 (?!\d)[a-zA-Z0-9_]+
296            (?:\.(?!\d)[a-zA-Z0-9_]+)+
297            (?:\(\)|\b(?!\(\)))  # we either end on () or on a word boundary.
298            (?!</a>)  # not an existing link
299            (?![/#])  # heuristic: not part of a URL
300
301            | # Part 2: `foo` or `foo()`. `foo.bar` is already covered with part 1.
302            (?<=<code>)
303                 (?!\d)[a-zA-Z0-9_]+
304            (?:\(\))?
305            (?=</code>(?!</a>))
306            """,
307            linkify_repl,
308            code,
309            flags=re.VERBOSE,
310        )
311    )
312
313
314@pass_context
315def link(context: Context, spec: tuple[str, str], text: str | None = None) -> str:
316    """Create a link for a specific `(modulename, qualname)` tuple."""
317    mod: pdoc.doc.Module = context["module"]
318    modulename, qualname = spec
319
320    # Check if the object we are interested is also imported and re-exposed in the current namespace.
321    doc = mod.get(qualname)
322    if doc and doc.taken_from == spec and context["is_public"](doc).strip():
323        if text:
324            text = text.replace(f"{modulename}.", f"{mod.modulename}.")
325        modulename = mod.modulename
326
327    if mod.modulename == modulename:
328        fullname = qualname
329    else:
330        fullname = removesuffix(f"{modulename}.{qualname}", ".")
331
332    if qualname:
333        qualname = f"#{qualname}"
334    if modulename in context["all_modules"]:
335        return Markup(
336            f'<a href="{relative_link(context["module"].modulename, modulename)}{qualname}">{text or fullname}</a>'
337        )
338    return text or fullname
339
340
341def edit_url(
342    modulename: str, is_package: bool, mapping: Mapping[str, str]
343) -> str | None:
344    """Create a link to edit a particular file in the used version control system."""
345    for m, prefix in mapping.items():
346        if m == modulename or modulename.startswith(f"{m}."):
347            filename = modulename[len(m) + 1 :].replace(".", "/")
348            if is_package:
349                filename = f"{filename}/__init__.py".lstrip("/")
350            else:
351                filename += ".py"
352            return f"{prefix}{filename}"
353    return None
354
355
356def root_module_name(all_modules: Mapping[str, pdoc.doc.Module]) -> str | None:
357    """
358    Return the name of the (unique) top-level module, or `None`
359    if no such module exists.
360
361    For example, assuming `foo`, `foo.bar`, and `foo.baz` are documented,
362    this function will return `foo`. If `foo` and `bar` are documented,
363    this function will return `None` as there is no unique top-level module.
364    """
365    shortest_name = min(all_modules, key=len, default=None)
366    prefix = f"{shortest_name}."
367    all_others_are_submodules = all(
368        x.startswith(prefix) or x == shortest_name for x in all_modules
369    )
370    if all_others_are_submodules:
371        return shortest_name
372    else:
373        return None
374
375
376def minify_css(css: str) -> str:
377    """Do some very basic CSS minification."""
378    css = re.sub(r"[ ]{4}|\n|(?<=[:{}]) | (?=[{}])", "", css)
379    css = re.sub(
380        r"/\*.+?\*/", lambda m: m.group(0) if m.group(0).startswith("/*!") else "", css
381    )
382    return Markup(css.replace("<style", "\n<style"))
383
384
385@contextmanager
386def defuse_unsafe_reprs():
387    """This decorator is applied by pdoc before calling an object's repr().
388    It applies some heuristics to patch our sensitive information.
389    For example, `os.environ`'s default `__repr__` implementation exposes all
390    local secrets.
391    """
392    with (patch.object(os._Environ, "__repr__", lambda self: "os.environ")):
393        yield
394
395
396class DefaultMacroExtension(ext.Extension):
397    """
398    This extension provides a new `{% defaultmacro %}` statement, which defines a macro only if it does not exist.
399
400    For example,
401
402    ```html+jinja
403    {% defaultmacro example() %}
404        test 123
405    {% enddefaultmacro %}
406    ```
407
408    is equivalent to
409
410    ```html+jinja
411    {% macro default_example() %}
412    test 123
413    {% endmacro %}
414    {% if not example %}
415        {% macro example() %}
416            test 123
417        {% endmacro %}
418    {% endif %}
419    ```
420
421    Additionally, the default implementation is also available as `default_$macroname`, which makes it possible
422    to reference it in the override.
423    """
424
425    tags = {"defaultmacro"}
426
427    def parse(self, parser):
428        m = nodes.Macro(lineno=next(parser.stream).lineno)
429        name = parser.parse_assign_target(name_only=True).name
430        m.name = f"default_{name}"
431        parser.parse_signature(m)
432        m.body = parser.parse_statements(("name:enddefaultmacro",), drop_needle=True)
433
434        if_stmt = nodes.If(
435            nodes.Not(nodes.Name(name, "load")),
436            [nodes.Macro(name, m.args, m.defaults, m.body)],
437            [],
438            [],
439        )
440        return [m, if_stmt]
lexer = <pygments.lexers.PythonLexer>

The pygments lexer used for pdoc.render_helpers.highlight. Overwrite this to configure pygments lexing.

formatter = <pygments.formatters.html.HtmlFormatter object>

The pygments formatter used for pdoc.render_helpers.highlight. Overwrite this to configure pygments highlighting of code blocks.

The usage of the .codehilite CSS selector in custom templates is deprecated since pdoc 10, use .pdoc-code instead.

signature_formatter = <pygments.formatters.html.HtmlFormatter object>

The pygments formatter used for pdoc.render_helpers.format_signature. Overwrite this to configure pygments highlighting of signatures.

markdown_extensions = {'code-friendly': None, 'cuddled-lists': None, 'fenced-code-blocks': {'cssclass': 'pdoc-code codehilite'}, 'footnotes': None, 'header-ids': None, 'markdown-in-html': None, 'pyshell': None, 'strike': None, 'tables': None, 'task_list': None, 'toc': {'depth': 2}}

The default extensions loaded for markdown2. Overwrite this to configure Markdown rendering.

@cache
def highlight(doc: pdoc.doc.Doc) -> str:
73@cache
74def highlight(doc: pdoc.doc.Doc) -> str:
75    """Highlight the source code of a documentation object using pygments."""
76    if isinstance(doc, str):  # pragma: no cover
77        warnings.warn(
78            "Passing a string to the `highlight` render helper is deprecated, pass a pdoc.doc.Doc object instead.",
79            DeprecationWarning,
80        )
81        return Markup(pygments.highlight(doc, lexer, formatter))
82
83    # set up correct line numbers and anchors
84    formatter.linespans = doc.qualname or "L"
85    formatter.linenostart = doc.source_lines[0] + 1 if doc.source_lines else 1
86    return Markup(pygments.highlight(doc.source, lexer, formatter))

Highlight the source code of a documentation object using pygments.

def format_signature(sig: inspect.Signature, colon: bool) -> str:
 89def format_signature(sig: inspect.Signature, colon: bool) -> str:
 90    """Format and highlight a function signature using pygments. Returns HTML."""
 91    # First get a list with all params as strings.
 92    result = pdoc.doc._PrettySignature._params(sig)  # type: ignore
 93    return_annot = pdoc.doc._PrettySignature._return_annotation_str(sig)  # type: ignore
 94
 95    multiline = (
 96        sum(len(x) + 2 for x in result) + len(return_annot)
 97        > pdoc.doc._PrettySignature.MULTILINE_CUTOFF
 98    )
 99
100    # Next, individually highlight each parameter using pygments and wrap it in a span.param.
101    # This later allows us to properly control line breaks.
102    pretty_result = []
103    for i, param in enumerate(result):
104        pretty = pygments.highlight(param, lexer, signature_formatter).strip()
105        if multiline:
106            pretty = f"""<span class="param">\t{pretty},</span>"""
107        else:
108            pretty = f"""<span class="param">{pretty}, </span>"""
109        pretty_result.append(pretty)
110
111    # remove last comma.
112    if pretty_result:
113        pretty_result[-1] = pretty_result[-1].rpartition(",")[0] + "</span>"
114
115    # Add return annotation.
116    rendered = "(%s)" % "".join(pretty_result)
117    if return_annot:
118        anno = pygments.highlight(return_annot, lexer, signature_formatter).strip()
119        rendered = (
120            rendered[:-1]
121            + f'<span class="return-annotation">) -> {anno}{":" if colon else ""}</span>'
122        )
123
124    if multiline:
125        rendered = f'<span class="signature pdoc-code multiline">{rendered}</span>'
126    else:
127        rendered = f'<span class="signature pdoc-code condensed">{rendered}</span>'
128
129    return Markup(rendered)

Format and highlight a function signature using pygments. Returns HTML.

@cache
def to_html(docstring: str) -> str:
132@cache
133def to_html(docstring: str) -> str:
134    """
135    Convert `docstring` from Markdown to HTML.
136    """
137    # careful: markdown2 returns a subclass of str with an extra
138    # .toc_html attribute. don't further process the result,
139    # otherwise this attribute will be lost.
140    return pdoc.markdown2.markdown(docstring, extras=markdown_extensions)  # type: ignore

Convert docstring from Markdown to HTML.

@pass_context
def to_markdown_with_context(context: jinja2.runtime.Context, docstring: str) -> str:
143@pass_context
144def to_markdown_with_context(context: Context, docstring: str) -> str:
145    """
146    Converts `docstring` from a custom docformat to Markdown (if necessary), and then from Markdown to HTML.
147    """
148    module: pdoc.doc.Module = context["module"]
149    docformat: str = context["docformat"]
150    return to_markdown(docstring, module, docformat)

Converts docstring from a custom docformat to Markdown (if necessary), and then from Markdown to HTML.

def to_markdown(docstring: str, module: pdoc.doc.Module, default_docformat: str) -> str:
153def to_markdown(docstring: str, module: pdoc.doc.Module, default_docformat: str) -> str:
154    docformat = getattr(module.obj, "__docformat__", default_docformat) or ""
155    return docstrings.convert(docstring, docformat, module.source_file)
def possible_sources( all_modules: collections.abc.Collection[str], identifier: str) -> collections.abc.Iterable[tuple[str, str]]:
158def possible_sources(
159    all_modules: Collection[str], identifier: str
160) -> Iterable[tuple[str, str]]:
161    """
162    For a given identifier, return all possible sources where it could originate from.
163    For example, assume `examplepkg._internal.Foo` with all_modules=["examplepkg"].
164    This could be a Foo class in _internal.py, or a nested `class _internal: class Foo` in examplepkg.
165    We return both candidates as we don't know if _internal.py exists.
166    It may not be in all_modules because it's been excluded by `__all__`.
167    However, if `examplepkg._internal` is in all_modules we know that it can only be that option.
168    """
169    if identifier in all_modules:
170        yield identifier, ""
171        return
172
173    modulename = identifier
174    qualname = None
175    while modulename:
176        modulename, _, add = modulename.rpartition(".")
177        qualname = f"{add}.{qualname}" if qualname else add
178        yield modulename, qualname
179        if modulename in all_modules:
180            return
181    raise ValueError(f"Invalid identifier: {identifier}")

For a given identifier, return all possible sources where it could originate from. For example, assume examplepkg._internal.Foo with all_modules=["examplepkg"]. This could be a Foo class in _internal.py, or a nested class _internal: class Foo in examplepkg. We return both candidates as we don't know if _internal.py exists. It may not be in all_modules because it's been excluded by __all__. However, if examplepkg._internal is in all_modules we know that it can only be that option.

def split_identifier( all_modules: collections.abc.Collection[str], fullname: str) -> tuple[str, str]:
184def split_identifier(all_modules: Collection[str], fullname: str) -> tuple[str, str]:
185    """
186    Split an identifier into a `(modulename, qualname)` tuple. For example, `pdoc.render_helpers.split_identifier`
187    would be split into `("pdoc.render_helpers","split_identifier")`. This is necessary to generate links to the
188    correct module.
189    """
190    warnings.warn(
191        "pdoc.render_helpers.split_identifier is deprecated and will be removed in a future release. "
192        "Use pdoc.render_helpers.possible_sources instead.",
193        DeprecationWarning,
194    )
195    *_, last = possible_sources(all_modules, fullname)
196    return last

Split an identifier into a (modulename, qualname) tuple. For example, pdoc.render_helpers.split_identifier would be split into ("pdoc.render_helpers","split_identifier"). This is necessary to generate links to the correct module.

def qualname_candidates(identifier: str, context_qualname: str) -> list[str]:
219def qualname_candidates(identifier: str, context_qualname: str) -> list[str]:
220    """
221    Given an identifier in a current namespace, return all possible qualnames in the current module.
222    For example, if we are in Foo's subclass Bar and `baz()` is the identifier,
223    return `Foo.Bar.baz()`, `Foo.baz()`, and `baz()`.
224    """
225    end = len(context_qualname)
226    ret = []
227    while end > 0:
228        ret.append(f"{context_qualname[:end]}.{identifier}")
229        end = context_qualname.rfind(".", 0, end)
230    ret.append(identifier)
231    return ret

Given an identifier in a current namespace, return all possible qualnames in the current module. For example, if we are in Foo's subclass Bar and baz() is the identifier, return Foo.Bar.baz(), Foo.baz(), and baz().

@pass_context
def linkify(context: jinja2.runtime.Context, code: str, namespace: str = '') -> str:
234@pass_context
235def linkify(context: Context, code: str, namespace: str = "") -> str:
236    """
237    Link all identifiers in a block of text. Identifiers referencing unknown modules or modules that
238    are not rendered at the moment will be ignored.
239    A piece of text is considered to be an identifier if it either contains a `.` or is surrounded by `<code>` tags.
240    """
241
242    def linkify_repl(m: re.Match):
243        text = m.group(0)
244        identifier = removesuffix(text, "()")
245
246        # Check if this is a local reference within this module?
247        mod: pdoc.doc.Module = context["module"]
248        for qualname in qualname_candidates(identifier, namespace):
249            doc = mod.get(qualname)
250            if doc and context["is_public"](doc).strip():
251                return f'<a href="#{qualname}">{text}</a>'
252
253        module = ""
254        qualname = ""
255        try:
256            # Check if the object we are interested in is imported and re-exposed in the current namespace.
257            for module, qualname in possible_sources(
258                context["all_modules"], identifier
259            ):
260                doc = mod.get(qualname)
261                if (
262                    doc
263                    and doc.taken_from == (module, qualname)
264                    and context["is_public"](doc).strip()
265                ):
266                    if text.endswith("()"):
267                        text = f"{doc.fullname}()"
268                    else:
269                        text = doc.fullname
270                    return f'<a href="#{qualname}">{text}</a>'
271        except ValueError:
272            # possible_sources did not find a parent module.
273            return text
274        else:
275            # It's not, but we now know the parent module. Does the target exist?
276            doc = context["all_modules"][module]
277            if qualname:
278                assert isinstance(doc, pdoc.doc.Module)
279                doc = doc.get(qualname)
280            target_exists_and_public = (
281                doc is not None and context["is_public"](doc).strip()
282            )
283            if target_exists_and_public:
284                if qualname:
285                    qualname = f"#{qualname}"
286                return f'<a href="{relative_link(context["module"].modulename, module)}{qualname}">{text}</a>'
287            else:
288                return text
289
290    return Markup(
291        re.sub(
292            r"""
293            # Part 1: foo.bar or foo.bar() (without backticks)
294            (?<![/=?#&])  # heuristic: not part of a URL
295            \b
296                 (?!\d)[a-zA-Z0-9_]+
297            (?:\.(?!\d)[a-zA-Z0-9_]+)+
298            (?:\(\)|\b(?!\(\)))  # we either end on () or on a word boundary.
299            (?!</a>)  # not an existing link
300            (?![/#])  # heuristic: not part of a URL
301
302            | # Part 2: `foo` or `foo()`. `foo.bar` is already covered with part 1.
303            (?<=<code>)
304                 (?!\d)[a-zA-Z0-9_]+
305            (?:\(\))?
306            (?=</code>(?!</a>))
307            """,
308            linkify_repl,
309            code,
310            flags=re.VERBOSE,
311        )
312    )

Link all identifiers in a block of text. Identifiers referencing unknown modules or modules that are not rendered at the moment will be ignored. A piece of text is considered to be an identifier if it either contains a . or is surrounded by <code> tags.

def edit_url( modulename: str, is_package: bool, mapping: collections.abc.Mapping[str, str]) -> str | None:
342def edit_url(
343    modulename: str, is_package: bool, mapping: Mapping[str, str]
344) -> str | None:
345    """Create a link to edit a particular file in the used version control system."""
346    for m, prefix in mapping.items():
347        if m == modulename or modulename.startswith(f"{m}."):
348            filename = modulename[len(m) + 1 :].replace(".", "/")
349            if is_package:
350                filename = f"{filename}/__init__.py".lstrip("/")
351            else:
352                filename += ".py"
353            return f"{prefix}{filename}"
354    return None

Create a link to edit a particular file in the used version control system.

def root_module_name(all_modules: collections.abc.Mapping[str, pdoc.doc.Module]) -> str | None:
357def root_module_name(all_modules: Mapping[str, pdoc.doc.Module]) -> str | None:
358    """
359    Return the name of the (unique) top-level module, or `None`
360    if no such module exists.
361
362    For example, assuming `foo`, `foo.bar`, and `foo.baz` are documented,
363    this function will return `foo`. If `foo` and `bar` are documented,
364    this function will return `None` as there is no unique top-level module.
365    """
366    shortest_name = min(all_modules, key=len, default=None)
367    prefix = f"{shortest_name}."
368    all_others_are_submodules = all(
369        x.startswith(prefix) or x == shortest_name for x in all_modules
370    )
371    if all_others_are_submodules:
372        return shortest_name
373    else:
374        return None

Return the name of the (unique) top-level module, or None if no such module exists.

For example, assuming foo, foo.bar, and foo.baz are documented, this function will return foo. If foo and bar are documented, this function will return None as there is no unique top-level module.

def minify_css(css: str) -> str:
377def minify_css(css: str) -> str:
378    """Do some very basic CSS minification."""
379    css = re.sub(r"[ ]{4}|\n|(?<=[:{}]) | (?=[{}])", "", css)
380    css = re.sub(
381        r"/\*.+?\*/", lambda m: m.group(0) if m.group(0).startswith("/*!") else "", css
382    )
383    return Markup(css.replace("<style", "\n<style"))

Do some very basic CSS minification.

@contextmanager
def defuse_unsafe_reprs()
386@contextmanager
387def defuse_unsafe_reprs():
388    """This decorator is applied by pdoc before calling an object's repr().
389    It applies some heuristics to patch our sensitive information.
390    For example, `os.environ`'s default `__repr__` implementation exposes all
391    local secrets.
392    """
393    with (patch.object(os._Environ, "__repr__", lambda self: "os.environ")):
394        yield

This decorator is applied by pdoc before calling an object's repr(). It applies some heuristics to patch our sensitive information. For example, os.environ's default __repr__ implementation exposes all local secrets.

class DefaultMacroExtension(jinja2.ext.Extension):
397class DefaultMacroExtension(ext.Extension):
398    """
399    This extension provides a new `{% defaultmacro %}` statement, which defines a macro only if it does not exist.
400
401    For example,
402
403    ```html+jinja
404    {% defaultmacro example() %}
405        test 123
406    {% enddefaultmacro %}
407    ```
408
409    is equivalent to
410
411    ```html+jinja
412    {% macro default_example() %}
413    test 123
414    {% endmacro %}
415    {% if not example %}
416        {% macro example() %}
417            test 123
418        {% endmacro %}
419    {% endif %}
420    ```
421
422    Additionally, the default implementation is also available as `default_$macroname`, which makes it possible
423    to reference it in the override.
424    """
425
426    tags = {"defaultmacro"}
427
428    def parse(self, parser):
429        m = nodes.Macro(lineno=next(parser.stream).lineno)
430        name = parser.parse_assign_target(name_only=True).name
431        m.name = f"default_{name}"
432        parser.parse_signature(m)
433        m.body = parser.parse_statements(("name:enddefaultmacro",), drop_needle=True)
434
435        if_stmt = nodes.If(
436            nodes.Not(nodes.Name(name, "load")),
437            [nodes.Macro(name, m.args, m.defaults, m.body)],
438            [],
439            [],
440        )
441        return [m, if_stmt]

This extension provides a new {% defaultmacro %} statement, which defines a macro only if it does not exist.

For example,

{% defaultmacro example() %}
    test 123
{% enddefaultmacro %}

is equivalent to

{% macro default_example() %}
test 123
{% endmacro %}
{% if not example %}
    {% macro example() %}
        test 123
    {% endmacro %}
{% endif %}

Additionally, the default implementation is also available as default_$macroname, which makes it possible to reference it in the override.

tags: Set[str] = {'defaultmacro'}
def parse(self, parser)
428    def parse(self, parser):
429        m = nodes.Macro(lineno=next(parser.stream).lineno)
430        name = parser.parse_assign_target(name_only=True).name
431        m.name = f"default_{name}"
432        parser.parse_signature(m)
433        m.body = parser.parse_statements(("name:enddefaultmacro",), drop_needle=True)
434
435        if_stmt = nodes.If(
436            nodes.Not(nodes.Name(name, "load")),
437            [nodes.Macro(name, m.args, m.defaults, m.body)],
438            [],
439            [],
440        )
441        return [m, if_stmt]

If any of the tags matched this method is called with the parser as first argument. The token the parser stream is pointing at is the name token that matched. This method has to return one or a list of multiple nodes.

identifier: ClassVar[str] = 'pdoc.render_helpers.DefaultMacroExtension'
Inherited Members
jinja2.ext.Extension
Extension
priority
bind
preprocess
filter_stream
attr
call_method