# Редактор acme: пишем почтовый клиент
hugeping(ping,1) — All
2021-10-13 17:46:59


Медленно, но верно, редактор acme становится моим основным редактором-средой в Linux. Почему это происходит, вопрос отдельный и он не сводится к утилитарности. Проще, удобнее, быстрее -- это всё категории, которые в большей степени определяются нашими привычками. А мной в IT движет любопытство и тяга к простоте.

Идея acme очень простая, но при этом мощная. Это не редактор, это прослойка между Unix средой и человеком. Когда вы работаете с acme, "редактором" становится вся ОС. В начале это очень непривычно, но потом -- затягивает.

До последнего момента в качестве основного редактора я пользовался emacs и пользовался им как средой. То-есть, кроме собственно редактирования файлов я читал в нём почту (mu4e), общался в телеграм (telega.el), читал pdf ну и так далее...

Отвыкнуть от emacs очень сложно, а мне было интересно проводить в acme больше времени, поэтому я решил попробовать перенести в него работу с почтой.

Вероятно, можно было бы завести upas, который есть в составе plan9port, но мне этот вариант не очень подходит. Потому что я параноик. Так как почтовые серверы мне не принадлежат у меня есть непреодолимое желание хранить копию своих почтовых ящиков локально на диске. Кроме того, это даёт возможность быстро искать нужное письмо. Поэтому я пошёл другим путём.

# Синхронизация почты mbsync

Для синхронизации почты между Maildir на диске и imap на сервере нашлась отличная штука: isync (или mbsync). Замечательна она тем, что синхронизация работает в обе стороны. То-есть, удаляя письмо в Maildir вы тем самым удаляете его в imap mailbox. Ну и так далее. Таким образом, вы получаете единый срез почты на многих машинах и всё это прекрасным образом синхронизируется через imap.

Конфигурация выглядит примерно так:

IMAPAccount gmail
Host imap.gmail.com
User user@gmail.com
Pass password
SSLType IMAPS
CertificateFile /etc/ssl/certs/ca-certificates.crt

IMAPStore gmail-remote
UseUTF8Mailboxes yes
Account gmail

MaildirStore gmail-local
SubFolders Verbatim
Path ~/Mail/gmail/
Inbox ~/Mail/gmail/Inbox

Channel gmail
Far :gmail-remote:
Near :gmail-local:
Patterns * ![Gmail]* "[Gmail]/Sent Mail" "[Gmail]/9front"
Create Both
Expunge Both
SyncState *

Правда, mbsync из апстрима создаёт каталоги на диске в кодировке UTF-7. Но в aur есть пакет с поддержкой UTF-8. Обратите внимание на UseUTF8Mailboxes в конфиге. Для других Linux можно собрать версию отсюда: https://sourceforge.net/u/shashurup/isync/ci/utf8-mailboxes/tree/

После того, как всё настроили, можно поставить задачу на таймер в systemd или cron и всё.

# Индексация

Не так давно я открыл для себя mu. Mu позволяет индексировать почту в Maildir и дальше делать выборку, показ писем, распаковку аттачей и так далее. И всё это очень быстро. Вместе с mu идёт почтовый клиент для emacs -- mu4e.

Для создания базы просто делаем что-то вроде:

$ mu init --my-address='ваш емейл'

И потом периодически делаем индексацию:

$ mu index

# Идея почтового клиента на acme

Mu -- это почти полноценный клиент, по крайней мере, для чтения почты. Практически всё можно сделать из командной строки. Например, вывести последние сообщения:

$ mu find --sortfield d --reverse "" | head -n10

Чтобы просмотреть сообщение, вы должны указать путь к конкретному файлу в Maildir. Например:

mu view `mu find "" --sortfield d --reverse -f l | head -n1`

Конечно, пользоваться в таком виде почтой малореально, но возможностей для скриптования -- масса. И когда я это понял, то решил написать свой фронтенд к mu для acme.

# Приложение на acme

Acme с помощью файловой системы предоставляет доступ к некоторым функциям по работе с своими окнами, которых оказывается достаточно для написания "приложений". Если вы работаете в Plan9, то файловая система доступна всегда. Если же вы запускаете acme в рамках plan9port, то для доступа к ней можно:

1. Использовать утилиту 9p
2. Подмонтировать ФС через fuse: 9pfuse

Допустим, у нас есть скрипт hello, который доступен по PATH. Если запустить acme и вписать hello в заголовок главного окна (там где Newcol Kill Putall Dump..), а потом нажать на hello 2-й кнопкой мыши, то скрипт запустится.

