У нас во «Фланте» инженеры работают еще и с технической документацией. При этом многие термины, например, относящиеся к Kubernetes, пишут по-разному: кто-то использует сленг, кто-то — латиницу, а кто-то — кириллицу. Чтобы навести порядок в терминологии, а заодно и исправлять опечатки, мы решили создать спелл-чекера и автоматизировать проверку орфографии.

В этой статье мы расскажем, как проходила настройка спелл-чекера для сайта werf, сгенерированного внутри Docker-контейнера, и что получилось в итоге. Пройдёмся по всем этапам создания этого инструмента, а также разберём проблемы, которые могут усложнить настройку автоматической проверки орфографии.

Содержание:

С чего всё начиналось

Часто в наших текстах встречались опечатки, которые на стадии ревью мы не всегда замечали. А иногда некоторые термины, понятные и привычные для инженера, оставались как есть, хотя по идее их нужно приводить к единому стилю и написанию. Например, написание «mysql» вместо «MySQL» или использование сленговых терминов наподобие «мускуль» и так далее. 

Поэтому мы решили, что нам нужен механизм, который будет автоматически проверять документацию сразу после её добавления на сайт. Конечно, мы могли звать на каждую правку человека, который знает, как правильно пишется тот или иной термин. Но даже такой человек может допустить ошибку или просто просмотреть то или иное слово в силу банальной усталости и замыленного к вечеру взгляда, поэтому автоматическая проверка терминов и орфографии до прихода корректора казалась нужной и полезной вещью.

Выбор реализации

Сначала мы попытались поискать уже готовые способы проверить тексты: изучали страницы поисковой выдачи в Google, Yandex и даже DuckDuckGo. Мы нашли несколько проектов, которые выглядели подходящими под наши задачи. При этом некоторые моменты в них не нравились. Например, в одном из проектов формат словаря был в виде списка неправильных написаний слов с вариантами замены:

abanond->abandon
abanonded->abandoned
abanonding->abandoning
abanondment->abandonment
abanonds->abandons

Это казалось неудобным, так как подразумевалось, что нужно собрать все варианты неправильного написания слов. Возможно, авторы собрали уже какой-то словарь, но есть вероятность, что некоторые неправильные написания слов могли упустить. Ещё такой словарь получился бы огромным, так как количество неправильных вариантов написания того или иного термина растёт пропорционально количеству букв в этом слове и может достигать десятков, а то и сотен значений. По этой и подобным причинам мы не выбрали найденные инструменты.

Мы стали думать, какое решение для нас будет идеальным — использование полноценного сервиса, который умеет не только работать с орфографией, но и проверять пунктуацию, а также будет предлагать варианты замены слов и предложений. Такой вариант выглядит жизнеспособным, но для него потребуется написать дополнительные инструменты, которые будут искать текст в файлах, отправлять запросы на сервер, обрабатывать ответы и показывать результат. Выглядит, как оверкилл: придётся приложить слишком много усилий, что для этой задачи кажется избыточным.

Здесь мы поняли, что хотим найти утилиту, у которой будет словарь правильных написаний, при этом не нужно будет разрабатывать дополнительные обвязки и запуски бэкенда. Также она должна выдавать понятный для человека результат без обработки дополнительными инструментами.

И тут мы вспомнили про уже не новую утилиту hunspell, представляющую собой один бинарник и используемую в некоторых известных проектах. Например, за проверку орфографии в LibreOffice отвечает именно она. В итоге мы остановились на ней.

Настройка проверки орфографии

Исходные данные

Некоторые сайты наших проектов собраны на Jekyll — генераторе статических сайтов. Проект Jekyll — это набор файлов, которые описывают:

  • шаблоны страниц (layouts) и размещение на них контента;

  • сами страницы, содержание которых будет генерироваться в HTML и вставляться в нужные места шаблонов;

  • «вставки», которые можно импортировать в нужные места страниц для удобства и повторяемости блоков.

В качестве языка разметки контента страниц обычно используется Markdown (MD), но часто он смешивается с HTML — на странице могут быть как вставки MD в HTML-разметку, так и, наоборот, блоки HTML-кода в полноценном MD-документе. А для шаблонизации используются конструкции на языке Liquid с поддержкой специальных параметров, таких как определение страницы, сайта и так далее.

Тестирование hunspell

Хотелось верить, что получится просто взять hunspell, скормить ему файлы как есть и получить вразумительный ответ. Мы решили протестировать это — взяли несколько файлов из содержимого репозитория с сайтом и скормили утилите в чистом виде. 

Рассмотрим на примере файла guides.md, который содержит главную страницу «Самоучителя по Kubernetes». Сам файл выглядит так:

---
title: Самоучитель по Kubernetes
permalink: /guides.html
layout: plain
breadcrumbs: none
---


{% asset overview.css %}
{% asset guides.css %}


<h1 class="docs__title">Самоучитель по Kubernetes</h1>
<p>Самоучитель ориентирован на разработчиков, которые хотят научиться работать с Kubernetes и доставлять в него код своих приложений. Также эти материалы будут полезны DevOps-инженерам, которые хотят эффективнее решать задачи по CI/CD в K8s и познакомиться с werf на практике.</p>


<p>Самоучитель — это и пошаговые практические инструкции, и необходимая теория. Он разбит на несколько разделов: от базового уровня до более продвинутых фич. В руководствах учтена специфика языков/фреймворков и приложены примеры исходного кода приложения и инфраструктуры (IaC).</p>


<p>Выберите наиболее близкую вам технологию:</p>


{% include common/guides-landing-tiles.html %}

Здесь содержатся Jekyll-специфичная разметка заголовка, HTML-коды блоков текста и Liquid-шаблоны для вставки ассетов и части страницы из «вставок» (include).

Мы сохранили файл в отдельный каталог для тестов и «натравили» на него hunspell c русским словарём, так как текст на русском:

$ cat guides.md | hunspell -d ru_RU -l
title
Kubernetes
permalink
layout
plain
breadcrumbs
none
asset
overview
css
asset
guides
css
h
class
docs
title
Kubernetes
p
Kubernetes
DevOps
CI
CD
K
s
werf
p
фич
фреймворков
IaC
p
include
common
guides
landing
tiles
html

На первый взгляд показалось, что всё работает как надо: найдены неизвестные слова вроде «фреймворков», а также все англоязычное. Чтение манов показало, что можно указать утилите сразу несколько словарей, по которым он будет проверять текст, поэтому попробуем указать ему и русский, и английский словари:

$ cat guides.md | hunspell -d ru_RU,en_US -l
error - iconv: ISO8859-1 -> UTF-8
error - iconv: ISO8859-1 -> UTF-8
Kubernetes
permalink
...

