Как составить задачи Thor в отдельных классах / модулях / файлах? - PullRequest
20 голосов
/ 20 апреля 2011

У меня возникли проблемы с получением Тора , чтобы сделать это, так что, надеюсь, кто-то может указать, что я делаю неправильно.

У меня есть основной класс class MyApp < Thor, который я хочу разбить на отдельные файлы для нескольких пространств имен, например thor create:app_type и thor update:app_type.Я не могу найти примеров, показывающих, как нужно разбивать приложение Thor на части, а то, что я пробовал, похоже, не работает.

Возьмем, к примеру, этот класс, который я пытаюсьвырваться из основного класса Тора:

module Things
  module Grouping

    desc "something", "Do something cool in this group"
    def something
      ....
    end
  end
end

Когда я пытаюсь включить или требовать это в своем основном классе:

class App < Thor
  ....
  require 'grouping_file'
  include Things::Grouping
  ....
end

Я получаю исключение: '<module:Grouping>': undefined method 'desc' for Things::Grouping:Module (NoMethodError)

Возможно ли иметь несколько пространств имен для задач Thor, и если да, то как его разбить, чтобы у вас не было одного монолитного класса, занимающего несколько сотен строк?

Ответы [ 6 ]

13 голосов
/ 01 октября 2011

Используйте сверхархивирующий модуль, скажем, Foo, внутри которого вы будете определять все подмодули и подклассы.

Запустите определение этого модуля в одном файле foo.thor, который находится в каталоге, из которого вы будете запускать все задачи Thor. Вверху модуля Foo в этом foo.thor определите этот метод:

# Load all our thor files
module Foo
  def self.load_thorfiles(dir)
    Dir.chdir(dir) do
      thor_files = Dir.glob('**/*.thor').delete_if { |x| not File.file?(x) }
      thor_files.each do |f|
        Thor::Util.load_thorfile(f)
      end
    end
  end
end

Затем в нижней части вашего основного foo.thor файла добавьте:

Foo.load_thorfiles('directory_a')
Foo.load_thorfiles('directory_b')

Это будет рекурсивно включать все файлы *.thor в этих каталогах. Вложите модули в ваш основной Foo модуль для именования ваших задач. Не имеет значения, где находятся файлы или как они называются в этот момент, если вы включаете все свои каталоги, связанные с Thor, с помощью метода, описанного выше.

12 голосов
/ 08 августа 2011

Почему это не работает: когда вы используете desc внутри Thor класса, вы фактически вызываете метод класса Thor.desc.Когда вы делаете это в модуле, он вызывает YourModule.desc, который явно не существует.

Существует два способа, которые я могу предложить исправить это.

Исправить один: использование Module.included

Хотите ли вы, чтобы эти задачи повторно использовались в нескольких классах Thor?

Когда модуль используется в Ruby как include, вызывается метод класса included.http://www.ruby -doc.org / core / classes / Module.html # M000458

module MyModule
  def self.included(thor)
    thor.class_eval do

      desc "Something", "Something cool"
      def something
        # ...
      end

    end
  end
end

Исправить два: разделить классы Тора на несколько файлов

просто хотите отдельно определять задачи в другом файле?

Если это так, просто снова откройте свой класс приложения в другом файле.Ваш Thorfile будет выглядеть примерно так:

# Thorfile
Dir['./lib/thor/**/*.rb'].sort.each { |f| load f }

Тогда ваш lib/thor/app.rb будет содержать некоторые задачи для App, тогда как другой файл lib/thor/app-grouping.rb будет содержать еще несколько задач для того жеApp класс.

4 голосов
/ 01 августа 2011

У меня была такая же проблема, и я почти сдался, но потом у меня появилась идея:

Если вы записываете свои задачи в Thorfile с, а не в рубиновые классы, тогда вы можете просто requireв Ruby-файлах, которые содержат подклассы Thor, и они появятся в списке доступных задач при запуске thor -T.

. Все это управляется классом Thor::Runner.Если вы посмотрите на это, вы увидите метод #thorfiles, который отвечает за поиск файлов с именем Thorfile в текущем рабочем каталоге.

Все, что мне нужно было сделать, чтобы а) разбить мои задачи Thor на несколько файлов, в то время как б) не иметь единственного Thorfile, это создать локальный подкласс Thor::Runner, переписать его #thorfile методс одним, который возвратил мой список приложений для файлов задач Thor, а затем вызвал его метод #start, и все это заработало:

class MyApp::Runner < ::Thor::Runner
  private
  def thorfiles(*args)
    Dir['thortasks/**/*.rb']
  end
end

MyApp::Runner.start

Так что я могу иметь любое количество классов Ruby, определяющих задачи Thor, в thortasksнапример,

class MyApp::MyThorNamespace < ::Thor
  namespace :mynamespace

  # Unless you include the namespace in the task name the -T task list
  # will list everything under the top-level namespace
  # (which I think is a bug in Thor)
  desc "#{namespace}:task", "Does something"
  def task
    # do something
  end
end

Я почти отказался от Тора, пока не понял этого, но не так много библиотек, которые занимаются созданием генераторов, а также созданием задач с пространством имен, поэтому я рад, что нашелрешение.

3 голосов
/ 07 августа 2011

Thor Документация действительно нуждается в улучшении. Следующее почерпнуто из часов чтения кода, спецификаций, проблем и google-fu. Я не могу сказать, что это так, как он должен работать, но он точно будет работать при такой установке.

Когда класс наследует от Thor, он получает несколько важных методов класса.

  1. зарегистрироваться. Это позволяет зарегистрировать новую подкоманду в качестве задачи
  2. class_options. Это дает вам хэш всех опций класса.
  3. задачи. Это дает вам хэш всех определенных задач.

Мы можем использовать их для включения задач из нескольких классов в одного бегуна.

Я включил несколько дополнительных файлов, чтобы вы могли видеть все работающее приложение. Grantesd это не делает много ...

#############################################################
#my_app/bin/my_app                                          #
#                                                           #
#This file is the executable that requires the MyApp module,#
#then starts the runner.                                    #
#############################################################
#!/usr/bin/env ruby
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib') unless $LOAD_PATH.include(File.dirname(__FILE__) + '/../lib')

require "rubygems" # ruby1.9 doesn't "require" it though
require "my_app"
MyApp::Runner.start

########################################################
#my_app/lib/my_app.rb                                  #
#                                                      #
#This is the main module, used to control the requires #
#the my_app requires should be done last to make sure  #
#everything else is defined first.                     #
########################################################
require 'thor'
require 'thor/group'

module MyApp
  #include other helper apps here

  require 'my_app/runner' #first so all subcommands can register
  require 'my_app/more'
  require 'my_app/config'
end

###################################################################
#my_app/lib/my_app/runner.rb                                      #
#                                                                 #
#This is the main runner class.                                   #
#ALL class_methods should be defined here except for Thor::Groups #
###################################################################
class MyApp::Runner < ::Thor
  class_option :config, :type => :string,
         :desc => "configuration file.  accepts ENV $MYAPP_CONFIG_FILE",
         :default => ENV["MYAPP_CONFIG_FILE"] || "~/.my_apprc" 

  method_option :rf, :type => :numeric,
         :desc => "repeat greeting X times",
         :default => 3
  desc "foo","prints foo"
  def foo
    puts "foo" * options.rf
  end
end

#######################################################################
#my_app/lib/my_app/more.rb                                            #
#                                                                     #
#A Thor Group example.                                                #
#Class_options defined for a Thor Group become method_options when    #
#used as a subcommand.                                                #
#Since MyApp::Runner is already defined when this class is evaluated  #
#It can automatcially register itself as a subcommand for the runner, #
#######################################################################
class Revamp::Init < ::Thor::Group

  class_option :repeat, :type => :numeric,
         :desc => "repeat greeting X times",
         :default => 3

  desc "prints woot"
  def woot
    puts "woot! " * options.repeat
  end

  desc "prints toow"
  def toow
    puts "!toow" * options.repeat
  end

  #This line registers this group as a sub command of the runner
  MyApp::Runner.register MyApp::More, :more, "more", "print more stuff"
  #This line copies the class_options for this class to the method_options of the :more task 
  MyApp::Runner.tasks["more"].options = MyApp::More.class_options
end

#####################################################################
#my_app/lib/my_app/config.rb                                        #
#                                                                   #
#For normal Thor classes, each task must be registered individually #
#####################################################################
class MyApp::Config < Thor

  method_option :dr, :type => :numeric,
         :desc => "repeat greeting X times",
         :default => 3
  desc "show_default", "show the default config"
  def show_default
    puts "default " * options.dr
  end
  MyApp::Runner.register MyApp::Config, :show_default, "show_default", "print default"
  MyApp::Runner.tasks["show_default"].options = MyApp::Config.tasks["show_default"].options

  method_option :cr, :type => :numeric,
         :desc => "repeat greeting X times",
         :default => 3
  desc "show_config", "show the config"
  def show_config
    puts "config " * options.cr
  end
  MyApp::Runner.register MyApp::Config, :show_config, "show_config", "print config"
  MyApp::Runner.tasks["show_config"].options = MyApp::Config.tasks["show_config"].options

end
2 голосов
/ 07 декабря 2012

Вы можете найти это полезным: https://github.com/lastobelus/cleanthor

Я хотел бы иметь исполняемый файл для драгоценного камня на основе тора с подкомандами пространства имен, но организовал файлы задач в соответствии с обычным рубиновым гемом lib / mygem /* / .rb структура.

Я также хотел иметь корневой уровень Thorfile, чтобы при обычном запуске thor в каталоге проекта во время разработки также отображались все гемные задачи.

Решение включало следующие шаги:

  • создание подклассов Thor::Runner в Mygem::Thor::Runner и переопределение его частных thorfiles и method_missing методов.В method_missing я также удалил имя гема из команды, если оно появилось.
  • исполняемый файл гема вызывает Mygem::Thor::Runner.start
  • подклассы Thor::Task в Mygem::Thor::Task и
    • переопределяя свой закрытый namespace метод класса.Пользовательский метод namespace удаляет часть Mygem::Thor::Tasks иерархии модулей задач.
    • переопределяет свой закрытый метод thorfiles, возвращая Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rb')]
  • теперь задачи могут быть организованы в lib/mygem/thor/tasks/**/*.rb.Все они должны наследовать от Mygem::Thor::Task
  • Thorfile в корне проекта и загружает все задачи в lib/mygem/thor/tasks/**/*.rb
0 голосов
/ 19 мая 2011

desc - это метод класса, вам нужно использовать exte вместо include.Смотрите здесь для объяснения.

...