Как-то раз, мне захотелось собрать свои заготовки мейкфайлов, скелеты для скриптов и man страниц, тексты лицензий и прочую ерунду в одной директории и научиться удобно их использовать. Я не люблю привязанные к редактору схемы, так что мне нужен был максимально простой консольный менеджер шаблонов. Ну а люблю я костыли и велосипеды, поэтому такой менеджер я решил написать себе сам.
Первая версия была написана на C, использовала максимально простую
реализацию и максимально уродский синтаксис для подстановки значений в
шаблоны. Вот такой вот: {|variable|}
.
Программа искала шаблон по имени в захардкоженной директории, построчно обрабатывала его и, если находила подстановку сначала искала переменную окружения с таким именем, а потом, в случае если она отсутствовала, спрашивала значение у пользователя на stderr.
Почему на stderr? Чтобы stdout можно было перенаправить в файл и всё равно увидеть, что прога чего-то от тебя хочет.
Так она и работал, причем вполне неплохо, чуть больше двух лет, если верить истории коммитов, пока я не решил переписать её на языке Shell. Я хотел упростить синтаксис подстановок, передать почти всю работу envsubst из состава GNU gettext utilities, и реализовать ещё несколько идей, которые позволили мне вокруг простой программы сделать такую же простую и маленькую экосистему использования.
Основная идея не поменялась. Всё так же есть директория с шаблонами,
правда она переехала в $XDG_DATA_HOME/.local/share/sttemp
, в знак
уважения к free desktop, в них есть подстановки, но уже более
привычного вида, в духе $VARIABLE
и мы либо находим их в переменных
окружения, либо спрашиваем у пользователя на stderr.
Shell и envsubst позволили мне сильно сократить размер программы, я
удалил из репозитория 408 строк, вместе с .h и Makefile, и добавил 62,
получив аналогичную программу. Она настолько проста, что не буду
останавливаться на ней подробнее, приведу только одну функцию, которая
будет иметь значение для дальнейшего повествования. А именно - ask
.
Всё что она делает, это принимает на вход имя подстановки из шаблона,
запрашивает её значение у пользователя и возвращает текстом из себя.
ask () {
echo "Enter $1:" >&2
read -r value
echo "$value"
}
Покончив с переписыванием, я двинулся к следующей идее. Я хотел иметь
возможность использовать те же самые шаблоны, по точно такому
сценарию, но уже в GUI приложениях. В редакторе, в браузере, где мне
будет угодно. Так родился скрипт dsttemp
.
Главной проблемой было то, как же мне спросить у пользователя, что он
хочет подставить в шаблон. На помощь пришла утилита dmenu
, от ребят
из suckless. Утилита невероятно простая и настолько же невероятно
полезная. Всё что она делает, это читает со стандартного ввода список
вариантов, разделенных переносом строки, а потом позволяет
пользователю, с помощью ввода какой-то части варианта, выбрать нужный,
который и возвращает. Если вы когда-либо использовали fzf, то
должны понять как оно работает. При этом, если пользователь вводит
что-то не из набора изначальных вариантов, то возвращен будет этот
самый ввод. В итоге, мы можем переписать нашу функцию ask
как-то
так:
ask () {
echo $(dmenu -p "Enter $1" </dev/null)
}
Через -p
мы задаем промпт для пользователя, а перенаправление
/dev/null
на вход нам нужно чтобы никаких вариантов пользователю не
высвечивалось. Осталась одна проблема. Эта функция написана в одном
файле - dsttemp
, а вызывается в совсем другом, в sttemp
.
Для решения этой проблемы я засунул код функции в переменную окружения
STTEMP_ASK
, а в sttemp
добавил следующий код:
[ -n "${STTEMP_ASK:-}" ] && eval "$STTEMP_ASK"
Я уже говорил, что люблю костыли? В итоге, dsttemp
принял следующий
вид:
#!/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
Заголовок я введу сам, чтобы понимать что и для чего я вообще решил сохранить, а вот текст заметки и её источник должны заполняться скриптом на основании того, что и на какой странице в браузере я выделил.
С телом нет никаких проблем, просто считаем выделение в переменную.
export BODY=$(xclip -o)
С источником заметки история чуть более хитрая. Мы опять заскриптуем своё поведение в иксах, а именно нажатие Ctrl + l, для выделения содержимого address bar (у меня Chromium btw), а потом скопируем его и заключим в очередную переменную.
xdotool key --clearmodifiers "Ctrl+l"
xdotool key --clearmodifiers "Ctrl+c"
export URL=$(xclip -o -selection primary)
xdotool key --clearmodifiers "Escape"
Escape мы нажмем просто чтобы вернуться к тому состоянию, в котором
пользователь начал выполнение скрипта. Теперь у нас есть всё, кроме
заголовка, который пользователь заполнит сам. Осталось просто вызвать
sttemp
и сохранить его вывод туда, куда мы хотим.
FILE_NAME=$(date '+%Y-%m-%d-%H%M%S')
sttemp webnote >> "/home/anton/public_gopher/web/$FILE_NAME.md"
Вот собственно и всё. Полный исходный код доступен как на Github, так и на self host git. Зачем я вам всё это рассказал? Меня очаровывает тот факт, что даже простые программы, если объединять их с другими простыми программами, могут давать крутые эффекты, и даже наши действия в GUI можно и нужно скриптовать. А очаровывает ли это вас?