Два основных улучшения заключаются в том, что любой вывод ошибок, возникающий во время работы jq, должен выводиться в STDERR
и что программа должна завершиться раньше, если jq не выполнился успешно.
Эти два улучшения позволяют пользователю понять, что пошло не так, и предотвращают дальнейшее выполнение приложения, которое в противном случае привело бы к попытке преобразовать сообщение об ошибке в YAML.
Поддержка других IO
В последнем разделе мы уже внесли немало улучшений: нам больше не нужно жестко кодировать входные данные, и мы лучше справляемся с ошибками, исходящими от jq. Но помните, как мы также хотели поддержать использование нашего приложения в контексте библиотеки? Как кто-то будет обрабатывать тело ответа HTTP и выводить его в файл, если наш процессор тесно связан с концепцией терминала?
В этом разделе мы собираемся устранить этот недостаток, снова проведя рефакторинг, чтобы разрешить
Первым шагом в этом является повторное введение аргументов в Processor#process
: один для входных аргументов, входной IO, выходной IO и IO ошибок. В конечном итоге это будет выглядеть так:
class Transform::Processor
def process(input_args : Array(String), input : IO,
output : IO, error : IO) : Nil
filter = input_args.shift
input = input.gets_to_end
output_data = String.build do |str|
run = Process.run(
"jq",
[filter],
input: IO::Memory.new(
Transform::YAML.deserialize input
),
output: str,
error: error
)
exit 1 unless run.success?
End
output.puts Transform::YAML.serialize output_data
end
end
Затем мы, конечно, должны обновить связанные константы, добавив в них новые переменные-аргументы. Как упоминалось ранее, вывод этого метода непосредственно в STDOUT
делал его не таким гибким, как тогда, когда он просто возвращал окончательные преобразованные данные. Однако теперь, когда он поддерживает любой тип IO в качестве вывода, кто-то может легко использовать String.build
для получения строки преобразованных данных. Далее нам нужно будет обновить нашу логику преобразования, чтобы она также основывалась на IO.
Откройте .parse
поддерживают String | IO
входы, поэтому нам там ничего особенного делать не нужно. Методы #to_*
также имеют перегрузку на основе IO, которой мы передадим новый выходной аргумент. Наконец, поскольку этот метод больше не будет возвращать преобразованные данные в виде строки, мы можем обновить тип возвращаемого значения на Nil
. В конечном итоге это должно выглядеть следующим образом:
require "yaml"
require "json"
module Transform::YAML
def self.deserialize(input : IO, output : IO) : Nil
::YAML.parse(input).to_json output
end
def self.serialize(input : IO, output : IO) : Nil
JSON.parse(input).to_yaml output
end
end
Поскольку мы добавили второй аргумент, нам, конечно, также потребуется обновить процессор для передачи второго аргумента. Аналогичным образом, поскольку сейчас мы работаем исключительно с операциями IO, нам нужно будет реализовать новый способ хранения/перемещения данных. Мы можем решить обе эти задачи, используя объекты IO::Memory
для хранения преобразованных данных. Кроме того, поскольку они сами относятся к типу IO, мы можем передавать их непосредственно в качестве входных данных в jq.
Конечный результат этого рефакторинга следующий:
class Transform::Processor
def process(input_args : Array(String), input : IO,
output : IO, error : IO) : Nil
filter = input_args.shift
input_buffer = IO::Memory.new
output_buffer = IO::Memory.new
Transform::YAML.deserialize input, input_buffer
input_buffer.rewind
run = Process.run(
"jq",
[filter],
input: input_buffer,
output: output_buffer,
error: error
)
exit 1 unless run.success?
output_buffer.rewind
Transform::YAML.serialize output_buffer, output
end
end