mt5-manager/docs/articles/part-04/publish/build_article_html.py
150 lines
6.2 KiB
Python
'''
File: build_article_html.py
Description: Собирает из docs/articles/part-04/article.md HTML-фрагмент для визуального
редактора статей на MQL5.community и отдельные файлы с блоками кода.
Запуск из корня проекта:
python docs/articles/part-04/publish/build_article_html.py
'''
import html
import re
from pathlib import Path
import markdown
ARTICLE_DIR = Path(__file__).resolve().parents[1]
SRC = ARTICLE_DIR / 'article.md'
OUT_DIR = ARTICLE_DIR / '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<p style="text-align:center;">[[РИС. %d: файл %s]]</p>\n\n'
'<p style="text-align:center;"><span class="small">%s</span></p>\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('<h3>', '<h4>').replace('</h3>', '</h4>')
body = body.replace('<h2>', '<h3>').replace('</h2>', '</h3>')
# Инлайн-код -> курсив: редактор сайта не умеет моноширинный текст в строке
body = re.sub(r'<code>(.*?)</code>', r'<i>\1</i>', 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('<strong>', '<b>').replace('</strong>', '</b>')
body = body.replace('<em>', '<i>').replace('</em>', '</i>')
# Якоря у заголовков разделов и оглавление со ссылками на них
toc = []
def add_anchor(m):
title = m.group(1)
anchor = ANCHORS[title]
toc.append((anchor, title))
return '<h3 id="%s">%s</h3>' % (anchor, title)
body = re.sub(r'<h3>(.*?)</h3>', add_anchor, body)
# Якорные ссылки с путём статьи: короткие вида #id сайт вырезает при сохранении
items = '\n'.join(
'<li><a href="/ru/articles/20150/#%s">%s</a></li>' % t for t in toc)
body = '<p><b>Содержание</b></p>\n<ul>\n%s\n</ul>\n' % items + body
# Ссылки: на сайте MQL5 относительные, все открываются в новой вкладке
body = ARTICLE_URL_RE.sub(r'href="\1"', body)
body = re.sub(r'<a href="((?!/ru/articles/20150/#)[^"]*)">',
r'<a href="\1" target="_blank">', body)
# Предупреждение
body = re.sub(r'<blockquote>\s*<p><b>Предупреждение\.</b>',
'<div class="atten">\n<p><b>Предупреждение</b>.', body)
body = body.replace('</blockquote>', '</div>')
# Таблицы в стандартном стиле сайта
body = body.replace(
'<table>',
'<table class="standart" width="100%" cellspacing="0" cellpadding="3">')
# Блоки кода
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 = ('<div class="code-box"><pre class="code">%s</pre></div>'
% html.escape(code, quote=False))
body = body.replace(f'<p>@@CODE{i}@@</p>', 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()