ruby module thor

ruby - ¿Cómo componer las tareas de Thor en clases/módulos/archivos separados?



module (6)

La documentación de Thor realmente necesita ser mejorada. Lo siguiente se obtiene de horas de lectura de códigos, especificaciones, problemas y google-fu. No puedo decir que esta es la forma en que se supone que funciona, pero ciertamente funcionará cuando se configure de esta manera.

Cuando una clase hereda de Thor, gana algunos métodos importantes de clase.

  1. registro. Esto le permite registrar un nuevo subcomando como una tarea
  2. Opciones de clase. Esto le da un hash de todas las opciones de clase.
  3. Tareas. Esto le da un hash de todas las tareas definidas.

Podemos usarlos para incluir tareas de muchas clases en un solo corredor.

Incluí algunos archivos adicionales para que pudiera ver una aplicación completa de thor de trabajo. Grantesd no hace mucho ...

############################################################# #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

Estoy teniendo algunos problemas para que Thor haga esto, así que espero que alguien pueda señalar lo que estoy haciendo mal.

Tengo una clase principal de clase class MyApp < Thor que quiero dividir en archivos separados para múltiples espacios de nombres, como thor create:app_type y thor update:app_type . No puedo encontrar ningún ejemplo que muestre cómo se debe separar una aplicación de Thor, y lo que he intentado no parece funcionar.

Tomemos, por ejemplo, esta clase que estoy tratando de romper con la clase principal de Thor:

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

Cuando intento incluir o requerir esto en mi clase principal:

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

Obtengo una excepción: ''<module:Grouping>'': undefined method ''desc'' for Things::Grouping:Module (NoMethodError)

¿Es posible tener múltiples espacios de nombres para las tareas de Thor y, en caso afirmativo, cómo se divide uno para que no tenga una clase monolítica que tome varios cientos de líneas?


Por qué no funciona: cuando usas desc dentro de una clase Thor , en realidad estás llamando a un método de clase Thor.desc . Cuando haces eso en el módulo, llama a YourModule.desc que obviamente no existe.

Hay dos maneras en que puedo sugerir para solucionar este problema.

Arreglo uno: usando Module.included

¿Quería reutilizar esas tareas en varias clases de Thor?

Cuando se usa un módulo como un include en Ruby, se llama al método de clase 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

Arregla dos: separando tus clases de Thor en múltiples archivos

¿Simplemente deseaba definir tareas por separado en otro archivo?

Si es así, simplemente vuelva a abrir su clase de aplicación en otro archivo. Tu Thorfile se vería algo así como:

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

Entonces su lib/thor/app.rb contendría algunas tareas para la App , mientras que otro archivo lib/thor/app-grouping.rb contendría algunas tareas más para la misma clase de App .


Puede encontrar esto útil: https://github.com/lastobelus/cleanthor

Quería tener un ejecutable basado en thor para una gema, con subcomandos con espacios de nombre, pero organizar los archivos de tareas según la estructura normal de ruby ​​gem lib / mygem / * / .rb.

También quería tener un Thorfile de nivel raíz para que la ejecución de thor normalmente en el directorio del proyecto durante el desarrollo también mostrara todas las tareas de gemas.

La solución involucró los siguientes pasos:

  • Mygem::Thor::Runner Thor::Runner en Mygem::Thor::Runner y anulando sus thorfiles privados de thorfiles y method_missing . En method_missing también method_missing el nombre de gema de un comando si aparecía.
  • el ejecutable de la gema llama a Mygem::Thor::Runner.start
  • subclasificación Thor::Task en Mygem::Thor::Task y
    • anulando su método de clase de namespace privado. El método de namespace personalizado elimina la parte Mygem::Thor::Tasks de las jerarquías de módulos de las tareas.
    • anulando su método de thorfiles privado para devolver Dir[File.join(File.dirname(__FILE__), ''tasks/**/*.rb'')]
  • ahora las tareas se pueden organizar en lib/mygem/thor/tasks/**/*.rb . Todos deben heredar de Mygem::Thor::Task
  • el Thorfile en la raíz del proyecto también carga todas las tareas en lib/mygem/thor/tasks/**/*.rb

Tuve este mismo problema, y ​​casi me había rendido, pero luego tuve una idea:

Si escribe sus tareas en Thorfile s en lugar de Thorfile como clases de ruby, entonces simplemente puede require archivos de Ruby que contengan subclases de Thor y aparecerán en la lista de tareas disponibles cuando ejecute thor -T .

Todo esto es administrado por la clase Thor::Runner . Si observa esto, verá un método #thorfiles que se encarga de buscar archivos llamados Thorfile en el directorio de trabajo actual.

Todo lo que tenía que hacer para a) dividir mis tareas de Thor en varios archivos mientras que b) no tener que tener un solo Thorfile era crear una subclase local de Thor::Runner , sobrescribir su método #thorfile con uno que devolvía mi lista específica de aplicaciones de los archivos de tareas de Thor y luego llame a su método #start y todo funcionó:

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

Así que puedo tener cualquier número de clases de Ruby que definan las tareas de Thor bajo las tareas

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

Casi había renunciado a Thor hasta que descubrí esto, pero no hay muchas bibliotecas que se ocupen de crear generadores y de crear tareas con espacios de nombre, así que me alegro de haber encontrado una solución.


Utilice un módulo sobre-arqueo, digamos Foo , dentro del cual definirá todos los submódulos y subclases.

Inicie la definición de este módulo en un solo archivo foo.thor , que se encuentra en el directorio desde donde ejecutará todas las tareas de Thor. En la parte superior del módulo Foo en este foo.thor , define este método:

# 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

Luego, en la parte inferior de su archivo principal foo.thor , agregue:

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

Eso incluirá recursivamente todos los archivos *.thor en esos directorios. Anide módulos dentro de su módulo principal de Foo para espaciar sus tareas con nombres No importa dónde se encuentren los archivos o cómo se llamen en ese momento, siempre que incluya todos los directorios relacionados con Thor a través del método descrito anteriormente.


desc es un método de clase, debe usar extend en lugar de incluir. Busque here una explicación.