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.
- registro. Esto le permite registrar un nuevo subcomando como una tarea
- Opciones de clase. Esto le da un hash de todas las opciones de clase.
- 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
enMygem::Thor::Runner
y anulando susthorfiles
privados dethorfiles
ymethod_missing
. Enmethod_missing
tambiénmethod_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
enMygem::Thor::Task
y- anulando su método de clase de
namespace
privado. El método denamespace
personalizado elimina la parteMygem::Thor::Tasks
de las jerarquías de módulos de las tareas. - anulando su método de
thorfiles
privado para devolverDir[File.join(File.dirname(__FILE__), ''tasks/**/*.rb'')]
- anulando su método de clase de
- ahora las tareas se pueden organizar en
lib/mygem/thor/tasks/**/*.rb
. Todos deben heredar deMygem::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.