I'm having some trouble getting Thor to do this, so hopefully someone can point out what I'm doing wrong.
I have a main class class MyApp < Thor
that I want to break out into separate files for multiple namespaces, like thor create:app_type
and thor update:app_type
. I can't find any examples that show how one should break apart a Thor app into pieces, and what I've tried doesn't seem to work.
Take for instance, this class I'm trying to break out from the main Thor class:
module Things
module Grouping
desc "something", "Do something cool in this group"
def something
....
end
end
end
When I try to include or require this in my main class:
class App < Thor
....
require 'grouping_file'
include Things::Grouping
....
end
I get an exception: '<module:Grouping>': undefined method 'desc' for Things::Grouping:Module (NoMethodError)
Is it possible to have multiple namespaces for Thor tasks, and if so, how does one break it out so that you don't have one monolithic class that takes several hundred lines?
Why doesn't it work: when you use desc
inside a Thor
class, you are actually calling a class method Thor.desc
. When you do that in the module, it calls YourModule.desc
which obviously doesn't exist.
There are two ways I can suggest to fix this.
Did you want to have those tasks reused in multiple Thor classes?
When a module is used as an include
in Ruby, the included
class method is called. 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
Did you merely want to separately define tasks in another file?
If so, just reopen your App class in another file. Your Thorfile would look something like:
# Thorfile
Dir['./lib/thor/**/*.rb'].sort.each { |f| load f }
Then your lib/thor/app.rb
would contain some tasks for App
, while another file lib/thor/app-grouping.rb
would contain some more tasks for the same App
class.
Use an over-arching module, let's say Foo
, inside of which you will define all sub-modules and sub-classes.
Start the definition of this module in a single foo.thor
file, which is in the directory from which you will run all Thor tasks. At the top of the Foo
module in this foo.thor
, define this method:
# 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
Then at the bottom of your main foo.thor
file, add:
Foo.load_thorfiles('directory_a')
Foo.load_thorfiles('directory_b')
That will recursively include all the *.thor
files in those directories. Nest modules within your main Foo
module to namespace your tasks. Doesn't matter where the files live or what they're called at that point, as long as you include all your Thor-related directories via the method described above.
I had this same problem, and had almost given up but then I had an idea:
If you write your tasks into Thorfile
s rather than as ruby classes, then you can simply require
in Ruby files that contain Thor subclasses and they will appear in the list of available tasks when you run thor -T
.
This is all managed by the Thor::Runner
class. If you look through this you'll see a #thorfiles
method which is responsible for looking for files named Thorfile
under the current working directory.
All I had to do to a) break my Thor tasks into multiple files whilst b) not having to have a single Thorfile
was to create a local subclass of Thor::Runner
, overwrite its #thorfile
method with one that returned my app specific list of Thor task files and then call its #start
method and it all worked:
class MyApp::Runner < ::Thor::Runner
private
def thorfiles(*args)
Dir['thortasks/**/*.rb']
end
end
MyApp::Runner.start
So I can have any number of Ruby classes defining Thor tasks under thortasks
e.g.
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
I'd almost given up on Thor until I figured this out but there aren't many libraries that deal with creating generators as well as building namespaced tasks, so I'm glad I found a solution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With