Предисловие
Продолжая цикл статей о других направления разработки на 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 во многих разных местах, много раз для коротких утилит, для перемещения файлов, или поиска в данных. Я считаю это очень полезным инструментом, и, надеюсь, вы тоже.
Источник для этой статьи можно найти здесь.
mpetrunin
Спасибо за статью про Thor.
Несклько моментов по содержанию:
1. Кажется, в тексте статьи отсуствует ссылка на сам Thor.
2. Ссылка:
> Источник для этой статьи можно найти здесь.
ведёт на github-репозиторий, где сам источник найти затруднительно.
Также стоит упомянуть, что статья-источник для перевода от 2013 (!) года.
sailordev Автор
Это начало работы с Thor. Все что тут указанно, работает до сих пор. Дальше будет больше материала по этому гему и не только, как авторский, так и переведенный.
Спасибо за фидбек.