Подтвердить что ты не робот

Как составить задачи Thor в отдельных классах/модулях/файлах?

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

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

Возьмем, к примеру, этот класс, который я пытаюсь вырваться из основного класса 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, и если да, то как их разбить, чтобы у вас не было одного монолитного класса, который занимает несколько сотен строк?

4b9b3361

Ответ 1

Используйте модуль с расширением, скажем 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 в эти каталоги. Модули Nest в вашем основном модуле Foo для пространственного хранения ваших задач. Не имеет значения, где находятся файлы или что они вызывают в этот момент, до тех пор, пока вы включите все ваши связанные с Тором каталоги с помощью метода, описанного выше.

Ответ 2

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

Есть два способа предложить исправить это.

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

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

Когда модуль используется как include в Ruby, вызывается метод класса 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

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

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

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

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

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

Ответ 3

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

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

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

Все, что я должен был сделать, чтобы: а) разбить задачи Thor на несколько файлов, тогда как b) не нужно было иметь один 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

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

Ответ 4

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

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

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

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

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

Ответ 5

Вы можете счесть это полезным: https://github.com/lastobelus/cleanthor

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

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

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

  • подклассом Thor::Runner в Mygem::Thor::Runner и переопределением его частных методов thorfiles и method_missing. В method_missing я также удалил имя gem из команды, если она появилась.
  • исполняемые вызовы gem 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

Ответ 6

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