aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--railties/lib/rails/generators.rb148
-rw-r--r--railties/lib/rails/generators/base.rb60
-rw-r--r--railties/test/generators_test.rb17
3 files changed, 97 insertions, 128 deletions
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 6419961415..3713a38b33 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -117,11 +117,15 @@ module Rails
end
# Remove the color from output.
- #
def self.no_color!
Thor::Base.shell = Thor::Shell::Basic
end
+ # Track all generators subclasses.
+ def self.subclasses
+ @subclasses ||= []
+ end
+
# Generators load paths used on lookup. The lookup happens as:
#
# 1) lib generators
@@ -147,18 +151,10 @@ module Rails
end
load_paths # Cache load paths. Needed to avoid __FILE__ pointing to wrong paths.
- # Rails finds namespaces exactly as thor, with three conveniences:
- #
- # 1) If your generator name ends with generator, as WebratGenerator, it sets
- # its namespace to "webrat", so it can be invoked as "webrat" and not
- # "webrat_generator";
+ # Rails finds namespaces similar to thor, it only adds one rule:
#
- # 2) If your generator has a generators namespace, as Rails::Generators::WebratGenerator,
- # the namespace is set to "rails:generators:webrat", but Rails allows it
- # to be invoked simply as "rails:webrat". The "generators" is added
- # automatically when doing the lookup;
- #
- # 3) Rails looks in load paths and loads the generator just before it's going to be used.
+ # Generators names must end with "_generator.rb". This is required because Rails
+ # looks in load paths and loads the generator just before it's going to be used.
#
# ==== Examples
#
@@ -166,113 +162,81 @@ module Rails
#
# Will search for the following generators:
#
- # "rails:generators:webrat", "webrat:generators:integration", "webrat"
- #
- # On the other hand, if "rails:webrat" is given, it will search for:
+ # "rails:webrat", "webrat:integration", "webrat"
#
- # "rails:generators:webrat", "rails:webrat"
- #
- # Notice that the "generators" namespace is handled automatically by Rails,
- # so you don't need to type it when you want to invoke a generator in specific.
+ # Notice that "rails:generators:webrat" could be loaded as well, what
+ # Rails looks for is the first and last parts of the namespace.
#
def self.find_by_namespace(name, base=nil, context=nil) #:nodoc:
- name, attempts = name.to_s, [ ]
-
- case name.count(':')
- when 1
- base, name = name.split(':')
- return find_by_namespace(name, base)
- when 0
- attempts += generator_names(base, name) if base
- attempts += generator_names(name, context) if context
- end
-
- attempts << name
- attempts += generator_names(name, name) unless name.include?(?:)
- attempts.uniq!
-
- unloaded = attempts - namespaces
- lookup(unloaded)
+ # Mount regexps to lookup
+ regexps = []
+ regexps << /^#{base}:[\w:]*#{name}$/ if base
+ regexps << /^#{name}:[\w:]*#{context}$/ if context
+ regexps << /^[(#{name}):]+$/
+ regexps.uniq!
+
+ # Check if generator happens to be loaded
+ checked = subclasses.dup
+ klass = find_by_regexps(regexps, checked)
+ return klass if klass
+
+ # Try to require other generators by looking in load_paths
+ lookup(name, context)
+ unchecked = subclasses - checked
+ klass = find_by_regexps(regexps, unchecked)
+ return klass if klass
+
+ # Invoke fallbacks
+ invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name)
+ end
- attempts.each do |namespace|
- klass = Thor::Util.find_by_namespace(namespace)
- return klass if klass
+ # Tries to find a generator which the namespace match the regexp.
+ def self.find_by_regexps(regexps, klasses)
+ klasses.find do |klass|
+ namespace = klass.namespace
+ regexps.find { |r| namespace =~ r }
end
-
- invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name)
end
# Receives a namespace, arguments and the behavior to invoke the generator.
# It's used as the default entry point for generate, destroy and update
# commands.
- #
def self.invoke(namespace, args=ARGV, config={})
- if klass = find_by_namespace(namespace, "rails")
+ names = namespace.to_s.split(':')
+
+ if klass = find_by_namespace(names.pop, names.shift || "rails")
args << "--help" if klass.arguments.any? { |a| a.required? } && args.empty?
- klass.start args, config
+ klass.start(args, config)
else
puts "Could not find generator #{namespace}."
end
end
# Show help message with available generators.
- #
def self.help
- rails = Rails::Generators.builtin.map do |group, name|
- name if group == "rails"
- end
- rails.compact!
- rails.sort!
-
- puts "Please select a generator."
- puts "Builtin: #{rails.join(', ')}."
-
- # Load paths and remove builtin
- paths, others = load_paths.dup, []
- paths.pop
-
- paths.each do |path|
- tail = [ "*", "*", "*_generator.rb" ]
-
- until tail.empty?
- others += Dir[File.join(path, *tail)].collect do |file|
- name = file.split('/')[-tail.size, 2]
- name.last.sub!(/_generator\.rb$/, '')
- name.uniq!
- name.join(':')
- end
- tail.shift
- end
- end
+ builtin = Rails::Generators.builtin.each { |n| n.sub!(/^rails:/, '') }
+ builtin.sort!
+ lookup("*")
+ others = subclasses.map{ |k| k.namespace.gsub(':generators:', ':') }
+ others -= Rails::Generators.builtin
others.sort!
+
+ puts "Please select a generator."
+ puts "Builtin: #{builtin.join(', ')}."
puts "Others: #{others.join(', ')}." unless others.empty?
end
protected
- # Return all defined namespaces.
- #
- def self.namespaces #:nodoc:
- Thor::Base.subclasses.map { |klass| klass.namespace }
- end
-
- # Keep builtin generators in an Array[Array[group, name]].
- #
+ # Keep builtin generators in an Array.
def self.builtin #:nodoc:
Dir[File.dirname(__FILE__) + '/generators/*/*'].collect do |file|
- file.split('/')[-2, 2]
+ file.split('/')[-2, 2].join(':')
end
end
- # By default, Rails strips the generator namespace to make invocations
- # easier. This method generaters the both possibilities names.
- def self.generator_names(first, second) #:nodoc:
- [ "#{first}:generators:#{second}", "#{first}:#{second}" ]
- end
-
- # Try callbacks for the given base.
- #
+ # Try fallbacks for the given base.
def self.invoke_fallbacks_for(name, base) #:nodoc:
return nil unless base && fallbacks[base.to_sym]
invoked_fallbacks = []
@@ -290,10 +254,10 @@ module Rails
# Receives namespaces in an array and tries to find matching generators
# in the load path.
- #
- def self.lookup(attempts) #:nodoc:
- attempts = attempts.map { |a| "#{a.split(":").last}_generator" }.uniq
- attempts = "{#{attempts.join(',')}}.rb"
+ def self.lookup(*attempts) #:nodoc:
+ attempts.compact!
+ attempts.uniq!
+ attempts = "{#{attempts.join(',')}}_generator.rb"
self.load_paths.each do |path|
Dir[File.join(path, '**', attempts)].each do |file|
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index 226ae63963..a10d1c9fce 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -76,17 +76,18 @@ module Rails
#
# The controller generator will then try to invoke the following generators:
#
- # "rails:generators:test_unit", "test_unit:generators:controller", "test_unit"
+ # "rails:test_unit", "test_unit:controller", "test_unit"
#
- # In this case, the "test_unit:generators:controller" is available and is
- # invoked. This allows any test framework to hook into Rails as long as it
- # provides any of the hooks above.
+ # Notice that "rails:generators:test_unit" could be loaded as well, what
+ # Rails looks for is the first and last parts of the namespace. This is what
+ # allows any test framework to hook into Rails as long as it provides any
+ # of the hooks above.
#
# ==== Options
#
- # This lookup can be customized with two options: :base and :as. The first
- # is the root module value and in the example above defaults to "rails".
- # The later defaults to the generator name, without the "Generator" ending.
+ # The first and last part used to find the generator to be invoked are
+ # guessed based on class invokes hook_for, as noticed in the example above.
+ # This can be customized with two options: :base and :as.
#
# Let's suppose you are creating a generator that needs to invoke the
# controller generator from test unit. Your first attempt is:
@@ -97,7 +98,7 @@ module Rails
#
# The lookup in this case for test_unit as input is:
#
- # "test_unit:generators:awesome", "test_unit"
+ # "test_unit:awesome", "test_unit"
#
# Which is not the desired the lookup. You can change it by providing the
# :as option:
@@ -108,18 +109,18 @@ module Rails
#
# And now it will lookup at:
#
- # "test_unit:generators:awesome", "test_unit"
+ # "test_unit:controller", "test_unit"
#
# Similarly, if you want it to also lookup in the rails namespace, you just
# need to provide the :base value:
#
# class AwesomeGenerator < Rails::Generators::Base
- # hook_for :test_framework, :base => :rails, :as => :controller
+ # hook_for :test_framework, :in => :rails, :as => :controller
# end
#
# And the lookup is exactly the same as previously:
#
- # "rails:generators:test_unit", "test_unit:generators:controller", "test_unit"
+ # "rails:test_unit", "test_unit:controller", "test_unit"
#
# ==== Switches
#
@@ -151,11 +152,11 @@ module Rails
# ==== Custom invocations
#
# You can also supply a block to hook_for to customize how the hook is
- # going to be invoked. The block receives two parameters, an instance
+ # going to be invoked. The block receives two arguments, an instance
# of the current class and the klass to be invoked.
#
# For example, in the resource generator, the controller should be invoked
- # with a pluralized class name. By default, it is invoked with the same
+ # with a pluralized class name. But by default it is invoked with the same
# name as the resource generator, which is singular. To change this, we
# can give a block to customize how the controller can be invoked.
#
@@ -178,11 +179,11 @@ module Rails
end
unless class_options.key?(name)
- class_option name, defaults.merge!(options)
+ class_option(name, defaults.merge!(options))
end
hooks[name] = [ in_base, as_hook ]
- invoke_from_option name, options, &block
+ invoke_from_option(name, options, &block)
end
end
@@ -193,7 +194,7 @@ module Rails
# remove_hook_for :orm
#
def self.remove_hook_for(*names)
- remove_invocation *names
+ remove_invocation(*names)
names.each do |name|
hooks.delete(name)
@@ -219,12 +220,16 @@ module Rails
# and can point to wrong directions when inside an specified directory.
base.source_root
- if base.name && base.name !~ /Base$/ && base.base_name && base.generator_name && defined?(Rails.root) && Rails.root
- path = File.expand_path(File.join(Rails.root, 'lib', 'templates'))
- if base.name.include?('::')
- base.source_paths << File.join(path, base.base_name, base.generator_name)
- else
- base.source_paths << File.join(path, base.generator_name)
+ if base.name && base.name !~ /Base$/
+ Rails::Generators.subclasses << base
+
+ if defined?(Rails.root) && Rails.root
+ path = File.expand_path(File.join(Rails.root, 'lib', 'templates'))
+ if base.name.include?('::')
+ base.source_paths << File.join(path, base.base_name, base.generator_name)
+ else
+ base.source_paths << File.join(path, base.generator_name)
+ end
end
end
end
@@ -290,12 +295,10 @@ module Rails
# Rails::Generators::MetalGenerator will return "metal" as generator name.
#
def self.generator_name
- if name
- @generator_name ||= begin
- if klass_name = name.to_s.split('::').last
- klass_name.sub!(/Generator$/, '')
- klass_name.underscore
- end
+ @generator_name ||= begin
+ if generator = name.to_s.split('::').last
+ generator.sub!(/Generator$/, '')
+ generator.underscore
end
end
end
@@ -339,6 +342,7 @@ module Rails
#
def self.prepare_for_invocation(name, value) #:nodoc:
if value && constants = self.hooks[name]
+ value = name if TrueClass === value
Rails::Generators.find_by_namespace(value, *constants)
else
super
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 4b7b80c7f5..2df218debc 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -9,6 +9,11 @@ class GeneratorsTest < GeneratorsTestCase
Gem.stubs(:respond_to?).with(:loaded_specs).returns(false)
end
+ def test_invoke_add_generators_to_raw_lookups
+ TestUnit::Generators::ModelGenerator.expects(:start).with(["Account"], {})
+ Rails::Generators.invoke("test_unit:model", ["Account"])
+ end
+
def test_invoke_when_generator_is_not_found
output = capture(:stdout){ Rails::Generators.invoke :unknown }
assert_equal "Could not find generator unknown.\n", output
@@ -51,12 +56,6 @@ class GeneratorsTest < GeneratorsTestCase
assert_equal "foobar:foobar", klass.namespace
end
- def test_find_by_namespace_add_generators_to_raw_lookups
- klass = Rails::Generators.find_by_namespace("test_unit:model")
- assert klass
- assert_equal "test_unit:generators:model", klass.namespace
- end
-
def test_find_by_namespace_lookup_to_the_rails_root_folder
klass = Rails::Generators.find_by_namespace(:fixjour)
assert klass
@@ -96,7 +95,7 @@ class GeneratorsTest < GeneratorsTestCase
end
def test_builtin_generators
- assert Rails::Generators.builtin.include? %w(rails model)
+ assert Rails::Generators.builtin.include?("rails:model")
end
def test_rails_generators_help_with_builtin_information
@@ -107,7 +106,7 @@ class GeneratorsTest < GeneratorsTestCase
def test_rails_generators_with_others_information
output = capture(:stdout){ Rails::Generators.help }.split("\n").last
- assert_equal "Others: active_record:fixjour, fixjour, foobar, mspec, rails:javascripts.", output
+ assert_equal "Others: active_record:fixjour, fixjour, foobar:foobar, mspec, rails:javascripts, xspec.", output
end
def test_warning_is_shown_if_generator_cant_be_loaded
@@ -178,6 +177,8 @@ class GeneratorsTest < GeneratorsTestCase
end
assert_equal false, klass.class_options[:generate].default
+ ensure
+ Rails::Generators.subclasses.delete(klass)
end
def test_source_paths_for_not_namespaced_generators