init
This commit is contained in:
commit
d14a3f95a7
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
build/
|
||||||
|
TODO.*
|
40
Makefile
Normal file
40
Makefile
Normal file
@ -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 <theme> 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
|
52
content/bash_prompt.rst
Normal file
52
content/bash_prompt.rst
Normal file
@ -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>
|
||||||
|
|
4
content/index.rst
Normal file
4
content/index.rst
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
:title: Ещё один сайт про cисадмиство и программирование
|
||||||
|
:date: 1 Jan 70
|
||||||
|
:type: page
|
||||||
|
:template: index.jinja2
|
18
content/projects.rst
Normal file
18
content/projects.rst
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
:title: Projects
|
||||||
|
:date: 1 Jan 70
|
||||||
|
:type: page
|
||||||
|
|
||||||
|
=======
|
||||||
|
Проекты
|
||||||
|
=======
|
||||||
|
|
||||||
|
* `rSW </rsw/>`_ -- генератор статических сайтов из reStructuredText
|
||||||
|
* tui.sh -- библиотека элементов TUI для POSIX-совместимых оболочек
|
||||||
|
* boring-backup -- Bash фреймворк для скриптов резервного копирования
|
||||||
|
* `imgs <https://git.nxhs.cloud/ge/imgs>`_ -- минималистичный хостинг картинок
|
||||||
|
* `vk-toot <https://git.nxhs.cloud/ge/vk-toot>`_ -- кросспостер VK -> Mastodon
|
||||||
|
* `piglet <https://git.hxhs.cloud/ge/piglet>`_ -- клиент DNS API Porkbun
|
||||||
|
* roadwarrior -- менеджер подключений к VPN и прокси
|
||||||
|
* `n! <https://git.nxhs.cloud/ge/n>` -- консольный менеджер заметок
|
||||||
|
|
||||||
|
Остальное есть в `Gitea <https://git.nxhs.cloud/ge/>`_.
|
59
content/python_oop.rst
Normal file
59
content/python_oop.rst
Normal file
@ -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
|
339
content/this_blog.rst
Normal file
339
content/this_blog.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<!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
|
||||||
|
|
43
content/todo_text_markup.rst
Normal file
43
content/todo_text_markup.rst
Normal file
@ -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
|
42
content/yt_sync.rst
Normal file
42
content/yt_sync.rst
Normal file
@ -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
|
||||||
|
|
40
layouts/base.jinja2
Normal file
40
layouts/base.jinja2
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<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">
|
||||||
|
<meta property="og:site_name" content="{{ site.name }}"/>
|
||||||
|
<meta property="og:title" content="{{ page.title }}"/>
|
||||||
|
<meta property="og:type" content="article" />
|
||||||
|
<meta property="og:image" content="{{ site.og.image }}"/>
|
||||||
|
<link rel="icon" href="data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=">
|
||||||
|
<link rel="stylesheet" href="/css/pygments/{{ pygments_theme }}.css">
|
||||||
|
<link rel="stylesheet" href="/css/style.css">
|
||||||
|
<title>{{ page.title }} | {{ site.title }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<p>
|
||||||
|
{% if page.template == 'index.jinja2' %}
|
||||||
|
{{ site.title }}
|
||||||
|
{% else %}
|
||||||
|
<a href="/">{{ site.title }}</a> / {{ page.title }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{% for link in site.links %}
|
||||||
|
<a href="{{ link.url }}" target="{{ link.target }}">
|
||||||
|
{{ link.title }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<p>{{ site.footer.text }}</p>
|
||||||
|
<p>{{ site.footer.copyright }}</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
13
layouts/index.jinja2
Normal file
13
layouts/index.jinja2
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{% extends "base.jinja2" %}
|
||||||
|
{% block content %}
|
||||||
|
<section id="posts">
|
||||||
|
<ul id="posts">
|
||||||
|
{% for post in aggr.posts %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ post.path }}">{{ post.title }}</a>
|
||||||
|
<span class="meta"> — {{ post.date }}</span>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
6
layouts/post.jinja2
Normal file
6
layouts/post.jinja2
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{% extends "base.jinja2" %}
|
||||||
|
{% block content %}
|
||||||
|
<article>
|
||||||
|
{{ html | safe }}
|
||||||
|
<article>
|
||||||
|
{% endblock %}
|
41
settings.toml
Normal file
41
settings.toml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
[defaults]
|
||||||
|
template = "post.jinja2"
|
||||||
|
type = "post"
|
||||||
|
|
||||||
|
[pygments]
|
||||||
|
theme = "rrt"
|
||||||
|
|
||||||
|
[site]
|
||||||
|
name = 'Ещё один сайт про cисадмиство и программирование'
|
||||||
|
title = 'Nixhacks'
|
||||||
|
datetime_format = "%d %b %y"
|
||||||
|
|
||||||
|
[[site.links]]
|
||||||
|
title = '[home]'
|
||||||
|
url = '/'
|
||||||
|
[[site.links]]
|
||||||
|
title = '[projects]'
|
||||||
|
url = '/projects.html'
|
||||||
|
[[site.links]]
|
||||||
|
title = '[code]'
|
||||||
|
url = 'https://git.nxhs.cloud/ge/'
|
||||||
|
target = '_blank'
|
||||||
|
[[site.links]]
|
||||||
|
title = '[kb]'
|
||||||
|
url = '/kb/'
|
||||||
|
target = '_blank'
|
||||||
|
[[site.links]]
|
||||||
|
title = '[markdown]'
|
||||||
|
url = '/writing/'
|
||||||
|
target = '_blank'
|
||||||
|
[[site.links]]
|
||||||
|
title = '[my ip]'
|
||||||
|
url = '/cgi-bin/ip.cgi'
|
||||||
|
target = '_blank'
|
||||||
|
|
||||||
|
[site.footer]
|
||||||
|
text = ''
|
||||||
|
copyright = ''
|
||||||
|
|
||||||
|
[site.og]
|
||||||
|
image = 'https://nixhacks.net/img/DEC_VT100_terminal.jpg'
|
84
static/css/pygments/rrt.css
Normal file
84
static/css/pygments/rrt.css
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
pre { line-height: 125%; }
|
||||||
|
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
|
||||||
|
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
|
||||||
|
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
|
||||||
|
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
|
||||||
|
.highlight .hll { background-color: #0000ff }
|
||||||
|
.highlight { background: #000000; color: #dddddd }
|
||||||
|
.highlight .c { color: #00ff00 } /* Comment */
|
||||||
|
.highlight .err { color: #dddddd } /* Error */
|
||||||
|
.highlight .esc { color: #dddddd } /* Escape */
|
||||||
|
.highlight .g { color: #dddddd } /* Generic */
|
||||||
|
.highlight .k { color: #ff0000 } /* Keyword */
|
||||||
|
.highlight .l { color: #dddddd } /* Literal */
|
||||||
|
.highlight .n { color: #dddddd } /* Name */
|
||||||
|
.highlight .o { color: #dddddd } /* Operator */
|
||||||
|
.highlight .x { color: #dddddd } /* Other */
|
||||||
|
.highlight .p { color: #dddddd } /* Punctuation */
|
||||||
|
.highlight .ch { color: #00ff00 } /* Comment.Hashbang */
|
||||||
|
.highlight .cm { color: #00ff00 } /* Comment.Multiline */
|
||||||
|
.highlight .cp { color: #e5e5e5 } /* Comment.Preproc */
|
||||||
|
.highlight .cpf { color: #00ff00 } /* Comment.PreprocFile */
|
||||||
|
.highlight .c1 { color: #00ff00 } /* Comment.Single */
|
||||||
|
.highlight .cs { color: #00ff00 } /* Comment.Special */
|
||||||
|
.highlight .gd { color: #dddddd } /* Generic.Deleted */
|
||||||
|
.highlight .ge { color: #dddddd } /* Generic.Emph */
|
||||||
|
.highlight .gr { color: #dddddd } /* Generic.Error */
|
||||||
|
.highlight .gh { color: #dddddd } /* Generic.Heading */
|
||||||
|
.highlight .gi { color: #dddddd } /* Generic.Inserted */
|
||||||
|
.highlight .go { color: #dddddd } /* Generic.Output */
|
||||||
|
.highlight .gp { color: #dddddd } /* Generic.Prompt */
|
||||||
|
.highlight .gs { color: #dddddd } /* Generic.Strong */
|
||||||
|
.highlight .gu { color: #dddddd } /* Generic.Subheading */
|
||||||
|
.highlight .gt { color: #dddddd } /* Generic.Traceback */
|
||||||
|
.highlight .kc { color: #ff0000 } /* Keyword.Constant */
|
||||||
|
.highlight .kd { color: #ff0000 } /* Keyword.Declaration */
|
||||||
|
.highlight .kn { color: #ff0000 } /* Keyword.Namespace */
|
||||||
|
.highlight .kp { color: #ff0000 } /* Keyword.Pseudo */
|
||||||
|
.highlight .kr { color: #ff0000 } /* Keyword.Reserved */
|
||||||
|
.highlight .kt { color: #ee82ee } /* Keyword.Type */
|
||||||
|
.highlight .ld { color: #dddddd } /* Literal.Date */
|
||||||
|
.highlight .m { color: #dddddd } /* Literal.Number */
|
||||||
|
.highlight .s { color: #87ceeb } /* Literal.String */
|
||||||
|
.highlight .na { color: #dddddd } /* Name.Attribute */
|
||||||
|
.highlight .nb { color: #dddddd } /* Name.Builtin */
|
||||||
|
.highlight .nc { color: #dddddd } /* Name.Class */
|
||||||
|
.highlight .no { color: #7fffd4 } /* Name.Constant */
|
||||||
|
.highlight .nd { color: #dddddd } /* Name.Decorator */
|
||||||
|
.highlight .ni { color: #dddddd } /* Name.Entity */
|
||||||
|
.highlight .ne { color: #dddddd } /* Name.Exception */
|
||||||
|
.highlight .nf { color: #ffff00 } /* Name.Function */
|
||||||
|
.highlight .nl { color: #dddddd } /* Name.Label */
|
||||||
|
.highlight .nn { color: #dddddd } /* Name.Namespace */
|
||||||
|
.highlight .nx { color: #dddddd } /* Name.Other */
|
||||||
|
.highlight .py { color: #dddddd } /* Name.Property */
|
||||||
|
.highlight .nt { color: #dddddd } /* Name.Tag */
|
||||||
|
.highlight .nv { color: #eedd82 } /* Name.Variable */
|
||||||
|
.highlight .ow { color: #dddddd } /* Operator.Word */
|
||||||
|
.highlight .pm { color: #dddddd } /* Punctuation.Marker */
|
||||||
|
.highlight .w { color: #dddddd } /* Text.Whitespace */
|
||||||
|
.highlight .mb { color: #dddddd } /* Literal.Number.Bin */
|
||||||
|
.highlight .mf { color: #dddddd } /* Literal.Number.Float */
|
||||||
|
.highlight .mh { color: #dddddd } /* Literal.Number.Hex */
|
||||||
|
.highlight .mi { color: #dddddd } /* Literal.Number.Integer */
|
||||||
|
.highlight .mo { color: #dddddd } /* Literal.Number.Oct */
|
||||||
|
.highlight .sa { color: #87ceeb } /* Literal.String.Affix */
|
||||||
|
.highlight .sb { color: #87ceeb } /* Literal.String.Backtick */
|
||||||
|
.highlight .sc { color: #87ceeb } /* Literal.String.Char */
|
||||||
|
.highlight .dl { color: #87ceeb } /* Literal.String.Delimiter */
|
||||||
|
.highlight .sd { color: #87ceeb } /* Literal.String.Doc */
|
||||||
|
.highlight .s2 { color: #87ceeb } /* Literal.String.Double */
|
||||||
|
.highlight .se { color: #87ceeb } /* Literal.String.Escape */
|
||||||
|
.highlight .sh { color: #87ceeb } /* Literal.String.Heredoc */
|
||||||
|
.highlight .si { color: #87ceeb } /* Literal.String.Interpol */
|
||||||
|
.highlight .sx { color: #87ceeb } /* Literal.String.Other */
|
||||||
|
.highlight .sr { color: #87ceeb } /* Literal.String.Regex */
|
||||||
|
.highlight .s1 { color: #87ceeb } /* Literal.String.Single */
|
||||||
|
.highlight .ss { color: #87ceeb } /* Literal.String.Symbol */
|
||||||
|
.highlight .bp { color: #dddddd } /* Name.Builtin.Pseudo */
|
||||||
|
.highlight .fm { color: #ffff00 } /* Name.Function.Magic */
|
||||||
|
.highlight .vc { color: #eedd82 } /* Name.Variable.Class */
|
||||||
|
.highlight .vg { color: #eedd82 } /* Name.Variable.Global */
|
||||||
|
.highlight .vi { color: #eedd82 } /* Name.Variable.Instance */
|
||||||
|
.highlight .vm { color: #eedd82 } /* Name.Variable.Magic */
|
||||||
|
.highlight .il { color: #dddddd } /* Literal.Number.Integer.Long */
|
122
static/css/style.css
Normal file
122
static/css/style.css
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
:root {
|
||||||
|
--b: #000;
|
||||||
|
--w: #fff;
|
||||||
|
--a: #0b6adc;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--b: #fff;
|
||||||
|
--w: #000;
|
||||||
|
--a: #0b6adc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
color: var(--b);
|
||||||
|
background-color: var(--w);
|
||||||
|
padding: 1rem;
|
||||||
|
max-width: 79ch;
|
||||||
|
font-family: 'Source Code Pro', monospace;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
margin: 2rem 0 auto;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
h1 { font-size: 3.6rem; line-height: 1.2; letter-spacing: -.1rem;}
|
||||||
|
h2 { font-size: 3.0rem; line-height: 1.25; letter-spacing: -.1rem; }
|
||||||
|
h3 { font-size: 2.6rem; line-height: 1.3; letter-spacing: -.1rem; }
|
||||||
|
h4 { font-size: 2.0rem; line-height: 1.35; letter-spacing: -.08rem; }
|
||||||
|
h5 { font-size: 1.6rem; line-height: 1.5; letter-spacing: -.05rem; }
|
||||||
|
h6 { font-size: 1.2rem; line-height: 1.6; letter-spacing: 0; }
|
||||||
|
p {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: var(--b);
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: var(--a);
|
||||||
|
}
|
||||||
|
ol, ul {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
section#posts {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
ul#posts li {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: .5rem 0;
|
||||||
|
}
|
||||||
|
ul#posts {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
dt { /* Defenition list title */
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
span.meta {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
span.docutils.literal { /* Inline literal */
|
||||||
|
background-color: #d0d7de;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0 4px 2px 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
font-size: 85%;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
padding: .2rem .5rem;
|
||||||
|
margin: 0 .2rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
background: #f1f1f1;
|
||||||
|
border: 1px solid #e1e1e1;
|
||||||
|
}
|
||||||
|
pre > code {
|
||||||
|
display: block;
|
||||||
|
padding: 1rem;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
blockquote {
|
||||||
|
padding: 0 1em;
|
||||||
|
border-left: .25em solid #d0d7de;
|
||||||
|
}
|
||||||
|
table, th, td {
|
||||||
|
border: 1px solid;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-color: #afb4b9;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
font-weight: 600;
|
||||||
|
background-color: #d0d7de;
|
||||||
|
}
|
||||||
|
td p {
|
||||||
|
margin: .5rem 0;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
color: #d0d7de;
|
||||||
|
}
|
||||||
|
.admonition {
|
||||||
|
padding: .5rem 1rem;
|
||||||
|
}
|
||||||
|
.admonition-title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.highlight {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.highlight pre {
|
||||||
|
padding: 1rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
BIN
static/img/DEC_VT100_terminal.jpg
Normal file
BIN
static/img/DEC_VT100_terminal.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 293 KiB |
Loading…
Reference in New Issue
Block a user