/yapper
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 that would let me generate the documentation from python docstrings while injecting custom html elements that would play nicely with third-party static site generators that incorporate use of vue components: my flavour of choice being the great gridsome project. Before creating this package I tried a variety of 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 and the latter all tend to have that dreaded ‘look-and-feel’ about them.

pydoc-markdown looked really promising and is probably the way to go if you’re using a site generator 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 outputted markdown 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 fairly easy to get to grips with for customised workflows and I was consequently able to build a fairly 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 which 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 and, if using the 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.