Linting Markdown with pymarkdownlnt

16Feb24

TL;DR

pymarkdownlnt provides an easy way of checking that any Markdown you’re working on is complying to some sensible guidelines. If you’re comfortable with Python virtual environments you won’t really need the rest of this post.

Why?

I’ve spent a bunch of time recently adding OpenSSF Scorecards to the key Atsign repos.

Build better security habits, one test at a time

https://securityscorecards.dev/

When it came to the docs repos I needed some way of testing them, and checking the Markdown against a set of lints seemed like a sensible way of doing things.

Yak shave 1 – newer Python

The WSL2 environment I use day to day is Ubuntu 20.04. I should probably have bumped it to 22.04 ages ago, but given that 24.04 is weeks away…

Ubuntu 20.04 carries Python 3.8 as the system Python. Not too old for pymarkdownlnt, but also not ideal. I generally like to be using the latest stable release, which is now 3.12. Thankfully it’s pretty easy to install newer Python alongside the system one using the deadsnakes repo:

sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update
sudo apt install python3.12

Yak shave 2 – Python virtual environment

I could have just run pip3 install pymarkdownlnt but there’s now an increasing awareness that installing Python packages with the system package manager isn’t a good idea. Newer distros like Debian 12 and Raspberry Pi OS ‘Bookworm’ prevent the use of pip. Lots of people find this annoying, but breaking system packages by installing never versions of their dependencies that don’t work is also annoying. After many years of avoiding virtual environments (venvs) it seems like it’s time to get with the programme.

I created a venv for the linter with:

python3 -m venv ~/python/venvs/pymarkdownlnt

Installing pymarkdownlnt into the venv

First the venv needs to be activated:

. ~/python/venvs/pymarkdownlnt/bin/activate

and then the linter can be installed.

pip install pymarkdownlnt

Side quest – using uv for venvs and pip

uv is a shiny new (released yesterday) tool from Astral. I’ve been using their ruff linter for Python, and it’s impressively quick.

With uv installed I could repeat the process above, with a few more keystrokes and a bit less waiting:

uv venv ~/python/venvs/pymarkdownlnt -p python3.12
. ~/python/venvs/pymarkdownlnt/bin/activate
uv pip install pymarkdownlnt

Now I can lint some Markdown

Whenever I want to check some Markdown all I need to do is activate my pymarkdownlnt venv and run the linter against my file:

pymarkdownlnt scan somefile.md

Automating lint checks with GitHub Actions

It’s nice that I’m able to check stuff before committing it. But what I really want is to stop badly formed Markdown from getting into repos in the first place. That’s done with a GitHub Actions workflow pymarkdownlnt.yml.

The key section is:

      - name: Install and run linter
run: |-
python3 -m pip install --require-hashes \
-r tools/requirements.txt
pymarkdownlnt -d MD013,MD024 scan */*.md

This installs pymarkdownlnt from a requirements.txt file where I have the package hashes pinned (to keep the scorecard happy), and then runs the linter against all Markdown files in the repo.

In this case a couple of rules are being suppressed as we have some tables with (unavoidably) very long lines, and the functionality to set siblings_only doesn’t seem to work as it should.

Update

I’ve added pragmas above the offending lines in the various files that must have long lines, so no need to exclude MD013 any more. This is good, as it will now catch new files that have long lines that don’t need them.

I also opened an issue about the problems I was having with setting siblings_only for MD024. Meanwhile I’ve modified the workflow thus:

  - name: Install and run linter
run: |-
python3 -m pip install --require-hashes \
-r tools/requirements.txt
pymarkdownlnt -c tools/pymarkdownlnt.conf \
scan */*.md

to use a config file tools/pymarkdownlnt.conf:

{"plugins": {"md024": {"siblings_only": True}}}


No Responses Yet to “Linting Markdown with pymarkdownlnt”

  1. Leave a Comment

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.