Skip to content

feat: lint headers and validate anchor links in markdown files

Artur Sabirov requested to merge lint_anchors into main

Задачи

  1. Написать линтер заголовков файлов Markdown, который проверяет их на наличие якорей формата {: #anchor }
  2. Написать валидатор якорей в относительных ссылках файлов Markdown

Реализация — в виде хуков или плагина MkDocs.

Решение

Нововведения

  • Линтер lint_headers.py:
    • проверяет заголовки в файлах .md на наличие якорей формата {: #anchor }
    • создает индекс якорей anchor_index
  • Валидатор validate_anchor_links.py:
    • валидирует относительные ссылки на якори в файлах .md с помощью индекса якорей anchor_index

Принцип работы линтера lint_headers.py

  • Хук выполняется в событии on_pre_build этапа build, чтобы команда mkdocs serve -s обрабатывала записи хука со статусом WARNING
  • Генератор os.walk() возвращает пути к файлам .md в папке docs
  • В индексе anchor_index — тип dict[str, set] — создается запись, соответствующая файлу .md
  • Из каждого файла .md удаляется закомментированный текст — заключен в <!-- и -->
  • В оставшемся тексте построчно парсятся заголовки:
    • первый заголовок пропускается
    • в следующих заголовках проверяется наличие якоря формата {: #anchor } — символ : и пробелы с обеих сторон обязательны
    • якорь нужного формата добавляется в индекс anchor_index
    • при отсутствии в заголовке якоря нужного формата в лог MkDocs выводится запись со статусом DEBUG
  • Индекс anchor_index записывается в файл anchor.yml

Принцип работы валидатора validate_anchor_links.py

  • Хук выполняется в событии on_pre_build этапа build, чтобы команда mkdocs serve -s обрабатывала записи хука со статусом WARNING
  • Генератор os.walk() возвращает пути к файлам .md в папке docs
  • Из файла anchor.yml читается индекс anchor_index
  • Из каждого файла .md удаляется закомментированный текст — заключен в <!-- и -->
  • В оставшемся тексте парсятся ссылки:
    • ищутся ссылки link в формате [text](full_path)
    • если у ссылки link есть якорь, т. е. full_path == path#anchor, и нет подстроки https://, из нее извлекается путь path и в ней валидируется якорь anchor
    • при обнаружении невалидного якоря в лог MkDocs выводится запись со статусом DEBUG
Примеры записей в логе
lint_headers.py
DEBUG -  lint_headers: INVALID or MISSING anchor @ docs/reference/api.md -> ### pico.wait_ddl_finalize
validate_anchor_links.py
DEBUG -  validate_anchor_links: BROKEN link @ docs/reference/api.md -> [Vclock](../overview/glossary.md#vclock-vector-clock)
Поиск решения

Поиск решения

Первая итерация

Написан хук lint_anchors.py.

Принцип работы:

  • Хук выполняется в событии on_config в начале этапа build, чтобы команда mkdocs serve -s обрабатывала записи хука со статусом WARNING
  • Генератор os.walk() возвращает пути к файлам Markdown в папке docs
  • Из каждого файла Markdown читается список строк data
  • Нулевой элемент списка data удаляется — подразумевается, что это самый первый заголовок, которому не нужен якорь
  • В списке data считается количество заголовков headers и якорей anchors. Закомментированные заголовки не учитываются
  • Регулярное выражение для поиска якорей \{:\s#[\w.-]*\s\} соответствует формату {: #anchor }
  • Если якорей меньше, чем заголовков, в лог MkDocs выводится запись со статусом WARNING. Пример:
WARNING -  lint_anchors: Number of missing anchors in docs/reference/api.md >>> 4

Доработать:

  • Нужно исключать не первую строку файла Markdown, а первый заголовок — файл может начинаться с блока метаданных
  • Плагин attr-list допускает следующие форматы якорей: {:#anchor }, {: #anchor}, { #anchor }, {#anchor }, {#anchor}. Возможно, следует добавить эти форматы в линтер. Источник:

The colon after the opening brace is optional, but is supported to maintain consistency with other implementations.

<...>

In addition, the spaces after the opening brace and before the closing brace are optional. They are recommended as they improve readability, but they are not required.

See:

Refer:

Вторая итерация

Изменения:

  • Из проверки исключается первый найденный заголовок — ранее исключалась первая строка файла Markdown
  • В результате работы хука создается словарь anchors_dict с записями в формате {document: anchors_set}, где:
    • document (str) — путь к файлу Markdown
    • anchors_set (set) — множество якорей заголовков из файла Markdown

Третья итерация

Изменения:

  • В хук lint_anchors.py добавлена функция write_dict(), которая записывает словарь — dict — в файл
  • Переименование lint_anchors.py -> lint_headers.py

Четвертая итерация

Изменения:

  • Написан хук validate_anchor_links.py

Коммиты

  • chore: move filter_plugin_records.py to hooks
  • feat: lint attr-list anchors in markdown files
  • rename: lint_anchors.py -> lint_headers.py
  • feat: validate anchor links in markdown files
  • refactor: reorganize lint_headers.py and validate_anchor_links.py code

Close #147 (closed)

Саморевью

  • Контент отображается корректно
  • Ссылки в контенте работоспособны и корректны
Edited by Artur Sabirov

Merge request reports