Когда скрипт запускается в рамках acme, то переменная окружения winid содержит номер текущего окна или 0, если нет никаких открытых окон, кроме главного.

Для работы с файловой системы acme пока будем пользоваться 9p.

#!/bin/sh
9p ls acme
9p read acme acme/$winid/tag

Запустите этот вариант скрипта и увидите в отдельном окне корень ФС acme и список пунктов меню вашего текущего активного окна (если такое есть).

То же самое можно сделать с fuse:

#!/bin/sh
mnt=`mktemp -d /tmp/acmeXXXX`
9pfuse `namespace`/acme $mnt

ls $mnt
cat $mnt/$winid/tag

fusermount -u "$mnt" && rmdir "$mnt"

На самом деле, в теории, fuse вариант удобнее и быстрее, но я столкнулся с проблемой. Мой ноутбук с ArchLinux не хотел уходить в сон, пока есть хоть одна подмонтированная точка fuse. Так что приходится всё время монтировать и размонтировать, что не очень удобно.

В man 4 acme (из plan9port) описана файловая система acme. Я не буду здесь пересказывать эту информацию, но отмечу только некоторые моменты с которыми столкнулся.

1. По идее, мы можем считать позицию селектора (курсор + выделение) с помощью записи в ctl строки addr=dot и и последующего чтения из addr. Однако, при открытии addr он каждый раз ресетится. Таким образом, с помощью 9p вы не сможете получить текущую позицию курсора, так как это две операции: запись addr=dot и чтение addr. А нужно, открыть (и не закрывать addr), потом записать addr=dot, потом прочитать addr. Это можно проделать при использовании 9pfuse. Например:

pos=`{ echo 'addr=dot' >> $mnt/$winid/ctl; cat; }<$mnt/$winid/addr`

2. Если добавлять в окно текст (в data), содержащий в себе переводы строк, то просто так читать из event построчно не получится. Потому что первая строка будет содержать событие, а следующая -- уже просто кусок текста и так далее. Да, число символов текстового блока тоже при этом приходит, но писать обработку такого протокола на shell неудобно. Поэтому я добавлял текст только построчно.

# Версия на shell

Первую черновую версию я написал на shell. Всё, что она делала -- показывала последние 100 писем и реагировала на нажатие средней кнопки мыши на путь к письму (при этом, нужно было сначала этот путь выделить). Она была неудобна, неполна. Но очень проста. Поэтому, в качестве иллюстраций я привожу этот вариант целиком:

#!/bin/sh
mail_view()
{
	mu view "$MAILDIR/$2" --nocolor | 9p write acme/$1/data
	echo -n 'clean' | 9p write acme/$1/ctl
	toline $1 0
}

mail_ls()
{
	mu find --nocolor -s d --reverse -f "l|d|f|s" "" | \
		/usr/bin/sed -e 's|'$MAILDIR'/\([^ ]\+\)|\1|g' | \
		head -n 100 | 9p write acme/$1/data
	echo -n 'clean' | 9p write acme/$1/ctl
	toline $1 0
}

toline()
{
	echo -n "$2" | 9p write acme/$1/addr
	echo -n 'dot=addr' | 9p write acme/$1/ctl
	echo -n 'show' | 9p write acme/$1/ctl
}

if [ -z "$winid" ]; then
	exit 1
fi

# создаём новое окно
winid=`9p read acme/new/ctl | awk '{ print $1 }'`
# показываем 100 сообщений
mail_ls $winid
# добавляем "кнопку" Get
echo -n "Get" | 9p write acme/$winid/tag

# цикл обработки событий
9p read acme/$winid/event | while read a b c d e; do
	if echo "$a" | grep -q -e '^Mx' 2>/dev/null; then # mx
		if [ "$e" = "Get" ]; then
			mail_ls $winid
			continue
		fi
	elif echo "$a" | grep -q -e '^ML' 2>/dev/null; then
		if [ -f "$MAILDIR/$e" ]; then
			msgid=`9p read acme/new/ctl | awk '{ print $1 }'`
			mail_view $msgid $e
			continue
		fi
	fi
	echo "$a $b" | 9p write acme/$winid/event 2>/dev/null
done

Кстати, этот текст был вставлен в статью так:

- ввел в tagline <cat email;
- выделил этот текст;
- нажал 2ю кнопку мыши.

Обратите внимание на цикл обработки событий. Мы просто ждём их, читая из event и реагируем нужным образом. Или снова перечитываем сообщения, или открываем текст нужного сообщения в новом окне.

