commit 0c3a676dca1ed509a17a4797d2d9739f1fe6cd39
parent c4309b996189e5b217ddd789a0e7b85e415b43c6
Author: Anton Konyahin <me@konyahin.xyz>
Date: Sun, 25 Feb 2024 22:23:35 +0300
post: sttemp and others
Diffstat:
1 file changed, 191 insertions(+), 0 deletions(-)
diff --git a/content/blog/sttemp.md b/content/blog/sttemp.md
@@ -0,0 +1,191 @@
+---
+title: "sttemp - пишем простой менеджер шаблонов"
+date: 2024-02-25T15:11:58+03:00
+---
+
+Как-то раз, мне захотелось собрать свои заготовки мейкфайлов, скелеты
+для скриптов и man страниц, тексты лицензий и прочую ерунду в одной
+директории и научиться удобно их использовать. Я не люблю привязанные
+к редактору схемы, так что мне нужен был максимально простой
+консольный менеджер шаблонов. Ну а люблю я костыли и велосипеды,
+поэтому такой менеджер я решил написать себе сам.
+
+Первая версия была написана на C, использовала максимально простую
+реализацию и максимально уродский синтаксис для подстановки значений в
+шаблоны. Вот такой вот: `{|variable|}`.
+
+Программа искала шаблон по имени в захардкоженной директории,
+построчно обрабатывала его и, если находила подстановку сначала искала
+переменную окружения с таким именем, а потом, в случае если она
+отсутствовала, спрашивала значение у пользователя на stderr.
+
+Почему на stderr? Чтобы stdout можно было перенаправить в файл и всё
+равно увидеть, что прога чего-то от тебя хочет.
+
+Так она и работал, причем вполне неплохо, чуть больше двух лет, если
+верить [истории коммитов][1], пока я не решил переписать её на языке
+Shell. Я хотел упростить синтаксис подстановок, передать почти всю
+работу [envsubst][2] из состава GNU gettext utilities, и реализовать
+ещё несколько идей, которые позволили мне вокруг простой программы
+сделать такую же простую и маленькую экосистему использования.
+
+Основная идея не поменялась. Всё так же есть директория с шаблонами,
+правда она переехала в `$XDG_DATA_HOME/.local/share/sttemp`, в знак
+уважения к [free desktop][3], в них есть подстановки, но уже более
+привычного вида, в духе `$VARIABLE` и мы либо находим их в переменных
+окружения, либо спрашиваем у пользователя на stderr.
+
+Shell и envsubst позволили мне сильно сократить размер программы, я
+удалил из репозитория 408 строк, вместе с .h и Makefile, и добавил 62,
+получив аналогичную программу. Она настолько проста, что не буду
+останавливаться на ней подробнее, приведу только одну функцию, которая
+будет иметь значение для дальнейшего повествования. А именно - `ask`.
+Всё что она делает, это принимает на вход имя подстановки из шаблона,
+запрашивает её значение у пользователя и возвращает текстом из себя.
+
+```sh
+ask () {
+ echo "Enter $1:" >&2
+ read -r value
+ echo "$value"
+}
+```
+
+Покончив с переписыванием, я двинулся к следующей идее. Я хотел иметь
+возможность использовать те же самые шаблоны, по точно такому
+сценарию, но уже в GUI приложениях. В редакторе, в браузере, где мне
+будет угодно. Так родился скрипт `dsttemp`.
+
+Главной проблемой было то, как же мне спросить у пользователя, что он
+хочет подставить в шаблон. На помощь пришла утилита `dmenu`, от ребят
+из [suckless][4]. Утилита невероятно простая и настолько же невероятно
+полезная. Всё что она делает, это читает со стандартного ввода список
+вариантов, разделенных переносом строки, а потом позволяет
+пользователю, с помощью ввода какой-то части варианта, выбрать нужный,
+который и возвращает. Если вы когда-либо использовали [fzf][5], то
+должны понять как оно работает. При этом, если пользователь вводит
+что-то не из набора изначальных вариантов, то возвращен будет этот
+самый ввод. В итоге, мы можем переписать нашу функцию `ask` как-то
+так:
+
+```sh
+ask () {
+ echo $(dmenu -p "Enter $1" </dev/null)
+}
+```
+
+Через `-p` мы задаем промпт для пользователя, а перенаправление
+`/dev/null` на вход нам нужно чтобы никаких вариантов пользователю не
+высвечивалось. Осталась одна проблема. Эта функция написана в одном
+файле - `dsttemp`, а вызывается в совсем другом, в `sttemp`.
+
+Для решения этой проблемы я засунул код функции в переменную окружения
+`STTEMP_ASK`, а в `sttemp` добавил следующий код:
+
+```sh
+[ -n "${STTEMP_ASK:-}" ] && eval "$STTEMP_ASK"
+```
+
+Я уже говорил, что люблю костыли? В итоге, `dsttemp` принял следующий
+вид:
+
+```sh
+#!/usr/bin/env sh
+
+set -eu
+
+export STTEMP_ASK=$(
+ cat <<'EOF'
+ask () {
+ echo $(dmenu -p "Enter $1" </dev/null)
+}
+EOF
+)
+
+TEMPL_NAME=$(sttemp -l | dmenu)
+TEMPL_TEXT=$(sttemp "$TEMPL_NAME")
+
+echo "$TEMPL_TEXT" | xclip -selection clipboard
+xdotool key --clearmodifiers "Shift+Insert"
+```
+
+Сначала мы засовываем в переменную окружения код, которым хотим
+заменить оригинальную, консольную, функцию `ask`. Потом, тоже через
+`dmenu` предлагаем пользователю выбрать шаблон из списка, который
+возвращает `sttemp -l`. Дождавшись, когда пользователь заполнит все
+нужные переменные и к нам попадет итоговый текст, мы передаем его в
+иксовый буфер обмена, из которого вставляем его туда, где стоит курсор
+пользователя. В иксах несколько буферов обмена, возможно конкретный
+буфер и конкретную комбинацию клавиш нужно будет настроить исходя из
+того, каким окружением вы пользуетесь. У меня в `dwm` всё работает как
+часы.
+
+Как видите, мы можем заскриптовать своё поведение в иксах с помощью
+консольных утилит, таких как `xclip` и `xdotool`. Это круто и это
+играет важную роль в следующем, и финальном на сегодня, скрипте.
+
+Я читаю достаточно много статей в браузере и иногда мне хочется
+скопировать часть текста и отправить в свои заметки, чтобы потом над
+этим текстом отдельно подумать. В общем, хотелось что-то вроде Pocket,
+но попроще и чтобы он работал на меня, а не на Mozilla Corporation. И
+конечно же я не хочу ни писать, ни искать браузерное расширение. Я
+предпочитаю расширять Unix, а не браузер. Браузер и так расширился
+дальше некуда.
+
+Для начала я набросал вот такой шаблон, назвав его `webnote`.
+
+```
+# $TITLE
+
+$BODY
+
+Source: $URL
+```
+
+Заголовок я введу сам, чтобы понимать что и для чего я вообще решил
+сохранить, а вот текст заметки и её источник должны заполняться
+скриптом на основании того, что и на какой странице в браузере я
+выделил.
+
+С телом нет никаких проблем, просто считаем выделение в переменную.
+
+```sh
+export BODY=$(xclip -o)
+```
+
+С источником заметки история чуть более хитрая. Мы опять заскриптуем
+своё поведение в иксах, а именно нажатие Ctrl + l, для выделения
+содержимого address bar (у меня Chromium btw), а потом скопируем его и
+заключим в очередную переменную.
+
+```sh
+xdotool key --clearmodifiers "Ctrl+l"
+xdotool key --clearmodifiers "Ctrl+c"
+export URL=$(xclip -o -selection primary)
+xdotool key --clearmodifiers "Escape"
+```
+
+Escape мы нажмем просто чтобы вернуться к тому состоянию, в котором
+пользователь начал выполнение скрипта. Теперь у нас есть всё, кроме
+заголовка, который пользователь заполнит сам. Осталось просто вызвать
+`sttemp` и сохранить его вывод туда, куда мы хотим.
+
+```sh
+FILE_NAME=$(date '+%Y-%m-%d-%H%M%S')
+sttemp webnote >> "/home/anton/public_gopher/web/$FILE_NAME.md"
+```
+
+
+Вот собственно и всё. Полный исходный код доступен как на [Github][6],
+так и на [self host git][7]. Зачем я вам всё это рассказал? Меня
+очаровывает тот факт, что даже простые программы, если объединять их с
+другими простыми программами, могут давать крутые эффекты, и даже наши
+действия в GUI можно и нужно скриптовать. А очаровывает ли это вас?
+
+[1]: https://git.konyahin.xyz/sttemp/log.html
+[2]: https://www.gnu.org/software/gettext/manual/html_node/envsubst-Invocation.html
+[3]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
+[4]: https://suckless.org/
+[5]: https://github.com/junegunn/fzf
+[6]: https://github.com/konyahin/sttemp
+[7]: https://git.konyahin.xyz/sttemp/log.html