И здесь начались странности в виде ошибки конвертации кодировки в UTF-8. Это было внезапно, так как файл изначально сделан и сохранён именно в ней. Попытки перегнать кодировки с помощью enconv и iconv ни к чему не приводили — файл был в правильной кодировке, но hunspell упорно считал, что ему нужно перекодировать его из ISO-8859-1.

Мы потратили много часов, чтобы найти решение этой проблемы. В итоге на одном из англоязычных форумов десятилетней давности мы случайно наткнулись на подобную ошибку. В комментариях к теме один из участников предположил, что надо посмотреть в словарь, которым пользуется hunspell, так как в нём есть указание кодировки. А если ещё точнее, то даже не в словарь, а в файл аффиксов (приставок, суффиксов, постфиксов, флексий), который прилагается к словарю.

Дальше пришлось усердно погружаться в правила составления словарей для hunspell. Пока опустим их до следующей части статьи, где мы будем собирать словарь с нашими DevOps-терминами, но если вкратце — суть проблемы заключалась в том, что рядом со словарём идет файл *.aff, и в самом его начале действительно указана кодировка ISO-8859-1.

В macOS словари hunspell лежат в каталоге /Users/user/Library/Spelling (стандартный путь для ОС на базе Linux – /usr/local/bin/hunspell, и он же указан в man-страничке утилиты для обеих ОС). Вот его содержимое:

$ ls -l
total 8328
-rw-r--r--  1 zhbert  staff      209 17 май  2023 dynamic-counts.dat
-rw-r--r--@ 1 zhbert  staff    11352 28 авг  2007 en_US.aff
-rw-r--r--@ 1 zhbert  staff   696228 29 авг  2007 en_US.dic
-rw-r--r--@ 1 zhbert  staff    71236 17 май  2023 ru_RU.aff
-rw-r--r--@ 1 zhbert  staff  3473191 17 май  2023 ru_RU.dic

Словари имеют такое же название, как и указываемый при вызове команды ключ, — en_US и ru_RU. Файл аффиксов для английского словаря начинается с установки кодировки в ту самую ISO-8859-1:

$ cat en_US.aff
SET ISO8859-1
KEY qwertyuiop|asdfghjkl|zxcvbnm
TRY esianrtolcdugmphbyfvkwzESIANRTOLCDUGMPHBYFVKWZ'-
NOSUGGEST !
...

Если после установки утилиты в указанном каталоге словарей у вас нет, их можно скачать, например, из репозитория LibreOffice. В нём представлены словари для всех поддерживаемых языков. Для установки словаря достаточно просто положить его в указанный каталог. 

Причина странного поведения утилиты с заведомо верной кодировкой оказалась в одной строке. Из-за этого перед тем, как начинать проверку, hunspell пытался перекодировать строки.

Очевидное решение — указать в файле нужную кодировку, то есть UTF-8:

sed -i 's/SET ISO8859-1/SET UTF-8/' ./en_US.aff

После этого проблемы с кодировкой исчезли:

$ cat guides.md | hunspell -d ru_RU,en_US -l
Kubernetes
permalink
css
css
Kubernetes
Kubernetes
DevOps
werf
фич
фреймворков
IaC
html

Всё выглядело логичным — найдены только те слова, которые не состоят ни в одном из словарей. Но изначальный файл был довольно простой и не содержал сложного форматирования. Поэтому тесты были прогнаны ещё по нескольким файлам:

$ cat 030_resource_management.md.liquid | hunspell -d ru_RU,en_US -l
priorityClassName
ы
иться
ы
ов
PriorityClass
...
targetRef
apiVersion
apps
app
...

Здесь стало очевидно, что в перечень найденных ошибок попадает также и содержимое блоков кода, которые в MD обрамляются апострофами, при этом hunspell их определяет как осмысленный текст. Значит, нужно очищать страницы от таких блоков перед тем, как подавать их на проверку.

Также в процессе вспомнилось, что некоторые части сайта лежат в виде данных в каталоге _data и представляют собой YAML-файлы с перечнем ссылок или наименований. Например, так сделан перечень публикаций о werf — ссылки на них лежат в файле _data/ru/publications.yml:

i18n:
  default_btn_title: "Читать далее"
  habr_btn_title: "Прочитать на Хабре"
  medium_btn_title: "Прочитать на Medium"
  blog_btn_title: "Прочитать в блоге Флант"
  custom_url_btn_title_prefix: "Прочитать на "
  youtube_btn_title: "Посмотреть на YouTube"
  tproger_btn_title: "Прочитать на Tproger"


  - title: "Организация стенда локальной разработки для самых маленьких с автоматической пересборкой приложения (фронтенд + бэкенд)"
    habr_url: "https://habr.com/ru/companies/flant/articles/771678/"
    img: "/assets/images/publications/ru_031123.png"
    created: 2023-11-03
    comment: |
      <p>Вносить изменения в код приложения и тут же автоматически получать задеплоенные изменения, чтобы быстро тестировать его, — мечта разработчика. В этой статье мы посмотрим, как реализовать такой подход для небольшого приложения с фронтендом и бэкендом: организуем два варианта локального стенда на базе minikube или Docker с автоматическим развертыванием всех изменений или только закоммиченых в Git.<br><br>Бэкенд приложения напишем на Go, а фронтенд — на Vue.js. Все это позволит быстро запускать проект для тестирования прямо во время разработки, что, несомненно, повысит удобство работы с приложением.</p>
  - title: "Установка Deckhouse в kind: пробуем K8s-платформу на ноутбуке — без серверов и облаков"
    habr_url: "https://habr.com/ru/companies/flant/articles/767872/"
    img: "/assets/images/publications/ru_121023.png"
    created: 2023-10-12
    comment: |
      <p>Чтобы попробовать Kubernetes-платформу Deckhouse в деле, придется найти мощный сервер, пространство у облачного провайдера или несколько машин и прокси-сервер. Но что делать, если хочется просто потестировать Deckhouse, а технических возможностей для этого нет? Конечно же, установить Deckhouse в kind — ведь для этой задачи сгодится даже ноутбук, а на выходе у нас будет рабочая инсталляция Kubernetes-платформы, правда, с некоторыми ограничениями.</p>
...

