litbaza книги онлайнРазная литератураCrystal Programming. Введение на основе проекта в создание эффективных, безопасных и читаемых веб-приложений и приложений CLI - Джордж Дитрих

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 32 33 34 35 36 37 38 39 40 ... 75
Перейти на страницу:
class="p1">Мы собираемся создать подкаталог lib_notify, чтобы хотя бы обеспечить некоторое разделение организации между типами, связанными с привязками, и нашей реальной логикой. Это также облегчит переключение на выделенный сегмент, если мы решим сделать это позже. Давайте создадим новый файл src/lib_notify/lib_notify.cr, который будет содержать код, связанный с привязкой. Обязательно добавьте require “./lib_notify” в файл src/transform.cr.

Поскольку сами привязки не зависят от нашего приложения CLI, мы можем протестировать их независимо. Мы можем сделать это, добавив следующие строки в наш файл привязки, запускающий его, и обязательно удалив этот тестовый код после его запуска:

LibNotify.notify_init "Transform"

notification = LibNotify.notify_notification_new "Hello",

"From Crystal!", nil

LibNotify.notify_notification_show notification, nil LibNotify.notify_uninit

Если все работает правильно, вы должны увидеть уведомление на рабочем столе с заголовком “Привет” и текстом “От Crystal!”. Мы передаем nil аргументам, для которых не имеем значения. Это работает нормально, поскольку эти аргументы являются необязательными, и Crystal автоматически преобразует их в нулевой указатель. Однако это не сработало бы, если бы переменная представляла собой объединение Pointer и Nil. Работа с необработанными привязками функциональна, но не удобна для пользователя. Обычной практикой является определение стандартных типов Crystal, которые обертывают типы привязки C. Это позволяет скрыть внутренние компоненты библиотеки C за API, который более удобен для пользователя и его легче документировать. Давайте начнем с этого сейчас.

Абстрагирование привязок

Основываясь на логике C, которую мы использовали ранее, нам нужны следующие две основные абстракции:

• Лучший способ отправить уведомление, чтобы избежать необходимости вызывать методы init и uninit.

• Улучшен способ создания/редактирования уведомления, ожидающего отправки.

Чтобы обработать первую абстракцию, давайте создадим новый файл src/lib_notify/notify.cr со следующим кодом:

require "./lib_notify"

class Transform::Notification

  @notification : LibNotify::NotifyNotification*

  getter summary : String

  getter body : String

  getter icon : String

  def initialize(@summary : String, @body : String, @icon : String = "")

    @notification = LibNotify.notify_notification_new @summary, @body, @icon

  end

  def summary=(@summary : String) : Nil

    self.update

  end

  def body=(@body : String) : Nil

    self.update

  end

  def icon=(@icon : String?) : Nil

    self.update

  end

  def to_unsafe : LibNotify::NotifyNotification* @notification

  end

  private def update : Nil

    LibNotify.notify_notification_update @notification, @summary, @body, @icon

  end

end

По сути, этот класс представляет собой просто обертку вокруг указателя уведомления C. Мы определяем метод #to_unsafe, который возвращает завернутый указатель, чтобы позволить предоставить экземпляр этого класса функциям C. В этом типе мы также будем использовать notify_notification_update. Этот тип реализует установщики для каждого свойства уведомления, которые обновляют значение внутри типа-оболочки, а также обновляют значения структур C.

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

Создайте новый файл src/lib_notify/notification_emitter.cr со следующим кодом:

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. Я не буду объяснять причину этого, поскольку она будет рассмотрена в Главе 14 «Тестирование».

Давайте сначала сосредоточимся на обработке контекста ошибки. К сожалению, поскольку jq будет выводить сообщения об ошибках непосредственно на IO, ошибок, мы не сможем их обработать. Однако мы можем обрабатывать реальные исключения из нашего кода Crystal. Поскольку мы хотим обрабатывать любые исключения, возникающие в нашем методе #process, мы можем использовать короткую форму для определения блока rescue:

rescue ex : Exception

  if message = ex.message

    @emitter.emit "Oh no!", message

  end

  raise ex

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

1 ... 32 33 34 35 36 37 38 39 40 ... 75
Перейти на страницу:

Комментарии
Минимальная длина комментария - 20 знаков. Уважайте себя и других!
Комментариев еще нет. Хотите быть первым?