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

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 21 22 23 24 25 26 27 28 29 ... 75
Перейти на страницу:
Мы передаем ввод-вывод из памяти в качестве входных данных для команды. Не обращайте на это особого внимания; более подробно это будет рассмотрено в следующей главе. Наконец, мы устанавливаем для выходных данных команды значение :inherit, что заставляет программу наследовать выходные данные своего родительского модуля, которым является наш терминал.

Выполнение этого файла через crystal src/transform.cr приводит к тому же результату, что и в предыдущем примере jq, который удовлетворяет второму требованию нашего CLI. Однако нам все еще нужно выполнить требования 1 и 3. Давайте начнем с этого.

Преобразование данных

Следуя предыдущей рекомендации, я собираюсь создать новый файл, который будет содержать логику преобразования. Для начала создайте файл src/yaml.cr со следующим содержимым:

require "yaml"

require "json"

module Transform::YAML

   def self.deserialize(input : String) : String

      ::YAML.parse(input).to_json

   end

   def self.serialize(input : String) : String

      JSON.parse(input).to_yaml

   end

end

Кроме того, не забудьте запросить этот файл в src/transform.cr, добавив require "./ yaml" в начало файла.

Crystal поставляется с довольно надежной стандартной библиотекой общих / полезных функций. Хорошим примером этого являются модули https://crystal-lang.org/api/YAML.html и https://crystal-lang.org/api/JSON.html, которые упрощают написание логики преобразования. Я определил два метода: один для обработки YAML => JSON, а другой для обработки JSON => YAML. Обратите внимание, что я использую ::YAML для ссылки на модуль стандартной библиотеки. Это связано с тем, что метод уже определен в пространстве имен YAML. Без :: Crystal будет искать метод .parse в своем текущем пространстве имен вместо того, чтобы обращаться к стандартной библиотеке. Этот синтаксис также работает с методами, что может пригодиться, если вы случайно определите свой собственный метод #raise, а затем захотите, например, также вызвать реализацию стандартной библиотеки.

Затем я обновил файл src/transform.cr, чтобы он выглядел следующим образом:

require "./yaml"

   module Transform

      VERSION = "0.1.0"

      INPUT_DATA = <←YAML

      ---

      - id: 1

         author:

            name: Jim

      - id: 2

      author:

         name: Bob

      YAML

   output_data = String.build do |str|

      Process.run(

         "jq",

         [%([.[] | {"id": (.id + 1), "name": .author.name}])],

         input: IO::Memory.new(

            Transform::YAML.deserialize(INPUT_DATA)

         ),

         output: str

      )

      end

   puts Transform::YAML.serialize(output_data)

end

Код в основном тот же, но теперь он предоставляет входные данные на языке YAML и включает нашу логику преобразования. Стоит отметить, что теперь мы используем String.build для создания строки в коде, как вы могли видеть на своем терминале ранее. Основная причина этого заключается в том, что строка нужна нам для того, чтобы преобразовать ее обратно в YAML перед выводом на экран нашего терминала.

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

Улучшение возможности повторного использования

С этого момента мы начнем использовать файл src/transform_cli.cr. Чтобы решить эту проблему повторного использования, мы планируем определить тип процессора, который будет содержать логику, связанную с вызовом jq и преобразованием данных.

Давайте начнем с создания файла src/processor.cr, обязательно указав его в src/transform.cr, со следующим содержимым:

class Transform::Processor

  def process(input : String) : String

    output_data = String.build do |str|

      Process.run(

        "jq",

        [%([.[] | {"id": (.id + 1), "name": .author.name}])],

        input: IO::Memory.new(

          Transform::YAML.deserialize input

        ),

        output: str

      )

    end

    Transform::YAML.serialize output_data

  end

end

Наличие этого класса делает наш код намного более гибким и пригодным для повторного использования. Мы можем создать объект Transform::Processor и вызывать его метод #process несколько раз с различными входными строками. Далее, давайте используем этот новый тип в src/transform_cli.cr:

require "./transform"

  INPUT_DATA = <←YAML

  ---

    - id: 1

      author:

        name: Jim

    - id: 2

      author:

        name: Bob

  YAML

puts Transform::Processor.new.process INPUT_DATA

Наконец, src/transform.cr теперь должен выглядеть следующим образом:

require "./processor"

require "./yaml"

module Transform

   VERSION = "0.1.0"

end

Запуск src/transform_cli.cr по-прежнему приводит к тому же результату, что и раньше, но теперь можно повторно использовать нашу логику преобразования для разных входных данных. Однако цель CLI – разрешить использование аргументов из терминала и использовать значения внутри CLI. Учитывая, что в настоящее время входной фильтр жестко привязан к типу процессора, я думаю, что это то, к чему нам следует обратиться, прежде чем завершать начальную реализацию.

Аргументы, передаваемые программе CLI, отображаются через константу ARGV в виде Array(String). Сам код, позволяющий использовать это, довольно прост, учитывая, что аргументы jq уже принимают массив строк, который у нас на данный момент жестко запрограммирован. Мы можем просто заменить этот массив константой ARGV, и все будет в порядке. src/processor.cr теперь выглядит следующим образом:

class Transform::Processor

  def process(input : String) : String

    output_data = String.build do |str|

      Process.run("jq",

        ARGV,

        input: IO::Memory.new(Transform::YAML.deserialize

          input

        ),

        output: str

      )

    end

    Transform::YAML.serialize output_data

  end

end

Кроме того, поскольку фильтр больше не является жестко запрограммированным, нам нужно будет ввести его вручную. Запуск crystal src/transform_cli.cr '[.[] | {"id": (.id + 1), "name": .author.name}]' снова выдает тот же результат, но гораздо более гибким способом.

Если вы предпочитаете использовать crystal run, команду нужно будет немного изменить, чтобы учесть различную семантику каждого варианта. В этом случае команда была бы crystal run src/transform_cli.cr -- '[.[] | {"id": (.id + 1), "name": .author.name }]', где параметр -- сообщает команде запуска, что должны быть переданы будущие аргументы к исполняемому файлу, а не в качестве аргументов для самой команды запуска.

Стандартная библиотека Crystal также включает тип OptionParser, который предоставляет DSL, позволяющий описывать аргументы, которые принимает CLI, обрабатывать их синтаксический анализ из ARGV и генерировать справочную информацию на основе этих параметров. Мы будем использовать этот тип в одной из следующих глав, так что следите за обновлениями!

Резюме

1 ... 21 22 23 24 25 26 27 28 29 ... 75
Перейти на страницу:

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