Если отправить этот файл на проверку напрямую, в результате мы получим много лишней информации. Система будет считывать все элементы файла, в том числе default_btn_title, habr_btn_title и так далее. Чтобы такие элементы файла не отображались как ошибка, их нужно внести в словарь, но это дополнительная работа, которая в итоге окажется лишней. Поэтому после всех тестов пришла идея проверять уже собранный сайт, где на входе будет только HTML-контент, работать с которым гораздо проще, чем подстраиваться под каждый тип файла, представленный в репозитории проекта. Весь процесс условно разделился на несколько этапов: 

  1. Сборка сайта и запуск проверки.

  2. Тюнинг и доработка.

  3. Сборка кастомного словаря и дополнительный инструментарий.

  4. Настройка GitHub Actions.

Этап №1: сборка сайта и запуск проверки

Сайт в собранном и запущенном виде представляет собой набор из нескольких Docker-контейнеров с русской и английской версиями контента, а также с вспомогательными контейнерами для определенных нужд. Сборка может запускаться в CI/CD GitHub'а, откуда потом разворачивается в продакшен или тестовое окружение локально в виде docker-compose.

Для задач проверки орфографии мы решили использовать идентичную продовой сборку контейнеров с сайтом. Это, во-первых, минимизирует любые возможные расхождения между продовой и локальной версиями, во-вторых, позволит ускорить процесс в пайплайне CI/CD, так как достаточно будет собрать контейнер один раз, а дальше его можно использовать как источник файлов сайта и сущность в кластере.

Принцип такой:

Так как мы планировали использовать это в Actions на GitHub, а основной задачей являлась валидация контента, на последнем шаге возврата результата должны соблюдаться два требования:

  1. В случае удовлетворительного завершения проверки ничего не отображается, код возврата при этом — «0». Это позволит отобразить успешность прохождения в виде зеленой галочки в GitHub Actions.

  2. В случае же нахождения ошибок должны отобразиться список файлов, в которых найдены опечатки, и сами опечатки, а также вернуться код возврата «1». Так GitHub покажет в задаче валидации красный крест, сообщающий разработчикам, что один из тестов не прошёл.

Далее мы попробовали сделать первую итерацию: забрать сгенерированный контент и прогнать его через hunspell.

Подготовка контейнера для спелл-чекера

Для сборки контейнеров у нас традиционно используется утилита werf. Она отвечает за сборку контейнеров, публикацию их в registry и развёртывание затем в кластере Kubernetes.

Собираемые контейнеры описаны в файле конфигурации werf.yaml в корне проекта. Контейнеры с содержимым сайта описаны в этой секции файла:

{{- range $lang := list "en" "ru" }}
image: doc_{{ $lang }}
fromImage: jekyll_base
fromCacheVersion: {{ $.CacheVersion }}
import:
  - image: common_artifacts
    add: /artifacts
    to: /artifacts
    before: setup
  - image: configuration_artifacts
    add: /configurator
    includePaths:
      - static
      - generated
    to: /tmp
    after: install
git:
  - add: /
    to: /app
    owner: jekyll
    group: jekyll
    includePaths:
      - _data
      - _includes
      - _layouts
      - _plugins
      - assets
      - examples
      - pages_{{ $lang }}
      - ssi
      - _config.yml
      - _config_dev.yml
      - _config_{{ $lang }}.yml
      - "*.xml"
      - "*.sh"
      - "*.asc"
      - "*.png"
      - "*.svg"
      - favicon.ico
      - robots.txt
      - site.webmanifest
    stageDependencies:
      beforeSetup: ["**/*"]
shell:
  beforeSetup:
    - cp -R /tmp/static/* /tmp/generated/* /app
  setup:
    - cd /app
    - set -u
    - mkdir -m 0777 -p /app/_site
    - cp -f /artifacts/*.json /app/_data/_common/
    - cp -f /artifacts/pages_{{ $lang }}/* /app/pages_{{ $lang }}/
    - export JEKYLL_ENV="{{ $.Env }}"
    {{- if eq $.Env "development" }}
    - bundle exec jekyll build -tV --config _config.yml,_config_dev.yml,_config_{{ $lang }}.yml --destination _site/
    {{- else }}
    - bundle exec jekyll build --config _config.yml,_config_{{ $lang }}.yml --destination _site/
    {{- end }}
---
{{ end -}}

Здесь использована шаблонизация в Stapel — альтернативном синтаксисе описания сборочных конструкций, встроенном в werf. В этом блоке кода собираются два контейнера — doc_ru и doc_en — с одинаковыми правилами сборки за исключением языка контента, который отражается на именах каталогов и файлов конфигурации Jekyll.

Генерация контента выполняется на стадии setup, в результате чего получается собранный контент в директории _site. Поэтому на первом шаге мы добавили ещё один контейнер с названием spell-checker и скопировали в него содержимое из других контейнеров:

image: spell-checker
from: ubuntu:20.04
fromCacheVersion: {{ $.CacheVersion }}
import:
  - image: doc_ru
    add: /app/_site
    to: /spelling/ru
    after: install
  - image: doc_en
    add: /app/_site
    to: /spelling/en
    after: install

Здесь содержимое каталогов /app/_site контейнеров doc_ru и doc_en копируется в соответствующие подкаталоги каталога /spelling нового контейнера.

Следующим шагом мы скачали английский и русский словари из репозитория LibreOffice и сложили их в каталог ./scripts/docs/spelling/dictionaries (выбор каталога обусловлен тем, что в нём уже лежал скрипт для запуска линтера ссылок), а затем добавили копирование словарей в контейнер:

git:
  - add: /scripts/docs/spelling/dictionaries/
    to: /temp/dictionaries
    stageDependencies:
      setup: '**/*'

Итак, теперь нужно установить необходимые утилиты. Для этого в секции install раздела shell файла werf.yaml мы прописали установку hunspell:

shell:
  install:
    - apt -y update && apt -y install hunspell

Контейнер готов к сборке. Теперь нужно настроить запуск.

Настройка запуска проверки внутри контейнера

Чтобы осуществить проверку, нужно перебрать все файлы *.html в директории сайта и отдать их на вход hunspell. Для этого нужно запустить контейнер и выполнить в нём скрипт, который и займётся перебором и проверкой.

Для этого мы подготовили специальный bash-скрипт scripts/docs/spelling/spell_check.sh. Так как языков у нас два, на вход скрипту нужно обязательно подать ru или en, чтобы он выбрал, какие из словарей использовать для проверки:

#!/bin/bash


set -e


arg_site_lang="${1:?ERROR: Site language \'en\' or \'ru\' should be specified as the first argument.}"


script=$(cat <<EOF
cd /spelling/$arg_site_lang && \
  ./container_spell_check.sh $arg_site_lang
EOF
)


werf run spell-checker --env='test' --dev --docker-options="--entrypoint=bash" -- -c "$script"

