336 lines
15 KiB
ReStructuredText
336 lines
15 KiB
ReStructuredText
|
:title: Как я написал этот блог
|
|||
|
:date: 5 Aug 2022
|
|||
|
|
|||
|
=======================
|
|||
|
Как я написал этот блог
|
|||
|
=======================
|
|||
|
|
|||
|
Предыстория
|
|||
|
===========
|
|||
|
|
|||
|
Я очень давно планировал написать блог. Именно что написать CMS для ведения
|
|||
|
блога. Если погуглить, то CMS для этих целей сотни на разных языках с разным
|
|||
|
позиционированием и фичами. Но мне хотелось своего.
|
|||
|
|
|||
|
Первые попытки создать сайт были на `Flask`_, особым успехом не увенчались,
|
|||
|
но это была хорошая практика и я немного прокачал свой уровень программирования.
|
|||
|
|
|||
|
Для блога я не хотел использовать базу данных. Вместо этого я хранил статьи в
|
|||
|
исходном Markdown, а метаданные по каждой статье писал в отдельный JSON, который
|
|||
|
заменял мне БД. С тем же успехом я мог бы использовать SQLite, но мне нужно было
|
|||
|
хранить контент в text/plain.
|
|||
|
|
|||
|
Блог даже работал, но мне хотелось сделать админку, чтобы можно было писать
|
|||
|
прямо в браузере. На этой части разработка заглохла и я окончательно запутался в
|
|||
|
импортах в Python)
|
|||
|
|
|||
|
Прошло ещё много времени, я вернулся к идее, но решил сделать ставку на простоту
|
|||
|
и начал писать на `Bottle`_. Здесь разработка остановилась не дойдя даже до
|
|||
|
варианта хоть как-то работающей системы. Причины скорее всего надо искать в
|
|||
|
лени.
|
|||
|
|
|||
|
Устав я взял `CMS Bludit`_ и стал вести свои редкие заметки в нём. Решение не
|
|||
|
самое плохое, но и не супер отличное. Для простой кастомизации футера пришлось
|
|||
|
городить костыли.
|
|||
|
|
|||
|
.. _Flask: https://flask.palletsprojects.com/
|
|||
|
.. _Bottle: https://bottlepy.org/
|
|||
|
.. _CMS Bludit: https://www.bludit.com/ru/
|
|||
|
|
|||
|
Статика VS динамика
|
|||
|
===================
|
|||
|
|
|||
|
Преимуществ у статических сайтов много. Об этом уже много написано. Мне больше
|
|||
|
всего в статических сайтах нравится то, что:
|
|||
|
|
|||
|
* роутингом запросов не управляет какой-либо скрипт, я могу свободно
|
|||
|
распоряжаться тем, что у меня есть в DocumentRoot;
|
|||
|
* не нужно настраивать application-сервер и реверс-прокси на него.
|
|||
|
|
|||
|
С редактированием сайта чуть сложнее, так как сперва надо внести изменения
|
|||
|
локально, а затем заменить изменившиеся файлы на сервере. Но и тут есть
|
|||
|
какие-то решения.
|
|||
|
|
|||
|
Я рассматривал уже готовые генераторы статических сайтов, но у них всех были
|
|||
|
фатальные недостатки:
|
|||
|
|
|||
|
* они сложные и требуют изучения документации чтобы начать писать;
|
|||
|
* написать собственную тему задача весьма непростая;
|
|||
|
* они написаны не мной.
|
|||
|
|
|||
|
Здесь и рождается мой велосипед, которому я не придумал названия. Буду называть
|
|||
|
его `генератором`.
|
|||
|
|
|||
|
*re*\ **Structured**\ *Text*
|
|||
|
============================
|
|||
|
|
|||
|
Я очень долго писал на Markdown, но в один момент понял, что возможностей языка
|
|||
|
стало нехватать. Тут пришёлся очень кстати `Python-Markdown`_ к которому можно
|
|||
|
было прикручивать `расширения`_. Одно даже сам `написал`_.
|
|||
|
|
|||
|
Постепенно я пришёл к `reStructuredText`_. Все приколюхи, которые в нём есть
|
|||
|
существуют не за счёт расширения синтаксиса плагинами, а заложены в спецификации.
|
|||
|
Функциональности из коробки хватает чтобы писать даже `формулы`_.
|
|||
|
|
|||
|
Такие вещи делаются через `роли` и `директивы`. Некоторые готовые директивы есть
|
|||
|
в системе документации `Sphinx`_. Их я собираюсь потихоньку скопировать к себе.
|
|||
|
|
|||
|
Ещё одно большое преимущество перед Markdown состоит в том, что reStructuredText
|
|||
|
поддерживает атрибуты. Вот как это выглядит:
|
|||
|
|
|||
|
.. code-block:: rst
|
|||
|
|
|||
|
:title: Как я написал этот блог
|
|||
|
:date: 5 Aug 2022
|
|||
|
|
|||
|
Это нативная фича, которую можно использовать в статьях для добавления метаданных.
|
|||
|
|
|||
|
По сути это место, где можно сделать настройки, касающиеся отдельно взятой статьи.
|
|||
|
|
|||
|
Большинство генераторов статических сайтов колхозят нечно подобное в Markdown, делая
|
|||
|
исходник статьи непригодным для парсинга другими инструментами. Это причина по
|
|||
|
которой в старой реализации блога мне пришлось использовать JSON для метаданных.
|
|||
|
|
|||
|
.. _Sphinx: https://www.sphinx-doc.org/en/master/index.html
|
|||
|
.. _Python-Markdown: https://python-markdown.github.io/
|
|||
|
.. _расширения: https://python-markdown.github.io/extensions/
|
|||
|
.. _написал: https://pypi.org/project/markdown-alerts/
|
|||
|
.. _rST: https://docutils.sourceforge.io/rst.html
|
|||
|
.. _формулы: https://docutils.sourceforge.io/docs/ref/rst/mathematics.html
|
|||
|
|
|||
|
Обзор
|
|||
|
=====
|
|||
|
|
|||
|
Сайт состоит из следующих частей.
|
|||
|
|
|||
|
rst_blg.py
|
|||
|
Непосредственно код генератора. Сейчас там всего около 200 строк без учёта
|
|||
|
комментариев.
|
|||
|
|
|||
|
requirements.txt
|
|||
|
Список зависимостей. Стараюсь держать минимум. Пока что там: `docutils`,
|
|||
|
`jinja2`, `toml` и `pygments`.
|
|||
|
|
|||
|
settings.toml
|
|||
|
Файл с настройками. Здесь можно переопределить практически всё.
|
|||
|
|
|||
|
Makefile
|
|||
|
Через него запускаются команды для сборки сайта и некоторые другие. Не
|
|||
|
является обязательным, но с ним удобнее.
|
|||
|
|
|||
|
layouts
|
|||
|
Директория для шаблонов Jinja2.
|
|||
|
|
|||
|
assets
|
|||
|
Директория для хранения статических файлов, будь то CSS, JS или изображения.
|
|||
|
Всё что нужно для визуального оформления страниц. Внутренняя структура
|
|||
|
каталога может быть произвольной.
|
|||
|
|
|||
|
content
|
|||
|
Директория с исходниками статей в reStructuredText. Сюда можно также
|
|||
|
положить любые файлы и директории.
|
|||
|
|
|||
|
build
|
|||
|
В эту директорию копируются ассеты, файлы и сгенерированный HTML.
|
|||
|
|
|||
|
Статьи, шаблоны и ассеты могут быть оформлены абсолютно любым образом. Скрипту
|
|||
|
безразлично что собирать. Пути и имена всех директорий можно переопределить в
|
|||
|
settings.toml.
|
|||
|
|
|||
|
Написание постов
|
|||
|
================
|
|||
|
|
|||
|
Тут всё предельно просто. Пишем файл и кладём в директорию `content`. В файле
|
|||
|
должны быть указаны обязательные атрибуты ``:title:`` и ``:date:``.
|
|||
|
|
|||
|
Блог пока не умеет работать с вложенной структурой статей. Поэтому всё будет
|
|||
|
свалено в кучу в корневую директорию сайта.
|
|||
|
|
|||
|
Все статьи добавляются в список постов и отображаются на главной странице.
|
|||
|
|
|||
|
Для того, чтобы сделать "отдельную" страницу, надо добавить атрибут
|
|||
|
``:not_a_post:``.
|
|||
|
|
|||
|
================= ======= =========== ====================================
|
|||
|
Атрибут Тип Умолчание Описание
|
|||
|
================= ======= =========== ====================================
|
|||
|
``:title:`` Строка Заголовок статьи
|
|||
|
``:date:`` Дата Дата публикации, формат задаётся в
|
|||
|
settings.toml
|
|||
|
``:not_a_post:`` Флаг для одиночных страниц
|
|||
|
================= ======= =========== ====================================
|
|||
|
|
|||
|
Темы оформления
|
|||
|
===============
|
|||
|
|
|||
|
Никаких готовых тем. Пишем CSS вручную. Генератору сайтов всё равно что там
|
|||
|
будет. От уровеня представления он никак не зависит. Так что можно писать любые
|
|||
|
шаблоны и стили для них.
|
|||
|
|
|||
|
Отдельно можно задать тему для блоков кода. Список тем для Pygments с превью
|
|||
|
есть на странице https://pygments.org/styles/
|
|||
|
|
|||
|
Чтобы поменять тему, например, на `default` надо выполнить команду:
|
|||
|
|
|||
|
.. code-block:: shell
|
|||
|
|
|||
|
make css default
|
|||
|
# или
|
|||
|
pygmentize -f html -S default -a .highlight > assets/css/pygments/default.css
|
|||
|
|
|||
|
Затем поменять значение ``theme`` в секции ``pygments`` settings.toml.
|
|||
|
|
|||
|
.. code-block:: toml
|
|||
|
|
|||
|
[pygments]
|
|||
|
theme = 'default'
|
|||
|
|
|||
|
Шаблоны
|
|||
|
========
|
|||
|
|
|||
|
Шаблоны можно писать какие угодно. Для этогой сайта я написал три файла.
|
|||
|
Приведу их без лишних строк.
|
|||
|
|
|||
|
**base.j2**
|
|||
|
|
|||
|
.. code-block:: html+jinja
|
|||
|
|
|||
|
<!DOCTYPE html>
|
|||
|
<html lang="en" dir="ltr">
|
|||
|
<head>
|
|||
|
<meta charset="utf-8">
|
|||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|||
|
<link rel="icon" href="data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=">
|
|||
|
<link rel="stylesheet" href="assets/css/pygments/{{ pygments_theme }}.css">
|
|||
|
<link rel="stylesheet" href="assets/css/custom.css">
|
|||
|
<title>{{ page_title }} | {{ site_title }}</title>
|
|||
|
</head>
|
|||
|
<body>
|
|||
|
|
|||
|
<header>
|
|||
|
<p>
|
|||
|
{% if posts %}
|
|||
|
{{ site_title }}
|
|||
|
{% else %}
|
|||
|
<a href="/">{{ site_title }}</a> / {{ page_title }}</p>
|
|||
|
{% endif %}
|
|||
|
</p>
|
|||
|
</header>
|
|||
|
|
|||
|
{% block content %}{% endblock %}
|
|||
|
|
|||
|
<footer>
|
|||
|
<!-- Footer content -->
|
|||
|
</footer>
|
|||
|
|
|||
|
</body>
|
|||
|
</html>
|
|||
|
|
|||
|
**post.j2**
|
|||
|
|
|||
|
.. code-block:: html+jinja
|
|||
|
|
|||
|
{% extends "base.j2" %}
|
|||
|
{% block content %}
|
|||
|
|
|||
|
<article>
|
|||
|
{{ post | safe }}
|
|||
|
<article>
|
|||
|
|
|||
|
{% endblock %}
|
|||
|
|
|||
|
**index.j2**
|
|||
|
|
|||
|
.. code-block:: html+jinja
|
|||
|
|
|||
|
{% extends "base.j2" %}
|
|||
|
{% block content %}
|
|||
|
|
|||
|
<section>
|
|||
|
<ul id="posts">
|
|||
|
{% for post in posts %}
|
|||
|
<li>
|
|||
|
<a href="/{{ post['path'] }}">{{ post['title'] }}</a>
|
|||
|
<span class="meta"> — {{ post['date'] }}</span>
|
|||
|
</li>
|
|||
|
{% endfor %}
|
|||
|
</ul>
|
|||
|
</section>
|
|||
|
|
|||
|
{% endblock %}
|
|||
|
|
|||
|
settings.toml
|
|||
|
=============
|
|||
|
|
|||
|
Сначала я хотел использовать обычный INI, но мне нужно было получать из конфига
|
|||
|
словарь. Немного подумал и выбрал TOML. Он отлично сериализируется в словарь,
|
|||
|
визуально повторяет INI.
|
|||
|
|
|||
|
**settings.toml** разделён на несколько секций.
|
|||
|
|
|||
|
site
|
|||
|
Данные касающиеся непосредственно сайта.
|
|||
|
|
|||
|
title
|
|||
|
Название сайта
|
|||
|
|
|||
|
index_page_title
|
|||
|
Заголовок главной страницы. Для всех остальных страниц заголовок
|
|||
|
берётся из атрибута.
|
|||
|
|
|||
|
datatime_format
|
|||
|
Формат даты для атрибута ``:date:``.
|
|||
|
|
|||
|
build
|
|||
|
Параметры, используетмые при сборке.
|
|||
|
|
|||
|
build_dir
|
|||
|
Директория, куда будет сохранён собранный сайт.
|
|||
|
|
|||
|
content_dir
|
|||
|
Директория с исходниками статей.
|
|||
|
|
|||
|
templates_dir
|
|||
|
Директория с шаблонами Jinja2.
|
|||
|
|
|||
|
assets_dir
|
|||
|
Директория с ассетами.
|
|||
|
|
|||
|
pygments
|
|||
|
Параметры подсветки синтаксиса в блоках кода.
|
|||
|
|
|||
|
theme
|
|||
|
Стиль Pygments.
|
|||
|
|
|||
|
docutils
|
|||
|
Конфигурация для docutils. Здесь можно указать любые параметры, которые
|
|||
|
есть здесь: https://docutils.sourceforge.io/docs/user/config.html
|
|||
|
|
|||
|
Мне достаточно:
|
|||
|
|
|||
|
.. code-block:: toml
|
|||
|
|
|||
|
[docutils]
|
|||
|
initial_header_level = 2
|
|||
|
section_self_link = true
|
|||
|
syntax_highlight = 'short'
|
|||
|
|
|||
|
Что ещё хочется сделать
|
|||
|
=======================
|
|||
|
|
|||
|
В перспективе я планирую добавить следующие фичи.
|
|||
|
|
|||
|
- RSS
|
|||
|
- OpenGraph
|
|||
|
- Webmention
|
|||
|
- Расширить возможности rST до уровня Sphinx
|
|||
|
- Улучшить CSS
|
|||
|
- Комментарии
|
|||
|
- Кастомный лейаут для отдельных статей
|
|||
|
- Поддержка вложенной структуры статей
|
|||
|
|
|||
|
.. * https://jinja.palletsprojects.com/en/3.0.x/templates/
|
|||
|
* https://docutils.sourceforge.io/docs/user/config.html
|
|||
|
|