Documentation from code

Limits

This theme tries to support the output of the python handler first. We do not know how it behaves with other languages.

Installation

pip install 'mkdocstrings[python]'
uv add 'mkdocstrings[python]'
poetry add 'mkdocstrings[python]'

Configuration

# mkdocs.yml

plugins:
  - search
  - mkdocstrings

You can look at all the available options in the python handler documentation.

Important

If you do not define show_root_heading, the theme sets it to true by default.

Examples

::: shadcn.plugins.excalidraw.ExcalidrawPlugin
    options:
        heading_level: 3
        docstring_section_style: table
        members: true
        inherited_members: true
        merge_init_into_class: true

shadcn.plugins.excalidraw.ExcalidrawPlugin()

Bases: RouterMixin, BasePlugin[ExcalidrawPluginConfig]

This plugin enabled the real time edition of excalidraw scenes in development mode

Source code in shadcn/plugins/_router.py
11
12
13
14
15
16
def __init__(self):
    self.bottle = Bottle()
    """We use `bottle` to handle plugin routes since
    its `wsgi()` method is very convenient with regards to
    the `_serve_request()` method of the mkdocs dev server
    """

bottle = Bottle() instance-attribute

We use bottle to handle plugin routes since its wsgi() method is very convenient with regards to the _serve_request() method of the mkdocs dev server

is_dev_server = False class-attribute instance-attribute

Internal flag to detect if we are in development mode.

add_route(path, handler, method='GET')

Add a route to the router.

Source code in shadcn/plugins/_router.py
18
19
20
21
22
23
24
25
def add_route(
    self,
    path: str,
    handler: Callable,
    method: Union[str, List[str]] = "GET",
):
    """Add a route to the router."""
    self.bottle.route(path, method=method)(handler)

extend_server(server)

Extend the mkdocs dev server to add custom behavior.

Source code in shadcn/plugins/_router.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def extend_server(self, server: LiveReloadServer):
    """Extend the mkdocs dev server to add custom behavior."""
    original = server._serve_request

    @wraps(original)
    def _serve_request(environ, start_response):
        # put priority to base routes
        result = original(environ, start_response)
        if result is not None:
            return result
        # run our extra route handler
        return self.bottle.wsgi(environ, start_response)

    # monkey patch the _serve_request method of the server
    setattr(server, "_serve_request", _serve_request)

on_config(config, **kwargs)

Three operations are performed:

  • detect and create the excalidraw directory
  • load the internal excalidraw markdown extension
  • inject the HTTP routes needed to handle excalidraw scenes and SVGs

Parameters:

Name Type Description Default
config MkDocsConfig

MkDocs configuration object.

required
Source code in shadcn/plugins/excalidraw.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
def on_config(self, config: MkDocsConfig, **kwargs):
    """Three operations are performed:

    - detect and create the excalidraw directory
    - load the internal excalidraw markdown extension
    - inject the HTTP routes needed to handle excalidraw scenes and SVGs

    Parameters:
        config: MkDocs configuration object.
    """
    base = os.path.dirname(config["config_file_path"])
    excalidraw_path = os.path.join(base, self.config.directory)
    extension_config = {
        "base_dir": excalidraw_path,
        "svg_only": not self.is_dev_server,
    }
    log.debug(
        f"Loading markdown extension 'shadcn.extensions.excalidraw' "
        f"with configuration: {extension_config}"
    )
    config["markdown_extensions"].append("shadcn.extensions.excalidraw")
    config["mdx_configs"]["shadcn.extensions.excalidraw"] = (
        extension_config
    )
    # create directory
    log.debug(f"creating excalidraw directory: {excalidraw_path}")
    os.makedirs(excalidraw_path, exist_ok=True)

    # these routes are injected in on_serve
    log.debug("injecting HTTP routes for excalidraw plugin")
    self.add_route(
        "/excalidraw/scene",
        scene_handler_factory(excalidraw_path),
        method=["GET", "POST"],
    )
    self.add_route(
        "/excalidraw/svg",
        svg_handler_factory(excalidraw_path),
        method=["GET", "POST"],
    )

on_serve(server, /, *, config, builder)

This method is called when the server is started. At the end of the mkdocs workflow. At this moment we have access to the live server to inject our new routes. See https://www.mkdocs.org/dev-guide/plugins/#events to see the mkdocs workflow.

Source code in shadcn/plugins/_router.py
43
44
45
46
47
48
49
def on_serve(self, server: LiveReloadServer, /, *, config, builder):
    """This method is called when the server is started. At the end of the mkdocs
    workflow. At this moment we have access to the live server to inject our new
    routes.
    See https://www.mkdocs.org/dev-guide/plugins/#events to see the mkdocs workflow.
    """
    self.extend_server(server)

on_startup(*, command, dirty)

