did_you_mean 0.9.6-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +27 -0
- data/.travis.yml +35 -0
- data/Appraisals +23 -0
- data/Gemfile +18 -0
- data/LICENSE.txt +22 -0
- data/README.md +110 -0
- data/Rakefile +61 -0
- data/did_you_mean.gemspec +35 -0
- data/ext/did_you_mean/extconf.rb +3 -0
- data/ext/did_you_mean/method_missing.c +20 -0
- data/ext/did_you_mean/org/yukinishijima/ReceiverCapturer.java +57 -0
- data/gemfiles/activerecord_32.gemfile +21 -0
- data/gemfiles/activerecord_40.gemfile +21 -0
- data/gemfiles/activerecord_41.gemfile +21 -0
- data/gemfiles/activerecord_42.gemfile +21 -0
- data/gemfiles/activerecord_edge.gemfile +25 -0
- data/lib/did_you_mean.rb +16 -0
- data/lib/did_you_mean/core_ext/name_error.rb +28 -0
- data/lib/did_you_mean/finders.rb +31 -0
- data/lib/did_you_mean/finders/name_error_finders.rb +24 -0
- data/lib/did_you_mean/finders/name_error_finders/similar_class_finder.rb +50 -0
- data/lib/did_you_mean/finders/name_error_finders/similar_name_finder.rb +61 -0
- data/lib/did_you_mean/finders/similar_attribute_finder.rb +26 -0
- data/lib/did_you_mean/finders/similar_method_finder.rb +45 -0
- data/lib/did_you_mean/levenshtein.rb +49 -0
- data/lib/did_you_mean/receiver_capturer.jar +0 -0
- data/lib/did_you_mean/test_helper.rb +7 -0
- data/lib/did_you_mean/version.rb +3 -0
- data/lib/did_you_mean/word_collection.rb +27 -0
- data/test/all_test.rb +17 -0
- data/test/name_error_extension_test.rb +65 -0
- data/test/no_method_error_extension_test.rb +14 -0
- data/test/null_finder_test.rb +19 -0
- data/test/similar_attribute_finder_test.rb +17 -0
- data/test/similar_class_finder_test.rb +85 -0
- data/test/similar_method_finder_test.rb +60 -0
- data/test/similar_name_finder_test.rb +62 -0
- data/test/test_helper.rb +32 -0
- metadata +165 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "activerecord", "~> 4.0.0"
|
6
|
+
|
7
|
+
platforms :ruby do
|
8
|
+
gem "sqlite3"
|
9
|
+
end
|
10
|
+
|
11
|
+
platforms :jruby do
|
12
|
+
gem "activerecord-jdbcsqlite3-adapter"
|
13
|
+
end
|
14
|
+
|
15
|
+
platforms :rbx do
|
16
|
+
gem "rubysl", "~> 2.0"
|
17
|
+
gem "racc"
|
18
|
+
gem "rubinius-developer_tools"
|
19
|
+
end
|
20
|
+
|
21
|
+
gemspec :path => "../"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "activerecord", "~> 4.1.0"
|
6
|
+
|
7
|
+
platforms :ruby do
|
8
|
+
gem "sqlite3"
|
9
|
+
end
|
10
|
+
|
11
|
+
platforms :jruby do
|
12
|
+
gem "activerecord-jdbcsqlite3-adapter"
|
13
|
+
end
|
14
|
+
|
15
|
+
platforms :rbx do
|
16
|
+
gem "rubysl", "~> 2.0"
|
17
|
+
gem "racc"
|
18
|
+
gem "rubinius-developer_tools"
|
19
|
+
end
|
20
|
+
|
21
|
+
gemspec :path => "../"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "activerecord", "~> 4.2.0"
|
6
|
+
|
7
|
+
platforms :ruby do
|
8
|
+
gem "sqlite3"
|
9
|
+
end
|
10
|
+
|
11
|
+
platforms :jruby do
|
12
|
+
gem "activerecord-jdbcsqlite3-adapter"
|
13
|
+
end
|
14
|
+
|
15
|
+
platforms :rbx do
|
16
|
+
gem "rubysl", "~> 2.0"
|
17
|
+
gem "racc"
|
18
|
+
gem "rubinius-developer_tools"
|
19
|
+
end
|
20
|
+
|
21
|
+
gemspec :path => "../"
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
git "git://github.com/rails/rails.git" do
|
6
|
+
gem "activerecord", :require => "activerecord"
|
7
|
+
end
|
8
|
+
|
9
|
+
gem "arel", :github => "rails/arel"
|
10
|
+
|
11
|
+
platforms :ruby do
|
12
|
+
gem "sqlite3"
|
13
|
+
end
|
14
|
+
|
15
|
+
platforms :jruby do
|
16
|
+
gem "activerecord-jdbcsqlite3-adapter"
|
17
|
+
end
|
18
|
+
|
19
|
+
platforms :rbx do
|
20
|
+
gem "rubysl", "~> 2.0"
|
21
|
+
gem "racc"
|
22
|
+
gem "rubinius-developer_tools"
|
23
|
+
end
|
24
|
+
|
25
|
+
gemspec :path => "../"
|
data/lib/did_you_mean.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "interception"
|
2
|
+
|
3
|
+
require "did_you_mean/version"
|
4
|
+
require "did_you_mean/core_ext/name_error"
|
5
|
+
require "did_you_mean/finders"
|
6
|
+
|
7
|
+
module DidYouMean
|
8
|
+
Interception.listen(->(exception, binding) {
|
9
|
+
# On IRB/pry console, this event is called twice. In the second event,
|
10
|
+
# we get IRB/pry binding. So it shouldn't override @frame_binding if
|
11
|
+
# it's already defined.
|
12
|
+
if exception.is_a?(NameError) && !exception.instance_variable_defined?(:@frame_binding)
|
13
|
+
exception.instance_variable_set(:@frame_binding, binding)
|
14
|
+
end
|
15
|
+
})
|
16
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class NameError
|
2
|
+
attr_reader :frame_binding
|
3
|
+
|
4
|
+
IGNORED_CALLERS = [
|
5
|
+
/( |`)missing_name'/,
|
6
|
+
/( |`)safe_constantize'/
|
7
|
+
].freeze
|
8
|
+
private_constant :IGNORED_CALLERS
|
9
|
+
|
10
|
+
def to_s_with_did_you_mean
|
11
|
+
msg = original_message
|
12
|
+
msg << did_you_mean?.to_s if IGNORED_CALLERS.all? {|ignored| caller.first(8).grep(ignored).empty? }
|
13
|
+
msg
|
14
|
+
rescue
|
15
|
+
original_message
|
16
|
+
end
|
17
|
+
|
18
|
+
alias original_message to_s
|
19
|
+
alias to_s to_s_with_did_you_mean
|
20
|
+
|
21
|
+
def did_you_mean?
|
22
|
+
finder.did_you_mean?
|
23
|
+
end
|
24
|
+
|
25
|
+
def finder
|
26
|
+
@finder ||= DidYouMean.finders[self.class.to_s].new(self)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "did_you_mean/word_collection"
|
2
|
+
|
3
|
+
module DidYouMean
|
4
|
+
module BaseFinder
|
5
|
+
def did_you_mean?
|
6
|
+
return if similar_words.empty?
|
7
|
+
|
8
|
+
output = "\n\n"
|
9
|
+
output << " Did you mean? #{format(similar_words.first)}\n"
|
10
|
+
output << similar_words.drop(1).map{|word| "#{' ' * 18}#{format(word)}\n" }.join
|
11
|
+
output << " " # for pry
|
12
|
+
end
|
13
|
+
|
14
|
+
def similar_words
|
15
|
+
@similar_words ||= WordCollection.new(words).similar_to(target_word)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class NullFinder
|
20
|
+
def initialize(*); end
|
21
|
+
def did_you_mean?; end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.finders
|
25
|
+
@@finders ||= Hash.new(NullFinder)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
require 'did_you_mean/finders/name_error_finders'
|
30
|
+
require 'did_you_mean/finders/similar_attribute_finder'
|
31
|
+
require 'did_you_mean/finders/similar_method_finder'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module DidYouMean
|
2
|
+
module NameErrorFinders
|
3
|
+
def self.included(*)
|
4
|
+
raise "Do not include this module since it overrides Class.new method."
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.new(exception)
|
8
|
+
klass = if /uninitialized constant/ =~ exception.original_message
|
9
|
+
SimilarClassFinder
|
10
|
+
elsif /undefined local variable or method/ =~ exception.original_message
|
11
|
+
SimilarNameFinder
|
12
|
+
else
|
13
|
+
NullFinder
|
14
|
+
end
|
15
|
+
|
16
|
+
klass.new(exception)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
finders["NameError"] = NameErrorFinders
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'did_you_mean/finders/name_error_finders/similar_name_finder'
|
24
|
+
require 'did_you_mean/finders/name_error_finders/similar_class_finder'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module DidYouMean
|
2
|
+
class SimilarClassFinder
|
3
|
+
include BaseFinder
|
4
|
+
attr_reader :class_name, :original_message
|
5
|
+
|
6
|
+
def initialize(exception)
|
7
|
+
@class_name, @original_message = exception.name, exception.original_message
|
8
|
+
end
|
9
|
+
|
10
|
+
def words
|
11
|
+
scopes.flat_map do |scope|
|
12
|
+
scope.constants.map {|name| ConstantName.new(name, scope) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def name_from_message
|
17
|
+
class_name || /([A-Z]\w*$)/.match(original_message)[0]
|
18
|
+
end
|
19
|
+
alias target_word name_from_message
|
20
|
+
|
21
|
+
def similar_words
|
22
|
+
super.map(&:full_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def scopes
|
26
|
+
@scopes ||= scope_base.size.times.map do |count|
|
27
|
+
eval(scope_base[0..(- count)].join("::"))
|
28
|
+
end.reverse << Object
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def scope_base
|
34
|
+
@scope_base ||= (/(([A-Z]\w*::)*)([A-Z]\w*)$/ =~ original_message ? $1 : "").split("::")
|
35
|
+
end
|
36
|
+
|
37
|
+
class ConstantName < String
|
38
|
+
attr_reader :scope
|
39
|
+
|
40
|
+
def initialize(str, scope)
|
41
|
+
super(str.to_s)
|
42
|
+
@scope = scope.to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
def full_name
|
46
|
+
scope == "Object" ? to_s : "#{scope}::#{to_s}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module DidYouMean
|
2
|
+
class SimilarNameFinder
|
3
|
+
include BaseFinder
|
4
|
+
attr_reader :name, :_methods, :_local_variables, :_instance_variables
|
5
|
+
|
6
|
+
def initialize(exception)
|
7
|
+
@name = exception.name
|
8
|
+
@_methods = exception.frame_binding.eval("methods")
|
9
|
+
@_local_variables = exception.frame_binding.eval("local_variables")
|
10
|
+
@_instance_variables = exception.frame_binding.eval("instance_variables").map do |name|
|
11
|
+
name.to_s.tr("@", "")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def words
|
16
|
+
local_variable_names + method_names + instance_variable_names
|
17
|
+
end
|
18
|
+
|
19
|
+
alias target_word name
|
20
|
+
|
21
|
+
def local_variable_names
|
22
|
+
_local_variables.map {|word| LocalVariableName.new(word.to_s) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def similar_local_variables
|
26
|
+
similar_words.select{|word| word.is_a?(LocalVariableName) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_names
|
30
|
+
_methods.map {|word| MethodName.new(word.to_s) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def similar_methods
|
34
|
+
similar_words.select{|word| word.is_a?(MethodName) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def instance_variable_names
|
38
|
+
_instance_variables.map {|word| InstanceVariableName.new(word.to_s) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def similar_instance_variables
|
42
|
+
similar_words.select {|word| word.is_a?(InstanceVariableName) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def format(word)
|
46
|
+
"#{word.prefix}#{word}"
|
47
|
+
end
|
48
|
+
|
49
|
+
class MethodName < String
|
50
|
+
def prefix; "#"; end
|
51
|
+
end
|
52
|
+
|
53
|
+
class LocalVariableName < String
|
54
|
+
def prefix; ""; end
|
55
|
+
end
|
56
|
+
|
57
|
+
class InstanceVariableName < String
|
58
|
+
def prefix; "@"; end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module DidYouMean
|
2
|
+
class SimilarAttributeFinder
|
3
|
+
include BaseFinder
|
4
|
+
attr_reader :columns, :attribute_name
|
5
|
+
|
6
|
+
def initialize(exception)
|
7
|
+
@columns = exception.frame_binding.eval("self.class").columns
|
8
|
+
@attribute_name = (/unknown attribute(: | ')(\w+)/ =~ exception.original_message) && $2
|
9
|
+
end
|
10
|
+
|
11
|
+
def words
|
12
|
+
columns.map(&:name)
|
13
|
+
end
|
14
|
+
|
15
|
+
alias target_word attribute_name
|
16
|
+
|
17
|
+
def format(column_name)
|
18
|
+
"%{column}: %{type}" % {
|
19
|
+
column: column_name,
|
20
|
+
type: columns.detect{|c| c.name == column_name }.type
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
finders["ActiveRecord::UnknownAttributeError"] = SimilarAttributeFinder
|
26
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module DidYouMean
|
2
|
+
class SimilarMethodFinder
|
3
|
+
include BaseFinder
|
4
|
+
attr_reader :method_name, :receiver
|
5
|
+
|
6
|
+
def initialize(exception)
|
7
|
+
@method_name, @receiver = exception.name, exception.receiver
|
8
|
+
end
|
9
|
+
|
10
|
+
def words
|
11
|
+
method_names = receiver.methods + receiver.singleton_methods
|
12
|
+
method_names.delete(@method_name)
|
13
|
+
method_names.uniq
|
14
|
+
end
|
15
|
+
|
16
|
+
alias target_word method_name
|
17
|
+
|
18
|
+
def format(word)
|
19
|
+
"#{separator}#{word}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def class_method?
|
23
|
+
receiver.is_a? Class
|
24
|
+
end
|
25
|
+
|
26
|
+
def separator
|
27
|
+
class_method? ? "." : "#"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
if defined?(RUBY_ENGINE)
|
32
|
+
finders["NoMethodError"] = SimilarMethodFinder
|
33
|
+
|
34
|
+
case RUBY_ENGINE
|
35
|
+
when 'ruby'
|
36
|
+
require 'did_you_mean/method_missing'
|
37
|
+
when 'jruby'
|
38
|
+
require 'did_you_mean/receiver_capturer'
|
39
|
+
org.yukinishijima.ReceiverCapturer.setup(JRuby.runtime)
|
40
|
+
NoMethodError.send(:attr, :receiver)
|
41
|
+
else
|
42
|
+
finders.delete("NoMethodError")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module DidYouMean
|
2
|
+
module Levenshtein # :nodoc:
|
3
|
+
# This code is based directly on the Text gem implementation
|
4
|
+
# Returns a value representing the "cost" of transforming str1 into str2
|
5
|
+
def distance(str1, str2)
|
6
|
+
n = str1.length
|
7
|
+
m = str2.length
|
8
|
+
return m if n.zero?
|
9
|
+
return n if m.zero?
|
10
|
+
|
11
|
+
d = (0..m).to_a
|
12
|
+
x = nil
|
13
|
+
|
14
|
+
str1.each_char.with_index(1) do |char1, i|
|
15
|
+
str2.each_char.with_index do |char2, j|
|
16
|
+
cost = (char1 == char2) ? 0 : 1
|
17
|
+
x = min3(
|
18
|
+
d[j+1] + 1, # insertion
|
19
|
+
i + 1, # deletion
|
20
|
+
d[j] + cost # substitution
|
21
|
+
)
|
22
|
+
d[j] = i
|
23
|
+
i = x
|
24
|
+
end
|
25
|
+
d[m] = x
|
26
|
+
end
|
27
|
+
|
28
|
+
x
|
29
|
+
end
|
30
|
+
module_function :distance
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# detects the minimum value out of three arguments. This method is
|
35
|
+
# faster than `[a, b, c].min` and puts less GC pressure.
|
36
|
+
# See https://github.com/yuki24/did_you_mean/pull/1 for a performance
|
37
|
+
# benchmark.
|
38
|
+
def min3(a, b, c)
|
39
|
+
if a < b && a < c
|
40
|
+
a
|
41
|
+
elsif b < c
|
42
|
+
b
|
43
|
+
else
|
44
|
+
c
|
45
|
+
end
|
46
|
+
end
|
47
|
+
module_function :min3
|
48
|
+
end
|
49
|
+
end
|
Binary file
|