Мы все еще смещаем фильтр с входных аргументов. Однако вместо использования #gets_to_end
для получения всех данных из IO мы теперь создаем два экземпляра IO::Memory
— первый для хранения данных JSON из преобразования десериализации, а второй для хранения выходных данных JSON через jq.
По сути, это работает так: процесс десериализации будет использовать все данные входного типа IO, выводя преобразованные данные в первый IO::Memory
. Затем мы передаем его в качестве входных данных в jq, который записывает обработанные данные во второй IO::Memory
. Затем второй экземпляр передается в качестве входного типа IO в метод serialize
, который выводит данные непосредственно в выходной тип IO.
Еще один ключевой момент, на который стоит обратить внимание, — это то, как нам нужно вызывать .rewind
для буферов до/после запуска логики преобразования. Причина этого связана с тем, как работает IO::Memory
. По мере записи в него данных он продолжает добавлять данные в конец.
Другой способ подумать об этом — представить, что вы пишете эссе. Чем длиннее и длиннее эссе, тем дальше и дальше вы отходите от начала. Вызов .rewind
имеет тот же эффект, как если бы вы переместили курсор обратно в начало эссе. Или, в случае с нашим буфером, он сбрасывает буфер, чтобы будущие чтения начинались с самого начала. Если бы мы этого не сделали, jq — и наша логика преобразования — начали бы читать с конца буфера, что привело бы к некорректному выводу, поскольку он по существу
Следуя нашей идее разрешить использование нашего приложения в чужом проекте, нам нужно улучшить еще одну вещь. В настоящее время мы выходим из процесса, если вызов jq завершается неудачей. Было бы нехорошо, если бы кто-то использовал это, например, в веб-фреймворке, а мы случайно отключили его сервер! К счастью, исправить это просто. Вместо вызова exit 1
нам следует просто вызвать исключение, которое мы можем проверить в точке входа, специфичной для CLI. Или, другими словами, замените эту строку на raise RuntimeError.new
, если только run.success?
. Затем обновите
require "./transform"
begin
Transform::Processor.new.process ARGV, STDIN, STDOUT, STDERR rescue ex : RuntimeError
exit 1
end
Сделав это таким образом, мы по-прежнему будем иметь правильный код завершения при использовании в качестве CLI, но также сможем лучше использовать наше приложение в контексте библиотеки, поскольку исключение можно будет спасти и корректно обработать. Но подождите — мы много говорили об использовании нашего приложения в качестве библиотеки в другом проекте, но как это выглядит?
Во-первых, пользователям нашей библиотеки необходимо будет установить наш проект как сегмент — подробнее об этом в Processor
и использовать его в соответствии со своими потребностями. Например, предположим, что они хотят обработать тело ответа HTTP-запроса, выведя преобразованные данные в файл. Это будет выглядеть примерно так:
require "http/client"
require "transform"
private FILTER = %({"name": .info.title, "swagger_version": .swagger, "endpoints": .paths | keys})
HTTP::Client.get "https://petstore.swagger.io/v2/swagger.yaml" do
|response|
File.open("./out.yml", "wb") do |file|
Transform::Processor.new.process [FILTER], response.body_io, file
end
end
В результате файл будет следующим:
---
name: Swagger Petstore swagger_version: "2.0" endpoints:
- /pet
- /pet/findByStatus
- /pet/findByTags
- /pet/{petId}
- /pet/{petId}/uploadImage
- /store/inventory
- /store/order
- /store/order/{orderId}
- /user
- /user/createWithArray
- /user/createWithList
- /user/login
- /user/logout
- /user/{username}
Эта способность может быть очень ценной для кого-то другого, поскольку может означать, что им не придется реализовывать эту логику самостоятельно.