Skip to content

Commit

Permalink
Merge pull request #32289 from gsamokovarov/did-you-mean-suggestions
Browse files Browse the repository at this point in the history
Use `did_you_mean` spell checker for option suggestions
  • Loading branch information
guilleiguaran committed Mar 29, 2018
2 parents 1765f09 + cb25f2c commit 480595e
Show file tree
Hide file tree
Showing 6 changed files with 14 additions and 57 deletions.
46 changes: 2 additions & 44 deletions railties/lib/rails/command/spellchecker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,8 @@
module Rails
module Command
module Spellchecker # :nodoc:
class << self
def suggest(word, from:, count: 3)
from.sort_by { |w| levenshtein_distance(word, w) }.take(count)
end

private
# 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 levenshtein_distance(str1, str2) # :doc:
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
end

x
end
def self.suggest(word, from:)
DidYouMean::SpellChecker.new(dictionary: from.map(&:to_s)).correct(word).first
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions railties/lib/rails/commands/server/server_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -283,10 +283,10 @@ def rack_server_suggestion(server)
Run `rails server --help` for more options.
MSG
else
suggestions = Rails::Command::Spellchecker.suggest(server, from: RACK_SERVERS).map(&:inspect)
suggestions = Rails::Command::Spellchecker.suggest(server, from: RACK_SERVERS)

<<~MSG
Could not find server "#{server}". Maybe you meant #{suggestions.first} or #{suggestions.second}?
Could not find server "#{server}". Maybe you meant #{suggestions.inspect}?
Run `rails server --help` for more options.
MSG
end
Expand Down
11 changes: 5 additions & 6 deletions railties/lib/rails/generators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -276,12 +276,11 @@ def invoke(namespace, args = ARGV, config = {})
klass.start(args, config)
else
options = sorted_groups.flat_map(&:last)
suggestions = Rails::Command::Spellchecker.suggest(namespace.to_s, from: options, count: 3)
suggestions.map! { |s| "'#{s}'" }
msg = "Could not find generator '#{namespace}'. ".dup
msg << "Maybe you meant #{ suggestions[0...-1].join(', ')} or #{suggestions[-1]}\n"
msg << "Run `rails generate --help` for more options."
puts msg
suggestion = Rails::Command::Spellchecker.suggest(namespace.to_s, from: options)
puts <<~MSG
Could not find generator '#{namespace}'. Maybe you meant #{suggestion.inspect}?\n"
Run `rails generate --help` for more options."
MSG
end
end

Expand Down
2 changes: 1 addition & 1 deletion railties/test/command/spellchecker_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@

class Rails::Command::SpellcheckerTest < ActiveSupport::TestCase
test "suggests a word correction from dictionary" do
assert_equal %w(thin cgi puma), Rails::Command::Spellchecker.suggest("tin", from: %w(puma thin cgi))
assert_equal "thin", Rails::Command::Spellchecker.suggest("tin", from: %w(puma thin cgi))
end
end
2 changes: 1 addition & 1 deletion railties/test/commands/server_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_explicit_using_option
end

def test_using_server_mistype
assert_match(/Could not find server "tin". Maybe you meant "thin" or "cgi"/, run_command("--using", "tin"))
assert_match(/Could not find server "tin". Maybe you meant "thin"?/, run_command("--using", "tin"))
end

def test_using_positional_argument_deprecation
Expand Down
6 changes: 3 additions & 3 deletions railties/test/generators_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def test_invoke_when_generator_is_not_found
def test_generator_suggestions
name = :migrationz
output = capture(:stdout) { Rails::Generators.invoke name }
assert_match "Maybe you meant 'migration'", output
assert_match 'Maybe you meant "migration"?', output
end

def test_generator_suggestions_except_en_locale
Expand All @@ -43,7 +43,7 @@ def test_generator_suggestions_except_en_locale
I18n.default_locale = :ja
name = :tas
output = capture(:stdout) { Rails::Generators.invoke name }
assert_match "Maybe you meant 'task', 'job' or", output
assert_match 'Maybe you meant "task"?', output
ensure
I18n.available_locales = orig_available_locales
I18n.default_locale = orig_default_locale
Expand All @@ -52,7 +52,7 @@ def test_generator_suggestions_except_en_locale
def test_generator_multiple_suggestions
name = :tas
output = capture(:stdout) { Rails::Generators.invoke name }
assert_match "Maybe you meant 'task', 'job' or", output
assert_match 'Maybe you meant "task"?', output
end

def test_help_when_a_generator_with_required_arguments_is_invoked_without_arguments
Expand Down

0 comments on commit 480595e

Please sign in to comment.