О Pelican

2019-04-14

Как вы, надеюсь, заметили, блог изрядно поменялся. Дело в том, что Google+ умер. А Blogger, который тоже продукт под крылышком Гугла, и на котором крутился блог, был сильно завязан на Google+. Там были комментарии из G+. Там была ссылка на мой профиль на G+. Всё это скурвилось. А комментарии из G+ превратились в какие-то комментарии самого Blogger. Потеряв всё содержимое, конечно же.

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

Ну и дизайн Bloggerа оставлял желать лучшего. Не нравился он мне. Чудовищные поля по бокам...

К тому же, я давно уже пишу статьи в Markdown, перегоняю его в HTML, и создаю черновики статей в этом HTML.

К тому же, в старых статьях, где я проставлял ссылки на внешние изображения, эти самые внешние изображения уже потерялись. Хорошо было бы починить. И утащить все картинки под крылышко своего хостинга.

К тому же, я уже баловался с генерацией статического контента для одного сайта. Из Markdown.

В общем, пришла пора всерьёз нагенерить весь мой блог статически. К чёрту SaaS. К чёрту Гугл. Только простой хостинг, чтобы раздавать статику.

Хостинг уже есть. И даже раздача через бесплатный CloudFlare. Осталось найти нормальный генератор.

Обычно про статическую генерацию сайтов или блогов вспоминают для размещения этих сайтов или блогов на GitHub Pages. И тут балом правит Jekyll. Но Jekyll на Ruby, а у меня, по непонятным причинам, идиосикразия на Ruby. Оказалось, что существует почти полный аналог на Python. Он называется Pelican.

logo

Что может Pelican? Рендерить статьи (article) блога и страницы (pages) из Markdown или reStructuredText. Статьи могут быть сгруппированы по дате, категориям (одна категория на статью), тегам (много тегов на статью), авторам. Генерятся отдельные страницы для списка всех статей (архив, archives), тегов, категорий, авторов. С пагинацией. Поддерживаются темы (с шаблонами на Jinja2) и плугины (на Python, конечно). URLы, менюхи, интеграция с Google Analytics и Disqus, всё настраивается.

Ставится Pelican, как и положено, через pip.

$ sudo pip3 install pelican markdown beautifulsoup4

BeautifulSoup нужен для плагинов. Им нужно ковыряться в статьях, когда они уже превратились в HTML.

Начать пеликанопроект проще всего командой pelican-quickstart. Она задаст вам несколько вопросов и сгенерит конфиги Пеликана, а также весьма недурной Makefile. У меня, после добавления плугина и темы, получилось так:

.
├── content
│   ├── 2013
│   ├── 2014
│   ├── 2015
│   ├── 2016
│   ├── 2017
│   ├── 2018
│   ├── 2019
│   ├── images
│   └── pages
├── Makefile
├── output
├── pelicanconf.py
├── plugins
│   └── tipue_search
├── publishconf.py
└── themes
    └── flex

В папочке content лежит контент, наши .md файлы. По умолчанию предполагается, что статьи лежат в одной куче, в лучшем случае по подкаталогам согласно категории, картинки лежат в другой куче, в content/images. Я использую другую схему. Категории я не использую, а складываю статьи по годам, для удобства. Каждая статья вместе со своими картинками живёт в отдельном подкаталоге. Так как картинки у каждой статьи свои, то указывать их в Markdown приходится через префикс {attach}: ![an image]({attach}image.png). Pelican имеет несколько таких префиксов для URL. {attach} означает, что ресурс находится рядом в том же каталоге, что и статья.

В Makefile есть несколько полезных целей. make serve запускает питоновый HTTP сервер, чтобы посмотреть свой блог на "http://localhost:8000". make html собирает девелоперскую версию блога. make publish собирает продакшеновую версию блога. make rsync_upload rsyncает блог на тот сервер, что вы указали при запуске pelican-quickstart. Ничто не мешает править Makefile руками и тем более нужно закоммитить его в систему контроля версий.

Генерируемые HTML помещаются в папку output. Именно её rsyncает Makefile.

В Pelican у вас есть два вида сборки: девелоперская и продакшеновая. Конфигурация первой задаётся в файле pelicanconf.py. Второй — в файле publishconf.py. Вторая конфигурация унаследована от первой, и лишь переопределяет несколько параметров. Разница в URL блога, на localhost или на настоящем домене. Ещё девелоперскую сборку стараются сделать побыстрее: не генерируют RSS. А ещё в девелоперской сборке лучше отключать Google Analytics и Disqus.

