magic-help 0.20170204

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 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