В скрипте мы добавили обязательный первый параметр командной строки arg_site_lang. Если его не указать, об этом будет показана ошибка. Также командой werf run запустили контейнер с линтером и передали в него bash-скрипт, в котором описаны переход в каталог с содержимым сайта на нужном языке и запуск ещё одного внутреннего скрипта, где и будет происходить перебор файлов с запуском hunspell.

Сначала мы попробовали передать в контейнер сразу весь скрипт с перебором, но это выглядело не слишком эстетично, так как добавлять в одну переменную скрипт из сотни строк и потом передавать его в другой скрипт — это выглядит как лишнее нагромождение. Через некоторое время в этом будет сложно разобраться. Поэтому в итоге решили запустить внутренний скрипт. Для этого сам скрипт мы создали в каталоге scripts/docs/spelling/internal, а его перенос в контейнер описали в werf.yaml:

git:
...
  - add: /scripts/docs/spelling/internal/
    to: /temp/internal
    stageDependencies:
      setup: '**/*'

Также на стадии setup содержимое этого каталога перенесли в каталог /spelling для всех языков. Здесь же копируются словари hunspell в стандартную для него директорию /usr/share/hunspell/, заменяя всё, что там могло быть после установки утилиты из репозитория:

 setup:
    - rm /usr/share/hunspell/*
    - cp /temp/dictionaries/* /usr/share/hunspell/
    - cp /temp/internal/* /spelling/en
    - cp /temp/internal/* /spelling/ru

В самом внутреннем скрипте описаны цикл перебора файлов и запуск проверки:

#!/bin/bash


set -e


arg_site_lang="${1:?ERROR: Site language \'en\' or \'ru\' should be specified as the first argument.}"


if [[ "$arg_site_lang" == "en" ]]; then
  indicator="EN"
  language="en_US"
elif [[ "$arg_site_lang" == "ru" ]]; then
  indicator="RU"
  language="ru_RU,en_US"
fi


echo "Checking $arg_site_lang docs..."


for file in `find ./ -type f -name "*.html"`
do
    echo "$indicator: checking $file..."
    cat $file | hunspell -d $language -l
done

Чтобы запустить все операции с сайтом, такие как линтеры или разворачивание локальной версии, мы использовали Task — утилиту, аналогичную по функционалу привычному make. При этом у неё есть свой синтаксис описания операций — это файл Taskfile.yaml в корне проекта. Поэтому мы добавили в него несколько команд для запуска проверки конкретной версии сайта (ru или en) и совместной проверки обоих версий:

site:run-spell-check:
    desc: 'Run spell check for all pages.'
    deps:
      - site:run-spell-check:ru
      - site:run-spell-check:en


  site:run-spell-check:ru:
    desc: 'Run spell check for ru pages.'
    cmds:
      - |
        ./scripts/docs/spelling/spell_check.sh ru


  site:run-spell-check:en:
    desc: 'Run spell check for en pages.'
    cmds:
      - |
        ./scripts/docs/spelling/spell_check.sh en

Получилась такая схема запуска теста:

Результаты первого теста

Мы убедились, что всё настроено как надо, и запустили проверку орфографии с помощью следующей команды в каталоге сайта:

task site:run-spell-check

Итог порадовал: спелл-чекер работает, проверяет файлы и показывает результат. Но этот результат получился огромным — в нём оказалось 529 тысяч строк. Поэтому для нормальной последующей работы с ним лучше всего не смотреть прямо в терминал, а перенаправить «выхлоп» в файл: task site:run-spell-check >> log — и работать уже с ним. 

Для примера результата приведём некоторые его части:

EN: checking ./about/backward_compatibility.html...
...
lang
charset
href
stylesheet
css
li
...
apiVersion
...

Плюс также во многих местах снова вылезли ошибки кодировки:

error - iconv: ISO8859-1 -> UTF-8

Поэтому было принято решение немного дотюнить весь процесс.

Этап №2: тюнинг и доработка

После изучения логов мы пришли к следующим выводам:

  • Нужно очищать входной документ от HTML-тегов, так как некоторые из них всё-таки попадают в результат как ошибки.

  • Нужно удалить из проверяемых документов блоки кода, содержащие множество «слов», которые не бьются ни по каким словарям и которые не нужно проверять на орфографию.

  • Нужно решить вопрос с кодировками. Здесь всё было понятно, исходя из опыта тестирования на рабочей машине без контейнеров.

  • Нужно собрать словарь кастомных DevOps-терминов, куда стоит добавить те слова, которых нет в дефолтных словарях русского и английского языков.

План работ определён, и мы приступили к реализации.

Очистка входного документа от HTML-тегов

Чтобы очистить HTML-страницы от тегов, мы решили использовать утилиту html2text, которая есть по умолчанию в репозиториях дистрибутива. Поэтому мы добавили её установку в описание контейнера в werf.yaml и пропустили текст файла через неё перед передачей его hunspell.

Удаление блоков кода

Далее нужно было удалить блоки кода перед тем, как отправлять файл на проверку.

Блоки кода на сайте представлены в виде двух вариантов: теги <code></code> и обычные div'ы с классом snippetcut. Самым логичным решением показалось использовать библиотеку Beautiful Soup для Python и написать небольшой скрипт, который будет принимать на вход файл и вырезать из него лишнее.

Для этого в описании контейнера добавили установку Python и pip, а в каталог с внутренними инструментами scripts/docs/spelling/internal положили скрипт clear_html_from_code.py:

#!/usr/bin/python3
# -*- coding: utf-8 -*-


from bs4 import BeautifulSoup
import sys




if len (sys.argv) > 1:
    html = open(sys.argv[1]).read()
    root = BeautifulSoup(html, 'html.parser')
    if root.find('code'):
        for code in root.select('code'):
            code.decompose()
    snippetcuts = root.find_all("div", {"class": "snippetcut"})
    for snippetcut in snippetcuts:
        snippetcut.decompose()
    print(root)

Игнорирование заданных частей страниц

Теперь после проверки вывод сократился практически на треть, но в нём все равно попадались странные наборы букв, непохожие на осмысленные слова. В результате дебага мы выяснили, что некоторые части страницы всё равно проскальзывают через очистку и определяются как нечто странное. Например, в некоторых местах изображение вставлялось на страницу средствами Jekyll, то есть конструкцией на языке liquid:

  <div class="landing__container">
    <a href="/" class="landing__header-title" data-proofer-ignore>
      {% asset werf-logo.svg alt="werf" width="167" height="70" %}
    </a>
    <a href="{{ site.site_urls['ru'] }}" class="landing__button" data-proofer-ignore>
      {% asset arrow.svg %}
      <span>ru.werf.io</span>
    </a>
  </div>

При генерации в тексте ссылки получалась ссылка на рисунок примерно такого вида:

  <a href="/" class="landing__header-title" data-proofer-ignore="">
    <img alt="werf" width="167" height="70" src="/assets/werf-logo-60228eb11e808ea4283d0863b4c8d7bc97dc7310f6c643f1c4df83b1b7f0ce57f627b7d577ba7f599dbac8e031544d795bd7f2b9862d3f4259aabfb0073f3a51.svg" integrity="sha512-YCKOsR6AjqQoPQhjtMjXvJfccxD2xkPxxN+Dsbfwzlf2J7fVd7p/WZ26yOAxVE15W9fyuYYtP0JZqr+wBz86UQ==" crossorigin="anonymous" />
  </a>

И затем это попадало в линтер, который разбивал её на составляющие при переводе из HTML в plain text и на выходе давал набор нечитаемых символов.

Для решения проблемы мы ввели инструмент игнорирования определённых блоков текста. Целевые фрагменты страницы обрамляли комментариями вида <!-- spell-check-ignore --> <!-- end-spell-check-ignore -->:

  <!-- spell-check-ignore -->
  <div class="landing__container">
      <a href="/" class="landing__header-title" data-proofer-ignore>
        {% asset werf-logo.svg alt="werf" width="167" height="70" %}
      </a>
      <a href="{{ site.site_urls['ru'] }}" class="landing__button" data-proofer-ignore>
        {% asset arrow.svg %}
        <span>ru.werf.io</span>
      </a>
  </div>
  <!-- end-spell-check-ignore -->

А в конвейер команд линтера добавили шаг с вырезанием содержимого между этими комментариями: sed '/<!-- spell-check-ignore -->/,/<!-- end-spell-check-ignore -->/d'.

Исправление кодировок

Помимо описанной в начале статьи проблемы с английским словарём внутри контейнера, вылезла ещё одна проблема: что бы мы ни делали, на выходе получалось много нечитабельных строк в неправильной кодировке со ссылками на ISO-8859-1. После нескольких экспериментов выяснилось, что нужно указать глобальную локаль в контейнере, добавить локаль в аффиксы и сконвертить весь словарь в UTF-8

Интересно, что поймать проблему на локальной машине не удавалось. На хосте всё работало как положено, а в контейнере всё шло не так — вместо вменяемого вывода мы получали подобное:

Checking ru docs...
[werf]
    *
    * ДÐ3/4куÐ1/4еÐ1/2тация
    * СаÐ1/4Ð3/4учитель_пÐ3/4_Kubernetes
    * О_прÐ3/4екте
          o Публикации
          o КаÐ1/2алы_Ð3/4бÐ1/2Ð3/4влеÐ1/2ий
          o ОбратÐ1/2ая_сÐ3/4вÐ1/4естиÐ1/4Ð3/4сть
          o ИстÐ3/4рия_изÐ1/4еÐ1/2еÐ1/2ий
    *
✕
ПрисÐ3/4едиÐ1/2яйтесь к аÐ1/2глÐ3/4язычÐ1/2Ð3/4Ð1/4у
сÐ3/4Ð3/4бществу в Slack CNCF
Шаг 1:
ПÐ3/4лучить_приглашеÐ1/2ие_в_Slack_CNCF
Шаг 2:
ВÐ3/4йти_в_каÐ1/2ал_#werf
Мы выбрали Slack Ð3/4т CNCF, т.к. таÐ1/4
зарегистрирÐ3/4ваÐ1/2Ð3/4 саÐ1/4Ð3/4е бÐ3/4льшÐ3/4е кÐ3/4личествÐ3/4
Kubernetes-эÐ1/2тузиастÐ3/4в.

В итоге мы добавили в werf.yaml раздел с переменными окружения, которые отвечают за локаль:

docker:
  ENV:
    LANG: ru_RU.UTF-8
    LANGUAGE: ru_RU:ru
    LC_LANG: ru_RU.UTF-8
    LC_ALL: ru_RU.UTF-8
    LC_MESSAGES: ru_RU.UTF-8
    TZ: Europe/Moscow

А на стадии beforeInstall указали генерацию локалей и выставление правильной таймзоны:

shell:
  beforeInstall:
    - apt -y update && apt -y upgrade
    - apt install -y locales
    - sed -i -e 's/# ru_RU.UTF-8 UTF-8/ru_RU.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
 cvfd21

Указывать данные tzdata пришлось по причине того, что при генерации локали в консоли вылезает вопрос, требующий выбора расположения в ручном режиме. Но сделать это в рамках CI/CD не получится, так как придётся вручную вводить ответ на запрос в командной строке, но ни доступа туда, ни возможности это автоматизировать нет. При указании таймзоны в параметрах необходимость ручной работы отпадает.

Во внутреннем скрипте при этом скопированный в контейнер английский словарь дополнительно конвертируется в UTF-8:

cp /usr/share/hunspell/en_US.aff  /usr/share/hunspell/en_US.aff.orig
cp /usr/share/hunspell/en_US.dic  /usr/share/hunspell/en_US.dic.orig
iconv --from ISO8859-1 /usr/share/hunspell/en_US.aff.orig > /usr/share/hunspell/en_US.aff
iconv --from ISO8859-1 /usr/share/hunspell/en_US.dic.orig > /usr/share/hunspell/en_US.dic
rm /usr/share/hunspell/en_US.aff.orig
rm /usr/share/hunspell/en_US.dic.orig
sed -i 's/SET ISO8859-1/SET UTF-8/' /usr/share/hunspell/en_US.aff

Этап №3: сборка кастомного словаря и дополнительный инструментарий

Итак, всё заработало, но в результаты попадали слова, которых нет в дефолтных словарях английского и русского языков, такие как «werf», «Kubernetes» и различные специфические DevOps-термины. Например:

RU: checking ./viewer.html...
werf
Kubernetes
CNCF
CNCF
werf
CNCF

Все такие термины и наименования было решено сложить в отдельный словарь и добавить его к уже существующим словарям hunspell. Формат словаря *.dic довольно прост — это построчное описание слов, отсортированное в алфавитном порядке. Но в самой первой строке указано общее количество слов в словаре. Ниже приведём для примера его отрывок:

abaser/M
abashed/UY
abashment/MS
abash/SDLG
abate/DSRLG
abated/U
abatement/MS
abater/M
abattoir/SM

Через слеш здесь указаны правила формирования склонений слов, описанные в файле аффиксов словаря.

С самим файлом аффиксов (*.aff) всё гораздо сложнее: это набор определённых правил формирования слов, которым руководствуется hunspell при обработке каждого поступившего ему на вход слова. Например, файл аффиксов для английского словаря содержит следующие правила (отрывок):

SET ISO8859-1
KEY qwertyuiop|asdfghjkl|zxcvbnm
TRY esianrtolcdugmphbyfvkwzESIANRTOLCDUGMPHBYFVKWZ'-
NOSUGGEST !


# ordinal numbers (1st, 2nd, 3th, 11th) and decads (0s, 10s, 1990s)
COMPOUNDMIN 1
# only in compounds: 1th, 2th, 3th
ONLYINCOMPOUND c
# compound rules:
# 1. [0-9]*1[0-9]th (10th, 11th, 12th, 56714th, etc.)
# 2. [0-9]*[02-9](1st|2nd|3rd|[4-9]th) (21st, 22nd, 123rd, 1234th, etc.)
COMPOUNDRULE 2
COMPOUNDRULE n*1t
COMPOUNDRULE n*mp
WORDCHARS 0123456789'


PFX A Y 1
PFX A   0     re         .


PFX I Y 1
PFX I   0     in         .


PFX U Y 1
PFX U   0     un         .


PFX C Y 1
PFX C   0     de          .


PFX E Y 1
PFX E   0     dis         .


PFX F Y 1
PFX F   0     con         .
...

Сразу найти вменяемую документацию к формату словаря не получалось, в этом не помог и Google. Помимо того, что мы не понимали этот файл, в самом словаре нужно было указать ключевые значения, чтобы hunspell знал, как соотнести слово из словаря с правилами аффиксов. Позже мы выяснили, что файл с аффиксами, хотя для работы словаря он обязателен, содержать полноценные правила не должен. Достаточно просто указать кодировку в первой строке.

Поэтому мы решили, что просто соберём все найденные ошибки в словарь, где сразу укажем слова со склонениями, и hunspell это нормально использует. В контексте задачи, где есть некий конечный потолок ошибок (когда проверен весь сайт, а дальше только появляется новое в небольших количествах), такое решение выглядит реализуемым и покрывает все требования.

Генератор словаря

Когда мы разбирали результаты проверки, то обнаружили, что многие слова повторяются в разных файлах. Например, опечатка в каком-то из слов в футере сайта «выстрелит» вообще на всех страницах. Глаза в таком режиме разбегались, поэтому мы решили добавить несколько дополнительных инструментов, с помощью которых можно будет упростить анализ полученных результатов. Нужно было минимизировать число повторений найденных слов, в идеале — получить единый отсортированный список ошибок.

Для этого мы добавили в Taskfile ещё три команды: site:get-words-with-typos:ru, site:get-words-with-typos:en и общую site:get-words-with-typos. В них запускается обычная проверка, но её «выхлоп» прогоняется через конвейер, который вырезает оттуда строки с названиями файла, лог сборки контейнера werf, сортирует слова, удаляет дубли и сохраняет результаты в файлы spell_log_en и spell_log_ru в корне проекта:

  site:get-words-with-typos:
    desc: 'Pulls out a list of all the terms in all pages that were considered a typo'
    deps:
      - site:get-words-with-typos:ru
      - site:get-words-with-typos:en


  site:get-words-with-typos:ru:
    desc: 'Pulls out a list of all the terms in RU pages that were considered a typo'
    cmds:
      - |
        task site:run-spell-check:ru | sed '1,/Checking/ d' | sed '/^$/d' | sed '/Checking/d' | sort -u > spell_log_ru


  site:get-words-with-typos:en:
    desc: 'Pulls out a list of all the terms in EN pages that were considered a typo'
    cmds:
      - |
        task site:run-spell-check:en | sed '1,/Checking/ d' | sed '/^$/d' | sed '/Checking/d' | sort -u > spell_log_en

Результаты оказались приятными — отформатированные списки слов с ошибками, удобные для анализа. Многие из сохранённых туда слов без изменений перешли в новый словарь.

Для генерации нового словаря мы создали промежуточный файл scripts/docs/spelling/wordlist, в который слова добавлялись в любое удобное место. Затем добавили специальные команды в task, чтобы отсортировать этот файл и сгенерировать из него словарь:

  site:generate-special-dictionary:
    desc: 'Generate a dictionary of special terms.'
    cmds:
      - |
        test -f ./scripts/docs/spelling/dictionaries/dev_OPS.dic && rm ./scripts/docs/spelling/dictionaries/dev_OPS.dic
        touch ./scripts/docs/spelling/dictionaries/dev_OPS.dic
        cat ./scripts/docs/spelling/wordlist | wc -l | sed 's/^[ \t]*//g' > ./scripts/docs/spelling/dictionaries/dev_OPS.dic
        sort -u -f ./scripts/docs/spelling/wordlist >> ./scripts/docs/spelling/dictionaries/dev_OPS.dic


  site:sort-custom-dict:
    desc: 'Sorts the list of words for a custom dictionary before pushing into the Git.'
    cmds:
      - |
        sort -u -f -o ./scripts/docs/spelling/wordlist{,}

