Вам в наследство досталось руководство пользователя МИС на 1160 страниц в формате .docx, тысячи повторяющихся скриншотов, процесс обновления вызывает глубокую грусть. Что с этим «добром» делать?
В этой статье я расскажу о своём проекте по переводу такой документации на рельсы docs-as-code: переосмысление структуры, укрощение размера скриншотов, борьба с кириллицей в Asciidoctor, настройка автоматической сборки HTML и PDF через GitLab CI/CD.

Наследие MS Word и синдром «талмуда»
За окном 2026 год. Представьте себе толстенный документ, набранный в MS Word 2010. Это официальное руководство пользователя для крупной медицинской информационной системы (МИС), которая ежедневно управляет потоками данных — от локальных карт пациентов до разнообразных видов взаимодействия с центральным компонентом электронной системы охраны здоровья (eHealth).
Чистый размер текстового массива составляет 1 Мб, но главная проблема в другом: документ написан кондовым языком с фиксацией на интерфейсе. Вместо того чтобы вести врача по процессам его реальной работы (оформление приёма, создание электронного направления и т.п.), «талмуд» описывает буквально каждый элемент: «При нажатии на кнопку А открывается окно Б, содержащее поле В». Когда интерфейс живой системы эволюционирует, поддержание такой книги превращается в ад: вёрстка «плывёт» от любого чиха, одновременная работа нескольких авторов невозможна, а контроль версий превращается во всем знакомое Руководство_v4_final_исправлено_copy(2).docx.
Как человек с бэкграундом в системном администрировании и опытом литературной правки, я не мог спокойно смотреть на это. Документация должна жить по законам разработки, и я инициировал пилотный проект по переработке и миграции этого руководства на рельсы "документация как код".
Выбор инструментария для работы
В мире docs-as-code есть три популярных пути, но для масштабной технической книги в данном случае подошёл только один.
Markdown. Слишком примитивен. Попробуйте сверстать на нём большой многостраничный документ с перекрёстными ссылками между главами, сложной структурой модульных включений (придерживаясь принципа «единого источника») и кастомными стилями — вы быстро упрётесь в ограничения синтаксиса или зоопарк несовместимых диалектов.
DITA / DocBook (XML). Профессионально, мощно, но неоправданно дорого и избыточно для гибкой разработки. XML-теги превращают чтение исходного кода в мучение, а порог входа для авторов весьма высок.
AsciiDoc (Asciidoctor). Легковесный разметчик, который читается глазами так же легко, как Markdown, но обладает мощью семантических блоков, родной поддержкой включений (
include::[]), условий, сложной вёрстки таблиц и готовым рендерингом в HTML и PDF «из коробки» (через плагины).
Инструменты для проекта разворачивались на обычном ноуте Lenovo ThinkPad под управлением Linux Mint. Рабочее место получилось спартанским, но максимально производительным:
VS Code + плагин Asciidoc в качестве основного текстового редактора.
Freeplane — мощная бесплатная программа для создания сложных интеллект-карт, которая ориентирована на структурирование больших объемов информации, анализ и планирование.
Emacs Org-mode для ведения "бортового журнала" разработчика (чтобы прочувствовать весь спектр текстового гиковства и усложнить себе жизнь).
Pomodorot — полезная программа, которая не даёт очманеть от работы, блокируя экран для отдыха каждые полчаса.
Планирование работы
Цель проекта формулировалась так: взять репрезентативный кусок старого руководства, безжалостно переработать его структуру под логику врача, очистить от «воды» и тяжёлых фраз, перевести в AsciiDoc и настроить автоматическую сборку через GitLab CI/CD.
Последовательность работ проекта со сроками я разработал в ProjectLibre (впоследствии в нём обнаружился огромный минус: в pdf-файл не выводится кириллица), а новую структуру документации (со ссылками на пункты исходной) — во Freeplane. При анализе структуры и её реорганизации здорово помог ИИ, без него перелопатить такой объём информации было бы куда сложнее.

