如何在单独的类/模块/文件中组合Thor任务?

时间:2011-04-20 10:49:11

标签: ruby module thor

我在Thor做这件事时遇到了一些麻烦,所以希望有人可以指出我做错了什么。

我有一个主要的课程class MyApp < Thor,我希望将其划分为多个名称空间的单独文件,例如thor create:app_typethor 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任务设置多个名称空间,如果是,那么如何将其分解出来以便您没有一个需要数百行的单一类?

6 个答案:

答案 0 :(得分:13)

使用覆盖模块,假设Foo,在其中定义所有子模块和子类。

在单个foo.thor文件中启动此模块的定义,该文件位于运行所有Thor任务的目录中。在此Foo的{​​{1}}模块的顶部,定义此方法:

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模块中嵌套模块以命名您的任务。只要您通过上述方法包含所有与Thor相关的目录,无论文件存在于何处或在此处调用它们都无关紧要。

答案 1 :(得分:12)

为什么不起作用:当您在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

修复二:将Thor类分成多个文件

您是否只想在另一个文件中单独定义任务?

如果是这样,只需在另一个文件中重新打开App类。你的 Thorfile 看起来像是:

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

然后,您的lib/thor/app.rb将包含App的一些任务,而另一个文件lib/thor/app-grouping.rb将包含同一App类的更多任务。

答案 2 :(得分:4)

我遇到了同样的问题,几乎放弃了,但后来我有了一个想法:

如果您将任务写入Thorfile而不是ruby类,那么您可以简单地在包含Thor子类的Ruby文件中require,并且当您运行时它们将出现在可用任务列表中thor -T

这全部由Thor::Runner类管理。如果你看一下这个,你会看到一个#thorfiles方法,它负责在当前工作目录下查找名为Thorfile的文件。

所有我必须做的事情a)将我的Thor任务分成多个文件,而b)不必有一个Thorfile就是创建一个Thor::Runner的本地子类,覆盖它{{1}方法,其中一个返回我的应用程序特定的Thor任务文件列表,然后调用其#thorfile方法,它都工作:

#start

所以我可以在class MyApp::Runner < ::Thor::Runner private def thorfiles(*args) Dir['thortasks/**/*.rb'] end end MyApp::Runner.start 例如

下定义任意数量的Ruby类来定义Thor任务
thortasks

我几乎放弃了Thor,直到我弄明白这一点,但是没有很多库可以处理创建生成器以及构建命名空间任务,所以我很高兴找到了解决方案。

答案 3 :(得分:3)

Thor文档确实需要改进。以下是从阅读代码,规格,问题和谷歌的几个小时收集到的。我不能说这是它应该工作的方式,但它肯定会以这种方式设置。

当一个类继承自Thor时,它会获得一些重要的Class方法。

  1. 寄存器。这允许您将新的子命令注册为任务
  2. class_options。这为您提供了所有类选项的哈希值。
  3. 任务。这为您提供了所有已定义任务的哈希值。
  4. 我们可以使用它们将许多类中的任务包含在单个运行中。

    我添加了一些额外的文件,以便您可以看到整个正在运行的应用程序。 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
    

答案 4 :(得分:2)

您可能会发现这有用:https://github.com/lastobelus/cleanthor

我想为gem创建一个基于thor的可执行文件,使用命名空间子命令,但是根据正常的ruby gem lib / mygem / * / .rb结构组织任务文件。

我还希望有一个根级Thorfile,以便在开发期间在项目目录中正常运行thor也显示所有gem任务。

解决方案包括以下步骤:

  • Thor::Runner中继承Mygem::Thor::Runner并覆盖其私有thorfilesmethod_missing方法。在method_missing中,我还从命令中删除了宝石名称。
  • gem可执行文件调用Mygem::Thor::Runner.start
  • Thor::TaskMygem::Thor::Task中继承namespace
    • 覆盖其私有namespace类方法。自定义Mygem::Thor::Tasks方法删除了任务模块层次结构的thorfiles部分。
    • 重写其私有Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rb')]方法以返回lib/mygem/thor/tasks/**/*.rb
  • 现在可以在Mygem::Thor::Task中组织任务。它们都应该继承自lib/mygem/thor/tasks/**/*.rb
  • 项目根目录中的Thorfile还会加载{{1}}
  • 中的所有任务

答案 5 :(得分:-4)

desc是一个类方法,需要使用extend而不是include。 请查看here以获取解释。