Сортировка wordlist'а нужна только для удобства работы с файлом: накидали туда всего подряд, а потом отсортировали и получили удобный для работы файл. Генерация словаря удаляет словарь devOps.dic, если он уже существует, и собирает его заново: сначала заполняет первую строку количеством слов из wordlist'а, а в следующие копирует его содержимое. Затем словарь вместе с двумя другими улетает в контейнер на стадии сборки и указывается для обоих языков в параметрах hunspell.

Проверка конкретного файла и просмотр его содержимого

Проверка всего содержимого сайта — долгий процесс, поэтому мы решили добавить возможность проверки одного конкретного файла. В качестве идентификатора мы указали путь, который отображается в списке с ошибками. Например, для строки RU: checking ./viewer.html... путь будет viewer.html.

Итак, первым делом мы добавили второй параметр к скриптам запуска и назвали его arg_target_page. При получении этого параметра первый скрипт spell_check.sh передаёт его дальше во внутренний скрипт container_spell_check.sh, в котором проверяется его наличие. Если этот параметр передан, тогда вместо перебора всех HTML-файлов запускается проверка только одного по полученному пути.

Также мы обновили запуск всей этой связки в Taskfile: указали параметр командной строки для команды task как второй параметр, передаваемый в скрипт:

  site:run-spell-check:ru:
    desc: 'Run spell check for ru pages. You can specify the path to a specific file with `-- ./path/to/file`'
    cmds:
      - |
        ./scripts/docs/spelling/spell_check.sh ru {{.CLI_ARGS}}

