22nd Apr, 2021Gareth Simons

yapper converts Python docstrings to markdown for use by static site generators. It uses the docspec package to read Python files.

It was originally part of the cityseer-api project where I needed something to generate the documentation from Python docstrings while injecting custom HTML elements that would play nicely with third-party static site generators. In my case, I needed to incorporate vue components into my static site generator of choice, the great gridsome. Before creating this package, I tried various strategies, including manually crafted markdown and some of the more generic Python documentation packages. The former proved too much hard work to keep up to date. The latter all tended to have that dreaded ‘look-and-feel’ about them.

pydoc-markdown looked very promising and is probably the way to go if you’re using a static site generator that they already cover, such as MkDocs or Hugo. In my case, I ended up needing more customisation and the ability to more easily understand how the pieces fit together so that I could overload the output with custom HTML elements dovetailing with the vue components I use in my site generator. It turns out that pydoc-markdown is based on the interesting docspec package by the same author, which parses docstrings from Python files and presents these in a structured format that can then be parsed and manipulated downstream. It is relatively easy to get to grips with the package for customised workflows, and I was consequently able to build a reasonably lightweight parser for my own purposes.

yapper is simply this lightweight parser that has been repackaged to allow easier use from other projects. It is based on a simple-as-possible configuration file that permits custom HTML to be injected for subsequent interpretation by downstream static site-generators. Linting and any other markdown processing is left to js workflows. For example, most IDEs have built-in linting. If using the generated markdown for a static site generator, then any linting, linking, footnoting, emoticons, etc., can be handled by the respective markdown ecosystem. e.g. remarkplugins or similar.

In its default configuration, the following code snippet:

def mock_function(param_a: str, param_b: int = 1) -> int:
    """
    A mock function for testing purposes

    Parameters
    ----------
    param_a
        A *test* _param_.
    param_b
        Another *test* _param_.

        | col A |: col B |
        |=======|========|
        | boo   | baa    |
    """
    pass

Will be interpreted as:

## mock_function

```py
mock_function(param_a,
             param_b=1)
             -> int
```

A mock function for testing purposes

#### Parameters

**param_a** _str_: A _test param_.

**param_b** _int_: Another _test param_.

| col A |: col B |
|=======|========|
| boo | baa |

So far, nothing that different or interesting. However, let’s now inject some custom html via the configuration file:

signature_template: "\n\n<FuncSignature>\n<pre>\n{signature}\n</pre>\n</FuncSignature>\n\n"
heading_template: "\n\n<FuncHeading>{heading}</FuncHeading>\n\n"
param_template: "\n\n<FuncElement name='{name}' type='{type}'>\n\n{description}\n\n</FuncElement>\n\n"

Which now gives:

## mock_function

<FuncSignature>
<pre>
mock_function(param_a,
              param_b=1)
              -> int
</pre>
</FuncSignature>

A mock function for testing purposes

<FuncHeading>Parameters</FuncHeading>

<FuncElement name='param_a' type='str'>

A _test param_.

</FuncElement>

<FuncElement name='param_b' type='int'>

Another _test param_.

| col A |: col B |
|=======|========|
| boo | baa |

</FuncElement>

This makes it easier to wrap custom vue components around various elements, and an example of an end-product can be found in the cityseer-api docs.

See the yapperrepo for more information.