提交 6716e4bc 编写于 作者: J José Valim

Use regexp in lookups instead of traversing namespaces. This removes the need of special cases.

上级 00f3f6dc
......@@ -117,11 +117,15 @@ def self.fallbacks
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 @@ def self.load_paths
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 @@ def self.load_paths
#
# 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 @@ def self.invoke_fallbacks_for(name, base) #:nodoc:
# 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 @@ def self.namespace(name=nil)
#
# 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 @@ def self.namespace(name=nil)
#
# 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 @@ def self.namespace(name=nil)
#
# 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 @@ def self.namespace(name=nil)
# ==== 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 @@ def self.hook_for(*names, &block)
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 @@ def self.hook_for(*names, &block)
# 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 @@ def self.inherited(base) #:nodoc:
# 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 @@ def self.base_name
# 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 @@ def self.hooks #:nodoc:
#
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 @@ def setup
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 @@ def test_find_by_namespace_with_duplicated_name
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 @@ def test_find_by_namespace_lookup_with_gem_specification
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 @@ def test_rails_generators_help_with_builtin_information
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 @@ def self.name() 'NewGenerator' end
end
assert_equal false, klass.class_options[:generate].default
ensure
Rails::Generators.subclasses.delete(klass)
end
def test_source_paths_for_not_namespaced_generators
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册