Не смотря на некоторые проблемы, о которых я узнал позже, скрипт как-то работал. Однако я быстро понял, что сложную логику писать на shell не очень весело, поэтому я перешёл на Lua. Вероятно, на go было бы ещё проще, но мне хотелось сделать всё "здесь и сейчас"...

# Версия на Lua

То, что в итоге получилось, можно посмотреть тут: https://github.com/gl00my/plan9hacks/blob/master/linux/mu-query

Скрипт довольно грязный, но он работает и делает именно то, что мне нужно. Я не буду подробно описывать что именно в нём происходит, отмечу только некоторые моменты.

1. Для отправки почты используется msmtp.

defaults
auth on
tls on
logfile ~/.msmtp.log

account gmail
host smtp.gmail.com
port 587
tls_certcheck off
from user@gmail.com
user user@gmail.com
password password

account default : gmail

2. mu-query умеет показывать запросы mu. Например, вводим в tagline: mu-query from:ivanov, выделяем, нажимаем 2-ю кнопку мыши и видим нужный результат. Если не указывать запрос, показываются последние 100 сообщений. Можно листать по 100 сообщений командой Page.

3. При просмотре сообщения все аттачи распаковываются во временную директорию. Например, если я вижу, что это html письмо, я могу дописать firefox file:/// перед путём к файлу прямо в теле сообщения и нажать.

4. На данный момент поддерживается только Reply, составление нового сообщения, удаление и пометка сообщений как Seen. Forward и добавлений аттачей нет. Это несложно добавить, но пока я не стал этого делать. Может быть, будет третья итерация уже на go. :)

5. Чтобы удалить пачку сообщений или пометить их как прочитанные нужно выделить все нужные сообщения и нажать на соответствующую "кнопку".

На данный момент я пользуюсь mu-query на постоянной основе и очень доволен. Мне действительно это удобно. И главное, я понял что относительно просто могу писать под себя интерфейсы. Например, я уже задумываюсь о команде git-log.

# Опыт acme

Если говорить о практической пользе, то кроме "персонализации" инструментария я получаю с acme тот же эффект, что получаю от Plan9. У меня разгружается сознание. Когда я работаю в emacs, мой мозг перегружен информацией о множестве клавиатурных комбинаций и конкретных рецептах. Если брать другие редакторы, то в целом, ситуация повторяется. Acme в этом плане не похож ни на один из них. Его "базис" очень простой, единообразный и при этом всё ещё мощный. Настоящий Unix инструмент!

Когда я первый раз увидел acme, я не мог даже себе представить, что в _этом_ можно работать. Что же, ещё один повод напомнить себе, что встречать по одёжке не стоит.

Кстати, об одёжке. Уверен, у 99% людей пропадает интерес к acme как только они узнают, что в нём нет подстветки синтаксиса. Или, что в этом редакторе нельзя перемещаться по строкам вверх-вниз с помощью клавиатуры... Я зря это сейчас сказал, да? :)

# Re: Редактор acme: пишем почтовый клиент
vvs(ping,12) — hugeping
2021-10-13 18:30:21


hugeping> Кстати, об одёжке. Уверен, у 99% людей пропадает интерес к acme как только они узнают, что в нём нет подстветки синтаксиса. Или, что в этом редакторе нельзя перемещаться по строкам вверх-вниз с помощью клавиатуры... Я зря это сейчас сказал, да? :)

Так ведь почему кому-то нравится Emacs? Причины могут быть разные, но я назову одну: Emacs - это интерфейс к elisp. Есть стремление всё делать средствами одного языка. А ACME - это ведь не язык, а чистый интерфейс и предполагает использование дополнительных средств. С точки зрения языка высокого уровня выгоднее вообще не использовать лишние концепции, такие как ОС.

Есть такая тенденция языковой среды, когда начинается с языка и постепенно переходит к решению его средствами любых задач, включая построение интерфейсов. В Web - это, наверное, JavaScript из которого выросла целая платформа Node.js. То же самое наблюдается в Python, OCaml, Haskell и т.д.

Когда-то в Xerox из этого придумали целую парадигму ОО программирования в стиле Smalltalk (сильно отличающаяся от Simula 67/C++), где вся среда исполнения и интерфейс - это Smalltalk и пользователь меняет эту среду прямо из языка в реальном времени, избегая цикла компиляция-линкование-запуск. До него что-то похожее делали на Lisp, откуда и родился в итоге Emacs.

Концепции же Unix, в целом, довольно низкоуровневые. Кстати, идея ACME пришла из языка Oberon Н.Вирта.

