Хотя сейчас мы читаем входные данные из STDIN, было бы также хорошим улучшением, если бы мы разрешили передачу входного файла для чтения входных данных. Crystal определяет константу ARGF, которая позволяет считывать данные из файла и возвращаться к STDIN, если файлы не предоставлены. ARGF также является вводом-выводом, поэтому мы можем просто заменить STDIN на ARGF в crystal src/transform_cli.cr. input.yaml
. Однако при запуске вы заметите ошибки: Необработанное исключение: Ошибка чтения файла: Является каталогом (IO::Error)
. Вы можете задаться вопросом, почему это так, но ответ заключается в том, как работает ARGF.
ARGF сначала проверит, пуст ли ARGV. Если да, то будет выполнено чтение из STDIN. Если ARGV не пуст, предполагается, что каждое значение в ARGV представляет файл для чтения. В нашем случае ARGV не пуст, поскольку содержит [.", "input.yaml"]
, поэтому он пытается прочитать первый файл, который в данном случае представляет собой точку, обозначающую текущую папку. Поскольку папку нельзя прочитать как файл, возникает исключение, которое мы видели. Чтобы обойти эту проблему, нам нужно убедиться, что ARGV содержит только тот файл, который мы хотим прочитать, прежде чем вызывать ARGF#gets_to_end
. Самый простой способ справиться с этой проблемой — вызвать метод #shift
для ARGV, который работает, поскольку это массив. Этот метод удаляет первый элемент массива и возвращает его, в результате чего в ARGV остается только файл.
Однако есть еще одна проблема, которую нам также необходимо решить. Поскольку мы используем ARGV напрямую для предоставления входных аргументов jq, нам нужно будет провести некоторый рефакторинг, чтобы иметь возможность получить доступ к фильтру перед вызовом #gets_to_end
. Мы можем добиться этого, переместив часть логики из
class Transform::Processor
def process : Nil
filter = ARGV.shift
input = ARGF.gets_to_end
output_data = String.build do |str|
Process.run(
"jq",
[filter],
input: IO::Memory.new(
Transform::YAML.deserialize input
),
output: str
)
end
STDOUT.puts Transform::YAML.serialize output_data
end
end
Ключевым дополнением здесь является введение filter = ARGV.shift
, который гарантирует, что остальная часть ARGV будет содержать
Также обратите внимание, что мы удалили #process
. Причина этого в том, что все входные данные теперь получаются изнутри самого метода, и поэтому нет смысла принимать внешние входные данные. Еще одним примечательным изменением было изменение типа возвращаемого значения метода на Nil
, поскольку мы выводим его непосредственно в STDOUT. Это немного снижает гибкость метода, но об этом также будет сказано в следующем разделе.
Есть еще одна вещь, которую нам нужно обработать, прежде чем мы сможем объявить рефакторинг завершенным: что произойдет, если в jq будет передан недопустимый фильтр (или данные)? В настоящее время это вызовет не очень дружелюбное исключение. Что нам действительно нужно сделать, так это проверить, успешно ли выполнен jq, и если нет, записать сообщение об ошибке в STDERR и выйти из приложения, внеся следующие изменения в
class Transform::Processor
def process : Nil
filter = ARGV.shift
input = ARGF.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: STDERR
)
exit 1 unless run.success?
end
STDOUT.puts Transform::YAML.serialize output_data
end
end