2. Передайте преобразованные данные в jq.
3. Преобразуйте выходные данные JSON в YAML.
Важно помнить, что конечной целью этого упражнения является демонстрация того, как различные концепции Crystal могут применяться для создания функционального и удобного приложения CLI. Таким образом, мы не собираемся уделять слишком много внимания попыткам сделать его на 100% надежным для каждого варианта использования, а вместо этого сосредоточимся больше на различных инструментах/концепциях, используемых в рамках реализации.
Имея это в виду, давайте перейдем к написанию первоначальной реализации, начав с чего-то простого и повторяя его, пока не получим полностью работающую реализацию. Начнем с самого простого случая: вызовите jq с жестко закодированными данными JSON, чтобы показать, как эта часть будет работать. К счастью для нас, стандартная библиотека Crystal включает тип https://crystal-lang.org/api/Process.html, который позволяет напрямую вызывать процесс jq, установленный в данный момент. Таким образом, мы можем использовать все его функции без необходимости переносить их в Crystal.
Откройте src/transform.cr в выбранной вами IDE и обновите его, чтобы он выглядел следующим образом:
module Transform
VERSION = "0.1.0"
# The same input data used in the example at the
# beginning of the chapter.
INPUT_DATA = %([{"id":1,"author":{"name":"Jim"}},{"id":2,
"author":{"name":"Bob"}}])
Process.run(
"jq",
[%([.[] | {"id": (.id + 1), "name": .author.name}])],
input: IO::Memory.new(INPUT_DATA),
output: :inherit
)
end
Сначала мы определяем константу с входными данными, которые использовались в предыдущем примере. Process.run запускает процесс и ожидает его завершения. Затем мы вызываем его, используя jq в качестве команды вместе с массивом аргументов (в данном случае только фильтр). Мы передаем ввод-вывод из памяти в качестве входных данных для команды. Не обращайте на это особого внимания; более подробно это будет рассмотрено в следующей главе. Наконец, мы устанавливаем для выходных данных команды значение :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