Crystal Programming. Введение на основе проекта в создание эффективных, безопасных и читаемых веб-приложений и приложений CLI - Джордж Дитрих
Шрифт:
Интервал:
Закладка:
Выполнение этого файла через 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 и генерировать справочную информацию на основе этих параметров. Мы будем использовать этот тип в одной из следующих глав, так что следите за обновлениями!
Резюме