0047 — Условия в Sphinx
Постановка задачи
Для создания документации используется Sphinx.
Требования:
- нужно собирать документацию для публичного и служебного пользования;
- документация для служебного пользования должна содержать дополнительные разделы и части контента, которых не должно быть видно обычным пользователям.
Решения
ifconfig
Самое первое, что приходит на ум — использовать директиву .. ifconfig::.
Добавляем в
conf.py
код, позволяющий получить значение переменной окруженияIS_INTERNAL
(её значение задаётся в настройках используемой системы сборки):from os import environ from typing import Final extensions = [ "sphinx.ext.ifconfig", # ... ] def setup (app): IS_INTERNAL: Final[bool] = bool(environ.get('IS_INTERNAL', False)) app.add_config_value('IS_INTERNAL', IS_INTERNAL, 'env')
Добавляем код в макет:
.. ifconfig:: IS_INTERNAL Список пользователей и паролей см. на `этой странице <https://internal.example.com/passwords/>`. .. ifconfig:: not IS_INTERNAL Для получения списка пользователей и паролей обратитесь к системному администратору.
Неплохо. Даже работает так, как и ожидается.
Теперь давайте расширим постановку задачи:
- нужно собирать документацию для публичного и служебного пользования;
- документация для служебного пользования должна содержать дополнительные разделы и части контента, которых не должно быть видно обычным пользователям;
- из документации для публичного пользования нужно убрать некоторые разделы.
Попробуем убрать раздел описанным выше способом:
.. ifconfig:: IS_INTERNAL
.. _users_passwords:
Пароли служебных пользователей
==============================
...
Ой! А почему на рендере у нас заголовок второго уровня (<h2>
) вдруг стал третьим (<h3>
)? Может, баг какой-то? Нет, ребята, это нормальное поведение .. ifconfig::
. Читаем вместе:
This directive is designed to control only content of document. It could not control sections, labels and so on.
То есть работать это не будет.
only
К счастью, у нас есть ещё теги и директива .. only::.
Однако, для её использования нужна небольшая подготовка:
Правим
conf.py
:from os import environ from typing import Final # ... def setup (app): IS_INTERNAL: Final[bool] = bool(environ.get('IS_INTERNAL', False)) if IS_INTERNAL: app.tags.add('is_internal')
Правим вёрстку:
.. only:: is_internal .. _users_passwords: Пароли служебных пользователей ==============================
Собираем:
export IS_INTERNAL=true; make dirhtml
Или так (даже проще чем возня с переменными окружения):
sphinx-build dirhtml --tag is_internal source/ build/
Вот сейчас всё рендерится как надо: целый раздел спрятан от анонимусов, зато доступен на внутреннем сервере компании.
Скрытие страниц
А если мы хотим спрятать целую страницу или раздел?
Добавьте в начало скрытых страниц макрос
.. orphan::
. Теперь Sphinx не будет падать с ошибкой «Страница не включена ни в одно оглавление».В
conf.py
добавьте код, управляющий исключениями:from os import environ from typing import Final # ... IS_INTERNAL: Final[bool] = bool(environ.get('IS_INTERNAL', False)) if IS_INTERNAL: exclude_patterns = [ 'secret/*', 'images/internal' ]
В публичную документацию не попадут все страницы в каталоге
secret/
и страницаimages/internal.rst
.В оглавлении нужных разделов вместо конкретных имён файлов используйте wildcard:
.. toctree:: secret/* images/*
Документация, собранная таким образом, будет содержать все нужные страницы, кроме перечисленных в параметре
exclude_patterns
.Если важен порядок следования разделов — измените имена файлов таким образом, чтобы они шли в нужном порядке, например:
01-index.rst 02-ssh.rst 03-users.rst 04-files.rst