aboutsummaryrefslogtreecommitdiffstats
path: root/railties/lib/rails/generators.rb
diff options
context:
space:
mode:
authorschneems <richard.schneeman@gmail.com>2014-06-03 18:22:45 -0500
committerschneems <richard.schneeman@gmail.com>2014-06-04 16:28:43 -0500
commit72f45ba2922771e744d91334b8680b9b51784eec (patch)
tree327247cc8f2868550e02d036a4d822e91d613a4f /railties/lib/rails/generators.rb
parent3036c4031ab0ce2632d6ac1479ab27cdc4bb4941 (diff)
downloadrails-72f45ba2922771e744d91334b8680b9b51784eec.tar.gz
rails-72f45ba2922771e744d91334b8680b9b51784eec.tar.bz2
rails-72f45ba2922771e744d91334b8680b9b51784eec.zip
Emit suggested generator names when not found
When someone types in a generator command it currently outputs all generators. Instead we can attempt to find a subtle mis-spelling by running all generator names through a levenshtein_distance algorithm provided by rubygems. So now a failure looks like this: ```ruby $ rails generate migratioooons Could not find generator 'migratioooons'. Maybe you meant 'migration' or 'integration_test' or 'generator' Run `rails generate --help` for more options. ``` If the suggestions are bad we leave the user with the hint to run `rails generate --help` to see all commands.
Diffstat (limited to 'railties/lib/rails/generators.rb')
-rw-r--r--railties/lib/rails/generators.rb60
1 files changed, 52 insertions, 8 deletions
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 2a0148fe9d..bf2390cb7e 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -156,8 +156,12 @@ module Rails
args << "--help" if args.empty? && klass.arguments.any? { |a| a.required? }
klass.start(args, config)
else
- puts "Could not find generator '#{namespace}'. Please choose a generator below."
- print_generators
+ options = sorted_groups.map(&:last).flatten
+ 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}'"}.join(" or ") }\n"
+ msg << "Run `rails generate --help` for more options."
+ puts msg
end
end
@@ -220,31 +224,71 @@ module Rails
print_generators
end
- def self.print_generators
+ def self.public_namespaces
lookup!
+ subclasses.map { |k| k.namespace }
+ end
- namespaces = subclasses.map{ |k| k.namespace }
- namespaces.sort!
+ def self.print_generators
+ sorted_groups.each { |b, n| print_list(b, n) }
+ end
+ 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
- # Print Rails defaults first.
rails = groups.delete("rails")
rails.map! { |n| n.sub(/^rails:/, '') }
rails.delete("app")
rails.delete("plugin")
- print_list("rails", rails)
hidden_namespaces.each { |n| groups.delete(n.to_s) }
- groups.sort.each { |b, n| print_list(b, n) }
+ [["rails", rails]] + groups.sort.to_a
end
protected
+ # This code is based directly on the Text gem implementation
+ # 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
+ max = n/2
+
+ return m if (0 == n)
+ return n if (0 == m)
+ return n if (n - m).abs > max
+
+ d = (0..m).to_a
+ x = nil
+
+ str1.each_char.each_with_index do |char1,i|
+ e = i+1
+
+ str2.each_char.each_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
+ end
+
+ return x
+ end
+
# Prints a list of generators.
def self.print_list(base, namespaces) #:nodoc:
namespaces = namespaces.reject do |n|