''' File: build_article_html.py Description: Собирает из docs/article4-draft.md HTML-фрагмент для визуального редактора статей на MQL5.community и отдельные файлы с блоками кода. Запуск из корня проекта: python docs/publish/build_article_html.py ''' import html import re from pathlib import Path import markdown ROOT = Path(__file__).resolve().parents[2] SRC = ROOT / 'docs' / 'article4-draft.md' OUT_DIR = ROOT / 'docs' / 'publish' CODE_DIR = OUT_DIR / 'code' ARTICLE_URL_RE = re.compile(r'href="https://www\.mql5\.com(/ru/[^"]*)"') # Якорные метки: латиница, цифры и «_», строчные, не начинаются с цифры ANCHORS = { 'Введение': 'intro', 'Чем не устраивает текущий подход': 'current_approach', 'Намечаем путь': 'plan', 'Сервис как тип MQL5-программы': 'service_type', 'Схема базы данных': 'db_schema', 'Реализация сервиса ServiceStateWriter.mq5': 'implementation', 'Что показал первый запуск': 'first_run', 'Обновление Python-стороны': 'python_side', 'Совместимость с уже написанным интерфейсом': 'compatibility', 'Развёртывание через config/services.ini': 'deploy', 'Тестирование': 'testing', 'Заключение': 'conclusion', } FIGURES = { 'all-terminals-stopped.png': 1, 'service-not-running.png': 2, 'all-terminals-running.png': 3, } def split_code(text: str): '''Выносит блоки кода в отдельный список, оставляя в тексте метки.''' blocks = [] def repl(m): blocks.append((m.group(1).strip(), m.group(2).rstrip('\n'))) return f'\n\n@@CODE{len(blocks) - 1}@@\n\n' text = re.sub(r'```(\w*)\n(.*?)\n```', repl, text, flags=re.S) return text, blocks def figures(text: str) -> str: '''Рисунок + подпись -> заглушка для картинки и подпись стилем small.''' pattern = re.compile( r'!\[[^\]]*\]\(images/([\w.-]+)\)\n\*(Рис\. \d+\..*?)\*') def repl(m): name, caption = m.group(1), m.group(2) return ( '\n\n
[[РИС. %d: файл %s]]
\n\n' '%s
\n\n' % (FIGURES[name], name, caption)) return pattern.sub(repl, text) def build() -> None: text = SRC.read_text(encoding='utf-8') # Название статьи на сайте задаётся отдельно, оглавление соберём ниже text = text[text.index('## Введение'):] # Блок «Содержание архива» оформляется как жирный абзац, а не заголовок text = text.replace('## Содержание архива', '**Содержание архива**') text, blocks = split_code(text) text = figures(text) body = markdown.markdown(text, extensions=['tables', 'sane_lists']) # Заголовки: название статьи в h2, разделы в h3, подразделы в h4 body = body.replace('(.*?)', r'\1', body, flags=re.S)
# В заголовках форматирования не нужно: они попадают в план статьи
body = re.sub(
r'<(h[34])>(.*?)(h[34])>',
lambda m: '<%s>%s%s>' % (
m.group(1), re.sub(r'?i>', '', m.group(2)), m.group(3)),
body)
body = body.replace('', '').replace('', '')
body = body.replace('', '').replace('', '')
# Якоря у заголовков разделов и оглавление со ссылками на них
toc = []
def add_anchor(m):
title = m.group(1)
anchor = ANCHORS[title]
toc.append((anchor, title))
return 'Содержание
\n\s*Предупреждение\.', '
\n') # Таблицы в стандартном стиле сайта body = body.replace( 'Предупреждение.', body) body = body.replace('', '
', '
') # Блоки кода CODE_DIR.mkdir(parents=True, exist_ok=True) for old in CODE_DIR.glob('*.txt'): old.unlink() for i, (lang, code) in enumerate(blocks): block = ('
' % html.escape(code, quote=False)) body = body.replace(f'%s@@CODE{i}@@
', block) (CODE_DIR / f'{i + 1:02d}_{lang or "text"}.txt').write_text( code + '\n', encoding='utf-8') (OUT_DIR / 'article4.html').write_text(body + '\n', encoding='utf-8') print(f'article4.html: {len(body)} символов, блоков кода: {len(blocks)}') if __name__ == '__main__': build()