Конвертирует страницы wiki.wirenboard.com в PDF-руководства с помощью Typst.
- Редактор вики добавляет
{{Wbincludes:pdf}}на страницу устройства - GitHub Actions каждые 10 минут находит такие страницы и генерирует/загружает PDF
- На странице вики появляется ссылка для скачивания PDF
python3 wiki2pdf.py "https://wiki.wirenboard.com/wiki/ZMCT205D"
python3 wiki2pdf.py "https://wiki.wirenboard.com/wiki/PageName" -o custom_output.pdf
python3 wiki2pdf.py "https://wiki.wirenboard.com/wiki/PageName" --keep-typstexport WIKI_BOT_USER="EvgenyBoger@PdfUploader"
export WIKI_BOT_PASS="..."
python3 wiki_publish.py # найти страницы, сгенерировать устаревшие PDF, загрузить
python3 wiki_publish.py --dry-run # только показать список страниц
python3 wiki_publish.py --force # перегенерировать все, игнорируя актуальность
python3 wiki_publish.py --page NAME # обработать одну страницу
python3 wiki_publish.py --no-upload # сгенерировать локально без загрузкиexport WIKI_BOT_USER="EvgenyBoger@PdfDev"
export WIKI_BOT_PASS="..."
python3 wiki_add_template.py "Название страницы 1" "Название страницы 2" ...Добавляет {{Wbincludes:pdf}} на каждую страницу и подтверждает ревизию (требуется право approverevisions).
Рабочий процесс (.github/workflows/update-pdfs.yml) работает в двух режимах:
- Запрашивает у вики все страницы с
{{Wbincludes:pdf}} - Для каждой страницы сравнивает текущую ревизию вики с ревизией, записанной в комментарии загруженного PDF
- Пропускает страницы, которые уже актуальны
- Генерирует и загружает только устаревшие PDF
- Срабатывает при изменении кода (конвертер, шаблон, шрифты и т.д.)
- Запускается с
--forceдля перегенерации ВСЕХ PDF, так как отрисовка могла измениться - Игнорирует пуши в файлы, не влияющие на генерацию (README, .gitignore, вспомогательные скрипты)
Настраиваются в репозитории: Settings → Secrets → Actions:
WIKI_BOT_USER— имя бота MediaWiki (например,EvgenyBoger@PdfUploader)WIKI_BOT_PASS— пароль бота MediaWiki
Бот-аккаунт должен иметь права upload, writeapi и состоять в группе wb_editors на вики.
Требуется Python 3.11+ с зависимостями:
pip install -r requirements.txtБинарник Typst должен находиться в bin/typst:
curl -sL https://github.com/typst/typst/releases/download/v0.13.1/typst-x86_64-unknown-linux-musl.tar.xz | tar xJ
mkdir -p bin
mv typst-x86_64-unknown-linux-musl/typst bin/typstПользовательские шрифты размещаются в fonts/ (PT Sans включён).
wiki2pdf.py Конвертация одной страницы (CLI)
wiki_publish.py Пакетная обработка: поиск страниц, генерация PDF, загрузка на вики
wiki_add_template.py Добавление {{Wbincludes:pdf}} на страницы и подтверждение
batch_generate.py Локальная пакетная генерация (фиксированный список страниц)
audit_pdfs.py Проверка сгенерированных PDF на ошибки рендеринга
lib/
fetcher.py Клиент API MediaWiki, загрузка изображений, встраивание разделов
html_converter.py Движок конвертации HTML → Typst
typst_runner.py Обёртка компиляции Typst (с автоисправлением colspan)
wiki_api.py Бот-клиент MediaWiki (авторизация, загрузка, запросы страниц)
templates/
manual.typ Шаблон Typst (макет, стили, обложка, оглавление)
fonts/ Файлы шрифтов (PT Sans)
.github/workflows/
update-pdfs.yml Рабочий процесс GitHub Actions
Вики использует множество шаблонов {{Wbincludes:...}} с параметрами (например, note=true, no_description=true). Разбор сырого викитекста потребовал бы реализации шаблонизатора MediaWiki. API action=parse возвращает полностью отрендеренный HTML со всеми раскрытыми шаблонами, вычисленными условиями и применённым class="hidden" к скрытым элементам.
BeautifulSoup с html.parser используется для обхода DOM. Это надёжнее разбора викитекста:
- Шаблоны уже вычислены с их параметрами
- Структура HTML предсказуема (
<table class="wikitable">,<div class="thumb">,<span class="note">и т.д.) - CSS-классы
hidden,noprint,mw-editsectionудаляются за один проход
Многие страницы вики содержат разделы, которые являются просто ссылкой на подстраницу (например, «Ревизии устройства» ссылается на отдельную страницу). Фетчер обнаруживает такие разделы с единственной ссылкой и встраивает содержимое связанной страницы:
- Заголовки подконтента понижаются относительно уровня родительского заголовка
- Обратные ссылки («Перейти на страницу устройства») удаляются
- Ссылки на встроенные страницы переписываются как локальные якоря (
#fragment) - Пространства имён MediaWiki (
File:,Special:,Template:и т.д.) исключаются из встраивания
При публикации скрипт сравнивает текущую ревизию страницы вики с ревизией, сохранённой в комментарии загруженного PDF ("Auto-generated from revision NNNNN"). Страницы пропускаются, если ревизии совпадают. Проверки ревизий выполняются пакетно (до 50 страниц за один API-запрос).
Typst выбран за:
- Нативный вывод в PDF с хорошей типографикой
- Простой язык разметки, хорошо отображающийся из HTML
- Встроенная поддержка таблиц с colspan/rowspan, нумерации рисунков, переворота страниц
- Функция
layout()для измерения и ограничения размеров изображений #page(flipped: true)для альбомных страниц
- Изображения загружаются параллельно (ThreadPoolExecutor, 8 потоков)
- Предпочитаются полноразмерные изображения вместо миниатюр (URL миниатюр конвертируются удалением сегмента
/thumb/) - Портретные изображения (высота > 1.2× ширины) определяются по заголовкам PNG/JPEG и автоматически группируются в 2-колоночные сетки при последовательном расположении
- Функция Typst
constrained-imageиспользуетlayout()+measure()для ограничения высоты изображений до 50% высоты страницы с сохранением пропорций - Изображения галерей используют более строгое ограничение высоты (40%), чтобы два помещались на странице
- Первое изображение извлекается для обложки
Из анимированных GIF извлекается до 8 визуально различных кадров методом жадного выбора наиболее удалённых точек. Короткие анимации (< 4 кадров) показывают 2 полных цикла. Каждый кадр подписан временной меткой. Кадры отображаются в сетке из 4 колонок внутри рисунка или инлайн в ячейках таблиц с размером, совпадающим со статичными изображениями.
Количество колонок определяется по строке заголовка (или моде всех строк), чтобы избежать завышения из-за строк легенды с увеличенными colspan. Режим отображения зависит от ширины содержимого:
- Обычный: таблицы с < 5 колонками
- Компактный: 5–7 колонок, уменьшенный шрифт (8.5pt)
- Альбомный: 8+ колонок с широким содержимым (максимальный текст строки > 80 символов) или 12+ колонок;
#page(flipped: true)с шрифтом 7pt
Когда заголовок непосредственно предшествует альбомной таблице, он перемещается внутрь перевёрнутой страницы.
Таблицы с <caption> оборачиваются в #figure(kind: table) для автонумерованных подписей. Цвета фона ячеек разрешаются из инлайн-стилей и CSS-классов (cell-green, cell-red, cell-yellow).
Компилятор Typst автоматически исправляет ошибки переполнения colspan, уменьшая значение вдвое и повторяя попытку (до 20 раз).
Элементы <ul class="gallery"> MediaWiki конвертируются в отдельные рисунки с подписью галереи (например, «Обновление прошивки. Выбор файла»). Изображения галерей ограничены по высоте (40% страницы) для компактной компоновки.
Два паттерна обнаружения:
<span class="note note-note">/<span class="note note-warning">— семантические классы<div style="border:...;background:...">— эвристика CSS для блоков шаблонов
Оба отображаются как стилизованные блоки с цветной левой границей.
Цвета разрешаются из нескольких источников:
- CSS-классы:
text-green,text-red,text-orangeдля текста;cell-green,cell-red,cell-yellowдля фона ячеек - Инлайн-стили:
style="color: #xxx"иstyle="background-color: #xxx"
Элементы с class="hidden" или class="noprint" удаляются. Это учитывает параметры шаблонов MediaWiki (например, no_description=true) и сам блок загрузки {{Wbincludes:pdf}}.
- Первое изображение извлекается и размещается на обложке
- URL страницы вики отображается и кликабелен
- ID ревизии и временная метка из API вики
- Заголовок на каждой странице ссылается на статью вики
PT Sans (Paratype) — свободный шрифт без засечек с полной поддержкой кириллицы. Пользовательские шрифты можно добавить в fonts/ и указать в templates/manual.typ.