File: griffe_extensions.py

package info (click to toggle)
python-markdown 3.7-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,128 kB
  • sloc: python: 11,989; makefile: 66; sh: 7
file content (96 lines) | stat: -rw-r--r-- 4,122 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
"""Griffe extensions."""

from __future__ import annotations

import ast
from typing import TYPE_CHECKING, Any
import textwrap

from griffe import Docstring, Extension, DocstringSectionAdmonition, DocstringSectionText, Visitor, Inspector

if TYPE_CHECKING:
    from griffe import Class, Function, ObjectNode


def _deprecated(obj: Class | Function) -> str | None:
    for decorator in obj.decorators:
        if decorator.callable_path == "markdown.util.deprecated":
            return ast.literal_eval(str(decorator.value.arguments[0]))
    return None


class DeprecatedExtension(Extension):
    """Griffe extension for `@markdown.util.deprecated` decorator support."""

    def _insert_message(self, obj: Function | Class, message: str) -> None:
        if not obj.docstring:
            obj.docstring = Docstring("", parent=obj)
        sections = obj.docstring.parsed
        sections.insert(0, DocstringSectionAdmonition(kind="warning", text=message, title="Deprecated"))

    def on_class_instance(self, node: ast.AST | ObjectNode, cls: Class, agent: Visitor | Inspector, **kwargs: Any) -> None:  # noqa: ARG002
        """Add section to docstrings of deprecated classes."""
        if message := _deprecated(cls):
            self._insert_message(cls, message)
            cls.labels.add("deprecated")

    def on_function_instance(self, node: ast.AST | ObjectNode, func: Function, agent: Visitor | Inspector, **kwargs: Any) -> None:  # noqa: ARG002
        """Add section to docstrings of deprecated functions."""
        if message := _deprecated(func):
            self._insert_message(func, message)
            func.labels.add("deprecated")


class PriorityTableExtension(Extension):
    """ Griffe extension to insert a table of processor priority in specified functions. """

    def __init__(self, paths: list[str] | None = None) -> None:
        super().__init__()
        self.paths = paths

    def linked_obj(self, value: str, path: str) -> str:
        """ Wrap object name in reference link. """
        return f'[`{value}`][{path}.{value}]'

    def on_function_instance(self, node: ast.AST | ObjectNode, func: Function, agent: Visitor | Inspector, **kwargs: Any) -> None:  # noqa: ARG002
        """Add table to specified function docstrings."""
        if self.paths and func.path not in self.paths:
            return  # skip objects that were not selected

        # Table header
        data = [
            'Class Instance | Name | Priority',
            '-------------- | ---- | :------:'
        ]

        # Extract table body from source code of function.
        for obj in node.body:
            # Extract the arguments passed to `util.Registry.register`.
            if isinstance(obj, ast.Expr) and isinstance(obj.value, ast.Call) and obj.value.func.attr == 'register':
                _args = obj.value.args
                cls = self.linked_obj(_args[0].func.id, func.path.rsplit('.', 1)[0])
                name = _args[1].value
                priority = str(_args[2].value)
                if func.name == ('build_inlinepatterns'):
                    # Include Pattern: first arg passed to class
                    if isinstance(_args[0].args[0], ast.Constant):
                        # Pattern is a string
                        value = f'`"{_args[0].args[0].value}"`'
                    else:
                        # Pattern is a variable
                        value = self.linked_obj(_args[0].args[0].id, func.path.rsplit('.', 1)[0])
                    cls = f'{cls}({value})'
                data.append(f'{cls} | `{name}` | `{priority}`')

        table = '\n'.join(data)
        body = (
            f"Return a [`{func.returns.canonical_name}`][{func.returns.canonical_path}] instance which contains "
            "the following collection of classes with their assigned names and priorities.\n\n"
            f"{table}"
        )

        # Add to docstring.
        if not func.docstring:
            func.docstring = Docstring("", parent=func)
        sections = func.docstring.parsed
        sections.append(DocstringSectionText(body, title="Priority Table"))