magic-help 0.20170204

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 59db211c23ba22dab20b3e5b3e16e3303dcfe1dd
4
+ data.tar.gz: b50b3dae0566f1807bd297df5d3113c0830f7ef6
5
+ SHA512:
6
+ metadata.gz: 5523804800dc7e7733e12e775900ce47fb7a568608a27abc3c78ae43157f6f285cb2f1133e0d302360e20da31a21a946b4861328f1047603c0d244679ce2e602
7
+ data.tar.gz: 101f7ccdfd34ddc90287b470f295c16dcbb2de5fc1041fa6bf861576e5e60d1306a0951f2bc57201455a14e4c38383d8b4cc18ef9b2310a2cba38e2356a75400
data/lib/magic/help.rb ADDED
@@ -0,0 +1,104 @@
1
+ require_relative "help/irb"
2
+ require_relative "help/tracepoint"
3
+
4
+ module Magic
5
+ module Help
6
+ def self.help_method_extract(m)
7
+ unless m.inspect =~ %r[\A#<(?:Unbound)?Method: (.*?)>\Z]
8
+ raise "Cannot parse result of #{m.class}#inspect: #{m.inspect}"
9
+ end
10
+ $1.sub(/\A.*?\((.*?)\)(.*)\Z/){ "#{$1}#{$2}" }.sub(/\./, "::").sub(/#<Class:(.*?)>#/) { "#{$1}::" }
11
+ end
12
+
13
+ # Magic::Help.postprocess is used to postprocess queries in two cases:
14
+ # * help "Foo.bar" queries - res defined, more hacks
15
+ # * help { Foo.bar } queries - res not defined, fewer hacks
16
+ def self.postprocess(m, res=nil)
17
+ # Kernel#method_missing here means class was found but method wasn't
18
+ # It is possible that such method exists, it was simply not included.
19
+ # Example - Time::rfc2822 from time.rb.
20
+ #
21
+ # Do not correct it if actual method_missing was called.
22
+ if res and m == "Kernel#method_missing"
23
+ m = res unless res =~ /\A(?:.*)(?:\#|::|\.)method_missing\Z/
24
+ # Most classes do not override Foo::new, but provide new documentation anyway !
25
+ # Two cases are possible
26
+ # * Class#new is used
27
+ # * Bar::new is used, for Bar being some ancestor of Foo
28
+ elsif res and (m =~ /\A(.*)\#new\Z/ or m =~ /\A(.*)::new\Z/)
29
+ cls = $1
30
+ # Do not correct requests for Foo#new
31
+ # If Foo#new became Class#new, it must have been
32
+ # by some evil metaclass hackery.
33
+ #
34
+ # Foo.new or Foo::new both become Foo::new
35
+ if res =~ /\A(.*)(::|\.)new\Z/
36
+ cls_requested, k = $1, $2
37
+ # Condition just to get "Class#new" working correctly
38
+ # Otherwise it would be changed to "Class::new"
39
+ m = "#{cls_requested}::new" unless cls == cls_requested
40
+ end
41
+ end
42
+
43
+ m
44
+ end
45
+
46
+ def self.resolve_help_res(res)
47
+ query = case res
48
+ when Module
49
+ res.to_s
50
+ when UnboundMethod, Method
51
+ help_method_extract(res)
52
+ when /\A(.*)(#|::|\.)(.*)\Z/
53
+ cp, k, m = $1, $2, $3
54
+ begin
55
+ # For multielement paths like File::Stat const_get must be
56
+ # called multiple times, that is:
57
+ # Object.const_get("File").const_get("Stat")
58
+ cls = cp.split(/::/).inject(Object){|c, path_elem| c.const_get(path_elem) }
59
+ case k
60
+ when "#"
61
+ m = cls.instance_method(m)
62
+ m = help_method_extract(m)
63
+ when "::"
64
+ m = cls.method(m)
65
+ # Make sure a module method is returned
66
+ # It fixes `Class::new' resolving to `Class#new'
67
+ # (Class::new and Class#new are the same thing,
68
+ # but their documentations are different)
69
+ m = help_method_extract(m)
70
+ m = m.sub(/\#/, "::") if cls == Class && m == "Class#new"
71
+ when "."
72
+ begin
73
+ m = cls.instance_method(m)
74
+ rescue NameError
75
+ m = cls.method(m)
76
+ end
77
+ m = help_method_extract(m)
78
+ end
79
+ postprocess(m, res)
80
+ rescue NameError
81
+ res
82
+ end
83
+ when String
84
+ res
85
+ else
86
+ res.class.to_s
87
+ end
88
+ query
89
+ end
90
+
91
+ def self.resolve_help_query(*args, &block)
92
+ raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" if args.size > 1
93
+ raise ArgumentError, "help cannot take both arguments and block" if args.size > 0 and block_given?
94
+ if block_given?
95
+ resolve_help_block(&block)
96
+ elsif args.empty?
97
+ # No block, no arguments
98
+ nil
99
+ else
100
+ resolve_help_res(args[0])
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,28 @@
1
+ require "irb"
2
+ require "irb/extend-command"
3
+
4
+ # Now let's hack irb not to alias irb_help -> help
5
+ # It saves us a silly warning at startup:
6
+ # irb: warn: can't alias help from irb_help.
7
+ module IRB::ExtendCommandBundle # :nodoc:
8
+ @ALIASES.delete_if{|a| a == [:help, :irb_help, NO_OVERRIDE]}
9
+ end
10
+
11
+ # help is a Do-What-I-Mean help function.
12
+ # It can be called with either a block or a single argument.
13
+ # When called with single argument, it behaves like normal
14
+ # help function, except for being much smarter:
15
+ #
16
+ # help "Array" - help on Array
17
+ # help "Array#sort" - help on Array#sort
18
+ # help "File#sync=" - help on IO#sync=
19
+ #
20
+ # help { [].sort } - help on Array#sort
21
+ # help { obj.foo = 1 } - help on obj.foo=
22
+ # help { Array } - help on Array
23
+ # help { [] } - help on Array
24
+ # help { Dir["*"] } - help on Dir::[]
25
+ def help(*args, &block)
26
+ query = Magic::Help.resolve_help_query(*args, &block)
27
+ irb_help(query) if query
28
+ end
@@ -0,0 +1,109 @@
1
+ # This is 1.8 API, it's still there in 2.0 but they broke it a bit
2
+
3
+ module Magic
4
+ module Help
5
+ def self.resolve_help_block(&block)
6
+ call_event = nil
7
+ res = nil
8
+ done = false
9
+ res_mm = nil
10
+ argument_error = false
11
+
12
+ # We want to capture calls to method_missing too
13
+ original_method_missing = BasicObject.instance_method(:method_missing)
14
+ BasicObject.class_eval {
15
+ def method_missing(*args)
16
+ throw :magically_irb_helped, [self, *args]
17
+ end
18
+ }
19
+
20
+ tf = lambda{|*xargs|
21
+ return if done
22
+ event = xargs[0]
23
+ if argument_error
24
+ if event == "return"
25
+ done = true
26
+ # For functions called with wrong number of arguments,
27
+ # call event is not generated (function is never called),
28
+ # but the return event is !
29
+ call_event = xargs
30
+ throw :magically_irb_helped
31
+ end
32
+ elsif event == "call" or event == "c-call"
33
+ call_event = xargs
34
+ if call_event.values_at(0, 3, 5) == ["c-call", :new, Class] and
35
+ eval("self", call_event[4]) == ArgumentError
36
+ argument_error = true
37
+ else
38
+ done = true
39
+ # Let Kernel#method_missing run, otherwise throw
40
+ unless call_event.values_at(0, 3, 5) == ["call", :method_missing, BasicObject]
41
+ throw :magically_irb_helped
42
+ end
43
+ end
44
+ end
45
+ }
46
+ res_mm = catch(:magically_irb_helped) {
47
+ set_trace_func tf
48
+ res = yield()
49
+ done = true
50
+ nil
51
+ }
52
+ done = true
53
+ set_trace_func nil
54
+ BasicObject.instance_eval {
55
+ define_method(:method_missing, original_method_missing)
56
+ # It was originally private, restore it as such
57
+ private :method_missing
58
+ }
59
+ # Handle captured method_missing
60
+
61
+ if res_mm
62
+ # This is explicit Foo#method_missing:
63
+ # It shouldn't really be allowed as it's private.
64
+ if res_mm.size == 1
65
+ return "Kernel#method_missing"
66
+ else
67
+ bound_self, meth = *res_mm
68
+ # method_missing is called if:
69
+ # * there was no such method
70
+ # * there was a method, but a private one only
71
+ begin
72
+ # Surprise ! A private method !
73
+ m = bound_self.method(meth)
74
+ query = help_method_extract(m)
75
+ rescue NameError
76
+ # No such method
77
+ if bound_self.is_a? Class
78
+ query = "#{bound_self}::#{meth}"
79
+ else
80
+ query = "#{bound_self.class}.#{meth}"
81
+ end
82
+ end
83
+ end
84
+ postprocess(query)
85
+ # Handle normal call events
86
+ elsif call_event
87
+ meth, bind, cls = call_event[3], call_event[4], call_event[5]
88
+ bound_self = bind.receiver
89
+ if meth == :new
90
+ cls = bound_self
91
+ end
92
+
93
+ # Foo::bar and Foo#bar look the same
94
+ # Check whether self == Foo to tell them apart
95
+ #
96
+ # Only Class::new is documented as such,
97
+ # all other Class::foo are really Class#foo
98
+ if bound_self == cls && (bound_self != Class || meth == :new)
99
+ query = "#{cls}::#{meth}"
100
+ else
101
+ query = "#{cls}##{meth}"
102
+ end
103
+ postprocess(query)
104
+ else
105
+ resolve_help_res(res)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,79 @@
1
+ module Magic
2
+ module Help
3
+ def self.resolve_help_block(&block)
4
+ call_event = nil
5
+ done = false
6
+ res = nil
7
+
8
+ # We want to capture calls to method_missing too
9
+ # This should be available via TracePoint but isn't
10
+ original_method_missing = BasicObject.instance_method(:method_missing)
11
+ BasicObject.class_eval do
12
+ define_method(:method_missing) do |*args|
13
+ if args.empty?
14
+ # This is presumably called on self, and without arguments
15
+ throw :done, {cls: method(:method_missing).owner, meth: :method_missing, self: self}
16
+ end
17
+
18
+ if self.is_a?(Class)
19
+ throw :done, {cls: self.singleton_class, meth: args[0], self: self}
20
+ else
21
+ throw :done, {cls: self.class, meth: args[0], self: self}
22
+ end
23
+ end
24
+ end
25
+
26
+ trace = TracePoint.new do |ev|
27
+ next if done
28
+ case ev.event
29
+ when :call, :c_call
30
+ if ev.defined_class == BasicObject and ev.method_id == :method_missing
31
+ done = true
32
+ # Let it reach our special handler
33
+ # elsif ev.self == ArgumentError and ev.method_id == :new
34
+ # Function was called without full number of arguments
35
+ # There doesn't seem to be any way to recover from this in ruby 2.x
36
+ # In 1.8 we'd get extra return event
37
+ #
38
+ # It's possible to hack argument name from stack trace,
39
+ # (with massive hacking)
40
+ # but not self/class, so it's not most useful
41
+ else
42
+ done = true
43
+ throw :done, {cls: ev.defined_class, meth: ev.method_id, self: ev.self}
44
+ end
45
+ else
46
+ # Ignore everything eles
47
+ end
48
+ end
49
+ call_event = catch(:done) do
50
+ trace.enable
51
+ res = yield
52
+ nil
53
+ end
54
+ done = true
55
+ trace.disable
56
+
57
+ BasicObject.instance_eval do
58
+ define_method(:method_missing, original_method_missing)
59
+ # It was originally private, restore it as such
60
+ private :method_missing
61
+ end
62
+
63
+ if call_event
64
+ cls = call_event[:cls]
65
+ meth = call_event[:meth]
66
+ bound_self = call_event[:self]
67
+ is_singleton = (cls.is_a?(Class) and cls.singleton_class?)
68
+ if is_singleton or meth == :new
69
+ query = "#{bound_self}::#{meth}"
70
+ else
71
+ query = "#{cls}##{meth}"
72
+ end
73
+ postprocess(query)
74
+ else
75
+ resolve_help_res(res)
76
+ end
77
+ end
78
+ end
79
+ end
data/lib/magic_help.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative "magic/help"
data/test/mass_test.rb ADDED
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This only tests how many items magic/help BREAKS,
4
+ # that is - how many would work otherwise.
5
+
6
+ require_relative "../lib/magic_help"
7
+ require "rdoc/ri"
8
+
9
+ # Use fake irb_help for testing
10
+ $irb_help = nil
11
+
12
+ class Object
13
+ def irb_help(arg)
14
+ $irb_help = arg
15
+ end
16
+ end
17
+
18
+ # A convenience method
19
+ def try_help(*args, &blk)
20
+ $irb_help = nil
21
+ help(*args, &blk)
22
+ rv, $irb_help = $irb_help, nil
23
+ rv
24
+ end
25
+
26
+ # Extract documentation
27
+
28
+ # FIXME: Hardcoded paths are not cross-platform compatible
29
+ default_ri_root_path = "#{RDoc::RI::Paths::BASE}/system"
30
+ ri_root_path = ARGV.shift || default_ri_root_path
31
+
32
+ docs = Dir["#{ri_root_path}/**/*"]
33
+
34
+ docs_class = []
35
+ docs_imeth = []
36
+ docs_cmeth = []
37
+
38
+ docs.each{|fn|
39
+ next if File.directory?(fn)
40
+ raise "Weird path: #{fn}" unless fn[0, ri_root_path.size] == ri_root_path
41
+ fn = fn[ri_root_path.size..-1].sub(/^\/*/, "")
42
+ # gsub after split to deal with Fixnum#/ etc.
43
+ path = fn.split(/\//).map{|x| x.gsub(/%(..)/){$1.hex.chr}}
44
+ case path[-1]
45
+ when /\A(.*)-i\.(yaml|ri)\Z/
46
+ docs_imeth << path[0..-2] + [$1]
47
+ when /\A(.*)-c\.(yaml|ri)\Z/
48
+ docs_cmeth << path[0..-2] + [$1]
49
+ when /\Acdesc-(.*)\.(yaml|ri)\Z/
50
+ raise "Malformatted file name: #{fn}" unless $1 == path[-2]
51
+ docs_class << path[0..-2]
52
+ else
53
+ # Ignore
54
+ end
55
+ }
56
+
57
+ # Go over documentation
58
+
59
+ cl_nc = []
60
+ cl_ni = []
61
+ cl_fc = []
62
+ cl_fi = []
63
+
64
+ docs_class.each{|class_path|
65
+ class_name = class_path.join("::")
66
+ begin
67
+ cls = class_path.inject(Object){|c,path_elem| c.const_get(path_elem)}
68
+ rescue NameError
69
+ rv = try_help class_name
70
+ if rv == class_name
71
+ cl_nc << class_name
72
+ else
73
+ cl_ni << [class_name, rv]
74
+ end
75
+ next
76
+ end
77
+ rv1 = try_help class_name
78
+ rv2 = try_help cls
79
+ if rv1 == class_name && rv2 == class_name
80
+ cl_fc << class_name
81
+ else
82
+ cl_fi << [class_name, rv1, rv2]
83
+ end
84
+ }
85
+
86
+ print <<EOS
87
+ Class documentation:
88
+ * #{docs_class.size} classes
89
+ * #{cl_fc.size} correct
90
+ * #{cl_fi.size} incorrect
91
+ * #{cl_nc.size} could not be verified (seem ok)
92
+ * #{cl_ni.size} could not be verified (seem bad)
93
+ EOS
94
+ if cl_fi.size != 0
95
+ puts "\nIncorrect:"
96
+ cl_fi.each{|ex,rv1,rv2|
97
+ puts "* #{ex} - #{rv1}/#{rv2}"
98
+ }
99
+ end
100
+
101
+ cm_nc = [] # Class not found
102
+ cm_ni = []
103
+ cm_fc = []
104
+ cm_fi = []
105
+
106
+ docs_cmeth.each{|path|
107
+ class_path, method_name = path[0..-2], path[-1]
108
+ class_name = class_path.join("::")
109
+ begin
110
+ cls = class_path.inject(Object){|c,path_elem| c.const_get(path_elem)}
111
+ rescue NameError
112
+ expected = "#{class_name}::#{method_name}"
113
+ rv = try_help expected
114
+ if rv == expected
115
+ cm_nc << expected
116
+ else
117
+ cm_ni << [expected, rv]
118
+ end
119
+ next
120
+ end
121
+ expected = "#{class_name}::#{method_name}"
122
+ rv1 = try_help "#{class_name}::#{method_name}"
123
+ # cls.send(:method_name) would find help for Object#send ...
124
+ rv2 = eval "try_help { cls.#{method_name}() }"
125
+ if rv1 == expected && rv2 == expected
126
+ cm_fc << expected
127
+ else
128
+ cm_fi << [expected, rv1, rv2]
129
+ end
130
+ }
131
+
132
+ print <<EOS
133
+
134
+ Class method documentation:
135
+ * #{docs_cmeth.size} class methods
136
+ * #{cm_fc.size} correct
137
+ * #{cm_fi.size} incorrect
138
+ * #{cm_nc.size} could not be verified (seem ok)
139
+ * #{cm_ni.size} could not be verified (seem bad)
140
+ EOS
141
+ if cm_fi.size != 0
142
+ puts "\nIncorrect:"
143
+ cm_fi.each{|ex,rv1,rv2|
144
+ puts "* #{ex} - #{rv1}/#{rv2}"
145
+ }
146
+ end
147
+
148
+ # And instance methods
149
+
150
+ im_nc = [] # Class not found
151
+ im_ni = []
152
+ im_fc = []
153
+ im_fi = []
154
+
155
+ docs_imeth.each{|path|
156
+ class_path, method_name = path[0..-2], path[-1]
157
+ class_name = class_path.join("::")
158
+ begin
159
+ cls = class_path.inject(Object){|c,path_elem| c.const_get(path_elem)}
160
+ rescue NameError
161
+ expected = "#{class_name}.#{method_name}"
162
+ rv = try_help expected
163
+ if rv == expected
164
+ im_nc << expected
165
+ else
166
+ im_ni << [expected, rv]
167
+ end
168
+ next
169
+ end
170
+ expected = "#{class_name}##{method_name}"
171
+ rv1 = try_help "#{class_name}##{method_name}"
172
+ # We don't know how to create a real cls object.
173
+ # We could try some hacks or mock objects later.
174
+
175
+ if rv1 == expected
176
+ im_fc << expected
177
+ else
178
+ im_fi << [expected, rv1]
179
+ end
180
+ }
181
+
182
+ print <<EOS
183
+
184
+ Instance method documentation:
185
+ * #{docs_imeth.size} instance methods
186
+ * #{im_fc.size} correct
187
+ * #{im_fi.size} incorrect
188
+ * #{im_nc.size} could not be verified (seem ok)
189
+ * #{im_ni.size} could not be verified (seem bad)
190
+ EOS
191
+ if im_fi.size != 0
192
+ puts "\nIncorrect:"
193
+ im_fi.each{|ex,rv1|
194
+ puts "* #{ex} - #{rv1}"
195
+ }
196
+ end
197
+
198
+ print <<EOS
199
+
200
+ Summary:
201
+ * #{docs_class.size + docs_cmeth.size + docs_imeth.size} documentation items
202
+ * #{cl_fc.size + cm_fc.size + im_fc.size} correct
203
+ * #{cl_fi.size + cm_fi.size + im_fi.size} incorrect
204
+ * #{cl_nc.size + cm_nc.size + im_nc.size} could not be verified (seem ok)
205
+ * #{cl_ni.size + cm_ni.size + im_ni.size} could not be verified (seem bad)
206
+ EOS
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "minitest/autorun"
4
+ require_relative "../lib/magic_help"
5
+ require "fileutils"
6
+
7
+ class Test_Magic_Help < Minitest::Test
8
+ def self.test_order
9
+ :alpha
10
+ end
11
+
12
+ def assert_irb_help(expected, *args, &block)
13
+ got = Magic::Help.resolve_help_query(*args, &block)
14
+ if expected == nil
15
+ assert_nil(got)
16
+ else
17
+ assert_equal(expected, got)
18
+ end
19
+ end
20
+
21
+ # There were ways to make it work in 1.8 set_trace_func,
22
+ # but TracePoint just doesn't seem to allow it
23
+ def test_argument_number_mismatch
24
+ # Correct number of arguments
25
+ assert_irb_help("FileUtils::compare_file"){ FileUtils.compare_file 1, 2 }
26
+ # Various incorrect argument counts
27
+ # assert_irb_help("FileUtils::compare_file"){ FileUtils.compare_file }
28
+ # assert_irb_help("FileUtils::compare_file"){ FileUtils.compare_file 1 }
29
+ # assert_irb_help("FileUtils::compare_file"){ FileUtils.compare_file 1, 2, 3 }
30
+ end
31
+
32
+ def test_argumenterror_new
33
+ assert_irb_help("ArgumentError::new"){ ArgumentError.new }
34
+ end
35
+
36
+ def test_class
37
+ assert_irb_help("Array"){ Array }
38
+ assert_irb_help("Array"){ [] }
39
+ x = [1, 2, 3]
40
+ assert_irb_help("Array"){ x }
41
+ end
42
+
43
+ def test_method
44
+ assert_irb_help("Array#sort"){ [].sort }
45
+ x = [1, 2, 3]
46
+ assert_irb_help("Array#sort"){ x.sort }
47
+ im = Array.instance_method(:sort)
48
+ assert_irb_help("Array#sort"){ im }
49
+ m = [].method(:sort)
50
+ assert_irb_help("Array#sort"){ m }
51
+ um = [].method(:sort).unbind
52
+ assert_irb_help("Array#sort"){ um }
53
+ end
54
+
55
+ def test_class_method
56
+ assert_irb_help("Dir::[]"){ Dir[""] }
57
+ m = Dir.method(:[])
58
+ assert_irb_help("Dir::[]"){ m }
59
+ um = Dir.method(:[]).unbind
60
+ assert_irb_help("Dir::[]"){ um }
61
+ end
62
+
63
+ def test_module
64
+ assert_irb_help("Enumerable", Enumerable)
65
+ assert_irb_help("Enumerable"){ Enumerable }
66
+ assert_irb_help("Enumerable", "Enumerable")
67
+ um = Enumerable.instance_method(:map)
68
+ assert_irb_help("Enumerable#map"){ um }
69
+ um2 = Range.instance_method(:any?)
70
+ assert_irb_help("Enumerable#any?"){ um2 }
71
+ m = (0..1).method(:any?)
72
+ assert_irb_help("Enumerable#any?"){ m }
73
+ end
74
+
75
+ def test_method_inherited
76
+ f = File.open(__FILE__)
77
+ assert_irb_help("IO#sync"){ f.sync }
78
+ im = File.instance_method(:sync)
79
+ assert_irb_help("IO#sync"){ im }
80
+ m = f.method(:sync)
81
+ assert_irb_help("IO#sync"){ m }
82
+ um = f.method(:sync).unbind
83
+ assert_irb_help("IO#sync"){ um }
84
+ end
85
+
86
+ def test_string
87
+ assert_irb_help("Array", "Array" )
88
+ assert_irb_help("Array#[]", "Array#[]")
89
+ assert_irb_help("Dir::[]", "Dir::[]" )
90
+ assert_irb_help("Array#[]", "Array.[]")
91
+ assert_irb_help("Dir::[]", "Dir.[]" )
92
+ assert_irb_help("IO#sync", "File#sync" )
93
+ end
94
+
95
+ def test_string_bogus
96
+ assert_irb_help("Xyzzy#foo", "Xyzzy#foo")
97
+ assert_irb_help("Xyzzy::foo", "Xyzzy::foo")
98
+ assert_irb_help("Xyzzy.foo", "Xyzzy.foo")
99
+
100
+ assert_irb_help("Array#xyzzy", "Array#xyzzy")
101
+ assert_irb_help("Array::xyzzy", "Array::xyzzy")
102
+ assert_irb_help("Array.xyzzy", "Array.xyzzy")
103
+ end
104
+
105
+ def test_operators
106
+ # Ruby apparently optimizes away 2+2, that's awkward...
107
+ assert_irb_help("Fixnum#**"){ 2 ** 2 }
108
+ assert_irb_help("Float#**"){ 2.0 ** 2.0 }
109
+ assert_irb_help("Array#[]"){ [][] }
110
+ # =~ is instance method of Kernel, but is documented as instance method of Object
111
+ # assert_irb_help("Kernel#=~"){ [] =~ [] }
112
+ assert_irb_help("Kernel#=~"){ [] =~ [] }
113
+ end
114
+
115
+ def test_nil
116
+ assert_irb_help(nil)
117
+ assert_irb_help("NilClass"){ nil }
118
+ assert_irb_help("NilClass"){ }
119
+ end
120
+
121
+ def test_superclass
122
+ # superclass is a method of Class
123
+ # So Foo::superclass should find Class#superclass
124
+
125
+ assert_irb_help("Class#superclass"){ Float.superclass }
126
+ assert_irb_help("Class#superclass", "Float::superclass")
127
+ assert_irb_help("Class#superclass", "Float.superclass")
128
+
129
+ assert_irb_help("Class#superclass"){ Class.superclass }
130
+ assert_irb_help("Class#superclass"){ "Class.superclass" }
131
+ assert_irb_help("Class#superclass"){ "Class::superclass" }
132
+ end
133
+
134
+ def test_class_new
135
+ # Most classes do not override Class#new, but have
136
+ # Documentation for Foo.new anyway (it actually documents Foo#initialize)
137
+
138
+ # First, handling of Class#new (default creator of instances)
139
+ # and Class::new (creator of new classses)
140
+ assert_irb_help("Class::new"){ Class.new }
141
+ assert_irb_help("Class::new"){ Class::new }
142
+ assert_irb_help("Class#new", "Class#new")
143
+ assert_irb_help("Class::new", "Class::new")
144
+ assert_irb_help("Class#new", "Class.new")
145
+
146
+ # Module::new is documented and it uses default Class#new
147
+ assert_irb_help("Module::new"){ Module.new }
148
+ assert_irb_help("Module::new", "Module.new")
149
+ assert_irb_help("Module::new", "Module::new")
150
+
151
+ # IO::new is documented and it has separate implementation
152
+ assert_irb_help("IO::new"){ IO.new }
153
+ assert_irb_help("IO::new", "IO.new")
154
+ assert_irb_help("IO::new", "IO::new")
155
+
156
+ # File::new is documented and it uses IO::new
157
+ assert_irb_help("File::new"){ File.new }
158
+ assert_irb_help("File::new", "File.new")
159
+ assert_irb_help("File::new", "File::new")
160
+ end
161
+
162
+ # This tests work-arounds for bugs in Ruby documentation !!!
163
+ # In the perfect world it should totally fail !!!
164
+ def test_object_methods
165
+ # Documentation mixes some Kernel and Object methods
166
+
167
+ # Ruby has Kernel#__id__ but documentation has Object#__id__
168
+ assert_irb_help("BasicObject#__id__"){ __id__ }
169
+ assert_irb_help("BasicObject#__id__"){ 42.__id__ }
170
+ assert_irb_help("BasicObject#__id__", "Object#__id__")
171
+ assert_irb_help("BasicObject#__id__", "Object.__id__")
172
+ assert_irb_help("Kernel#__id__", "Kernel#__id__")
173
+ assert_irb_help("BasicObject#__id__", "Kernel.__id__")
174
+
175
+ # Ruby has Kernel#sprintf and documentation has Kernel#sprintf
176
+ assert_irb_help("Kernel#sprintf"){ sprintf }
177
+ assert_irb_help("Kernel#sprintf", "Object#sprintf")
178
+ assert_irb_help("Kernel#sprintf", "Object.sprintf")
179
+ assert_irb_help("Kernel#sprintf", "Kernel#sprintf")
180
+ assert_irb_help("Kernel#sprintf", "Kernel.sprintf")
181
+
182
+ # TODO: For completion - Object method documented in Object
183
+ # TODO: For completion - Object method documented in Kernel
184
+ # TODO: For completion - class methods of both
185
+ end
186
+
187
+ def test_method_missing
188
+ # Some fake method
189
+ assert_irb_help("Time::rfc999"){ Time::rfc999 }
190
+ assert_irb_help("Time::rfc999"){ Time.rfc999 } # is that really best ?
191
+ assert_irb_help("Time::rfc999", "Time::rfc999")
192
+ assert_irb_help("Time.rfc999", "Time.rfc999")
193
+ end
194
+
195
+ def test_method_missing_explicit
196
+ assert_irb_help("Kernel#method_missing", "Kernel#method_missing")
197
+ assert_irb_help("BasicObject#method_missing", "Kernel.method_missing")
198
+ assert_irb_help("BasicObject#method_missing", "Float#method_missing")
199
+ assert_irb_help("BasicObject#method_missing", "Float.method_missing")
200
+ assert_irb_help("BasicObject#method_missing"){ 42.method_missing }
201
+ assert_irb_help("BasicObject#method_missing"){ method_missing }
202
+ end
203
+
204
+ def test_longpath
205
+ assert_irb_help("File::Stat::new", "File::Stat.new")
206
+ assert_irb_help("File::Stat::new"){ File::Stat.new }
207
+ assert_irb_help("File::Stat::new"){ File::Stat::new }
208
+ fs = File::Stat.new(__FILE__)
209
+ assert_irb_help("File::Stat#size"){ fs.size }
210
+ assert_irb_help("File::Stat#size", "File::Stat#size")
211
+ assert_irb_help("File::Stat#size", "File::Stat.size")
212
+ end
213
+
214
+ def test_private
215
+ # help should ignore public/protected/private
216
+ # private is a private function of Module
217
+ assert_irb_help("Module#private", "Module#private")
218
+ assert_irb_help("Module#private", "Module.private")
219
+ assert_irb_help("Module#private", "Module::private")
220
+
221
+ assert_irb_help("Module#private", "Class#private")
222
+ assert_irb_help("Module#private", "Class.private")
223
+ assert_irb_help("Module#private", "Class::private")
224
+
225
+ assert_irb_help("Module#private", "Float.private")
226
+ assert_irb_help("Module#private", "Float::private")
227
+
228
+ assert_irb_help("Module::private"){ Module::private }
229
+ assert_irb_help("Module::private"){ Module.private }
230
+
231
+ assert_irb_help("Class::private"){ Class::private }
232
+ assert_irb_help("Class::private"){ Class.private }
233
+
234
+ assert_irb_help("Float::private"){ Float::private }
235
+ assert_irb_help("Float::private"){ Float.private }
236
+
237
+ assert_irb_help("String#singleton_method_added"){ "".singleton_method_added }
238
+ assert_irb_help("BasicObject#singleton_method_added"){ singleton_method_added }
239
+ end
240
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: magic-help
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.20170204'
5
+ platform: ruby
6
+ authors:
7
+ - Tomasz Wegrzanowski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-02-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description:
42
+ email: Tomasz.Wegrzanowski@gmail.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - lib/magic/help.rb
48
+ - lib/magic/help/irb.rb
49
+ - lib/magic/help/set_trace_func.rb
50
+ - lib/magic/help/tracepoint.rb
51
+ - lib/magic_help.rb
52
+ - test/mass_test.rb
53
+ - test/tc_magic_help.rb
54
+ homepage: https://github.com/taw/magic-help
55
+ licenses: []
56
+ metadata: {}
57
+ post_install_message:
58
+ rdoc_options: []
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubyforge_project:
73
+ rubygems_version: 2.5.2
74
+ signing_key:
75
+ specification_version: 4
76
+ summary: Plugin for irb providing more intuitive documentation access.
77
+ test_files:
78
+ - test/mass_test.rb
79
+ - test/tc_magic_help.rb