340 lines
15 KiB
ReStructuredText
340 lines
15 KiB
ReStructuredText
:title: Как я написал этот блог
|
||
:date: 5 Aug 22
|
||
:type: page
|
||
|
||
.. note::
|
||
|
||
Статья малось устарела и не соответсвует текущей действительности.
|
||
|
||
=======================
|
||
Как я написал этот блог
|
||
=======================
|
||
|
||
Я очень давно планировал написать блог. Именно что написать 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.
|
||
|
||
Исходники с небольшой инструкцией я положил сюда: https://git.nxhs.cloud/ge/blog
|
||
|
||
Написание постов
|
||
================
|
||
|
||
Тут всё предельно просто. Пишем файл и кладём в директорию `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
|
||
|