Pelican-Jinja-Markdown

Какие-то плугины и какие-то темы есть в составе Pelican. Какие-то можно поставить через pip. Но гибче всего положить плугины и темы прямо в ваш проект. Я так и сделал. И да, темы и плугины мне пришлось немного допилить.

Оказалось, довольно сложно отключить в Pelican то, что мне не нужно. Отображение автора, категории совсем, переводы статей (по умолчанию он считает одинаковый slug переводами одной статьи). Но настроек в конфигах довольно много. Вот, вкратце, что получилось у меня:

RELATIVE_URLS = True
ARTICLE_TRANSLATION_ID = False
DEFAULT_DATE = 'fs'
DEFAULT_DATE_FORMAT = '%Y-%m-%d'
DEFAULT_PAGINATION = 10
USE_FOLDER_AS_CATEGORY = False
DISPLAY_CATEGORIES_ON_MENU = False
ARTICLE_URL = '{date:%Y}/{date:%m}/{slug}.html'
ARTICLE_SAVE_AS = '{date:%Y}/{date:%m}/{slug}.html'
SLUGIFY_SOURCE = 'basename'
CATEGORY_SAVE_AS = ''
MAIN_MENU = True

from functools import partial

JINJA_FILTERS = {
    'sort_by_article_count': partial(sorted,
                                     key=lambda tags: len(tags[1]),
                                     reverse=True)  # reversed for descending order
}

THEME = 'themes/flex'
PLUGINS = ['tipue_search']
DIRECT_TEMPLATES = ['index', 'tags', 'archives', 'search']

С темами всё густо, но не очень. Я искал тему с Bootstrap. Но они либо очень старые, либо очень сложные в установке. Я тупо не подружился с локализацией (через gettext) в Jinja2.

Остановился на Flex. Она не на Bootstrap, но с вполне внятным CSS, генерируемым из less. Отрубил там локализацию. Слегка поправил шаблоны.

Например, для сортировки тегов по популярности пришлось добавить в Jinja2 функцию sort_by_article_count и поправить шаблон tags.html.

    <ul class="list">
      {% for tag, articles in tags|sort_by_article_count %}
          <li><a href="{{ SITEURL }}/{{ tag.url }}#start">{{ tag }}</a> ({{ articles|count }})</li>
      {% endfor %}
    </ul>

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

Из плугинов я подключил только Tipue Search. Это такой яваскриптовый поиск по статическим сайтам. Собственно плагин генерирует здоровенный JSON с содержимым всех статей. А уже по этому JSON jQuery плугин осуществляет поиск и рендерит результат. Не самый интеллектуальный поиск, по сути просто прогон регулярных выражений по куче текстов. Но, по крайней мере, больший вес словам из заголовков и тегов он даёт. Нормально.

статика

Что изменилось при переходе на Pelican?

В начале .md файлов теперь нужно писать метаинформацию.

Title: О Pelican
Date: 2019-04-14
Tags: Pelican, статическая генерация, блог

Картинки нужно приделывать через префикс URL {attach}. Сами картинки нужно скачивать, обрезать, изменять размеры ручками. wget и ImageMagic в помощь.

К сожалению, плугин к IDEA для Markdown не понимает URL с {attach}. И вообще, синтаксис Markdown — не очень стандартная вещь. И то, что показывает IDEA, вовсе не обязательно совпадёт с тем, что сгенерирует Pelican. Нужно проверять.

А так, всё хорошо. Теперь я точно знаю, когда и сколько статей написал. Сто сорок шесть статей (эта — сто сорок седьмая). Первая — шестого ноября 2013.

Pelican умеет и импорт из Blogger. Он даже работает. Только вот Blogger, если заголовок статьи не содержит ни одной латинской буквы, пишет в URL blog-post.html. С точки зрения Pelican это одна и та же статья со slug "blog-post", поэтому сымпортирована будет только первая статья с одинаковыми slug. К тому же при импорте оно пытается сконвертировать HTML в Markdown, что почти никогда не получается без ошибок.

В общем, кучу времени пришлось убить на перебирание и правку старых статей. На скачивание утраченных картинок. Очень помогла Wayback Machine, там нашлось около 90% старья. Остальные картинки пришлось заменять на аналоги.

Надо ещё прошерстить, что я там выгрузил из G+. Для некоторых статей там были ценные комментарии. Надо перенести.

Зато теперь я могу контролировать в блоге сильно больше, чем в Blogger. CSSки, шаблоны, подсветка синтаксиса, копирайты, меню... Даёшь статическую генерацию!

Generate All The Things!