
Предисловие
Продолжая цикл статей о других направления разработки на Ruby, кроме веб-разработки. Пришла очередь многим известного Thor, который позволяет делать удобные cli-утилиты с применением Ruby.
Знакомство
Давайте сразу перейдем к простому примеру:
require 'thor' class SayHi < Thor desc "hi NAME", "say hello to NAME" def hi(name) puts "Hi #{name}!" end end SayHi.start(ARGV)
Если вы запустите это без каких-либо аргументов, вы должны получить что-то вроде этого на выходе:
Commands: first_steps.rb help [COMMAND] # Describe available commands or one specific command first_steps.rb hi NAME # say hello to NAME
С помощью всего нескольких строк кода у нас есть полное описание созданной нами команды! Конечно, если вы запустите скрипт с аргументами “Hi Danila”, вы должны получить ответ “Hello, Danila!”. Давайте разберем код.
Мы создали класс под названием “SayHi”, который является производным от класса Тhor. Затем в следующей строке говорится об описании конкретной команды. Первым аргументом функции “desc” является “hi NAME”, которое описывает, какая команда нам нужна. В этом случае в нем говорится, что мы хотим, чтобы пользователь мог ввести “hi”, а затем свое имя, которое будет передано в качестве переменной. Другим примером может быть “location LATITUDE LONGITUDE”, где пользователь может ввести “location 64.39 21.34”, и команда определения местоположения получит широту и долготу, указанные данными числами.
Что именно я подразумеваю под получением? Как только мы передаем аргумент, Thor
определяет, в какой формат он вписывается, и вызывает этот метод из нашего класса “SayHi”. В этом случае он вызывает метод “hi” с “именем” в качестве аргумента.
Наконец, у нас есть наш метод “hi”, который представляет собой просто стандартный скрипт на Ruby.
Приложения Thor обычно следуют такому шаблону; существует множество функций, которые можно использовать, но общие, основополагающие концепции остаются прежними.
Давайте напишем небольшую утилиту под названием file-op, в которой есть опция командной строки для вывода содержимого файла.
require 'thor' class FileOp < Thor desc 'output FILE_NAME', 'print out the contents of FILE_NAME' def output(file_name) puts File.read(file_name) end end FileOp.start(ARGV)
Это почти та же концепция, что и в примере “SayHi”, но на этот раз, если вы запустите его с “output FILENAME”, он выведет содержимое файла (которое обрабатывается с помощью метода “вывод” класса “FileOp”).

Одной из самых удивительных функций Thor является автоматическая “help generation”. Метод “desc”, использу��мый ранее, имеет второй аргумент, который фактически описывает, для чего предназначена каждая команда.
Итак, сделаем это помощью нашей утилиты для работы с файлами, если запустить:
ruby file-op.rb help output
Thor выводит приятное уведомление об использовании:
Usage: file_op_v1.rb output FILE_NAME print out the contents of FILE_NAME
Мы не только четко документируем команды для себя в скрипте, но и пользователь знает, что делает каждая команда!
Очевидно, что немногие значимые приложения командной строки будут удовлетворены такой базовой структурой опций. К счастью, Тhor может больше. Разве не было бы неплохо, если бы мы могли добавить флаг в нашу команду “вывод” для вывода файла в stderr? Я знаю, на самом деле это было бы не так уж приятно, но давай все равно сделаем это:
require 'thor' class FileOp < Thor desc 'output FILE_NAME', 'print out the contents of FILE_NAME' option :stderr, :type => :boolean def output(file_name) #options[:stderr] is either true or false depending #on whether or not --stderr was passed contents = File.read(file_name) if options[:stderr] $stderr.puts contents else $stdout.puts contents end end end FileOp.start(ARGV)
Мы добавили несколько строк, но самая важная - это опция :stderr. Эта строка сообщает Тору, что любая команда, которую мы только что определили (т.е. “вывод” в данном случае), может иметь флаг, переданный как логическое значение. Другими словами, он либо передается, либо не передается; к нему не привязано другое значение, как
--times15.
Итак, мы можем идти далее:
ruby file-ops-v2.rb output --stderr filename
который напечатал бы содержимое “filename” в stderr.

Как насчет команд, которые могут быть применены к любой команде? Что-то вроде:
some_utility.rb -v
У Thor и для этого есть решение. Эти параметры, которые не связаны с определенной командой, называются параметрами класса. Нам не помешала бы дополнительная информация, когда запустится наша утилита. Но обычно нам не нужна эта информация, поэтому мы включим опцию детализации:
require 'thor' class FileOp < Thor class_option :verbose, :type => :boolean desc 'output FILE_NAME', 'print out the contents of FILE_NAME' option :stderr, :type => :boolean def output(file_name) log("Starting to read file...") #options[:stderr] is either true or false depending #on whether or not --stderr was passed contents = File.read(file_name) log("File contents:") if options[:stderr] log("(in stderr)") $stderr.puts contents else log("(in stdout)") $stdout.puts contents end end no_commands do def log(str) puts str if options[:verbose] end end desc 'touch FILE_NAME', 'creates an empty file named FILE_NAME' option :chmod, :type => :numeric def touch(file_name) log("Touching file...") f = File.new(file_name, "w") f.chmod(options[:chmod]) if options[:chmod] end end FileOp.start(ARGV)
Если вы запустите, выдаст это:
ruby file_op_v5.rb output some_file --verbose
Вы должны увидеть некоторые сообщения журнала в дополнение к содержимому файла. Давайте взглянем на код.
Мы добавили метод log, однако он содержится в блоке nocommands. Чтобы сообщить Тору, что журнал не связан с командой, мы должны поместить его в этот блок. В методе журнала мы распечатываем заданную строку, если Тор получает --подробный. Затем мы используем этот метод в командах вывода.

Thor - невероятно универсальная библиотека, которая делает синтаксический анализ командной строки простым и интуитивно понятным. Однако есть несколько ошибок, таких как блок "нет команд", о которых следует знать, чтобы избежать потенциальных ошибок.
Я использовал Thor во многих разных местах, много раз для коротких утилит, для перемещения файлов, или поиска в данных. Я считаю это очень полезным инструментом, и, надеюсь, вы тоже.
Источник для этой статьи можно найти здесь.
