mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Use did_you_mean
spell checker for option suggestions
Now that we require Ruby over `2.3`, we can replace the current suggestion methods we have with tooling from the `did_you_mean` gem. There is a small user visible change and this is that we now offer a single suggestion for misspelled options. We are suggesting fixes during generator invocation and during a mistyped rails server rack handler. In both cases, if we don't make a proper prediction on the first match, we won't do so in the second or third one, so in my mind, this is okay.
This commit is contained in:
parent
ef73318e29
commit
cb25f2c989
6 changed files with 14 additions and 57 deletions
|
@ -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
|
||||
|
|
|
@ -283,10 +283,10 @@ module Rails
|
|||
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
|
||||
|
|
|
@ -276,12 +276,11 @@ module Rails
|
|||
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
|
||||
|
||||
|
|
|
@ -5,6 +5,6 @@ require "rails/command/spellchecker"
|
|||
|
||||
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
|
||||
|
|
|
@ -29,7 +29,7 @@ class Rails::Command::ServerCommandTest < ActiveSupport::TestCase
|
|||
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
|
||||
|
|
|
@ -33,7 +33,7 @@ class GeneratorsTest < Rails::Generators::TestCase
|
|||
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
|
||||
|
@ -43,7 +43,7 @@ class GeneratorsTest < Rails::Generators::TestCase
|
|||
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
|
||||
|
@ -52,7 +52,7 @@ class GeneratorsTest < Rails::Generators::TestCase
|
|||
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
|
||||
|
|
Loading…
Reference in a new issue