blog

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

commit 3fcdf21293668c215e04c707296dcdf5f896ae87
parent 40c241cc3bd5fee43c490fb72fcd3b5e2ad36629
Author: Anton Konyahin <me@konyahin.xyz>
Date:   Sun, 17 Nov 2024 16:34:27 +0300

post: less based gopher browser

Diffstat:
Acontent/blog/less-gopher-browser.md | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcontent/blog/pomodoro-attiny45.md | 2+-
2 files changed, 92 insertions(+), 1 deletion(-)

diff --git a/content/blog/less-gopher-browser.md b/content/blog/less-gopher-browser.md @@ -0,0 +1,91 @@ +--- +title: "less как фронтенд - делаем gopher browser" +date: 2024-11-17T15:31:16+03:00 +--- + +После долгого отсутствия я вернулся к вам, мои несуществующие читатели, с новой интересной идеей - использованием `less`, как фронта для скриптов. +Вы же знаете `less`? Это то приложение, которое позволит вам посмотреть длинный файл в консоли, не мучая прокрутку терминала. В нём можно отобразить текст, что-то поискать, прыгнуть туда или даже сюда. +Так почему бы не воспользоваться всем этим богатством функций в наших скриптах, особенно учитывая, что у `less` есть и чуть менее известная функциональность. А именно: +# lesskey - добавляем интерактивность +У `less` есть концепция горячих клавиш, на которые мы назначаем какую-то функциональность из набора встроенных возможностей. Ознакомиться с ними можно в `man lesskey`, но из всего богатства нас будет интересовать только возможность запускать shell скрипты прямо из `less`. +Биндинги мы будем описывать в обычном текстовом файле, который потом подадим программе на вход, через аргумент командной строки. +Старые версии less (< 582) требовали чтобы файл с биндингами был преобразован в бинарный формат программой `lesskey`, но мы будем использовать только новые, причем желательно собранные вручную [из исходников](https://www.greenwoodsoftware.com/less/). Это будет полезно ещё и потому, что системная версия `less` обычно собирается с прицелом на безопасность, и в ней выключена, или ограничена, возможность использования `lesskey`. Например, в OpenBSD `less` собран без поддержки запуска shell скриптов, а версия из MacOS вообще лишена возможности использовать свои биндинги. Ни на одной из них наш скрипт работать не будет. +# Протокол "суслик" +С помощью `less` (и небольшого shell скрипта), мы выйдем в интернет и будем посещать там сайты, работающие на протоколе gopher. Это старый протокол, который когда-то был альтернативой www и предназначался для организации иерархичного доступа к документам в сети. Протокол позволяет делать директории, содержащие ссылки на файлы в определенных форматах и другие (саб)директории. +Синтаксис формата очень прост. Каждая запись в директории занимает одну строку, первый символ которой обозначает тип контента, на который она ссылается. Мы будем поддерживать три типа: +- 0 текстовый файл +- 1 другая директория +- i информационное сообщение (это нестандартный, но очень широко используемый тип, позволяющий выводить текст, не являющийся ссылкой, прямо в директории). + +Стандарт определяет ещё несколько типов, но большая часть из них устарела и не используется сейчас сколько нибудь широко. Ознакомиться с ним можно [в RFC 1436](https://datatracker.ietf.org/doc/html/rfc1436#section-3.8). Интерес представляет тип 7 - позволяющий сослаться на сервис полнотекстового поиска - аналог google, только для гофера. Из известных поисковых сервисов есть Veronica-2, к которой можно получить доступ через веб прокси [вот здесь](https://gopher.floodgap.com/gopher/gw?ss=gopher%3A%2F%2Fgopher.floodgap.com%3A70%2F7%2Fv2%2Fvs). +После типа идет строка, которая говорит пользователю о том, что же он увидит по этой ссылке, а затем, разделенные знаком табуляции, идут хост и урл самой ссылки. +У формата до сих пор есть активные поклонники, держащие на нем свои сайты и блоги. Обычно это люди, утомленные современным вебом, наслаждающиеся минимализмом, отсутствием js, трекинга и рекламы. +# "Готовое" "решение" - skefir +Наш скрипт представляет собой 63 строки кода, написанного на ANSI Shell (отсутствие башизмов проверял с помощью [shellcheck](https://www.shellcheck.net/)). Он будет принимать на вход сервер и url нужного нам ресурса, скачивать его с помощью `nc` и, если это gopher директория, на лету генерировать набор биндингов, позволяющих перейти к документам в ней. +С полным текстом программы можно ознакомиться на [gist.github.com](https://gist.github.com/konyahin/333216e6343134ca662210e6d4698474), а здесь мы с вами разберем основные моменты, заслуживающие внимания. +```sh +LESSKEY_FILE=$(mktemp -t skefir.XXXXXX) +echo "#command" >> "$LESSKEY_FILE" +TEXT=$(printf "%s\r\n" "$2" | nc "$1" 70) + +[ -z "${NO_MAP:-}" ] && TEXT="$(echo "$TEXT" | viewer)" +echo "$TEXT" | less --lesskey-src="$LESSKEY_FILE" + +rm "$LESSKEY_FILE" +``` +В первой строке мы, с помощью `mktemp`, делаем временный файл, в котором будем хранить набор сгенерированных биндингов. При запуске `less` мы передадим этот файл с помощью аргумента `--lesskey-src`, а после того как пользователь насладится контентом и закроет `less`, мы этот файл удалим. +В переменных `$1` и `$2` у нас лежит хост и адрес контента, к которому мы хотим получить доступ. Протокол говорит, что мы должны открыть TCP соединение с сервером (обычно на 70-ом порту) и отправить туда нужный нам адрес, завершим его символами `\r\n`. Для этого мы используем утилиту `nc`, в которую передаем подготовленную нами строку-адрес и получаем в ответ весь контент, который нам вернул сервер. +TCP соединение может закрыться до того, как весь контент будет передан и у протокола нет никакой возможности дать нам знать, что мы скачали всё что было нужно (что делает в http заголовок Content-Length, например). Из-за этого некоторые авторы ставят в конце своих постов точку на отдельной строке, чтобы пользователь смог визуально убедиться, что у него скачался весь текст. +Переменная `NO_MAP` выставляется в `true`, если на вход программе был передан аргумент `-n`. Он говорит нам о том, что мы сейчас будем запрашивать текстовый файл, а не директорию с навигацией, и парсить его нам совсем не надо. В таком случае мы просто передадим весь вывод `nc` на вход `less`. +А вот если парсить все-таки надо, то в ход пойдет функция `viewer`, которая переформатирует содержимое в человекочитабельный вид и вызовет `link` для генерации тех самых биндингов. Давайте на неё посмотрим. +```sh +viewer () { + LINK=0 + while read -r LINE || [ -n "$LINE" ]; do + case "$LINE" in + i*) + printf " " + echo "$LINE" | awk -F '\t' '{print substr($1, 2)}' + ;; + 1*) + link "$LINK" "$LINE" "" + LINK=$((LINK + 1)) + ;; + 0*) + link "$LINK" "$LINE" "-n" + LINK=$((LINK + 1)) + ;; + *) + ;; + esac + done +} +``` +Мы читаем контент гофер директории строка за строкой и, ориентируясь на первый символ в строке, принимаем решение о том, как нам с ней поступить. +Если в начале символ `i`, то мы парсим строку с помощью `awk`, делим её по знаку табуляции и выводим пользователю только часть до `\t`, откусив от этой части первый символ - букву `i`. После табуляции идет хост и урл, которые не нужны информационному сообщению, так как оно не является ссылкой, но всё равно часто добавляются гофер серверами для обратной совместимости со старыми клиентами. +Если в начале строки стоит 0 или 1, то мы имеем дело либо с ссылкой на текстовый файл, либо с ссылкой на директорию. И для того и для того нам нужно сгенерировать кейбиндинг, который будет запускать новый инстанс `skefir` в текущем терминале. Единственная разница заключается в том, что для текстового файла мы будем прокидывать аргумент `-n`, говорящий скрипту, что парсить контент как директорию не нужно. +```sh +link () { + SHORTCUT=$(echo "$1" | base64 | tr -d '=') + LINE=$2 + ARGS=$3 + + printf "[%5s] " "$SHORTCUT" + echo "$LINE" | awk -F '\t' '{print substr($1, 2)}' + + KEFIR_CALL="$(echo "$LINE" | awk -F '\t' '{printf "%s %s", $3, $2}')" + COMMAND="$SHORTCUT shell skefir $ARGS $KEFIR_CALL" + printf "%s\n" "$COMMAND" >> "$LESSKEY_FILE" +} +``` +Изначально я помечал ссылки цифровыми шорткатами и, для первых десяти ссылок, это работало очень удобно. К сожалению, обработка горячих клавиш в `less` реализована таким образом, что при наличие в списке `1` и, например, `14`, он при вводе всегда будет сразу вызывать первый шорткат, не дожидаясь окончания ввода числа. Для текстовых команд такого не происходит и я не придумал ничего лучше, чем преобразовывать цифры в буквы через `base64`. +В функции `link` мы готовим биндинг в формате +``` +MAo shell skefir gopher.club /phlogs/ +``` +где `MAo` это кейбиндинг, `shell` означает, что мы хотим выполнить команду в терминале, ну а в конце идет эта самая команда, сформированная на основе просмторенной гофер директории. Теперь мы, просматривая гофер директорию в `less`, можем набрать `MAo` и открыть новый `less` с новым набором биндингов, который уже позволит нам передвигаться по новой директории. +Чтобы путешествовать по истории открытых ресурсов назад мы будем просто закрывать новые инстансы `less`. Ну а путешествие вперед у нас не реализовано. +Я выложил на asciinema запись работы `skefir` - [посмотрите, если интересно](https://asciinema.org/a/690137). Можно заметить, как долго открывается вторая страница - это огромный документ со списком всех пользователей, которые когда-либо делали гофер блоги на sdf.org и мы должны получить и спарсить его целиком до начала рендеринга. В нормальном клиенте, мы бы начали выводить контент до полного парсинга, но мы нормальный тут и не делали. +# Выводы +Получилось интересно, но не очень практично. Для комфортного путешествия по сусликам есть куда более классные клиенты с интересными идеями и качественным исполнением, взять хотя бы [VF-1](https://git.sr.ht/~solderpunk/VF-1). +Но свой потенциал у такого использования `less`все-таки есть. Например, если вам нужно просмотреть кучу файлов и выполнить над ними одно из нескольких типовых действий - то `less` с кастомными кейбиндингами может упростить вам жизнь. diff --git a/content/blog/pomodoro-attiny45.md b/content/blog/pomodoro-attiny45.md @@ -1,6 +1,6 @@ --- title: "Помодоро-таймер на Attiny45" -date: 2022-10-16:15:34+03:00 +date: 2022-10-16T16:15:34+03:00 --- Коробка с ардуино и кучкой электронных компонентов стояла у меня в шкафу уже