Detect if the server is running in development mode.

Parameters:

Name Type Description Default
command str

the command being run (e.g., "serve" or "build")

required
dirty bool

whether the site is dirty (i.e., needs to be rebuilt)

required
Source code in shadcn/plugins/excalidraw.py
 93
 94
 95
 96
 97
 98
 99
100
def on_startup(self, *, command: str, dirty: bool):
    """Detect if the server is running in development mode.

    Parameters:
        command: the command being run (e.g., "serve" or "build")
        dirty: whether the site is dirty (i.e., needs to be rebuilt)
    """
    self.is_dev_server = command == "serve"
::: shadcn.plugins._router.RouterMixin
    options:
        heading_level: 3
        show_symbol_type_heading: true

shadcn.plugins._router.RouterMixin

Mixin to add custom routes to the mkdocs dev server.

Source code in shadcn/plugins/_router.py
 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
class RouterMixin:
    """Mixin to add custom routes to the mkdocs dev server."""

    def __init__(self):
        self.bottle = Bottle()
        """We use `bottle` to handle plugin routes since
        its `wsgi()` method is very convenient with regards to
        the `_serve_request()` method of the mkdocs dev server
        """

    def add_route(
        self,
        path: str,
        handler: Callable,
        method: Union[str, List[str]] = "GET",
    ):
        """Add a route to the router."""
        self.bottle.route(path, method=method)(handler)

    def extend_server(self, server: LiveReloadServer):
        """Extend the mkdocs dev server to add custom behavior."""
        original = server._serve_request

        @wraps(original)
        def _serve_request(environ, start_response):
            # put priority to base routes
            result = original(environ, start_response)
            if result is not None:
                return result
            # run our extra route handler
            return self.bottle.wsgi(environ, start_response)

        # monkey patch the _serve_request method of the server
        setattr(server, "_serve_request", _serve_request)

    def on_serve(self, server: LiveReloadServer, /, *, config, builder):
        """This method is called when the server is started. At the end of the mkdocs
        workflow. At this moment we have access to the live server to inject our new
        routes.
        See https://www.mkdocs.org/dev-guide/plugins/#events to see the mkdocs workflow.
        """
        self.extend_server(server)

bottle = Bottle() instance-attribute

We use bottle to handle plugin routes since its wsgi() method is very convenient with regards to the _serve_request() method of the mkdocs dev server

add_route(path, handler, method='GET')

Add a route to the router.

Source code in shadcn/plugins/_router.py
18
19
20
21
22
23
24
25
def add_route(
    self,
    path: str,
    handler: Callable,
    method: Union[str, List[str]] = "GET",
):
    """Add a route to the router."""
    self.bottle.route(path, method=method)(handler)

extend_server(server)

Extend the mkdocs dev server to add custom behavior.

Source code in shadcn/plugins/_router.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def extend_server(self, server: LiveReloadServer):
    """Extend the mkdocs dev server to add custom behavior."""
    original = server._serve_request

    @wraps(original)
    def _serve_request(environ, start_response):
        # put priority to base routes
        result = original(environ, start_response)
        if result is not None:
            return result
        # run our extra route handler
        return self.bottle.wsgi(environ, start_response)

    # monkey patch the _serve_request method of the server
    setattr(server, "_serve_request", _serve_request)

on_serve(server, /, *, config, builder)

This method is called when the server is started. At the end of the mkdocs workflow. At this moment we have access to the live server to inject our new routes. See https://www.mkdocs.org/dev-guide/plugins/#events to see the mkdocs workflow.

Source code in shadcn/plugins/_router.py
43
44
45
46
47
48
49
def on_serve(self, server: LiveReloadServer, /, *, config, builder):
    """This method is called when the server is started. At the end of the mkdocs
    workflow. At this moment we have access to the live server to inject our new
    routes.
    See https://www.mkdocs.org/dev-guide/plugins/#events to see the mkdocs workflow.
    """
    self.extend_server(server)
::: shadcn.utils
    options:
        heading_level: 3
        members: true
        show_symbol_type_heading: true

shadcn.utils

deep_merge(base, override)

Recursively merges override into base.

Parameters:

Name Type Description Default
base MutableMapping

the dictionary to merge into (modified in place)

required
override Mapping

the dictionary to merge from (not modified)

required
Source code in shadcn/utils.py
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
def deep_merge(base: MutableMapping, override: Mapping):
    """Recursively merges override into base.

    Parameters:
        base: the dictionary to merge into (modified in place)
        override: the dictionary to merge from (not modified)
    """
    for key in override:
        if (
            key in base
            and isinstance(base[key], MutableMapping)
            and isinstance(override[key], Mapping)
        ):
            deep_merge(base[key], override[key])
        else:
            base[key] = override[key]
    return base