aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--railties/CHANGELOG.md4
-rw-r--r--railties/lib/rails/generators.rb60
-rw-r--r--railties/test/generators_test.rb8
3 files changed, 63 insertions, 9 deletions
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index b328619646..3350b1a4b2 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,7 @@
+* Invalid `bin/rails generate` commands will now show spelling suggestions.
+
+ *Richard Schneeman*
+
* Add `bin/setup` script to bootstrap an application.
*Yves Senn*
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|
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 8d6dbf80c2..0b4edafaca 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -24,7 +24,13 @@ class GeneratorsTest < Rails::Generators::TestCase
name = :unknown
output = capture(:stdout){ Rails::Generators.invoke name }
assert_match "Could not find generator '#{name}'", output
- assert_match "scaffold", output
+ assert_match "`rails generate --help`", output
+ end
+
+ def test_generator_suggestions
+ name = :migrationz
+ output = capture(:stdout){ Rails::Generators.invoke name }
+ assert_match "Maybe you meant 'migration'", output
end
def test_help_when_a_generator_with_required_arguments_is_invoked_without_arguments