require 'pathname' require File.dirname(__FILE__) + '/spec' class Object class << self # Lookup missing generators using const_missing. This allows any # generator to reference another without having to know its location: # RubyGems, ~/.rails/generators, and RAILS_ROOT/generators. def lookup_missing_generator(class_id) if md = /(.+)Generator$/.match(class_id.to_s) name = md.captures.first.demodulize.underscore Rails::Generator::Base.lookup(name).klass else const_missing_before_generators(class_id) end end unless respond_to?(:const_missing_before_generators) alias_method :const_missing_before_generators, :const_missing alias_method :const_missing, :lookup_missing_generator end end end # User home directory lookup adapted from RubyGems. def Dir.user_home if ENV['HOME'] ENV['HOME'] elsif ENV['USERPROFILE'] ENV['USERPROFILE'] elsif ENV['HOMEDRIVE'] and ENV['HOMEPATH'] "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}" else File.expand_path '~' end end module Rails module Generator # Generator lookup is managed by a list of sources which return specs # describing where to find and how to create generators. This module # provides class methods for manipulating the source list and looking up # generator specs, and an #instance wrapper for quickly instantiating # generators by name. # # A spec is not a generator: it's a description of where to find # the generator and how to create it. A source is anything that # yields generators from #each. PathSource and GemGeneratorSource are provided. module Lookup def self.included(base) base.extend(ClassMethods) base.use_component_sources! end # Convenience method to instantiate another generator. def instance(generator_name, args, runtime_options = {}) self.class.instance(generator_name, args, runtime_options) end module ClassMethods # The list of sources where we look, in order, for generators. def sources read_inheritable_attribute(:sources) or use_component_sources! end # Add a source to the end of the list. def append_sources(*args) sources.concat(args.flatten) invalidate_cache! end # Add a source to the beginning of the list. def prepend_sources(*args) write_inheritable_array(:sources, args.flatten + sources) invalidate_cache! end # Reset the source list. def reset_sources write_inheritable_attribute(:sources, []) invalidate_cache! end # Use application generators (app, ?). def use_application_sources! reset_sources sources << PathSource.new(:builtin, "#{File.dirname(__FILE__)}/generators/applications") end # Use component generators (model, controller, etc). # 1. Rails application. If RAILS_ROOT is defined we know we're # generating in the context of a Rails application, so search # RAILS_ROOT/generators. # 2. Look in plugins, either for generators/ or rails_generators/ # directories within each plugin # 3. User home directory. Search ~/.rails/generators. # 4. RubyGems. Search for gems named *_generator, and look for # generators within any RubyGem's # /rails_generators/_generator.rb file. # 5. Builtins. Model, controller, mailer, scaffold, and so on. def use_component_sources! reset_sources if defined? ::RAILS_ROOT sources << PathSource.new(:lib, "#{::RAILS_ROOT}/lib/generators") sources << PathSource.new(:vendor, "#{::RAILS_ROOT}/vendor/generators") Rails.configuration.plugin_paths.each do |path| relative_path = Pathname.new(File.expand_path(path)).relative_path_from(Pathname.new(::RAILS_ROOT)) sources << PathSource.new(:"plugins (#{relative_path})", "#{path}/*/**/{,rails_}generators") end end sources << PathSource.new(:user, "#{Dir.user_home}/.rails/generators") if Object.const_defined?(:Gem) sources << GemGeneratorSource.new sources << GemPathSource.new end sources << PathSource.new(:builtin, "#{File.dirname(__FILE__)}/generators/components") end # Lookup knows how to find generators' Specs from a list of Sources. # Searches the sources, in order, for the first matching name. def lookup(generator_name) @found ||= {} generator_name = generator_name.to_s.downcase @found[generator_name] ||= cache.find { |spec| spec.name == generator_name } unless @found[generator_name] chars = generator_name.scan(/./).map{|c|"#{c}.*?"} rx = /^#{chars}$/ gns = cache.select{|spec| spec.name =~ rx } @found[generator_name] ||= gns.first if gns.length == 1 raise GeneratorError, "Pattern '#{generator_name}' matches more than one generator: #{gns.map{|sp|sp.name}.join(', ')}" if gns.length > 1 end @found[generator_name] or raise GeneratorError, "Couldn't find '#{generator_name}' generator" end # Convenience method to lookup and instantiate a generator. def instance(generator_name, args = [], runtime_options = {}) lookup(generator_name).klass.new(args, full_options(runtime_options)) end private # Lookup and cache every generator from the source list. def cache @cache ||= sources.inject([]) { |cache, source| cache + source.to_a } end # Clear the cache whenever the source list changes. def invalidate_cache! @cache = nil end end end # Sources enumerate (yield from #each) generator specs which describe # where to find and how to create generators. Enumerable is mixed in so, # for example, source.collect will retrieve every generator. # Sources may be assigned a label to distinguish them. class Source include Enumerable attr_reader :label def initialize(label) @label = label end # The each method must be implemented in subclasses. # The base implementation raises an error. def each raise NotImplementedError end # Return a convenient sorted list of all generator names. def names map { |spec| spec.name }.sort end end # PathSource looks for generators in a filesystem directory. class PathSource < Source attr_reader :path def initialize(label, path) super label @path = path end # Yield each eligible subdirectory. def each Dir["#{path}/[a-z]*"].each do |dir| if File.directory?(dir) yield Spec.new(File.basename(dir), dir, label) end end end end class AbstractGemSource < Source def initialize super :RubyGems end end # GemGeneratorSource hits the mines to quarry for generators. The latest versions # of gems named *_generator are selected. class GemGeneratorSource < AbstractGemSource # Yield latest versions of generator gems. def each dependency = Gem::Dependency.new(/_generator$/, Gem::Requirement.default) Gem::cache.search(dependency).inject({}) { |latest, gem| hem = latest[gem.name] latest[gem.name] = gem if hem.nil? or gem.version > hem.version latest }.values.each { |gem| yield Spec.new(gem.name.sub(/_generator$/, ''), gem.full_gem_path, label) } end end # GemPathSource looks for generators within any RubyGem's /rails_generators/_generator.rb file. class GemPathSource < AbstractGemSource # Yield each generator within rails_generator subdirectories. def each generator_full_paths.each do |generator| yield Spec.new(File.basename(generator).sub(/_generator.rb$/, ''), File.dirname(generator), label) end end private def generator_full_paths @generator_full_paths ||= Gem::cache.inject({}) do |latest, name_gem| name, gem = name_gem hem = latest[gem.name] latest[gem.name] = gem if hem.nil? or gem.version > hem.version latest end.values.inject([]) do |mem, gem| Dir[gem.full_gem_path + '/{rails_,}generators/**/*_generator.rb'].each do |generator| mem << generator end mem end end end end end