# Re: Редактор acme: пишем почтовый клиент
Andrew Lobanov(tavern,1) — vvs
2021-10-14 05:29:39


vvs> Так ведь почему кому-то нравится Emacs? Причины могут быть разные, но я назову одну: Emacs - это интерфейс к elisp. Есть стремление всё делать средствами одного языка. А ACME - это ведь не язык, а чистый интерфейс и предполагает использование дополнительных средств. С точки зрения языка высокого уровня выгоднее вообще не использовать лишние концепции, такие как ОС.

Как емаксер со стажем, люблю емакс по куче причин. Во-первых, лисп. Я обожаю лисп и ничего не могу с этим поделать. Во-вторых: это классный текстовый интерфейс а-ля Genera только текстовый. А ещё его можно хачить хоть до посинения. Это отдельный кайф.

vvs> Есть такая тенденция языковой среды, когда начинается с языка и постепенно переходит к решению его средствами любых задач, включая построение интерфейсов. В Web - это, наверное, JavaScript из которого выросла целая платформа Node.js. То же самое наблюдается в Python, OCaml, Haskell и т.д.

Дайте программисту Тьюринг-полный язык и он рано или поздно напишет на нём всё.

vvs> Когда-то в Xerox из этого придумали целую парадигму ОО программирования в стиле Smalltalk (сильно отличающаяся от Simula 67/C++), где вся среда исполнения и интерфейс - это Smalltalk и пользователь меняет эту среду прямо из языка в реальном времени, избегая цикла компиляция-линкование-запуск. До него что-то похожее делали на Lisp, откуда и родился в итоге Emacs.

Smalltalk это была действительно крутая концепция. Правда я до сих пор не представляю как можно пользоваться огромной объектной штукой, когда всё мутабельно. Но я особо и не вникал. Так, по верхам pharo тыкал. Кстати, у меня он на старте чистого образа ругается, начиная с 8 версии. Досадно.

vvs> Концепции же Unix, в целом, довольно низкоуровневые. Кстати, идея ACME пришла из языка Oberon Н.Вирта.

А где можно почитать поподробнее? Я про оберон читал, но что-то поверхностное. И про язык и про систему. Интересуют, конечно, обе штуки.

+++ Caesium/0.4 RC1

# Re: Редактор acme: пишем почтовый клиент
Andrew Lobanov(tavern,1) — hugeping
2021-10-14 05:45:53


Большое спасибо за отличную статью. Даже если я не начну использовать acme для почты, полезного и интересного почерпнул из неё не мало.

Вообще люблю твои статьи. Их и читать приятно и интересно, и зачастую полезно.

+++ Caesium/0.4 RC1

# Re: Редактор acme: пишем почтовый клиент
vvs(ping,12) — hugeping
2021-10-14 13:25:21


Если кого-нибудь интересует практическая информация по языкам программирования, то проще начинать с википедии. Конкретно по Оберону там упоминается пару порталов на русском:

https://oberon.org/ru
https://oberoncore.ru/library/start

Я сам запускал Оберон лет двадцать назад. Меня тогда поразил его визуальный интерфейс. Когда гораздо позже я увидел ACME, то сразу вспомнил об Обероне. Поискав информацию убедился, что Пайк действительно позаимствовал идею оттуда. Помню, что тогда там не было никакой системы управления версиями и я качал исходники Оберона поштучно в браузере. Они даже должны у меня где-то до сих пор валяться. Но других интересных языков было много, поэтому именно Оберону я уделил мало внимания и до чтения литературы даже не добрался.

Lisp и Smalltalk - это отдельная песня. По ним я даже прочитал учебную литературу. Начинал с GNU Smalltalk, потом сразу перешёл на Squeak. Его пытался использовать на практике, в качестве рабочей системы. Пробовал разобраться с его виртуальной машиной, но меня отпугнула жутко запутанная система её построения. Еще у Squeak очень специфичная система управления версиями, в которой ведётся разработка. Поддержка Git там появилась только недавно и тоже довольно непривычная. Насколько я понял, Squeak больше ориентирован именно на конечного пользователя, чем Pharo. В своё время я постепенно переходил с Windows 98 на RedHat Linux несколько лет, когда использовал их параллельно и это был непростой опыт. На Smalltalk же перейти так и не удалось - не хватило мотивации и дальше отдельных экспериментов дело не пошло. Сейчас же я сижу на NixOS и там пока нет поддержки Squeek 5.x.

