blog

Source for my blog
git clone git://git.konyahin.xyz/blog
Log | Files | Refs

commit 0c3a676dca1ed509a17a4797d2d9739f1fe6cd39
parent c4309b996189e5b217ddd789a0e7b85e415b43c6
Author: Anton Konyahin <me@konyahin.xyz>
Date:   Sun, 25 Feb 2024 22:23:35 +0300

post: sttemp and others

Diffstat:
Acontent/blog/sttemp.md | 191+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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