magic-help 0.20170204
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/magic/help.rb +104 -0
- data/lib/magic/help/irb.rb +28 -0
- data/lib/magic/help/set_trace_func.rb +109 -0
- data/lib/magic/help/tracepoint.rb +79 -0
- data/lib/magic_help.rb +1 -0
- data/test/mass_test.rb +206 -0
- data/test/tc_magic_help.rb +240 -0
- metadata +79 -0
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
|