drx 0.0.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/{lib → examples}/drx_test.rb +3 -4
- data/{lib → examples}/drx_test2.rb +12 -7
- data/examples/drx_test3.rb +35 -0
- data/ext/{drx_ext.c → drx_core.c} +76 -45
- data/ext/extconf.rb +1 -1
- data/lib/drx/graphviz.rb +240 -0
- data/lib/drx/objinfo.rb +147 -0
- data/lib/drx/tempfiles.rb +44 -0
- data/lib/drx/tk/app.rb +363 -0
- data/lib/drx/tk/imagemap.rb +284 -0
- data/lib/drx.rb +21 -56
- metadata +16 -9
- data/lib/drxtk.rb +0 -210
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require '
|
2
|
+
require 'drx'
|
3
3
|
|
4
4
|
require 'date'
|
5
5
|
|
@@ -16,9 +16,8 @@ def zmn.koko
|
|
16
16
|
9090
|
17
17
|
end
|
18
18
|
|
19
|
-
Drx.
|
19
|
+
Drx::ObjInfo.new(zmn).examine
|
20
20
|
|
21
21
|
#############################
|
22
22
|
|
23
|
-
|
24
|
-
#Drx.examinetk("some_string")
|
23
|
+
zmn.see
|
@@ -1,25 +1,30 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'dm-core'
|
3
3
|
|
4
|
+
#
|
5
|
+
# This is part of a blogging website. Users write posts. A post
|
6
|
+
# belongs to a user.
|
7
|
+
#
|
8
|
+
|
4
9
|
class Post
|
5
10
|
include DataMapper::Resource
|
6
|
-
|
7
|
-
property :post_id,
|
11
|
+
|
12
|
+
property :post_id, Serial
|
8
13
|
property :title, String
|
9
14
|
property :body, Text
|
10
|
-
|
15
|
+
|
11
16
|
belongs_to :user
|
12
17
|
end
|
13
18
|
|
14
19
|
class User
|
15
20
|
include DataMapper::Resource
|
16
|
-
|
17
|
-
property :user_uid,
|
21
|
+
|
22
|
+
property :user_uid, Serial
|
18
23
|
property :name, String
|
19
24
|
property :mail, String
|
20
25
|
end
|
21
26
|
|
22
27
|
post = Post.new
|
23
28
|
|
24
|
-
require '
|
25
|
-
|
29
|
+
require 'drx'
|
30
|
+
post.see
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
require 'drx'
|
6
|
+
|
7
|
+
class Instrument
|
8
|
+
include Enumerable
|
9
|
+
end
|
10
|
+
|
11
|
+
class Guitar < Instrument
|
12
|
+
def initialize
|
13
|
+
@strings = 5
|
14
|
+
# puts class # .add_some
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.add_some
|
18
|
+
@max_strings = 10
|
19
|
+
@@approved = 'by obama team'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'dm-core'
|
24
|
+
class Post
|
25
|
+
include DataMapper::Resource
|
26
|
+
property :id, Serial
|
27
|
+
end
|
28
|
+
|
29
|
+
o = Guitar.new
|
30
|
+
o = Post.new
|
31
|
+
def o.unq
|
32
|
+
def g;end
|
33
|
+
end
|
34
|
+
|
35
|
+
o.see
|
@@ -30,7 +30,7 @@ static VALUE t_get_iv_tbl(VALUE self, VALUE obj)
|
|
30
30
|
if (TYPE(obj) != T_OBJECT && TYPE(obj) != T_CLASS && TYPE(obj) != T_ICLASS && TYPE(obj) != T_MODULE) {
|
31
31
|
rb_raise(rb_eTypeError, "Only T_OBJECT/T_CLASS/T_MODULE is expected as the argument (got %d)", TYPE(obj));
|
32
32
|
}
|
33
|
-
|
33
|
+
|
34
34
|
if (ROBJECT(obj)->iv_tbl) {
|
35
35
|
st_foreach(ROBJECT(obj)->iv_tbl, record_var, (st_data_t)hash);
|
36
36
|
}
|
@@ -124,73 +124,94 @@ static VALUE t_get_address(VALUE self, VALUE obj)
|
|
124
124
|
|
125
125
|
#define RSTR(s) rb_str_new2(s)
|
126
126
|
|
127
|
-
static t_do_locate_method(NODE *ND_method) {
|
127
|
+
static VALUE t_do_locate_method(NODE *ND_method) {
|
128
128
|
NODE *ND_scope = NULL, *ND_block = NULL;
|
129
129
|
VALUE place;
|
130
130
|
char line_s[20];
|
131
|
-
|
131
|
+
|
132
132
|
//
|
133
133
|
// The NODE_METHOD node
|
134
134
|
//
|
135
135
|
|
136
136
|
if (nd_type(ND_method) != NODE_METHOD/*0*/) {
|
137
|
-
return RSTR("I'm expecting a NODE_METHOD here
|
137
|
+
return RSTR("<I'm expecting a NODE_METHOD here...>");
|
138
138
|
}
|
139
|
-
|
139
|
+
|
140
140
|
//
|
141
141
|
// The NODE_SCOPE node
|
142
142
|
//
|
143
|
-
|
143
|
+
|
144
144
|
ND_scope = ND_method->u2.node;
|
145
|
+
if (!ND_scope) {
|
146
|
+
// When we use undef() to undefine a method.
|
147
|
+
return RSTR("<undef>");
|
148
|
+
}
|
149
|
+
|
150
|
+
if (nd_type(ND_scope) == NODE_FBODY/*1*/) {
|
151
|
+
return RSTR("<alias>");
|
152
|
+
}
|
145
153
|
|
146
154
|
if (nd_type(ND_scope) == NODE_CFUNC/*2*/) {
|
147
|
-
return RSTR("
|
155
|
+
return RSTR("<c>");
|
148
156
|
}
|
149
|
-
|
150
|
-
if (nd_type(ND_scope) ==
|
151
|
-
return RSTR("
|
157
|
+
|
158
|
+
if (nd_type(ND_scope) == NODE_IVAR/*50*/) {
|
159
|
+
return RSTR("<attr reader>");
|
152
160
|
}
|
153
161
|
|
154
|
-
if (nd_type(ND_scope) ==
|
155
|
-
return RSTR("
|
162
|
+
if (nd_type(ND_scope) == NODE_ATTRSET/*89*/) {
|
163
|
+
return RSTR("<attr setter>");
|
156
164
|
}
|
157
|
-
|
165
|
+
|
158
166
|
if (nd_type(ND_scope) == NODE_ZSUPER/*41*/) {
|
159
|
-
//
|
160
|
-
|
167
|
+
// When we change visibility (using 'private :method' or 'public :method') of
|
168
|
+
// a base method, this node is created.
|
169
|
+
return RSTR("<zsuper>");
|
170
|
+
}
|
171
|
+
|
172
|
+
if (nd_type(ND_scope) == NODE_BMETHOD/*99*/) {
|
173
|
+
// This is created by define_method().
|
174
|
+
VALUE proc;
|
175
|
+
proc = (VALUE)ND_scope->u3.node;
|
176
|
+
// proc_to_s(), in eval.c, shows how to extract the location. However,
|
177
|
+
// for this we need access to the BLOCK structure. Unfortunately,
|
178
|
+
// that BLOCK is defined in eval.c, not in any .h file we can #include.
|
179
|
+
// So instead we resort to a dirty trick: we parse the output of Proc#to_s.
|
180
|
+
return rb_funcall(proc, rb_intern("_location"), 0, 0);
|
161
181
|
}
|
162
182
|
|
163
183
|
if (nd_type(ND_scope) != NODE_SCOPE/*3*/) {
|
164
184
|
printf("I'm expecting a NODE_SCOPE HERE (got %d instead)\n", nd_type(ND_scope));
|
165
|
-
return RSTR("I'm expecting a NODE_SCOPE HERE
|
185
|
+
return RSTR("<I'm expecting a NODE_SCOPE HERE...>");
|
166
186
|
}
|
167
|
-
|
187
|
+
|
168
188
|
//
|
169
189
|
// The NODE_BLOCK node
|
170
190
|
//
|
171
|
-
|
191
|
+
|
172
192
|
ND_block = ND_scope->u3.node;
|
173
193
|
|
174
|
-
if (nd_type(ND_block) != NODE_BLOCK/*4*/) {
|
175
|
-
return RSTR("I'm expecting a NODE_BLOCK here
|
194
|
+
if (!ND_block || nd_type(ND_block) != NODE_BLOCK/*4*/) {
|
195
|
+
return RSTR("<I'm expecting a NODE_BLOCK here...>");
|
176
196
|
}
|
177
|
-
|
197
|
+
|
178
198
|
sprintf(line_s, "%d:", nd_line(ND_block));
|
179
199
|
place = RSTR(line_s);
|
180
200
|
rb_str_cat2(place, ND_block->nd_file);
|
181
|
-
|
201
|
+
|
182
202
|
return place;
|
183
203
|
}
|
184
204
|
|
185
205
|
/*
|
186
206
|
* call-seq:
|
187
|
-
* Drx
|
188
|
-
*
|
207
|
+
* Drx::Core::locate_method(Date, "to_s") => str
|
208
|
+
*
|
189
209
|
* Locates the filename and line-number where a method was defined. Returns a
|
190
|
-
* string of the form "89:/path/to/file.rb", or nil if method
|
191
|
-
*
|
192
|
-
*
|
193
|
-
*
|
210
|
+
* string of the form "89:/path/to/file.rb", or nil if the method doesn't exist,
|
211
|
+
* or a string of the form "<identifier>".
|
212
|
+
*
|
213
|
+
* If the method exists but isn't a Ruby method (e.g., if it's written in C),
|
214
|
+
* an <identifier> string is returned. E.g., <c>, <alias>, <attr reader>.
|
194
215
|
*/
|
195
216
|
static VALUE t_locate_method(VALUE self, VALUE obj, VALUE method_name)
|
196
217
|
{
|
@@ -205,7 +226,7 @@ static VALUE t_locate_method(VALUE self, VALUE obj, VALUE method_name)
|
|
205
226
|
}
|
206
227
|
c_name = StringValuePtr(method_name);
|
207
228
|
ID id = rb_intern(c_name);
|
208
|
-
if (st_lookup(RCLASS(obj)->m_tbl, id, &method_node)) {
|
229
|
+
if (st_lookup(RCLASS(obj)->m_tbl, id, (st_data_t *)&method_node)) {
|
209
230
|
return t_do_locate_method(method_node);
|
210
231
|
} else {
|
211
232
|
return Qnil;
|
@@ -215,21 +236,31 @@ static VALUE t_locate_method(VALUE self, VALUE obj, VALUE method_name)
|
|
215
236
|
// }}}
|
216
237
|
|
217
238
|
VALUE mDrx;
|
239
|
+
VALUE mCore;
|
240
|
+
|
241
|
+
void Init_drx_core() {
|
242
|
+
mDrx = rb_define_module("Drx");
|
243
|
+
mCore = rb_define_module_under(mDrx, "Core");
|
244
|
+
rb_define_module_function(mCore, "get_iv_tbl", t_get_iv_tbl, 1);
|
245
|
+
rb_define_module_function(mCore, "get_m_tbl", t_get_m_tbl, 1);
|
246
|
+
rb_define_module_function(mCore, "get_super", t_get_super, 1);
|
247
|
+
rb_define_module_function(mCore, "get_klass", t_get_klass, 1);
|
248
|
+
rb_define_module_function(mCore, "get_flags", t_get_flags, 1);
|
249
|
+
rb_define_module_function(mCore, "get_address", t_get_address, 1);
|
250
|
+
rb_define_module_function(mCore, "get_type", t_get_type, 1);
|
251
|
+
rb_define_module_function(mCore, "get_ivar", t_get_ivar, 2);
|
252
|
+
rb_define_module_function(mCore, "locate_method", t_locate_method, 2);
|
253
|
+
rb_define_const(mCore, "FL_SINGLETON", INT2FIX(FL_SINGLETON));
|
254
|
+
rb_define_const(mCore, "T_OBJECT", INT2FIX(T_OBJECT));
|
255
|
+
rb_define_const(mCore, "T_CLASS", INT2FIX(T_CLASS));
|
256
|
+
rb_define_const(mCore, "T_ICLASS", INT2FIX(T_ICLASS));
|
257
|
+
rb_define_const(mCore, "T_MODULE", INT2FIX(T_MODULE));
|
218
258
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
rb_define_module_function(mDrx, "get_address", t_get_address, 1);
|
227
|
-
rb_define_module_function(mDrx, "get_type", t_get_type, 1);
|
228
|
-
rb_define_module_function(mDrx, "get_ivar", t_get_ivar, 2);
|
229
|
-
rb_define_module_function(mDrx, "locate_method", t_locate_method, 2);
|
230
|
-
rb_define_const(mDrx, "FL_SINGLETON", INT2FIX(FL_SINGLETON));
|
231
|
-
rb_define_const(mDrx, "T_OBJECT", INT2FIX(T_OBJECT));
|
232
|
-
rb_define_const(mDrx, "T_CLASS", INT2FIX(T_CLASS));
|
233
|
-
rb_define_const(mDrx, "T_ICLASS", INT2FIX(T_ICLASS));
|
234
|
-
rb_define_const(mDrx, "T_MODULE", INT2FIX(T_MODULE));
|
259
|
+
// For the following, see explanation in t_do_locate_method().
|
260
|
+
rb_eval_string("\
|
261
|
+
class ::Proc;\
|
262
|
+
def _location;\
|
263
|
+
if to_s =~ /@(.*?):(\\d+)>$/ then \"#$2:#$1\" end;\
|
264
|
+
end;\
|
265
|
+
end");
|
235
266
|
}
|
data/ext/extconf.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
require 'mkmf'
|
2
|
-
create_makefile("
|
2
|
+
create_makefile("drx_core")
|
data/lib/drx/graphviz.rb
ADDED
@@ -0,0 +1,240 @@
|
|
1
|
+
# Adds Graphviz diagraming capability to ObjInfo
|
2
|
+
|
3
|
+
module Drx
|
4
|
+
|
5
|
+
class ObjInfo
|
6
|
+
|
7
|
+
# Notes:
|
8
|
+
# - Windows' CMD.EXE too supports "2>&1"
|
9
|
+
# - We're generating GIF, not PNG, because that's a format Tk
|
10
|
+
# supports out of the box.
|
11
|
+
GRAPHVIZ_COMMAND = 'dot "%s" -Tgif -o "%s" -Tcmapx -o "%s" 2>&1'
|
12
|
+
|
13
|
+
@@sizes = {
|
14
|
+
'100%' => "
|
15
|
+
node[fontsize=10]
|
16
|
+
",
|
17
|
+
'90%' => "
|
18
|
+
node[fontsize=10]
|
19
|
+
ranksep=0.4
|
20
|
+
edge[arrowsize=0.8]
|
21
|
+
",
|
22
|
+
'80%' => "
|
23
|
+
node[fontsize=10]
|
24
|
+
ranksep=0.3
|
25
|
+
nodesep=0.2
|
26
|
+
node[height=0.4]
|
27
|
+
edge[arrowsize=0.6]
|
28
|
+
",
|
29
|
+
'60%' => "
|
30
|
+
node[fontsize=8]
|
31
|
+
ranksep=0.18
|
32
|
+
nodesep=0.2
|
33
|
+
node[height=0]
|
34
|
+
edge[arrowsize=0.5]
|
35
|
+
"
|
36
|
+
}
|
37
|
+
|
38
|
+
# Create an ID for the DOT node representing this object.
|
39
|
+
def dot_id
|
40
|
+
('o' + address.to_s).sub('-', '_')
|
41
|
+
end
|
42
|
+
|
43
|
+
# Creates a pseudo URL for the HTML imagemap.
|
44
|
+
def dot_url
|
45
|
+
"http://server/obj/#{dot_id}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Quotes a string to be used in DOT source.
|
49
|
+
def dot_quote(s)
|
50
|
+
# @todo: find the documentation for tr()?
|
51
|
+
'"' + s.gsub('\\') { '\\\\' }.gsub('"', '\\"').gsub("\n", '\\n') + '"'
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the DOT style for the node.
|
55
|
+
def dot_style__default
|
56
|
+
if singleton?
|
57
|
+
# A singleton class
|
58
|
+
"shape=oval,color=skyblue1,style=filled"
|
59
|
+
elsif t_class?
|
60
|
+
# A class
|
61
|
+
"shape=oval,color=lightblue1,style=filled"
|
62
|
+
elsif t_iclass? or t_module?
|
63
|
+
# A module
|
64
|
+
if repr['#']
|
65
|
+
# Paint anonymous modules only lightly.
|
66
|
+
"shape=box,style=filled,color=\"#D9FFF2\",fontcolor=gray60"
|
67
|
+
else
|
68
|
+
"shape=box,style=filled,color=aquamarine"
|
69
|
+
end
|
70
|
+
else
|
71
|
+
# Else: a "normal" object, or an immediate.
|
72
|
+
"shape=house,color=wheat1,style=filled"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns the DOT style for the node.
|
77
|
+
def dot_style__crazy
|
78
|
+
craze = "distortion=#{2*rand-1},skew=#{2*rand-1},orientation=#{360*rand}"
|
79
|
+
crazy_oval = "shape=polygon,sides=25," + craze
|
80
|
+
crazy_rect = "shape=polygon,sides=#{4+rand(3)}," + craze
|
81
|
+
if singleton?
|
82
|
+
# A singleton class
|
83
|
+
"#{crazy_oval},color=palevioletred3,style=filled,fontcolor=white,peripheries=3"
|
84
|
+
elsif t_class?
|
85
|
+
# A class
|
86
|
+
"#{crazy_oval},color=palevioletred1,style=filled"
|
87
|
+
elsif t_iclass? or t_module?
|
88
|
+
# A module
|
89
|
+
"#{crazy_rect},color=peachpuff1,style=filled"
|
90
|
+
else
|
91
|
+
# Else: a "normal" object, or an immediate.
|
92
|
+
"shape=house,color=pink,style=filled"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns the DOT label for the node.
|
97
|
+
#
|
98
|
+
# The representation may be quite big, so we trim it.
|
99
|
+
def dot_label(max = 20)
|
100
|
+
if class_like?
|
101
|
+
# Let's be more lenient when trimming a class/module name.
|
102
|
+
# We want to show The::Last::Component and possibly a singleton's
|
103
|
+
# trailing 'S.
|
104
|
+
max = 60 if max < 60
|
105
|
+
end
|
106
|
+
r = repr
|
107
|
+
if r.length > max
|
108
|
+
r[0, max] + ' ...'
|
109
|
+
else
|
110
|
+
r
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def dot_source(level = 0, opts = {}, &block) # :yield:
|
115
|
+
out = ''
|
116
|
+
# Note: since 'obj' may be a T_ICLASS, it doesn't repond to many methods,
|
117
|
+
# including is_a?. So when we're querying things we're using Drx calls
|
118
|
+
# instead.
|
119
|
+
|
120
|
+
if level.zero?
|
121
|
+
out << 'digraph {' "\n"
|
122
|
+
out << @@sizes[opts[:size] || '100%']
|
123
|
+
@@seen = {}
|
124
|
+
end
|
125
|
+
|
126
|
+
seen = @@seen[address]
|
127
|
+
@@seen[address] = true
|
128
|
+
|
129
|
+
if not seen
|
130
|
+
dot_style = method('dot_style__' + (opts[:style] || 'default')).call
|
131
|
+
out << "#{dot_id} [#{dot_style}, label=#{dot_quote dot_label}, URL=#{dot_quote dot_url}];" "\n"
|
132
|
+
end
|
133
|
+
|
134
|
+
yield self if block_given?
|
135
|
+
|
136
|
+
return '' if seen
|
137
|
+
|
138
|
+
if class_like?
|
139
|
+
if spr = self.super and display_super?(spr)
|
140
|
+
out << spr.dot_source(level+1, opts, &block)
|
141
|
+
if [Module, ObjInfo.new(Module).klass.the_object].include? the_object
|
142
|
+
# We don't want these relatively insignificant lines to clutter the display,
|
143
|
+
# so we paint them lightly and tell DOT they aren't to affect the layout (width=0).
|
144
|
+
out << "#{dot_id} -> #{spr.dot_id} [color=gray85, weight=0];" "\n"
|
145
|
+
else
|
146
|
+
out << "#{dot_id} -> #{spr.dot_id};" "\n"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
kls = effective_klass
|
152
|
+
if display_klass?(kls)
|
153
|
+
out << kls.dot_source(level+1, opts, &block)
|
154
|
+
out << "#{dot_id} -> #{kls.dot_id} [style=dotted];" "\n"
|
155
|
+
out << "{ rank=same; #{dot_id}; #{kls.dot_id}; }" "\n"
|
156
|
+
end
|
157
|
+
|
158
|
+
if level.zero?
|
159
|
+
out << '}' "\n"
|
160
|
+
end
|
161
|
+
|
162
|
+
return out
|
163
|
+
end
|
164
|
+
|
165
|
+
# Whether to display the klass.
|
166
|
+
def display_klass?(kls)
|
167
|
+
if t_iclass?
|
168
|
+
# We're interested in an ICLASS's klass only if it isn't Module.
|
169
|
+
#
|
170
|
+
# Usualy this means that the ICLASS has a singleton (see "Singletons
|
171
|
+
# of included modules" in display_super?()). We want to see this
|
172
|
+
# singleton.
|
173
|
+
return Module != kls.the_object
|
174
|
+
else
|
175
|
+
# Displaying a singletone's klass is confusing and usually unneeded.
|
176
|
+
return !singleton?
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Whether to display the super.
|
181
|
+
def display_super?(spr)
|
182
|
+
if (singleton? or t_iclass?) and Module == spr.the_object
|
183
|
+
# Singletons of included modules, and modules included in them,
|
184
|
+
# have their chain eventually reach Module. To prevent clutter,
|
185
|
+
# we don't show this final link.
|
186
|
+
#
|
187
|
+
# "Singletons of included modules" often exist only for their
|
188
|
+
# #included method. For example, DataMapper#Resource have
|
189
|
+
# such a singleton.
|
190
|
+
return false
|
191
|
+
end
|
192
|
+
return true
|
193
|
+
end
|
194
|
+
|
195
|
+
# Like klass(), but without surprises.
|
196
|
+
#
|
197
|
+
# Since the klass of an ICLASS is the module itself, we need to
|
198
|
+
# invoke klass() twice.
|
199
|
+
def effective_klass
|
200
|
+
if t_iclass?
|
201
|
+
klass.klass
|
202
|
+
else
|
203
|
+
klass
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Generates a diagram of the inheritance hierarchy. It accepts a hash
|
208
|
+
# pointing to pathnames to write the result to. A Tempfiles hash
|
209
|
+
# can be used instead.
|
210
|
+
#
|
211
|
+
# the_object = "some object"
|
212
|
+
# Tempfiles.new do |files|
|
213
|
+
# ObjInfo.new(the_object).generate_diagram
|
214
|
+
# system('xview ' + files['gif'])
|
215
|
+
# end
|
216
|
+
#
|
217
|
+
def generate_diagram(files, opts = {}, &block)
|
218
|
+
source = self.dot_source(0, opts, &block)
|
219
|
+
File.open(files['dot'], 'w') { |f| f.write(source) }
|
220
|
+
command = GRAPHVIZ_COMMAND % [files['dot'], files['gif'], files['map']]
|
221
|
+
message = Kernel.`(command) # `
|
222
|
+
if $? != 0
|
223
|
+
error = <<-EOS % [command, message]
|
224
|
+
ERROR: Failed to run the 'dot' command. Make sure you have the GraphViz
|
225
|
+
package installed and that its bin folder appears in your PATH.
|
226
|
+
|
227
|
+
The command I tried to execute is this:
|
228
|
+
|
229
|
+
%s
|
230
|
+
|
231
|
+
And the response I got is this:
|
232
|
+
|
233
|
+
%s
|
234
|
+
EOS
|
235
|
+
raise error
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
end
|
data/lib/drx/objinfo.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
|
2
|
+
module Drx
|
3
|
+
|
4
|
+
# An object orieneted wrapper around the DrX::Core functions.
|
5
|
+
#
|
6
|
+
# This object lets you query various properties of an object.
|
7
|
+
#
|
8
|
+
# info = ObjInfo.new("foo")
|
9
|
+
# info.has_iv_tbl?
|
10
|
+
# info.klass
|
11
|
+
#
|
12
|
+
class ObjInfo
|
13
|
+
def initialize(obj)
|
14
|
+
@obj = obj
|
15
|
+
@type = Core::get_type(@obj)
|
16
|
+
end
|
17
|
+
|
18
|
+
def address
|
19
|
+
Core::get_address(@obj)
|
20
|
+
end
|
21
|
+
|
22
|
+
def the_object
|
23
|
+
@obj
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns true if this object is either a class or a module.
|
27
|
+
# When true, you know it has 'm_tbl' and 'super'.
|
28
|
+
def class_like?
|
29
|
+
[Core::T_CLASS, Core::T_ICLASS, Core::T_MODULE].include? @type
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the method-table of an object.
|
33
|
+
def m_tbl
|
34
|
+
Core::get_m_tbl(@obj)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the source-code position where a method is defined.
|
38
|
+
def locate_method(method_name)
|
39
|
+
Core::locate_method(@obj, method_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
def has_iv_tbl?
|
43
|
+
t_object? || class_like?
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the variable-table of an object.
|
47
|
+
def iv_tbl
|
48
|
+
return nil if not has_iv_tbl?
|
49
|
+
Core::get_iv_tbl(@obj)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @todo: this could be nicer. perhaps define an [] accessor.
|
53
|
+
def __get_ivar(name)
|
54
|
+
if class_like? and name.to_s =~ /^[A-Z]/
|
55
|
+
# If it's a constant, it may be 'autoloaded'. We
|
56
|
+
# trigger the loading by calling const_get().
|
57
|
+
@obj.const_get(name)
|
58
|
+
end
|
59
|
+
Core::get_ivar(@obj, name)
|
60
|
+
end
|
61
|
+
|
62
|
+
def singleton?
|
63
|
+
class_like? && (Core::get_flags(@obj) & Core::FL_SINGLETON).nonzero?
|
64
|
+
end
|
65
|
+
|
66
|
+
def t_iclass?
|
67
|
+
@type == Core::T_ICLASS
|
68
|
+
end
|
69
|
+
|
70
|
+
def t_class?
|
71
|
+
@type == Core::T_CLASS
|
72
|
+
end
|
73
|
+
|
74
|
+
def t_object?
|
75
|
+
@type == Core::T_OBJECT
|
76
|
+
end
|
77
|
+
|
78
|
+
def t_module?
|
79
|
+
@type == Core::T_MODULE
|
80
|
+
end
|
81
|
+
|
82
|
+
# Note: the klass of an iclass is the included module.
|
83
|
+
def klass
|
84
|
+
ObjInfo.new Core::get_klass(@obj)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns the 'super' of a class-like object. Returns nil for end of chain.
|
88
|
+
#
|
89
|
+
# Examples: Kernel has a NULL super. Modules too have NULL super, unless
|
90
|
+
# when 'include'ing.
|
91
|
+
def super
|
92
|
+
spr = Core::get_super(@obj)
|
93
|
+
# Note: we can't do 'if spr.nil?' because T_ICLASS doesn't "have" #nil.
|
94
|
+
spr ? ObjInfo.new(spr) : nil
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns a string representation of the object. Similar to Object#inspect.
|
98
|
+
def repr
|
99
|
+
if t_iclass?
|
100
|
+
'include ' + klass.repr
|
101
|
+
elsif singleton?
|
102
|
+
attached = __get_ivar('__attached__') || self
|
103
|
+
attached.inspect + " 'S"
|
104
|
+
else
|
105
|
+
@obj.inspect
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# A utility function to print the inheritance hierarchy of an object.
|
110
|
+
def examine(level = 0, title = '', &block) # :yield:
|
111
|
+
# Note: since '@obj' may be a T_ICLASS, it doesn't repond to may methods,
|
112
|
+
# including is_a?. So when we're querying things we're using Drx calls
|
113
|
+
# instead.
|
114
|
+
|
115
|
+
@@seen = {} if level.zero?
|
116
|
+
line = (' ' * level) + title + ' ' + repr
|
117
|
+
|
118
|
+
seen = @@seen[address]
|
119
|
+
@@seen[address] = true
|
120
|
+
|
121
|
+
if seen
|
122
|
+
line += " [seen]"
|
123
|
+
end
|
124
|
+
|
125
|
+
if block_given?
|
126
|
+
yield line, self
|
127
|
+
else
|
128
|
+
puts line
|
129
|
+
end
|
130
|
+
|
131
|
+
return if seen
|
132
|
+
|
133
|
+
if class_like?
|
134
|
+
if spr = self.super
|
135
|
+
spr.examine(level + 1, '[super]', &block)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Displaying a T_ICLASS's klass isn't very useful, because the data
|
140
|
+
# is already mirrored in the m_tbl and iv_tvl of the T_ICLASS itself.
|
141
|
+
if not t_iclass?
|
142
|
+
klass.examine(level + 1, '[klass]', &block)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|