Графический квест: выковыривание и чистка 5100+ скриншотов
В подопытном документе оказалось более пяти тысяч скриншотов! Они годами копились внутри вордовского файла, сделанные в разных версиях Windows, с кучей дубликатов и диким весом pdf-ки (без малого 100 мегабайт!).
Тащить этот графический хаос в новый проект — преступление против Git-репозитория и здравого смысла. Нужен был автоматизированный процесс очистки.
Для максимальной гибкости я решил обрабатывать файлы в терминале Linux и для этой цели выбрал несколько утилит.
1. pdfimages
Первым делом я выпотрошил из документа абсолютно всю графику в PNG-формат:
$ pdfimages -png legacy-guide.pdf ./screenshots_extract
Команда работала почти 10 минут и выдала картинок аж на 588 Мб.
2. jdupes
Хранить идентичные картинки по меньшей мере глупо. Для просмотра дублей сгодилась команда, которая сравнивает файлы по контрольным суммам:
$ jdupes -rMS screenshots_extract
Её вывод был красноречив:
3295 duplicate files (in 658 sets), occupying 173 MB
Узнать число групп дублей (они разделяются пустой строкой) можно и такой командой:
$ jdupes -r /путь/к/папке | grep -c "^$"
Из любопытства захотелось узнать минимальное, максимальное и среднее количество элементов в группе. Для решения этой задачи в терминале лучше всего подошла утилита awk, которой скармливается файл с протоколом работы jdupes.
1: Получение списка размеров групп.
Команда ниже превращает файл в список чисел, где каждое число — количество строк в одной группе.
$ awk -v RS='' '{print NF}' имя_файла
RS='' — настройка awk, которая заставляет его считать разделителями записей (блоков) пустые строки;
NF — количество «полей» (слов/строк) в этом блоке.
Чтобы узнать именно количество строк, а не слов, нужно слегка изменить команду:
$ awk -v RS='' '{print gsub(/\n/, "\n") + 1}' имя_файла
В моём случае результат был одинаков.
2: Подсчёт Min, Max и Average
$ awk -v RS='' '{n = gsub(/\n/, "\n") + 1; print n}' имя_файла | \ awk 'BEGIN {min=999999; max=0} { sum += $1; count++; if($1 > max) max=$1; if($1 < min) min=$1; } END{ if(count>0) print "Min: " min, "| Max: " max, "| Avg: " sum/count; else print "Групп не найдено"; }'
Первый awk разбивает файл на блоки по пустым строкам и для каждого блока выводит одно число (количество строк внутри), а второй собирает и выводит статистику. На моих файлах получился такой результат:
Scanning: 5136 files, 1 items (in 2 specified) Min: 2 | Max: 1440 | Avg: 6,0076
Дополнительный анализ показал, что аж 68% всех групп состояли из двух файлов.
Наконец, для автоматического удаления дубликатов использовалась команда fdupes:
$ fdupes -rdN /путь/к/папке
-r — рекурсивно (искать во всех подпапках)-d — удалять-N — noprompt (не спрашивать подтверждение для каждого файла, оставить первый найденный).
После автоматического удаления дубликатов осталось 1840 файлов. Уже полегче!
3. findimagedupes
Для визуального сравнения и ручного удаления оставшихся дубликатов среди похожих изображений (например, один и тот же скриншот, но пересохранённый с разным сжатием или сдвинутый на пару пикселей) я использовал утилиту findimagedupes и просмотрщик pix:
$ findimagedupes -t 98% -p `which pix` ./screenshots_extract
Эта процедура позволила убить ещё 186 файлов.
4. optipng
Оптимизировать объём файлов изображений без потери качества помогает команда:
$ find . -name "*.png" -exec optipng -o2 {} \;
Параллельная обработка на нескольких ядрах даже при значительно большем коэффициенте сжатия ускоряет процесс в 8–10 раз:
$ time find . -name "*.png" -print0 | \ xargs -0 -P 8 -I {} optipng -o5 -strip all "{}"
Какое шаманство здесь происходит?
find . -name "*.png" -print0: ищет все PNG. Опция -print0 нужна, чтобы корректно обработать имена файлов с пробелами (в тандеме с -0 у xargs).
-P 8: запускает 8 параллельных процессов (по одному на каждый поток).
-o5 -strip all: оптимальное сжатие и полное удаление метаданных (цветовые профили монитора и прочее).
time: выводит общее время работы всей цепочки:
real: сколько реально времени вы просидели за чашкой чая, пока шёл процесс (именно этот показатель нам и нужен);
user: суммарное время работы всех ядер (оно будет в несколько раз больше real, это нормально);
sys: время, затраченное процессором в режиме ядра.
Можно ли ещё сильнее ужать файлы? В скриншотах программы используется много цветов из-за сглаживания шрифтов. Если после optipng объём будет всё ещё велик, можно добавить флаг -nc (no color reduction) или преобразовать изображения в индексированные цвета (если это не портит вид).
Скрипт c разделением процесса обработки на этапы и выводом результатов
#!/usr/bin/env bash # Конфигурация LC_ALL=C OPTDIR=optim_imgs TIMEFORMAT="%R" # Подсчёт файлов COUNT=$(find . -maxdepth 1 -name "*.png" | wc -l) if [ "$COUNT" -eq 0 ]; then echo "PNG файлы не найдены в текущей папке." exit 1 fi # Проверка и удаление старой папки if [ -d "$OPTDIR" ]; then echo -e "\nУдалена старая папка $OPTDIR/" rm -rf "$OPTDIR" fi mkdir -p "$OPTDIR" # Основной процесс с замером времени echo "Оптимизация $COUNT файлов на 8 потоках..." ELAPSED=$( { time find . -maxdepth 1 -name "*.png" -print0 | \ xargs -0 -P 8 -I {} optipng -o5 -quiet -strip all "{}" \ -out "$OPTDIR/{}"; } 2>&1 ) # Расчёт времени MIN=$(awk "BEGIN {print int($ELAPSED / 60)}") SEC=$(awk "BEGIN {printf \"%.2f\", $ELAPSED % 60}") AVG=$(awk "BEGIN {printf \"%.2f\", $ELAPSED / $COUNT}") # Расчёт размеров OLD=$(du -Ssb . | cut -f1) NEW=$(du -sb "$OPTDIR/" | cut -f1) DIFF=$((OLD - NEW)) PERCENT=$(awk "BEGIN {printf \"%.2f\", ($DIFF/$OLD)*100}") # Вывод протокола работы echo -e "\n--- ПРОТОКОЛ ОПТИМИЗАЦИИ ---" echo "Обработано файлов: $COUNT шт." echo "Общее время: $MIN мин. $SEC сек." echo "Среднее на файл: $AVG сек." echo "----------------------------" echo "Исходный объём: $(numfmt --to=iec $OLD)" echo "Новый объём: $(numfmt --to=iec $NEW)" echo "Экономия: $(numfmt --to=iec $DIFF) ($PERCENT%)"
Файлы после обработки складываются в папку optim_imgs в текущей папке. При перезапуске скрипта она удаляется и пересоздаётся автоматом. После отработки скрипта получился такой вывод:
Удалена старая папка optim_imgs/
Оптимизация 1654 файлов на 8 потоках…
— ПРОТОКОЛ ОПТИМИЗАЦИИ —
Обработано файлов: 1654 шт.
Общее время: 27 мин. 35,69 сек.
Среднее на файл: 1,00 сек.
----------------------------
Исходный объём: 408M
Новый объём: 388M
Экономия: 21M (5,00%)
В итоге скриншотов стало втрое меньше (1654 вместо 5136), а их объём уменьшился на 200 МБ. Основную экономию дала очистка дубликатов, а optipng — ещё 5%. Файлы получили сквозную нумерацию, готовую к импорту в AsciiDoc.
Грабли с кириллицей
Когда структура проекта устаканилась и был готов первый adoc-файл, пришло время компиляции в PDF. И тут я столкнулся с суровой реальностью open-source инструментов, изначально заточенных под английский язык.
Попытка собрать документ дефолтным вызовом asciidoctor-pdf mis-doc.adoc выдало гирлянду сообщений: WARNING: The following text could not be fully converted to the Windows-1252 character set, а в pdf-файле — мешанину крокозябр вместо букв. Родные шрифты движка не имели понятия о существовании кириллицы.
Решением было создать кастомную тему оформления в формате YAML и подключить TTF-шрифты с кириллицей (я взял проверенные семейства Liberation). Для лучшей переносимости проекта я положил шрифты в отдельную локальную папку.
Фрагмент конфигурационного файла theme.yml со шрифтами:
font: catalog: merge: false LiberationSans: normal: LiberationSans-Regular.ttf bold: LiberationSans-Bold.ttf italic: LiberationSans-Italic.ttf bold_italic: LiberationSans-BoldItalic.ttf LiberationMono: normal: LiberationMono-Regular.ttf bold: LiberationMono-Bold.ttf italic: LiberationMono-Italic.ttf bold_italic: LiberationMono-BoldItalic.ttf M+ 1mn: normal: mplus1mn-regular-subset.ttf bold: mplus1mn-bold-subset.ttf italic: mplus1mn-italic-subset.ttf bold_italic: mplus1mn-bold_italic-subset.ttf fallbacks: - M+ 1mn main: family: LiberationSans
Asciidoctor-pdf при наследовании темы (extends: default) рассчитывает найти стандартные шрифты для кода и кнопок. Если вы отключаете слияние каталогов (merge: false), но не описываете эти шрифты в своём файле, движок впадает в панику. Поэтому пришлось положить в папочку и шрифт M+ 1mn.
Очень рекомендую проверять реальные имена шрифтов с помощью утилиты fc-query. Найти установленные шрифты с кириллицей можно так:
fc‑list :lang=ru family file
Ну а если что-то не работает — читайте предупреждения в консоли (asciidoctor-pdf выводит их в stderr), а для вывода деталей об ошибках добавьте ключ --trace.
Итак, сборка завелась, но тут же вылезла вторая проблема: конфликт абзацных отступов. Чтобы книга выглядела солидно, я включил в тему отступ для первой строки абзаца:
prose: first-line-text-indent: 18 # НЕДОКУМЕНТИРОВАННЫЙ ПАРАМЕТР! margin-bottom: 8 # уменьшаем стандартный отступ после абзаца
Однако теперь этот отступ применяется вообще ко всему, включая текст внутри ячеек таблиц и элементы списков. Таблицы стали выглядеть странно. Пришлось выкручиваться на уровне разметки: либо использовать тип форматирования ячеек d| (literal/raw data), который полностью сбрасывает стилизацию абзаца, либо оформлять вводные фразы перед списками как заголовки списков, защищая их от отступов. Что ж, красота требует жертв.
Борьба с галлюцинациями ИИ
Один из поучительных уроков этого проекта — не верить нейросетям на слово, когда дело касается специфических YAML-конфигураций. В большинстве случаев он чертовски помогал, но было и так, что бесцеремонно врал: например, предлагал ключи с подчеркиваниями вместо дефисов (font_family вместо font-family, page_size вместо page-size и т.д.). На сайте документации Asciidoctor я нашёл лишь одно исключение из общей схемы: bold_italic.
Мораль здесь такова: ИИ — отличный подмастерье для генерации идей и «рыбы» вашей документации, но синтаксис специфических предметных DSL-языков за ним нужно перепроверять буквально с лупой.
Пайплайн сборки в GitLab CI/CD
Облачным репозиторием был выбран GitLab, поскольку там приватные проекты можно размещать бесплатно. Я настроил лёгкий пайплайн .gitlab-ci.yml, использующий официальный Docker-образ asciidoctor/docker-asciidoctor:latest. При каждом git push система автоматически генерирует свежий PDF-документ и публикует статический HTML-сайт в GitLab Pages:
image: asciidoctor/docker-asciidoctor:latest stages: - build - deploy # Сборка PDF build_pdf: stage: build script: - asciidoctor-pdf -r asciidoctor-diagram mis-doc.adoc -o mis-user-guide.pdf artifacts: name: "docs-pdf-${CI_COMMIT_REF_SLUG}" paths: - mis-user-guide.pdf expire_in: 1 week only: - main # сборка выполняется при обновлении ветки main # Публикация на GitLab Pages pages: stage: deploy script: - mkdir public - asciidoctor -r asciidoctor-diagram mis-doc.adoc -o public/index.html # скопировать PDF в ту же папку, чтобы его можно было скачать по ссылке - cp mis-user-guide.pdf public/ || true # скопировать папку со скриншотами в public, чтобы сайт их увидел - cp -r images public/ || true artifacts: paths: - public only: - main
Итоги проекта
Этот проект показал, что MS Word плохо подходит для больших руководств в плане гибкости и сопровождения. Рефакторинг текста и перевод документации на технологию docs-as-code коренным образом изменил ситуацию:
Вместо описания кнопок интерфейса — описание логики сценариев работы врача.
Текст после рефакторинга сократился в 3,5 раза за счёт смысловой оптимизации.
Скриншотов стало втрое меньше, а их общий объём сократился более чем на треть.
Вся документация теперь хранится в обычном Git-репозитории, легко версионируется, поддаётся сквозному поиску через
grepи готова к быстрому внесению изменений под обновляющиеся требования eHealth.Интерактивное руководство пользователя (HTML) и его печатная PDF-версия собираются в CI/CD за несколько минут.
Asciidoctor в связке с простыми консольными утилитами Linux и GitLab CI/CD показал себя как мощный инструмент. Использование диаграмм типа PlantUML в ряде случаев позволит заменить текстовое описание процедур наглядными схемами.
Одностраничный HTML с оглавлением выглядит очень прилично, а с генератором сайтов Antora пользоваться руководством станет ещё удобнее.