libnotify также имеет различные дополнительные функции, с которыми мы могли бы поиграть, такие как приоритет уведомления или установка задержки перед отображением уведомления. На самом деле нам не нужны эти функции для нашего CLI, но вы можете свободно исследовать libnotify и настраивать все по своему усмотрению! Далее давайте создадим тип, который поможет отправлять эти экземпляры уведомлений.
Создайте новый файл
require "./lib_notify"
require "./notification"
class Transform: :NotificationEmitter
@@initialized : Bool = false
at_exit { LibNotify.notify_uninit if @@initialized }
def emit(summary : String, body : String) : Nil
self.emit Transform::Notification.new summary, body
end
def emit(notification : Transform::Notification) : Nil
self.init
LibNotify.notify_notification_show notification, nil
end
private def init : Nil
return if @@initialized
LibNotify.notify_init "Transform"
@@initialized = true
end
end
Основным методом этого типа является #emit
, который отображает предоставленное уведомление, гарантируя предварительную инициализацию libnotify. Первая перегрузка принимает сводку и тело, создает уведомление, а затем передает его второй перегрузке. Мы сохраняем статус инициализации libnotify как переменную класса, поскольку он не привязан к конкретному экземпляру NotificationEmitter
. Мы также зарегистрировали обработчик at_exit
, который деинициализирует libnotify перед завершением работы программы, если она была инициализирована ранее.
Также стоит отметить, что обработка инициализации libnotify в многопоточном приложении будет немного более затруднительной, поскольку libnotify необходимо инициализировать только один раз, а не для каждого потока или волокна. Однако, поскольку поддержка многопоточности в Crystal все еще считается экспериментальной, и эта тема немного выходит за рамки рассмотрения, мы просто пропустим этот сценарий. На данный момент мы будем использовать наше приложение. Это не будет проблемой.
Теперь, когда у нас есть абстракции, мы можем перейти к их реализации в нашем CLI.
Интеграция привязок
Учитывая то, что мы сделали в последнем разделе, это будет самая простая часть главы, и останется только один вопрос: какое уведомление мы хотим отправить? Хорошим вариантом использования было бы выдавать его при возникновении ошибки в процессе преобразования. Уведомление привлечет внимание пользователя к тому, что ему необходимо принять меры по поводу чего-то, что в противном случае могло бы остаться незамеченным, если бы ожидалось, что это займет некоторое время.
Теперь вы, возможно, думаете, что мы просто создаем новые экземпляры NotificationEmitter
по мере необходимости и используем их для каждого контекста. Однако мы собираемся применить несколько иной подход. План состоит в том, чтобы добавить инициализатор к нашему типу процессора, который будет хранить ссылку на эмиттер в качестве переменной экземпляра. Это будет выглядеть так: def initialize(@emitter : Transform::NotificationEmitter = Transform::NotificationEmitter.new); end
. Я не буду объяснять причину этого, поскольку она будет рассмотрена в
Давайте сначала сосредоточимся на обработке контекста ошибки. К сожалению, поскольку jq будет выводить сообщения об ошибках непосредственно на IO, ошибок, мы не сможем их обработать. Однако мы можем обрабатывать реальные исключения из нашего кода Crystal. Поскольку мы хотим обрабатывать любые исключения, возникающие в нашем методе #process
, мы можем использовать короткую форму для определения блока rescue
:
rescue ex : Exception
if message = ex.message
@emitter.emit "Oh no!", message
end
raise ex
Этот код должен располагаться непосредственно под последней строкой каждого метода, но перед закрывающим тегом метода. Этот блок спасет любое исключение, возникшее в методе. Затем он отправит уведомление с сообщением об исключении в качестве тела уведомления. Не все исключения имеют сообщение, поэтому мы обрабатываем этот случай, проверяя его перед отправкой уведомления. Наконец, мы повторно вызываем исключение.