Как стать автором
Обновить

Построение CLI-Утилит на Ruby при помощи Thor

Время на прочтение 4 мин
Количество просмотров 2.3K
Автор оригинала: Dhaivat Pandya

Предисловие

Продолжая цикл статей о других направления разработки на 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 во многих разных местах, много раз для коротких утилит, для перемещения файлов, или поиска в данных. Я считаю это очень полезным инструментом, и, надеюсь, вы тоже.

Источник для этой статьи можно найти здесь.

Теги:
Хабы:
+5
Комментарии 2
Комментарии Комментарии 2

Публикации

Истории

Работа

Ruby on Rails
17 вакансий
Программист Ruby
15 вакансий

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн