Thor – божественный фреймворк для создания задач
июня 28, 2012 | Published in Ruby, Ruby Gems, Ruby on Rails, Ruby on Rails 3 | 3 Comments
– это альтернатива для всем известного Rake. Автором Thor является небезызвестный , хотя если посмотреть на коммиты в репозитории Thor, то Катц находится только на 4 месте, но сейчас не об этом. Thor – создавался как более изящная альтернатива фреймворку Rake и это вполне удалось его авторам. Я надеюсь, что по прочтению данной статьи вы не только научитесь работать с Thor, но и замените им Rake.
Для установки thor воспользуйтесь командой:
$ gem install thor
Если вы программируете с использованием фреймворка Ruby on Rails, то Thor уже уже установлен так как находится в зависимостях у Rails и используется внутри Rails для создания генераторов.
Установив Thor у вас появится возможность использовать в консоли команду thor:
$ thor -v
Thor 0.15.3
$ thor
Tasks:
thor help [TASK] # Describe available tasks or one specific task
thor install NAME # Install an optionally named Thor file into your syst…
thor installed # List the installed Thor modules and tasks
thor list [SEARCH] # List the available thor tasks (—substring means .*S…
thor uninstall NAME # Uninstall a named Thor module
thor update NAME # Update a Thor file from its original location
thor version # Show Thor version
Прежде чем создать первую задачу Thor вы должны быть знакомы с некоторыми соглашениями:
- Задачи Thor имеют расширение .thor
- Имена файлов в которых имеются задачи должны соответствовать именам классов.
- Классы используются в для создания пространств имен.
- Каждая задача – это просто метод.
Теперь мы можем приступить к созданию первой задачи. В файл first_task.thor поместим следующий код на Ruby:
class FirstTask < Thor desc 'hello_world', "prints \"Hello, World!\"" def hello_world puts "Hello, World!" end end
После того, как задача создана, убедимся, что Thor ее «видит»:
$ thor list
first_task
———-
thor first_task:hello_world # prints «Hello, World!»
Команда thor list просто выводит список всех доступных задач с описанием, которое вы даете им используя метод desc. В качестве первого аргумента метод desc принимает название задачи (метода), а в качестве второго – строку описания того, что эта задача делает.
Когда мы убедились в том, что Thor «видит» нашу задачу давайте выполним ее:
$ thor first_task:hello_world
Hello, World!
Задачи Thor могут принимать передаваемые в них пользователем параметры, давайте напишем такую задачу, которая будет принимать параметр.
class FirstTask < Thor desc 'hello_world', "prints \"Hello, World!\"" def hello_world puts "Hello, World!" end desc 'hello USER_NAME', "prints \"Hello, %user_name%!\"" def hello(user_name) puts "Hello, #{user_name}!" end end
Теперь попробуем ее запустить:
$ thor list
first_task
———-
thor first_task:hello USER_NAME # prints «Hello, %user_name%!»
thor first_task:hello_world # prints «Hello, World!»
$ thor first_task:hello
thor hello requires at least 1 argument: «thor first_task:hello USER_NAME».
$ thor first_task:hello Vladimir
Hello, Vladimir!
Как вы уже, наверное, догадались, в метод desc в первом его аргументе следует передавать не только имя задачи, но и все необходимые для ее выполнения параметры.
Следующей важной возможностью Thor является возможность использования опций (еще их называют флагами). Для объявления опций следует использовать метод method_option. Ниже приведен пример задачи использующей опции.
class FirstTask < Thor desc 'hello_world', "prints \"Hello, World!\"" def hello_world puts "Hello, World!" end desc 'hello USER_NAME', "prints \"Hello, %user_name%!\"" def hello(user_name) puts "Hello, #{user_name}!" end desc 'authenticate USER_NAME TOKEN', 'authenticate user though token' method_option :admin, :aliases => '-a', :desc => 'authenticate as admin', :type => :boolean def authenticate(user_name, token) role = options[:admin] ? 'admin' : 'guest' puts <<-MESSAGE #{user_name}, you have been successfully authenticated as #{role} with #{token} token. MESSAGE end end
Вызываем задачу:
$ thor first_task:authenticate Vladimir ToKeN
Vladimir, you have been successfully authenticated as
guest with ToKeN token.
$ thor first_task:authenticate Vladimir ToKeN –a
Vladimir, you have been successfully authenticated as
admin with ToKeN token.
Помимо метода method_option вы можете использовать также метод method_options, который позволяет объявить сразу несколько опций. Доступ к опциям, как видно из примера осуществляется через хэш options.
При объявлении опций задачи вы можете пользоваться следующими опциями (простите за тавтологию) методов method_options и method_option:
- :aliases – псевдонимы опций
- :default – значение по умолчанию для опции
- :desc – описание опции (показывается при thor help )
- :banner – краткое описание опции, по умолчанию это название опции набранное заглавными буквами.
- :required – показывает, что передача опции при вызове задачи обязательна
- :type – тип опции. Тип может быть следующим: :boolean, :string, :numeric, :array, :hash.
В зависимости от того, каким типом обладают опции изменяется и синтаксис передачи их значений. Например, для опции типа :Boolean значение можно упускать. Если опция передана, то options[:option_name] будет true, а в противном случае false. Можно, конечно, явно передать значение true или false, например, так:
$ thor namespace:task_name —option=true
Передача значений опций типа :string и :numeric аналогично явной передаче значения опции типа :bolean, а вот для :array и :hash синтаксис несколько другой, соответственно:
$ thor namespace:task_name —option=1 2 3 4 5
$ thor namespace:task_name —option=login:string password:string
На этот момент вы уже умеете писать простые задачи, которые покроят 70% ваших нужд. Теперь давайте познакомимся с более сложными возможностями Thor.
Вызов других задач
Thor позволяет в контексте задач вызывать другие задачи, причем объявленные не только в текущем классе, но в любом другом. Для вызова другой задачи используется метод invoke:
class FirstTask < Thor desc 'authenticate_as_admin USER_NAME TOKEN', "prints \"Hello, %user_name%!\"" def authenticate_as_admin(user_name, token) puts "Hello, #{user_name}!" invoke :authenticate, [user_name, token], :admin => true end desc 'authenticate USER_NAME TOKEN', 'authenticate user though token' method_option :admin, :aliases => '-a', :desc => 'authenticate as admin', :type => :boolean def authenticate(user_name, token) role = options[:admin] ? 'admin' : 'guest' puts <<-MESSAGE #{user_name}, you have been successfully authenticated as #{role} with #{token} token. MESSAGE end end
$ thor first_task:authenticate_as_admin Vladimir ToKeN
Hello, Vladimir!
Vladimir, you have been successfully authenticated as
admin with ToKeN token.
Если задача, которую вы хотите вызвать находится в другом классе, то вы должны указать и имя класса:
invoke "second_task:parse_something"
Группы
Следующее с чем следует познакомиться – это группы. Группами называются классы, которые наследуются от Thor::Group. В группах задачи выполняются все вместе, в том порядке, в котором они определены. Давайте перепишем наши задачи с использованием Thor::Group.
class FirstTask < Thor::Group desc "authenticate user though token" argument :user_name, :type => :string argument :token, :type => :string class_option :admin, :aliases => "-a", :type => :boolean, :default => true def authenticate_as_admin puts "Hello, #{user_name}!" end def authenticate role = options[:admin] ? 'admin' : 'guest' puts "#{user_name}, you have been successfully authenticated as #{role} with #{token} token." end end
Запускаем:
$ thor list
root
—-
thor first_task USER_NAME TOKEN # authenticate user though token
$ thor first_task Vladimir Token
Hello, Vladimir!
Vladimir, you have been successfully authenticated as admin with Token token.
Обратите внимание на то, что мы используем метод argument для объявления параметров и метод class_option для объявления опций.
Экшены и хэлперы Thor
Чтобы сделать работу более удобной Thor поставляется с набором экшенов – так называются методы хэлперы, которые выполняют некоторые полезные функции.
Чтобы спользовать экшены Thor необходимо включить в подмешать в класс с задачами Thor::Actions. Ниже приведен краткий список экшенов с кратким описанием.
append_to_file(path, *args, &block)
Добавляет текст в конец файла.
chmod(path, mode, config = {})
Изменяет права доступа к файлу или директории.
copy_file(source, *args, &block)
Копирует файл по относительному пути в текущую директорию.
create_file(destination, *args, &block) (also: #add_file)
Создает файл и помещает в него отпределенный текст.
directory(source, *args, &block)
Полностью копирует структуру директорий и файлы из source директории в root директорию.
empty_directory(destination, config = {})
Создает пустую директорию.
inject_into_class(path, klass, *args, &block)
Помещает текст в файл сразу после объявления класса.
insert_into_file(destination, *args, &block) (или #inject_into_file)
Помещает текст в файл.
prepend_to_file(path, *args, &block) (also: #prepend_file)
Добавляет текст в начало файла.
run(command, config = {})
Выполняет команду и возвращает результат ее выполнения.
Помимо экшенов из Thor::Actions, с некоторыми из которых мы только что познакомились существует еще одно расширение — Thor::Shell::Basic. Это расширение предаставляет хэлперы для работой с консолью, например печати вопросов пользователю. Ниже приведены некоторые из хэлперов:
ask(statement, *args)
Спрашивает что-то у пользователя и сохраняет ответ.
error(statement)
Вызывается при возникновении ошибки во время исполнения.
file_collision(destination)
Спрашивает у пользователя можно ли перезаписать файл в случае необходимости.
yes?(statement, color = nil)
Делает вопрос пользователю и возвращает true, если ответ y — yes.
no?(statement, color = nil)
Антипод yes?. Возвращает true если ответ n — no.
print_table(table, options = {})
Печатает таблицу.
print_wrapped(message, options = {})
Печатает большой кусок текста с переносом строк по достижению приделов окна консоли.
say(message = «», color = nil, force_new_line = (message.to_s !~ /( |\t)$/))
Печатает что-то пользователю
Когда мы знаем столько экшенов и консольных хелперов, давайте напишем простую задачу с их применением.
class FirstTask < Thor include Thor::Actions desc 'print_dir', "It prints contents of directory." def print_dir if yes?("Do you want me print contents of directory?") run('dir') else error("You don't want to print contents of directory!") end end end
Запускаем:
$ thor first_task:print_dir
Do you want me print contents of directory? y
run dir from «.»
A5client A5server first_task.thor flagus rails spine.site
$ thor first_task:print_dir
Do you want me print contents of directory? n
You don’t want to print contents of directory!
Tips & Tricks (штуки-дрюки)
Thor задачи можно вызывать не только из консоли, но и из кода. Наследуясь от Thor классы задач получают метод start, вызов которого для класса реализует запуск задачи на выполнение, наприме:
FirstTask.start
Так вы можете запускать задачи по расписанию или использовать их как «обработчики событий».
Заключение
Я имел опыт работы с Rake и с Thor и хочу сказать, что Thor более удобен в пользовании. Вместо использования Rake’ового ущербного DSL, которое местами выглядит не очивидно (некоторые моменты в работе следует запоминать) я рекомендую вам переходить на использование замечательной альтернативы в стиле ООП от замечательных Ехуды Катца (wycatz), Жозе Валима (josevalim) и дугих.
июня 29, 2012 at 00:44 (#)
расширение у файла должно быть *.rake(«В файл first_task.rake поместим следующий код на Ruby:»), не *.thor???
июня 29, 2012 at 01:14 (#)
Serg, абсолютно верно, это я чего-то отвлекся и перепутал. Спасибо, поправил.
июля 11, 2012 at 15:51 (#)
Спасибо за статью!
Пара вопросов:
Вот например, я хочу иметь возможность запускать задачи так:
thor benchmark (== thor benchmark:run), thor benchmark:show
1)Как мне сделать односложный синоним по имени нэймспейса, и как заставить его принимать опции и аргументы командной строки?
2)Как эти опции и аргументы задачи передавать при invoke’е зависимостей?