23

我在让托尔这样做时遇到了一些麻烦,所以希望有人能指出我做错了什么。

我有一个主类class MyApp < Thor,我想将它分解为多个命名空间的单独文件,例如thor create:app_typeand 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 任务提供多个命名空间,如果是,如何将其拆分,以使您没有一个需要数百行代码的整体类?

4

6 回答 6

14

为什么它不起作用:当你desc在一个Thor类中使用时,你实际上是在调用一个类方法Thor.desc。当您在模块中执行此操作时,它会调用YourModule.desc显然不存在的。

我可以建议两种方法来解决这个问题。

修复一:使用 Module.included

你想让这些任务在多个 Thor 类中重复使用吗?

include当模块在 Ruby 中用作 an时,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类的更多任务。

于 2011-08-07T20:46:15.327 回答
14

使用一个总体模块,比方说Foo,您将在其中定义所有子模块和子类。

在单个foo.thor文件中启动此模块的定义,该文件位于您将运行所有 Thor 任务的目录中。Foo在this 模块的顶部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 相关的目录,文件所在的位置或此时调用的内容都无关紧要。

于 2011-09-30T21:25:17.840 回答
5

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

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

这都是由Thor::Runner班级管理的。如果您仔细查看,您将看到一个#thorfiles方法,该方法负责查找Thorfile在当前工作目录下命名的文件。

我所要做的就是a)将我的Thor任务分成多个文件,而b)不必Thorfile创建一个本地子类Thor::Runner,用返回我的应用程序特定的Thor任务文件列表的方法覆盖它的#thorfile方法,然后调用它的#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,但是没有多少库可以处理创建生成器以及构建命名空间任务,所以我很高兴找到了解决方案。

于 2011-08-01T12:57:56.467 回答
3

Thor 文档确实需要改进。以下内容是从阅读代码、规范、问题和 google-fu 的数小时中收集到的。我不能说这是它应该工作的方式,但是当以这种方式设置时它肯定会工作。

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

  1. 登记。这允许您将新的子命令注册为任务
  2. 类选项。这为您提供了所有类选项的哈希值。
  3. 任务。这为您提供了所有已定义任务的哈希值。

我们可以使用它们将来自许多类的任务包含到单个运行器中。

我包含了一些额外的文件,以便您可以看到整个工作的雷神应用程序。诚然,它并没有做太多...

#############################################################
#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
于 2011-08-07T11:32:49.610 回答
2

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

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

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

该解决方案涉及以下步骤:

  • 继承Thor::RunnerMygem::Thor::Runner覆盖其私有thorfilesmethod_missing方法。如果method_missing出现,我还从命令中删除了 gem 名称。
  • 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
  • 项目根目录下的 Thorfile 也将所有任务加载到lib/mygem/thor/tasks/**/*.rb
于 2012-12-07T06:14:41.380 回答
-4

desc 是一个类方法,你需要使用extend而不是include。在这里寻找解释。

于 2011-05-19T19:00:23.973 回答