№30. Написать клиент «Типографа» на языке Rust

Теги: #100 #rust

Это статья рассказывает об одном пункте лайфлиста — списка из 100 клёвых вещей, которые я хочу попробовать.

Мне давно хотелось что-нибудь написать на Rust'е. И здесь я подробно рассказываю о первом опыте.

Зачем нужен «Типограф»?

«Типограф» — это сервис, написанный «Студией Лебедева». Он помогает делать текст легко читаемым:

  • расставляет кавычки «ёлочки»;
  • меняет дефисы на длинные тире;
  • расставляет символы неразрывных пробелов, чтобы не получалось «висячих» предлогов в конце строки;
  • ...и, наверняка, делает ещё много полезного.

Артемий Лебедев написал о правилах типографики, которые легли в основу сервиса в 62-м параграфе Ководства.

Я пользуюсь им всегда, когда пишу в блог.

У Типографа есть веб-версия, но мне не хотелось запускать браузер каждый раз, когда что-то нужно оттипографировать. Поэтому я решил сделать утилиту командной строки и получать красивый текст сразу, как только сохранил его.

Почему Rust?

Rust мне любопытен по трём причинам: из-за статической типизации, интересных хаскелоподобных конструкций, плюс новой для меня концепции владения-заимствования.

№ 1. Статическая типизация

Я кайфую, когда язык задаёт жесткие правила игры, и просто нельзя вернуть из int-овой функции None, String или обвалиться с исключением, никак не отразив это в типе функции.

Это есть в Rust, это есть в Haskell, это есть в Swift.

Написание кода похоже на детскую игру «Геометрический сортер»:

«Раз у меня в руках треугольник, а тут квадратная прорезь — значит нужно взять переходник из треугольников в квадраты».

№ 2. Это как новый Haskell

Потому что:

  1. Ругается на косяки ещё до рантайма (см. предыдущий пункт).
  2. В Rust есть тип Option, похожий на Maybe из Haskell.
  3. Там есть паттерн-мэтчинг.
  4. Там есть трейты.

Любой из этих пунктах можно найти в других языках программирования (а тип Option есть даже в C++). Но когда все они встречаются в одном языке, такой язык я воспринимаю крайне положительно и сразу хочу написать Типограф :)

Новый ли это Haskell? — нет, это шутка.

Но мне, определенно, нравится, что элменты дизайна, привычные для функционального программирования, появились в языке, рассчитанном на скрупулезную работу с памятью.

№ 3. Владение и заимствование

Это «фишка» языка и совершенно новая концепция для меня. Она мне показалась интересной.

Рекомендую почитать официальные доки про владение и заимствование, они совсем не сложны для понимания и весьма полезны.

Если хотите обсудить что-то из этих трёх пунктов или добавить ещё что-нибудь, добро пожаловать в комменты :)

Постановка задачи

Написать программу, чтобы:

  • больше не надо было ходить в веб-версию;
  • можно было типографировать «на лету» — при нажатии Ctrl+S или автосохранении в текстовом редакторе.

Реализация

Кодом поделился на гитхабе. Буду рад, если риквестуете фичу или найдете баг.

Общие впечатления

В принципе ничего особенно сложного в процессе кодирования не было: я взял за основу реализацию на Python, выложенную на сайте студии.

Там всё очень просто:

  1. Сформировали XML-запрос с помощью конкатенации.
  2. Открыли сокет.
  3. Отправили запрос в сокет.
  4. Прочитали из сокета ответ.

Из интересного — добавил настройку для pre-commit, которую хочу использовать в других проектах: https://github.com/nskeip/typograf-client/blob/master/.pre-commit-config.yaml.

Какие редакторы попробовал:

  1. VS Code.
  2. PyCharm (вообще подойдет любая IDE от JetBrains).
  3. CLion.

Все работают здорово, никаких проблем не было замечено. В платный CLion пересел только потому что там дебаггер, к которому я привык, и лень было разбираться с консольным (а надо бы).

Что добавил

Чего не было в прототипе и появилось в моей версии:

  • Параметры командной строки с помощью structopt.
  • Возможность редактировать текст in-place, а не только выводить в stdout.
  • Возможность работать с форматом Mardown для блогового движка Zola — не типографировать блок мета-данных в начале файла.

Косяки

Ну, как без них.

  1. С непривычки: создал строку для чтения файла в неё, начал во всю использовать, но забыл поместить в нее содержимое файла. Здесь сразу не понял, в чем дело — только дебаггер помог :)
  2. Не закрыл тег при приготовлении XML. Вот она, конкатенация! Но библиотеку для «правильного» формирования XML здесь добавлять не хочется, так как не хочется раздувать размер бинарника.
  3. Обсчитался при работе с File.seek и File.set_len, когда делал опцию -s для Zola.

Тут не заметил, там не закрыл — вполне естественные штуки, когда пишешь на новом языке. Считаю, что в отношении ошибок и быстрого их исправления опыт был весьма позитивным.

Полировка

В итоге понял, что работает не так, как мне надо:

  • в конце строк добавлялись <br>,
  • «пули» в списках заменялись на что-то вроде &mdash;&nbsp;.

Это было избыточно для меня, потому что портило Markdown. Если бы я готовил текст в формате HTML, то это было бы норм.

Добавлять новые ключи не хотелось, чтобы не перегружать программку опциями из-за одного пользователя :)

Тем более что есть sed и годная статья на тему https://thoughtbot.com/blog/sed-102-replace-in-place.

В итоге я написал пару регулярок на sed, чтобы удалять избыточные <br> и приводить в порядок списки.

Заключение

«А как же сохранение по нажатию Ctrl+S?» — спросите вы.

Всё нормально, оно работает.

Я не стал шаманить его прямо в программе, как и тот костыль с sed’ом. Вместо этого я сделал Makefile, который помогает оттипографировать нужный мне файл командой вида make typograf TARGET=filename.md:

typograf:
	/path/to/typograf-client -i -s --entity-type=1 --use-br 1 $(TARGET)
	sed -i 's/<br \/>//' $(TARGET)
	sed -i 's/^&mdash;&nbsp;/ - /' $(TARGET)

А для того, чтобы типографировать сразу, как только файл изменился, я пользуюсь утилитой entr и указываю, за каким файлом нужно следить:

find -type f -name '*.md' | entr make typograf TARGET=./content/100/rust.md

Есть два важных момента:

  1. Редактор должен поддерживать перезагрузку файла с диска при его изменении.
  2. Команду нужно изменять под каждый новый файл — тут надо просто допрограммировать.

UPDATE: И ещё один момент. Очень жаль, но Типограф делает ошибки в сложных случаях — он не любит, когда вы вручную вставляете последовательность вида &что-нибудь. Тогда типографировать лучше в самом конце, затем вручную исправлять ошибки бездушного робота, и уже его не запускать, иначе он всё опять сломает. В таких ситуациях автоматический запуск при сохранении больше мешает, чем помогает.

Главное, что я действительно упростил себе жизнь и теперь занят только работой над текстом, а типографируется он сам собой.