mirror of
https://github.com/github/rails.git
synced 2026-04-26 03:00:59 -04:00
Use regexp in lookups instead of traversing namespaces. This removes the need of special cases.
This commit is contained in:
@@ -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:
|
||||
# Rails finds namespaces similar to thor, it only adds one rule:
|
||||
#
|
||||
# 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";
|
||||
#
|
||||
# 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"
|
||||
# "rails:webrat", "webrat:integration", "webrat"
|
||||
#
|
||||
# On the other hand, if "rails:webrat" is given, it will search for:
|
||||
#
|
||||
# "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, [ ]
|
||||
# Mount regexps to lookup
|
||||
regexps = []
|
||||
regexps << /^#{base}:[\w:]*#{name}$/ if base
|
||||
regexps << /^#{name}:[\w:]*#{context}$/ if context
|
||||
regexps << /^[(#{name}):]+$/
|
||||
regexps.uniq!
|
||||
|
||||
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
|
||||
# Check if generator happens to be loaded
|
||||
checked = subclasses.dup
|
||||
klass = find_by_regexps(regexps, checked)
|
||||
return klass if klass
|
||||
|
||||
attempts << name
|
||||
attempts += generator_names(name, name) unless name.include?(?:)
|
||||
attempts.uniq!
|
||||
|
||||
unloaded = attempts - namespaces
|
||||
lookup(unloaded)
|
||||
|
||||
attempts.each do |namespace|
|
||||
klass = Thor::Util.find_by_namespace(namespace)
|
||||
return klass if klass
|
||||
end
|
||||
# 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
|
||||
|
||||
# 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
|
||||
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!
|
||||
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: #{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
|
||||
|
||||
others.sort!
|
||||
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|
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user