Теперь стало возможным запустить команду, например:

task site:run-spell-check:ru -- publications.html

...и получить результат проверки указанного файла:

Checking ru docs...
Checking publications.html...
KLLRCODA

Несмотря на добавленную возможность, работа с полученными тысячами строк ошибок всё равно оказалось неудобной, так как не всегда было очевидно, почему то или иное слово вообще появляется в выводе и где его искать на странице. Поэтому мы решили дополнительно сделать возможность просмотра содержимого страницы после всех очисток и обработок в том виде, в котором она попадает на вход hunspell.

Для этого мы добавили ещё три команды: view-plain-text-of-target-html:en, view-plain-text-of-target-html:ru и view-plain-text-of-target-html. Они запускаются так же, как и проверка конкретного файла, но передают во внутренний скрипт дополнительный третий параметр plain_text:

  site:view-plain-text-of-target-html:en:
    desc: 'Displays EN HTML stripped of tags.'
    cmds:
      - |
        ./scripts/docs/spelling/spell_check.sh en {{.CLI_ARGS}} plain_text


  site:view-plain-text-of-target-html:ru:
    desc: 'Displays RU HTML stripped of tags.'
    cmds:
      - |
        ./scripts/docs/spelling/spell_check.sh ru {{.CLI_ARGS}} plain_text


  site:view-plain-text-of-target-html:
    desc: 'Displays both HTML stripped of tags.'
    cmds:
      - |
        task site:view-plain-text-of-target-html:en -- {{.CLI_ARGS}}
        task site:view-plain-text-of-target-html:ru -- {{.CLI_ARGS}}

