drx 0.0.2 → 0.3.0
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.
- 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
|