diff options
author | Kasper Timm Hansen <kaspth@gmail.com> | 2016-09-26 20:28:40 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-09-26 20:28:40 +0200 |
commit | c994a893c18c0456fd2a30efe4debfc2b18e2508 (patch) | |
tree | 0253b1343a1760dc4701f7d2ee2ff8cdd1d1b290 /railties/lib/rails/generators.rb | |
parent | a6da7938017647dd6e19e23d3d47126cb7a3e1fe (diff) | |
parent | c0c73738bd04c86fbcf6d22c961fec0d33aa2bf6 (diff) | |
download | rails-c994a893c18c0456fd2a30efe4debfc2b18e2508.tar.gz rails-c994a893c18c0456fd2a30efe4debfc2b18e2508.tar.bz2 rails-c994a893c18c0456fd2a30efe4debfc2b18e2508.zip |
Merge pull request #26414 from rails/rails-commands
Initial Rails Commands Infrastructure
Diffstat (limited to 'railties/lib/rails/generators.rb')
-rw-r--r-- | railties/lib/rails/generators.rb | 218 |
1 files changed, 65 insertions, 153 deletions
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index fd35dfd1a2..dd16b44786 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -2,6 +2,7 @@ activesupport_path = File.expand_path("../../../../activesupport/lib", __FILE__) $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) require "thor/group" +require "rails/command" require "active_support" require "active_support/core_ext/object/blank" @@ -13,6 +14,8 @@ require "active_support/core_ext/string/inflections" module Rails module Generators + include Rails::Command::Behavior + autoload :Actions, "rails/generators/actions" autoload :ActiveModel, "rails/generators/active_model" autoload :Base, "rails/generators/base" @@ -127,67 +130,6 @@ module Rails Thor::Base.shell = Thor::Shell::Basic end - # Track all generators subclasses. - def self.subclasses - @subclasses ||= [] - end - - # Rails finds namespaces similar to thor, it only adds one rule: - # - # 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. - # - # find_by_namespace :webrat, :rails, :integration - # - # Will search for the following generators: - # - # "rails:webrat", "webrat:integration", "webrat" - # - # 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: - lookups = [] - lookups << "#{base}:#{name}" if base - lookups << "#{name}:#{context}" if context - - unless base || context - unless name.to_s.include?(?:) - lookups << "#{name}:#{name}" - lookups << "rails:#{name}" - end - lookups << "#{name}" - end - - lookup(lookups) - - namespaces = Hash[subclasses.map { |klass| [klass.namespace, klass] }] - - lookups.each do |namespace| - klass = namespaces[namespace] - return klass if klass - 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={}) - names = namespace.to_s.split(":") - if klass = find_by_namespace(names.pop, names.any? && names.join(":")) - args << "--help" if args.empty? && klass.arguments.any?(&:required?) - klass.start(args, config) - else - options = sorted_groups.flat_map(&:last) - suggestions = options.sort_by { |suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3) - msg = "Could not find generator '#{namespace}'. " - msg << "Maybe you meant #{ suggestions.map { |s| "'#{s}'" }.to_sentence(last_word_connector: " or ", locale: :en) }\n" - msg << "Run `rails generate --help` for more options." - puts msg - end - end - # Returns an array of generator namespaces that are hidden. # Generator namespaces may be hidden for a variety of reasons. # Some are aliased such as "rails:migration" and can be @@ -260,11 +202,13 @@ module Rails def self.sorted_groups namespaces = public_namespaces namespaces.sort! + groups = Hash.new { |h,k| h[k] = [] } namespaces.each do |namespace| base = namespace.split(":").first groups[base] << namespace end + rails = groups.delete("rails") rails.map! { |n| n.sub(/^rails:/, "") } rails.delete("app") @@ -272,64 +216,69 @@ module Rails hidden_namespaces.each { |n| groups.delete(n.to_s) } - [["rails", rails]] + groups.sort.to_a + [[ "rails", rails ]] + groups.sort.to_a end - protected + # Rails finds namespaces similar to thor, it only adds one rule: + # + # 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. + # + # find_by_namespace :webrat, :rails, :integration + # + # Will search for the following generators: + # + # "rails:webrat", "webrat:integration", "webrat" + # + # 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: + lookups = [] + lookups << "#{base}:#{name}" if base + lookups << "#{name}:#{context}" if context - # This code is based directly on the Text gem implementation. - # Copyright (c) 2006-2013 Paul Battley, Michael Neumann, Tim Fletcher. - # - # Returns a value representing the "cost" of transforming str1 into str2 - def self.levenshtein_distance(str1, str2) - s = str1 - t = str2 - n = s.length - m = t.length - - return m if (0 == n) - return n if (0 == m) - - d = (0..m).to_a - x = nil - - # avoid duplicating an enumerable object in the loop - str2_codepoint_enumerable = str2.each_codepoint - - str1.each_codepoint.with_index do |char1, i| - e = i+1 - - str2_codepoint_enumerable.with_index do |char2, j| - cost = (char1 == char2) ? 0 : 1 - x = [ - d[j+1] + 1, # insertion - e + 1, # deletion - d[j] + cost # substitution - ].min - d[j] = e - e = x - end - - d[m] = x + unless base || context + unless name.to_s.include?(?:) + lookups << "#{name}:#{name}" + lookups << "rails:#{name}" end - - x + lookups << "#{name}" end - # Prints a list of generators. - def self.print_list(base, namespaces) #:nodoc: - namespaces = namespaces.reject do |n| - hidden_namespaces.include?(n) - end + lookup(lookups) + + namespaces = Hash[subclasses.map { |klass| [klass.namespace, klass] }] + lookups.each do |namespace| - return if namespaces.empty? - puts "#{base.camelize}:" + klass = namespaces[namespace] + return klass if klass + end - namespaces.each do |namespace| - puts(" #{namespace}") - end + invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name) + end - puts + # 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={}) + names = namespace.to_s.split(":") + if klass = find_by_namespace(names.pop, names.any? && names.join(":")) + args << "--help" if args.empty? && klass.arguments.any?(&:required?) + klass.start(args, config) + else + options = sorted_groups.flat_map(&:last) + suggestions = options.sort_by { |suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3) + msg = "Could not find generator '#{namespace}'. " + msg << "Maybe you meant #{ suggestions.map { |s| "'#{s}'" }.to_sentence(last_word_connector: " or ", locale: :en) }\n" + msg << "Run `rails generate --help` for more options." + puts msg + end + end + + protected + def self.print_list(base, namespaces) + namespaces = namespaces.reject { |n| hidden_namespaces.include?(n) } + super end # Try fallbacks for the given base. @@ -348,53 +297,16 @@ module Rails nil end - # Receives namespaces in an array and tries to find matching generators - # in the load path. - def self.lookup(namespaces) #:nodoc: - paths = namespaces_to_paths(namespaces) - - paths.each do |raw_path| - ["rails/generators", "generators"].each do |base| - path = "#{base}/#{raw_path}_generator" - - begin - require path - return - rescue LoadError => e - raise unless e.message =~ /#{Regexp.escape(path)}$/ - rescue Exception => e - warn "[WARNING] Could not load generator #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}" - end - end - end + def self.command_type + @command_type ||= "generator" end - # This will try to load any generator in the load path to show in help. - def self.lookup! #:nodoc: - $LOAD_PATH.each do |base| - Dir[File.join(base, "{rails/generators,generators}", "**", "*_generator.rb")].each do |path| - begin - path = path.sub("#{base}/", "") - require path - rescue Exception - # No problem - end - end - end + def self.lookup_paths + @lookup_paths ||= %w( rails/generators generators ) end - # Convert namespaces to paths by replacing ":" for "/" and adding - # an extra lookup. For example, "rails:model" should be searched - # in both: "rails/model/model_generator" and "rails/model_generator". - def self.namespaces_to_paths(namespaces) #:nodoc: - paths = [] - namespaces.each do |namespace| - pieces = namespace.split(":") - paths << pieces.dup.push(pieces.last).join("/") - paths << pieces.join("/") - end - paths.uniq! - paths + def self.file_lookup_paths + @file_lookup_paths ||= [ "{#{lookup_paths.join(',')}}", "**", "*_generator.rb" ] end end end |