Тогда меня интересовали чисто инженерные аспекты компьютерных наук. Сейчас же меня интересует, в основном, логика и теория формальных языков, да и то не как самоцель, а как средство более глубокого понимания других наук, которые можно на них формулировать. Так что мне сейчас интересней устройство компиляторов, а не их практическое использование. Впрочем, исключение составляют языки с зависимыми типами, такие как Coq, Lean или Agda. На них можно формализовывать различные математические теории и доказывать теоремы, что мне и нужно. Так что в данный момент штудирую теоретическую литературу, коей хватит на несколько лет, а временами решаю задачки по логике, что лучше любой игры. На эксперименты с другими языками времени уже не остаётся - это всё больше в прошлом.

P.S. Кстати, Lean использует Emacs или VSCode в качестве интерфейса пользователя. В отличие от многих других языков, здесь это не просто удобство, а совершенно необходимая часть взаимодействия с языком.
P.S. Edited: 2021-10-14 14:43:45

# Re: Редактор acme: пишем почтовый клиент
hugeping(ping,1) — hugeping
2023-05-24 09:32:15


Давно хотел переписать свой клиент на Go или на Python. И всё-таки переписал: https://github.com/hugeping/plan9hacks/blob/master/linux/mu-query.py

Код у него почище, чем грязная реализация на Lua и его можно понять. ;)

Интересно, что не смотря на то, что python реализация на ~250 строк меньше, по объёму исходников оба скрипта очень близки. И это не смотря на выразительность Python и тот факт, что в Lua варианте пришлось многие функции писать самому...

Прошло уже довольно много времени, а я всё-так же пользуюсь для работы с почтой acme + mu + mbsync + msmtp. Кстати, в апстрим mbsync уже всё хорошо с UTF-8, так что патчить его больше не надо.

https://club.hugeping.ru/lib/uploads/mu-query.png

# Re: Редактор acme: пишем почтовый клиент
vvs(ping,12) — hugeping
2023-05-24 13:31:09


hugeping> Код у него почище, чем грязная реализация на Lua и его можно понять. ;)

hugeping> Интересно, что не смотря на то, что python реализация на ~250 строк меньше, по объёму исходников оба скрипта очень близки. И это не смотря на выразительность Python и тот факт, что в Lua варианте пришлось многие функции писать самому...

У людей бывает очень разное понятие "выразительности", смотря о каком уровне абстракции идёт речь. Вот этот пример сортировки пузырьком на языке Wolfram мне представляется очень выразительным:
sortRule := {x___,y_,z_,k___} /; y>z -> {x,z,y,k}
{ 9, 5, 3, 1, 2, 4 } //. sortRule
(* = {1, 2, 3, 4, 5, 9} *)

Является ли выразительным машинный код? А FRACTRAN Конвея? А теория множеств?

P.S. Это я не для того чтобы кому-то возразить, а просто предлагаю посмотреть на красоту языка с разных сторон :) И кстати есть ведь ещё разница между синтаксисом и собственно семантикой. Как насчёт лиспа?

# Re: Редактор acme: пишем почтовый клиент
Andrew Lobanov(tavern,1) — hugeping
2023-05-25 06:23:46


hugeping> Давно хотел переписать свой клиент на Go или на Python. И всё-таки переписал: https://github.com/hugeping/plan9hacks/blob/master/linux/mu-query.py
hugeping> Код у него почище, чем грязная реализация на Lua и его можно понять. ;)
hugeping> Интересно, что не смотря на то, что python реализация на ~250 строк меньше, по объёму исходников оба скрипта очень близки. И это не смотря на выразительность Python и тот факт, что в Lua варианте пришлось многие функции писать самому...

Тоже заметил, что вроде на пайтоне и меньше строк, но объём кода на тех же задачах примерно тот же выходит. Правда я с Go сравнивал :)

+++ Caesium/0.4 RC1

# Re: Редактор acme: пишем почтовый клиент
hugeping(ping,1) — vvs
2023-05-27 20:03:16


vvs> У людей бывает очень разное понятие "выразительности", смотря о каком уровне абстракции идёт речь.

Согласен. Я это скорее в ироничной форме сказал. В том смысле, что в одну строку питона можно упихать кучу разных вещей. :)

# Re: Редактор acme: пишем почтовый клиент
vvs(ping,12) — hugeping
2023-05-27 20:31:48


Кому хочется впихнуть как можно больше, могут взглянуть на "Hello, world" в ELF всего 114 байт (это я знаю достаточно давно). А вот сегодня встретил компиляторы с Forth, Lisp и C всего 512 байт!

https://github.com/cesarblum/sectorforth
https://github.com/jart/sectorlisp
https://github.com/xorvoid/sectorc