commit d14a3f95a788037deec337d967404cf23413753b
Author: ge
Date: Fri Sep 30 17:54:01 2022 +0300
init
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e1a77ed
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+build/
+TODO.*
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..29882ae
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,40 @@
+# Makefile for reSW
+
+CONTENTDIR = content
+STATICDIR = static
+BUILDDIR = build
+CONFIG = settings.toml
+SERVER ?=
+
+all: build
+
+help:
+ @echo 'Build rSW site'
+ @echo
+ @echo 'Available targets:'
+ @echo
+ @echo ' build build HTML'
+ @echo ' serve run local HTTP server'
+ @echo ' css generate Pygments stylesheet'
+ @echo
+ @echo 'Run make without target to build html'
+
+build:
+ test -d "$(BUILDDIR)" && rm -rf "$(BUILDDIR)" || true
+ rsw build -c "$(CONFIG)"
+
+serve:
+ python -m http.server --directory $(BUILDDIR)/ --bind 0.0.0.0 8080
+
+css:
+ mkdir -pv "$(STATICDIR)"/css/pygments
+ pygmentize -f html -S $(filter-out $@,$(MAKECMDGOALS)) -a .highlight \
+ > "$(STATICDIR)"/css/pygments/$(filter-out $@,$(MAKECMDGOALS)).css
+
+publish:
+ rsync --verbose -r $(BUILDDIR)/ $(SERVER):/srv/http/nixhacks.net/public/
+
+%:
+ @:
+
+.PHONY: all help build serve css publish
diff --git a/content/bash_prompt.rst b/content/bash_prompt.rst
new file mode 100644
index 0000000..9f4a721
--- /dev/null
+++ b/content/bash_prompt.rst
@@ -0,0 +1,52 @@
+:title: Функциональный и простой Bash prompt
+:date: 10 Jul 22
+
+====================================
+Функциональный и простой Bash prompt
+====================================
+
+О промптинге в Bash написано очень много, в этой заметке я просто покажу
+лаконичный и функциональный вариант приглашения командной строки.
+
+Вот фрагмент из моего **~/.bashrc**:
+
+.. code-block:: shell
+
+ # Command line prompt
+ # Print a non-zero exit code
+ __exit_code_ps1() {
+ if [ "$1" -ne 0 ]; then
+ echo "$1 "
+ fi
+ }
+
+ PS1='\[\033[0;92m\]\w \[\033[0;31m\]$(__exit_code_ps1 $?)\[\033[0;15m\]\$ '
+
+ # Git prompt
+ # See /usr/share/git/completion/git-prompt.sh or download from:
+ # https://raw.githubusercontent.com/git/git/master/contrib/completion/git-prompt.sh
+ if [ -f /usr/share/git/completion/git-prompt.sh ]; then
+ . /usr/share/git/completion/git-prompt.sh
+ GIT_PS1_SHOWDIRTYSTATE=1
+ PS1='\[\033[0;96m\]$(__git_ps1 "(%s) ")\[\033[0;92m\]\w \[\033[0;31m\]$(__exit_code_ps1 $?)\[\033[0;15m\]\$ '
+ fi
+
+На GitHub можно найти много всяких скриптов или даже целых "фремворков" типа `этого`_
+или `отдельные скрипты`_ для Git. А вы знали, что скрипт для PS1 и так `поставляется`_
+в пакете Git? Не вижу смысла не использовать его, если только вам не надо
+как-то иначе работать с PS1.
+
+Помимо **git-prompt.sh** я использую функцию ``__exit_code_ps1()``, которая просто
+печатает число, если оно не равно нулю. Это очень удобно — всегда видишь код выхода
+предыдущей запущенной команды, порой очень помогает при отладке скриптов.
+
+В итоге всё это дело у меня выглядит вот так:
+
+.. image:: https://i.nxhs.cloud/ovD.png
+
+.. Links
+
+.. _этого: https://github.com/ohmybash/oh-my-bash
+.. _отдельные скрипты: https://github.com/magicmonty/bash-git-prompt
+.. _поставляется: https://github.com/git/git/tree/master/contrib/completion>
+
diff --git a/content/index.rst b/content/index.rst
new file mode 100644
index 0000000..5c5ec09
--- /dev/null
+++ b/content/index.rst
@@ -0,0 +1,4 @@
+:title: Ещё один сайт про cисадмиство и программирование
+:date: 1 Jan 70
+:type: page
+:template: index.jinja2
diff --git a/content/projects.rst b/content/projects.rst
new file mode 100644
index 0000000..8b6e0ec
--- /dev/null
+++ b/content/projects.rst
@@ -0,0 +1,18 @@
+:title: Projects
+:date: 1 Jan 70
+:type: page
+
+=======
+Проекты
+=======
+
+* `rSW `_ -- генератор статических сайтов из reStructuredText
+* tui.sh -- библиотека элементов TUI для POSIX-совместимых оболочек
+* boring-backup -- Bash фреймворк для скриптов резервного копирования
+* `imgs `_ -- минималистичный хостинг картинок
+* `vk-toot `_ -- кросспостер VK -> Mastodon
+* `piglet `_ -- клиент DNS API Porkbun
+* roadwarrior -- менеджер подключений к VPN и прокси
+* `n! ` -- консольный менеджер заметок
+
+Остальное есть в `Gitea `_.
diff --git a/content/python_oop.rst b/content/python_oop.rst
new file mode 100644
index 0000000..6307ef0
--- /dev/null
+++ b/content/python_oop.rst
@@ -0,0 +1,59 @@
+:title: Питонячье ООП на уточках
+:date: 24 Jul 21
+
+========================
+Питонячье ООП на уточках
+========================
+
+Это весьма скупая на информацию напоминалка о работе с классами.
+
+* `Class definitions`_
+* `ООП на Python: концепции, принципы и примеры реализации`_
+
+**duck.py**:
+
+.. code-block:: python
+
+ # Объявляем класс "птица". От него в последствии будет унаследован
+ # класс "уточка". Здесь задаются общие характеристики для всех птиц
+ # и метод "летать". Аргумент `self` – это ссылка на объект, в контексте
+ # которого вызывается метод. Она обязательна для методов классов.
+
+ class Bird(object):
+ can_fly = True
+ wings = 2
+ def fly(self):
+ print('I\'m flying!')
+
+ # Уточка это уже конкретный биологический вид (опускам породы и
+ # виды уточек).
+ # У неё есть имя и цвет (хотя цвет мог быть и у абстрактной птицы).
+ # Также уточка обладает уникальным методом "сказать кря".
+
+ class Duck(Bird):
+ name = 'Duck'
+ color = 'Yellow'
+ def say_quack(self):
+ print('quack!')
+
+Как этим пользоваться:
+
+.. code-block:: text
+
+ >>> import duck
+ >>> bird = duck.Bird()
+ >>> duck = duck.Duck()
+ >>> bird.fly()
+ I'm flying!
+ >>> duck.fly()
+ I'm flying!
+ >>> duck.say_quack()
+ quack!
+ >>> duck.name = 'Cool Duck'
+ >>> duck.name
+ 'Cool Duck'
+
+.. Links
+
+.. _ООП на Python\: концепции, принципы и примеры реализации: https://proglib.io/p/python-oop/
+.. _Class definitions: https://docs.python.org/3/reference/compound_stmts.html#class-definitions
diff --git a/content/this_blog.rst b/content/this_blog.rst
new file mode 100644
index 0000000..673aaa3
--- /dev/null
+++ b/content/this_blog.rst
@@ -0,0 +1,339 @@
+: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
+
+
+
+
+
+
+
+
+
+
+ {{ page_title }} | {{ site_title }}
+
+
+
+
+
+
+
+ {% 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
+
diff --git a/content/todo_text_markup.rst b/content/todo_text_markup.rst
new file mode 100644
index 0000000..0f725b7
--- /dev/null
+++ b/content/todo_text_markup.rst
@@ -0,0 +1,43 @@
+:title: Менеджер задач в текстовом файле
+:date: 2 Sep 21
+
+================================
+Менеджер задач в текстовом файле
+================================
+
+Кажется, я нашёл почти идеальную формулу для ведения списка задач. По сути я
+придумал новый формат разметки текста, специализированный для списков задач.
+Придумать для него название оказалось тяжело. Приложение Todo это второе по
+популярности приложение после Hello World и все хорошие названия уже давно
+заняты. Поэтому пусть будет просто **.todo**.
+
+Синтаксис выглядит таким образом
+
+.. code-block:: text
+
+ - Uncompleted task (light blue)
+ + Completed task (green)
+ x Rejected task (red)
+ # Comment
+ \Marked text (yellow background)\
+ `Code (magenta)`
+ Plain text
+
+.. image:: https://i.nxhs.cloud/MQ9.png
+
+Да, это все элементы синтаксиса. Предельно просто.
+
+Какие есть возможности (сравниваю с тем, что предлагают обычные
+todo-приложения):
+
+- Не нужно устанавливать никакого дополнительного ПО или каждый раз открывать
+ громоздкий веб-интерфейс. Всё что надо это текстовый редактор. В моём случае
+ идеально подошёл **vim**. Написал для него `скрипт для подсветки синтаксиса`_.
+ При желании можно написать подсветку для других редакторов.
+- Текстовая заметка и задача это одна сущность — один файл.
+- Всё управление полностью с клавиатуры.
+- Полная свобода включать в файл что угодно. Разумеется, текст, это не
+ специальный формат для встраивания изображений или других файлов, но можно
+ использовать элементы Markdown.
+
+.. _скрипт для подсветки синтаксиса: https://git.nxhs.cloud/ge/todolist-syntax
diff --git a/content/yt_sync.rst b/content/yt_sync.rst
new file mode 100644
index 0000000..e4c27d3
--- /dev/null
+++ b/content/yt_sync.rst
@@ -0,0 +1,42 @@
+:title: Синхронизация плейлистов YouTube
+:date: 14 Aug 22
+
+================================
+Синхронизация плейлистов YouTube
+================================
+
+YouTube, конечно, место прекрасное (кому как), но как и всё в этом бренном
+мире видеоролики могут исчезнуть в любой момент.
+
+Отсюда есть только один выход — хранить все видео локально. Диск достаточного
+объёма у меня имеется.
+
+С помощью `youtube-dl`_ или `yt-dlp`_ можно скачивать видео без регистрации и
+SMS.
+
+Скачивать можно целыми плейлистами и разработчиками предусмотрена
+возможность синхронизировать плейлист в YouTube с локальными файлами.
+
+Реализовано это весьма неочевидно. Ниже пример шелл-скрипта, с помощью которого
+можно удобно синкать плейлист. Видео будут сохранены в директорию одноимённую
+с плейлистом YouTube. С `шаблонами имён`_ можно поиграться.
+
+.. code-block:: shell
+
+ #!/bin/sh
+
+ echo 己龍 MUSIC VIDEO
+ yt-dlp --download-archive kiryu.txt \
+ --format 'bv*+ba' \
+ --output '%(playlist_title)s/%(title)s-%(id)s.%(ext)s' \
+ 'https://youtube.com/playlist?list=PLg5luStJrusE-PLBGQhCkrzQt-BLLu3Fu'
+
+Скрипт можно запускать по крону и быть спокойным, что видео останутся с тобой.
+А с домашним медиа-сервером становится совсем приятно.
+
+.. Links
+
+.. _youtube-dl: https://github.com/ytdl-org/youtube-dl
+.. _yt-dlp: https://github.com/yt-dlp/yt-dlp
+.. _шаблонами имён: https://github.com/yt-dlp/yt-dlp#output-template
+
diff --git a/layouts/base.jinja2 b/layouts/base.jinja2
new file mode 100644
index 0000000..fe15f5c
--- /dev/null
+++ b/layouts/base.jinja2
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ page.title }} | {{ site.title }}
+
+
+
+