При получении этого параметра запускается обрезанный конвейер, который заканчивается перед вызовом hunspell. В результате на выходе отображается текстовое содержимое конкретного файла, в котором уже можно искать ошибку хоть глазами, хоть с помощью удобных инструментов наподобие grep. Например, часть содержимого файла publications.yml, приведенного в примере выше:

Checking ru docs...
[werf]
    *
    * Документация
    * Самоучитель_по_Kubernetes
    * О_проекте
          o Публикации
          o Каналы_обновлений
          o Обратная_совместимость
          o История_изменений
    *
✕
Присоединяйтесь к англоязычному сообществу в Slack CNCF
Шаг 1:
Получить_приглашение_в_Slack_CNCF
Шаг 2:
Войти_в_канал_#werf
Мы выбрали Slack от CNCF, т.к. там зарегистрировано самое большое количество
Kubernetes-энтузиастов.
 [q                   ]
***** Публикации *****
2023
Nov
Организация стенда локальной разработки для самых маленьких с автоматической
пересборкой приложения (фронтенд + бэкенд)
Вносить изменения в код приложения и тут же автоматически получать задеплоенные
изменения, чтобы быстро тестировать его, — мечта разработчика. В этой статье мы
посмотрим, как реализовать такой подход для небольшого приложения с фронтендом
и бэкендом: организуем два варианта локального стенда на базе minikube или
Docker с автоматическим развертыванием всех изменений или только закоммиченых в
Git.
Бэкенд приложения напишем на Go, а фронтенд — на Vue.js. Все это позволит
быстро запускать проект для тестирования прямо во время разработки, что,
несомненно, повысит удобство работы с приложением.
Прочитать_на_Хабре
Oct

Игнорирование файлов по определённому пути

Когда мы начали использовать все перечисленные инструменты, разбор ошибок пошёл гораздо быстрее. При этом выявилась одна особенность — некоторые страницы в принципе не нужно проверять: они либо несут какую-то системную незначительную информацию, либо не важны, но засоряют результаты проверки. В связи с этим мы решили добавить механизм игнорирования определённых путей. Для этого мы создали файл scripts/docs/spelling/internal/filesignore, в котором построчно указали пути, наличие которых в пути к файлу должно останавливать проверку этого файла. Для нашего сайта получились следующие строки:

assets/demo
ssi
configurator/tabs
includes/configurator
includes/channel-menu-v2
includes/group-menu
includes/channel-menu
includes/version-menu

В этот список попали неважная демка, части меню и страницы, содержимое которых было небольшим, но засоряло результат неизвестными словами и их обрывками.

Чтобы обозначить игнорирование файлов, мы добавили в результат отображение их с ключевым словом Ignoring:

Checking ru docs...
Ignoring RU: ./assets/demo/successful_deploy_output.html...
Ignoring RU: ./assets/demo/failed_deploy_output.html...
Ignoring RU: ./includes/channel-menu.html...
Ignoring RU: ./includes/version-menu.html...
Ignoring RU: ./includes/group-menu-v2.html...
Ignoring RU: ./includes/channel-menu-v2.html...
Ignoring RU: ./includes/configurator.html...
Ignoring RU: ./includes/group-menu.html...

Этап №4: настройка GitHub Actions

Мы собрали словарь, настроили и оттюнинговали инструменты. Теперь проверка проходит без ошибок и отлавливает опечатки и неизвестные слова. Мы выполнили несколько тестов, в которых специально оставляли ошибки в тексте, и каждый раз проверка находила неправильно написанные слова.

Но ещё нам нужно было автоматизировать этот процесс, чтобы орфография проверялась сразу же при добавлении изменений в репозиторий. И для этого мы настроили GitHub Actions, прописав в workflow нужные шаги, — добавили в файл .github/workflows/content_validation.yml две секции:

  spell_check_ru:
    runs-on: ubuntu-latest-4-cores
    timeout-minutes: 60
    steps:
      - name: Install werf
        uses: werf/actions/install@v1.2


      - name: Checkout code
        uses: actions/checkout@v3


      - name: Install Task
        uses: arduino/setup-task@v1
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}


      - name: Login to GitHub container registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}


      - name: Spell check
        run: |
          source "$(werf ci-env github --as-file)"
          task -o group -p site:run-spell-check:ru
        env:
          WERF_REPO: "ghcr.io/${{ github.repository_owner }}/werfio"


  spell_check_en:
    runs-on: ubuntu-latest-4-cores
    timeout-minutes: 60
    steps:
      - name: Install werf
        uses: werf/actions/install@v1.2


      - name: Checkout code
        uses: actions/checkout@v4


      - name: Install Task
        uses: arduino/setup-task@v1
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}


      - name: Login to GitHub container registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}


      - name: Spell check
        run: |
          source "$(werf ci-env github --as-file)"
          task -o group -p site:run-spell-check:en
        env:
          WERF_REPO: "ghcr.io/${{ github.repository_owner }}/werfio"

