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:
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
---
Коробка с ардуино и кучкой электронных компонентов стояла у меня в шкафу уже