В каждой из них выполняются следующие шаги:

  • выбирается дистрибутив ubuntu-latest-4-cores;

  • устанавливается werf с использованием подготовленных Actions;

  • устанавливается Task аналогичным способом;

  • осуществляется логин в registry GitHub, в котором будет храниться собранный контейнер (для удобства дальнейшей проверки, чтобы не собирать его каждый раз);

  • запускается проверка орфографии для нужного языка.

Все эти задачи будут запускаться при изменении файлов по следующим маскам:

on:
  push:
    paths:
      - '.github/workflows/content_validation.yml'
      - '_data/en/publications.yml'
      - '_data/ru/publications.yml'
      - '**/*.md'
      - '**/*.md.liquid'
      - '**/*.html'

Сразу после пуша изменений в ветку со спелл-чекером новый Action запустился, проверил документацию, но нас ждало разочарование. В тексте были ошибки, но процесс всё равно завершился «удачно», а должен показывать красный крест:

Такое поведение нас не устраивало, поэтому мы добавили во внутренний скрипт флаг, который по умолчанию установлен в false, а при нахождении ошибок в одном из файлов меняется на true. В конце всего цикла проверки флаг проверяется: если была найдена хотя бы одна ошибка (флаг установлен в true), весь скрипт завершается с кодом возврата «1».

Результаты порадовали — Action с ошибкой теперь завершается с красным крестом, как и должно быть:

Результаты

В результате проделанной работы у нас получился сервис, который позволяет автоматически проверять орфографию на сайте. Начали мы с простого запуска утилиты, а в итоге сделали большой инструмент, у которого есть несколько команд для работы с контентом:

  • site:generate-special-dictionary — генерирует словарь специальных терминов из набора слов в wordlist'е;

  • site:get-words-with-typos — собирает все слова с опечатками в два файла — для русской и английской версий сайта:

    • site:get-words-with-typos:en — подкоманда для английской версии;

    • site:get-words-with-typos:ru — подкоманда для  русской версии;

  • site:run-spell-check — запускает проверку русской и английской версий сайта:

    • site:run-spell-check:en — только для английской версии. Позволяет выбрать конкретный файл для проверки, если указать параметр -- ./path/to/file;

    • site:run-spell-check:ru — только для русской версии. Позволяет выбрать конкретный файл для проверки, если указать параметр -- ./path/to/file;

  • site:sort-custom-dict — сортирует wordlist перед отправкой изменений в Git;

  • site:view-plain-text-of-target-html — отображает очищенное содержимое целевого файла;

    • site:view-plain-text-of-target-html:en — только для английской версии;

    • site:view-plain-text-of-target-html:ru — только для русской версии.

Алгоритм, описанный выше, не изменился. Всё так же запускается внешний скрипт, стартующий контейнер с hunspell и запускающий в нём внутренний скрипт. Проверяться одновременно могут обе версии сайта — английская и русская.

Внешний скрипт при этом получает на вход два параметра: путь к файлу и необходимость вывода только очищенного текста без проверки:

#!/bin/bash


set -e


arg_site_lang="${1:?ERROR: Site language \'en\' or \'ru\' should be specified as the first argument.}"


if [ -n "$2" ]; then
  arg_target_page=$2
fi


if [ -n "$3" ]; then
  arg_get_plain_text=$3
fi


script=$(cat <<EOF
cd /spelling/$arg_site_lang && \
  ./container_spell_check.sh $arg_site_lang $arg_target_page $arg_get_plain_text
EOF
)


werf run spell-checker --env='test' --dev --docker-options="--entrypoint=bash" -- -c "$script"

При этом он без изменений передаёт параметры дальше во внутренний скрипт, в котором и проходит вся работа:

С самим скриптом можно ознакомиться здесь.

Пример разбора найденных ошибок

Разберём на примере, как теперь проходит работа с ошибками. Для этого мы специально испортили содержимое страницы самоучителя по Kubernetes:

Запустим проверку всего сайта:

task site:run-spell-check

Результат:

RU: checking ./guides.html...
Самаучитель
ариентираван
разроботчиков

Все слова найдены. Теперь представим, что мы не знаем, в каком месте страницы найдены эти ошибки. Посмотрим содержимое страницы в очищенном виде и поищем по нему слова:

task site:view-plain-text-of-target-html:ru -- ./guides.html | grep Самаучитель

В результате отображается только та строка, в которой найдено исходное слово, а оно само подсвечено красным:

Остаётся исправить ошибки в нужных местах и проверить страницу снова:

task site:run-spell-check -- ./guides.html

Ошибки не найдены:

Checking ru docs...
Checking ./guides.html...

Заключение

В статье мы рассказали, как настраивали автоматическую проверку орфографии внутри Docker-контейнера с сайтом. В результате у нас получился функционирующий сервис, который корректно проверяет новые изменения на сайте сразу после их добавления в репозиторий. С момента запуска он уже отловил несколько десятков опечаток и новых терминов, которые мы добавили в кастомный словарь.

При этом у нашего инструмента есть недостаток. Его нельзя просто так развернуть для другого сайта, так как сейчас он «заточен» под конкретный сайт, и словарь может оказаться неполным. Поэтому логику очистки нужно поменять, например дописать что-то в стартовом python-скрипте. А ещё сборка документации в контейнере может отличаться. Мы убедились в этом, когда стали настраивать такой же сервис для сайта Deckhouse Kubernetes Platform. Мы уже решаем эту проблему, о результатах работы с которой напишем в будущей статье.

P. S.

Читайте также в нашем блоге:

Комментарии (3)


  1. mansurL
    13.04.2024 06:10
    +1

    Сразу найти вменяемую документацию к формату словаря не получалось, в этом не помог и Google

    Описание aff есть в manpage для hunspell, причём достаточно подробное и с примерами.


    1. Zhbert Автор
      13.04.2024 06:10

      Спасибо :) Как говорится, «слона-то я и не заметил».

      Видимо, сработала привычка последних лет сразу идти искать документацию в интернетах, а не лезть в маны. А ведь когда-то я без интернетов собирал генту по распечатанной на рабочем принтере рукокниге и разбирался с ошибками путем чтения логов, размышлений и как раз-таки вкуривания манов… Видимо, старею.


  1. AlexJameson
    13.04.2024 06:10

    Здравствуйте, спасибо за статью. Я пробежался по ней (внимательно читать и перечитывать буду еще и еще), но пока не понял одной вещи. После сборки в HTML вы безальтернативно проверяете два целых каталога — для русского и английского, то есть в вашей реализации не предусмотрена проверка только файлов из отдельного коммита/пулл-рекаеста, верно? Или я просто пропустил этот фрагмент?