drx 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/examples/{drx_test2.rb → drx_datamapper.rb} +0 -0
- data/examples/{drx_test.rb → drx_date.rb} +0 -0
- data/examples/{drx_test3.rb → drx_guitar.rb} +2 -10
- data/ext/drx_core.c +34 -49
- data/lib/drx/graphviz.rb +22 -55
- data/lib/drx/objinfo.rb +1 -22
- data/lib/drx/tk/app.rb +153 -39
- data/lib/drx/tk/imagemap.rb +41 -39
- metadata +8 -9
- data/examples/drx_datamapper19.rb +0 -35
File without changes
|
File without changes
|
@@ -1,7 +1,6 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
|
-
|
5
4
|
require 'drx'
|
6
5
|
|
7
6
|
class Instrument
|
@@ -11,23 +10,16 @@ end
|
|
11
10
|
class Guitar < Instrument
|
12
11
|
def initialize
|
13
12
|
@strings = 5
|
14
|
-
# puts class # .add_some
|
15
13
|
end
|
16
14
|
|
17
15
|
def self.add_some
|
18
16
|
@max_strings = 10
|
19
|
-
@@approved = 'by obama
|
17
|
+
@@approved = 'by team obama'
|
20
18
|
end
|
21
19
|
end
|
22
20
|
|
23
|
-
|
24
|
-
class Post
|
25
|
-
include DataMapper::Resource
|
26
|
-
property :id, Serial
|
27
|
-
end
|
28
|
-
|
21
|
+
Guitar.add_some
|
29
22
|
o = Guitar.new
|
30
|
-
o = Post.new
|
31
23
|
def o.unq
|
32
24
|
def g;end
|
33
25
|
end
|
data/ext/drx_core.c
CHANGED
@@ -1,14 +1,5 @@
|
|
1
1
|
#include "ruby.h"
|
2
|
-
|
3
|
-
#ifndef RUBY_VM
|
4
|
-
# define RUBY_1_8
|
5
|
-
#endif
|
6
|
-
|
7
|
-
#ifdef RUBY_1_8
|
8
|
-
# include "st.h"
|
9
|
-
#else
|
10
|
-
# include "ruby/st.h"
|
11
|
-
#endif
|
2
|
+
#include "st.h"
|
12
3
|
|
13
4
|
/**
|
14
5
|
* Gets the Ruby's engine type of a variable.
|
@@ -19,9 +10,11 @@ static VALUE t_get_type(VALUE self, VALUE obj)
|
|
19
10
|
}
|
20
11
|
|
21
12
|
// Helper for t_get_iv_tbl().
|
22
|
-
|
23
|
-
//
|
24
|
-
//
|
13
|
+
int record_var(st_data_t key, st_data_t value, VALUE hash) {
|
14
|
+
// I originally did the following, but it breaks for object::Tk*. Perhaps these
|
15
|
+
// values are T_NODEs? (e.g. aliases) @todo: debug using INT2FIX(TYPE(value)).
|
16
|
+
rb_hash_aset(hash, ID2SYM(key), value);
|
17
|
+
// So...
|
25
18
|
rb_hash_aset(hash, ID2SYM(key), Qtrue);
|
26
19
|
return ST_CONTINUE;
|
27
20
|
}
|
@@ -38,13 +31,9 @@ static VALUE t_get_iv_tbl(VALUE self, VALUE obj)
|
|
38
31
|
rb_raise(rb_eTypeError, "Only T_OBJECT/T_CLASS/T_MODULE is expected as the argument (got %d)", TYPE(obj));
|
39
32
|
}
|
40
33
|
|
41
|
-
#ifdef RUBY_1_8
|
42
34
|
if (ROBJECT(obj)->iv_tbl) {
|
43
35
|
st_foreach(ROBJECT(obj)->iv_tbl, record_var, (st_data_t)hash);
|
44
36
|
}
|
45
|
-
#else
|
46
|
-
rb_ivar_foreach(obj, record_var, hash);
|
47
|
-
#endif
|
48
37
|
|
49
38
|
return hash;
|
50
39
|
}
|
@@ -69,10 +58,11 @@ static VALUE t_get_ivar(VALUE self, VALUE obj, VALUE var_name)
|
|
69
58
|
*/
|
70
59
|
static VALUE t_get_super(VALUE self, VALUE obj)
|
71
60
|
{
|
61
|
+
VALUE super;
|
72
62
|
if (TYPE(obj) != T_CLASS && TYPE(obj) != T_ICLASS && TYPE(obj) != T_MODULE) {
|
73
63
|
rb_raise(rb_eTypeError, "Only T_CLASS/T_MODULE is expected as the argument (got %d)", TYPE(obj));
|
74
64
|
}
|
75
|
-
return
|
65
|
+
return RCLASS(obj)->super ? RCLASS(obj)->super : Qnil;
|
76
66
|
}
|
77
67
|
|
78
68
|
/**
|
@@ -95,13 +85,9 @@ static VALUE t_get_flags(VALUE self, VALUE obj)
|
|
95
85
|
}
|
96
86
|
|
97
87
|
// Helper for t_get_m_tbl().
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
if (!id_allocator_symb) {
|
102
|
-
id_allocator_symb = rb_intern("<Allocator>");
|
103
|
-
}
|
104
|
-
rb_hash_aset(hash, ID2SYM(key == ID_ALLOCATOR ? id_allocator_symb : key), Qtrue);
|
88
|
+
int record_method(st_data_t key, st_data_t value, VALUE hash) {
|
89
|
+
// @todo: Store something useful in the values?
|
90
|
+
rb_hash_aset(hash, key == ID_ALLOCATOR ? rb_str_new2("<Allocator>") : ID2SYM(key), INT2FIX(666));
|
105
91
|
return ST_CONTINUE;
|
106
92
|
}
|
107
93
|
|
@@ -132,7 +118,6 @@ static VALUE t_get_address(VALUE self, VALUE obj)
|
|
132
118
|
return INT2NUM(obj);
|
133
119
|
}
|
134
120
|
|
135
|
-
#ifdef RUBY_1_8
|
136
121
|
// {{{ Locating methods
|
137
122
|
|
138
123
|
#include "node.h"
|
@@ -141,7 +126,7 @@ static VALUE t_get_address(VALUE self, VALUE obj)
|
|
141
126
|
|
142
127
|
static VALUE t_do_locate_method(NODE *ND_method) {
|
143
128
|
NODE *ND_scope = NULL, *ND_block = NULL;
|
144
|
-
VALUE
|
129
|
+
VALUE place;
|
145
130
|
char line_s[20];
|
146
131
|
|
147
132
|
//
|
@@ -210,24 +195,23 @@ static VALUE t_do_locate_method(NODE *ND_method) {
|
|
210
195
|
return RSTR("<I'm expecting a NODE_BLOCK here...>");
|
211
196
|
}
|
212
197
|
|
213
|
-
|
214
|
-
|
215
|
-
|
198
|
+
sprintf(line_s, "%d:", nd_line(ND_block));
|
199
|
+
place = RSTR(line_s);
|
200
|
+
rb_str_cat2(place, ND_block->nd_file);
|
216
201
|
|
217
|
-
return
|
202
|
+
return place;
|
218
203
|
}
|
219
204
|
|
220
205
|
/*
|
221
206
|
* call-seq:
|
222
|
-
* Drx::Core::locate_method(Date, "to_s") =>
|
207
|
+
* Drx::Core::locate_method(Date, "to_s") => str
|
223
208
|
*
|
224
|
-
* Locates the filename and line-number where a method was defined.
|
209
|
+
* Locates the filename and line-number where a method was defined. Returns a
|
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>".
|
225
212
|
*
|
226
|
-
*
|
227
|
-
*
|
228
|
-
* - A string of the form "<identifier>" if the method isn't written
|
229
|
-
* in Ruby. Possibilities are <c>, <alias>, <attr reader>, and more.
|
230
|
-
* - raises NameError if the method doesn't exist.
|
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>.
|
231
215
|
*/
|
232
216
|
static VALUE t_locate_method(VALUE self, VALUE obj, VALUE method_name)
|
233
217
|
{
|
@@ -237,17 +221,19 @@ static VALUE t_locate_method(VALUE self, VALUE obj, VALUE method_name)
|
|
237
221
|
if (TYPE(obj) != T_CLASS && TYPE(obj) != T_ICLASS && TYPE(obj) != T_MODULE) {
|
238
222
|
rb_raise(rb_eTypeError, "Only T_CLASS/T_MODULE is expected as the argument (got %d)", TYPE(obj));
|
239
223
|
}
|
224
|
+
if (!RCLASS(obj)->m_tbl) {
|
225
|
+
return Qnil;
|
226
|
+
}
|
240
227
|
c_name = StringValuePtr(method_name);
|
241
228
|
ID id = rb_intern(c_name);
|
242
|
-
if (
|
229
|
+
if (st_lookup(RCLASS(obj)->m_tbl, id, (st_data_t *)&method_node)) {
|
243
230
|
return t_do_locate_method(method_node);
|
244
231
|
} else {
|
245
|
-
|
232
|
+
return Qnil;
|
246
233
|
}
|
247
234
|
}
|
248
235
|
|
249
236
|
// }}}
|
250
|
-
#endif // RUBY_1_8
|
251
237
|
|
252
238
|
VALUE mDrx;
|
253
239
|
VALUE mCore;
|
@@ -263,19 +249,18 @@ void Init_drx_core() {
|
|
263
249
|
rb_define_module_function(mCore, "get_address", t_get_address, 1);
|
264
250
|
rb_define_module_function(mCore, "get_type", t_get_type, 1);
|
265
251
|
rb_define_module_function(mCore, "get_ivar", t_get_ivar, 2);
|
266
|
-
#ifdef RUBY_1_8
|
267
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));
|
258
|
+
|
268
259
|
// For the following, see explanation in t_do_locate_method().
|
269
260
|
rb_eval_string("\
|
270
261
|
class ::Proc;\
|
271
262
|
def _location;\
|
272
|
-
if to_s =~ /@(.*?):(\\d+)>$/ then
|
263
|
+
if to_s =~ /@(.*?):(\\d+)>$/ then \"#$2:#$1\" end;\
|
273
264
|
end;\
|
274
265
|
end");
|
275
|
-
#endif
|
276
|
-
rb_define_const(mCore, "FL_SINGLETON", INT2FIX(FL_SINGLETON));
|
277
|
-
rb_define_const(mCore, "T_OBJECT", INT2FIX(T_OBJECT));
|
278
|
-
rb_define_const(mCore, "T_CLASS", INT2FIX(T_CLASS));
|
279
|
-
rb_define_const(mCore, "T_ICLASS", INT2FIX(T_ICLASS));
|
280
|
-
rb_define_const(mCore, "T_MODULE", INT2FIX(T_MODULE));
|
281
266
|
}
|
data/lib/drx/graphviz.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Adds Graphviz
|
1
|
+
# Adds Graphviz diagraming capability to ObjInfo
|
2
2
|
|
3
3
|
module Drx
|
4
4
|
|
@@ -38,19 +38,16 @@ module Drx
|
|
38
38
|
# Create an ID for the DOT node representing this object.
|
39
39
|
def dot_id
|
40
40
|
('o' + address.to_s).sub('-', '_')
|
41
|
-
# Tip: when examining the DOT output you may wish to
|
42
|
-
# uncomment the following line. It will show you which
|
43
|
-
# ruby object the DOT node represents.
|
44
|
-
#('o' + address.to_s).sub('-', '_') + " /* #{repr} */ "
|
45
41
|
end
|
46
42
|
|
47
43
|
# Creates a pseudo URL for the HTML imagemap.
|
48
44
|
def dot_url
|
49
|
-
"http://
|
45
|
+
"http://server/obj/#{dot_id}"
|
50
46
|
end
|
51
47
|
|
52
48
|
# Quotes a string to be used in DOT source.
|
53
49
|
def dot_quote(s)
|
50
|
+
# @todo: find the documentation for tr()?
|
54
51
|
'"' + s.gsub('\\') { '\\\\' }.gsub('"', '\\"').gsub("\n", '\\n') + '"'
|
55
52
|
end
|
56
53
|
|
@@ -114,26 +111,18 @@ module Drx
|
|
114
111
|
end
|
115
112
|
end
|
116
113
|
|
117
|
-
|
118
|
-
# in the output image, use generate_diagram() instead.
|
119
|
-
def dot_source(opts = {}, &block) # :yield:
|
120
|
-
opts = opts.dup
|
121
|
-
opts[:base] = self
|
122
|
-
@@seen = {}
|
123
|
-
|
124
|
-
out = 'digraph {' "\n"
|
125
|
-
out << @@sizes[opts[:size] || '100%']
|
126
|
-
out << dot_fragment(opts, &block)
|
127
|
-
out << '}' "\n"
|
128
|
-
out
|
129
|
-
end
|
130
|
-
|
131
|
-
def dot_fragment(opts = {}, &block) # :yield:
|
114
|
+
def dot_source(level = 0, opts = {}, &block) # :yield:
|
132
115
|
out = ''
|
133
|
-
# Note: since 'obj' may be a T_ICLASS, it doesn't
|
116
|
+
# Note: since 'obj' may be a T_ICLASS, it doesn't repond to many methods,
|
134
117
|
# including is_a?. So when we're querying things we're using Drx calls
|
135
118
|
# instead.
|
136
119
|
|
120
|
+
if level.zero?
|
121
|
+
out << 'digraph {' "\n"
|
122
|
+
out << @@sizes[opts[:size] || '100%']
|
123
|
+
@@seen = {}
|
124
|
+
end
|
125
|
+
|
137
126
|
seen = @@seen[address]
|
138
127
|
@@seen[address] = true
|
139
128
|
|
@@ -148,8 +137,8 @@ module Drx
|
|
148
137
|
|
149
138
|
if class_like?
|
150
139
|
if spr = self.super and display_super?(spr)
|
151
|
-
out << spr.
|
152
|
-
if
|
140
|
+
out << spr.dot_source(level+1, opts, &block)
|
141
|
+
if [Module, ObjInfo.new(Module).klass.the_object].include? the_object
|
153
142
|
# We don't want these relatively insignificant lines to clutter the display,
|
154
143
|
# so we paint them lightly and tell DOT they aren't to affect the layout (width=0).
|
155
144
|
out << "#{dot_id} -> #{spr.dot_id} [color=gray85, weight=0];" "\n"
|
@@ -161,38 +150,16 @@ module Drx
|
|
161
150
|
|
162
151
|
kls = effective_klass
|
163
152
|
if display_klass?(kls)
|
164
|
-
out << kls.
|
165
|
-
|
166
|
-
# inheritance and the singleton inheritance.
|
167
|
-
#
|
168
|
-
# When an ICLASS has a singleton, we want this singleton to appear close
|
169
|
-
# to the ICLASS, because we want to keep the two groups visually distinct.
|
170
|
-
# We do this by setting the arrow's weight to 1.0.
|
171
|
-
#
|
172
|
-
# (To see the effect of this, set the weight unconditionally to '0' and
|
173
|
-
# see the graph for DataMapper.)
|
174
|
-
weight = t_iclass? ? 1 : 0
|
175
|
-
out << "#{dot_id} -> #{kls.dot_id} [style=dotted, weight=#{weight}];" "\n"
|
153
|
+
out << kls.dot_source(level+1, opts, &block)
|
154
|
+
out << "#{dot_id} -> #{kls.dot_id} [style=dotted];" "\n"
|
176
155
|
out << "{ rank=same; #{dot_id}; #{kls.dot_id}; }" "\n"
|
177
156
|
end
|
178
157
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
# Whether the 'super' arrow is infignificant and must not affect the DOT
|
183
|
-
# layout
|
184
|
-
#
|
185
|
-
# A Ruby object graph is cyclic. We don't want to feed DOT a cyclic graph
|
186
|
-
# because it will ruin our nice "rectangular" layout. The purpose of the
|
187
|
-
# following method is to break the cycle. Normally we break the cycle at
|
188
|
-
# Module (and its singleton). When the user is examining a module, we
|
189
|
-
# instead break the cycle at Class (and its singleton).
|
190
|
-
def insignificant_super_arrow?(opts)
|
191
|
-
if opts[:base].t_module?
|
192
|
-
[Class, ObjInfo.new(Class).klass.the_object].include? the_object
|
193
|
-
else
|
194
|
-
[Module, ObjInfo.new(Module).klass.the_object].include? the_object
|
158
|
+
if level.zero?
|
159
|
+
out << '}' "\n"
|
195
160
|
end
|
161
|
+
|
162
|
+
return out
|
196
163
|
end
|
197
164
|
|
198
165
|
# Whether to display the klass.
|
@@ -200,12 +167,12 @@ module Drx
|
|
200
167
|
if t_iclass?
|
201
168
|
# We're interested in an ICLASS's klass only if it isn't Module.
|
202
169
|
#
|
203
|
-
#
|
170
|
+
# Usualy this means that the ICLASS has a singleton (see "Singletons
|
204
171
|
# of included modules" in display_super?()). We want to see this
|
205
172
|
# singleton.
|
206
173
|
return Module != kls.the_object
|
207
174
|
else
|
208
|
-
# Displaying a
|
175
|
+
# Displaying a singletone's klass is confusing and usually unneeded.
|
209
176
|
return !singleton?
|
210
177
|
end
|
211
178
|
end
|
@@ -248,7 +215,7 @@ module Drx
|
|
248
215
|
# end
|
249
216
|
#
|
250
217
|
def generate_diagram(files, opts = {}, &block)
|
251
|
-
source = self.dot_source(opts, &block)
|
218
|
+
source = self.dot_source(0, opts, &block)
|
252
219
|
File.open(files['dot'], 'w') { |f| f.write(source) }
|
253
220
|
command = GRAPHVIZ_COMMAND % [files['dot'], files['gif'], files['map']]
|
254
221
|
message = Kernel.`(command) # `
|
data/lib/drx/objinfo.rb
CHANGED
@@ -35,29 +35,8 @@ module Drx
|
|
35
35
|
end
|
36
36
|
|
37
37
|
# Returns the source-code position where a method is defined.
|
38
|
-
#
|
39
|
-
# Returns one of:
|
40
|
-
# - [ 'file.rb', 12 ]
|
41
|
-
# - "<c>", "<undef>", "<alias>", etc., if not a ruby code (for Ruby 1.9,
|
42
|
-
# returns only "<c>").
|
43
|
-
# - nil, if functionality not implemented.
|
44
|
-
# - raises NameError if method not found.
|
45
38
|
def locate_method(method_name)
|
46
|
-
|
47
|
-
# Ruby 1.8
|
48
|
-
Core::locate_method(@obj, method_name)
|
49
|
-
elsif Method.method_defined? :source_location
|
50
|
-
# Ruby 1.9
|
51
|
-
location = @obj.instance_method(method_name).source_location
|
52
|
-
if location
|
53
|
-
location
|
54
|
-
else
|
55
|
-
'<c>'
|
56
|
-
end
|
57
|
-
else
|
58
|
-
# Some version of ruby that doesn't have Method#source_loction
|
59
|
-
nil
|
60
|
-
end
|
39
|
+
Core::locate_method(@obj, method_name)
|
61
40
|
end
|
62
41
|
|
63
42
|
def has_iv_tbl?
|
data/lib/drx/tk/app.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
require 'tk'
|
2
|
-
#Tk.default_widget_set = :Ttk
|
3
2
|
require 'drx/tk/imagemap'
|
4
3
|
|
4
|
+
Tk.default_widget_set = :Ttk
|
5
|
+
|
5
6
|
module Drx
|
6
7
|
module TkGUI
|
7
8
|
|
@@ -17,6 +18,7 @@ module Drx
|
|
17
18
|
def initialize
|
18
19
|
@navigation_history = []
|
19
20
|
@eval_history = LineHistory.new
|
21
|
+
@graph_opts = { :size => '100%', :style => 'default' }
|
20
22
|
|
21
23
|
@eval_entry = TkEntry.new(toplevel) {
|
22
24
|
font 'Courier'
|
@@ -45,16 +47,45 @@ module Drx
|
|
45
47
|
navigate_back
|
46
48
|
}
|
47
49
|
|
48
|
-
@varsbox =
|
49
|
-
|
50
|
+
@varsbox = Tk::Tile::Treeview.new(toplevel) {
|
51
|
+
columns 'name value'
|
52
|
+
heading_configure('name', :text => 'Name')
|
53
|
+
heading_configure('value', :text => 'Value')
|
54
|
+
column_configure('name', :stretch => false )
|
55
|
+
column_configure('value', :stretch => false )
|
56
|
+
show 'headings'
|
50
57
|
}
|
51
|
-
@methodsbox =
|
52
|
-
|
58
|
+
@methodsbox = Tk::Tile::Treeview.new(toplevel) {
|
59
|
+
columns 'name location'
|
60
|
+
heading_configure('name', :text => 'Name')
|
61
|
+
heading_configure('location', :text => 'Location')
|
62
|
+
column_configure('name', :stretch => false )
|
63
|
+
column_configure('location', :stretch => false )
|
64
|
+
show 'headings'
|
65
|
+
}
|
66
|
+
|
67
|
+
@graph_size_menu = Tk::Tile::Combobox.new(toplevel) {
|
68
|
+
set '100%'
|
69
|
+
values ['100%', '90%', '80%', '60%']
|
70
|
+
state :readonly
|
71
|
+
width 6
|
72
|
+
}
|
73
|
+
@graph_style_menu = Tk::Tile::Combobox.new(toplevel) {
|
74
|
+
set 'default'
|
75
|
+
values ['default', 'crazy']
|
76
|
+
state :readonly
|
77
|
+
width 10
|
78
|
+
}
|
79
|
+
@save_btn = TkButton.new(toplevel) {
|
80
|
+
text 'Save...'
|
81
|
+
}
|
82
|
+
@save_btn.command {
|
83
|
+
save_graph tip
|
53
84
|
}
|
54
85
|
|
55
86
|
layout
|
56
87
|
|
57
|
-
@varsbox.bind('<
|
88
|
+
@varsbox.bind('<TreeviewSelect>') {
|
58
89
|
if @varsbox.has_selection?
|
59
90
|
require 'pp'
|
60
91
|
output "\n== Variable #{@varsbox.get_selection}\n\n", 'info'
|
@@ -74,7 +105,7 @@ module Drx
|
|
74
105
|
}
|
75
106
|
@methodsbox.bind('Double-Button-1') {
|
76
107
|
if @methodsbox.has_selection?
|
77
|
-
|
108
|
+
locate_method(current_object, @methodsbox.get_selection)
|
78
109
|
end
|
79
110
|
}
|
80
111
|
@eval_entry.bind('Key-Return') {
|
@@ -97,15 +128,47 @@ module Drx
|
|
97
128
|
toplevel.bind('Control-r') {
|
98
129
|
# Refresh the display. Useful if you eval'ed some code that changes the
|
99
130
|
# object inspected.
|
100
|
-
|
131
|
+
refresh
|
101
132
|
# Note: it seems that #instance_eval creates a singleton for the object.
|
102
133
|
# So after eval'ing something and pressing C-r, you're going to see this
|
103
134
|
# extra class.
|
104
135
|
}
|
136
|
+
@graph_size_menu.bind('<ComboboxSelected>') {
|
137
|
+
@graph_opts[:size] = @graph_size_menu.get
|
138
|
+
refresh
|
139
|
+
}
|
140
|
+
@graph_style_menu.bind('<ComboboxSelected>') {
|
141
|
+
@graph_opts[:style] = @graph_style_menu.get
|
142
|
+
refresh
|
143
|
+
}
|
105
144
|
|
106
145
|
output "Please visit the homepage, http://drx.rubyforge.org/, for usage instructions.\n", 'info'
|
146
|
+
|
147
|
+
one_time_initialization
|
148
|
+
user_customizations
|
107
149
|
end
|
108
150
|
|
151
|
+
def one_time_initialization
|
152
|
+
@@one_time_initialization ||= begin
|
153
|
+
# Try to make the Unixy GUI less ugly.
|
154
|
+
if Tk.windowingsystem == 'x11' and Ttk.themes.include? 'clam'
|
155
|
+
Ttk.set_theme 'clam'
|
156
|
+
end
|
157
|
+
rc = File.join(ENV['HOME'] || Dir.pwd, '.drxrc')
|
158
|
+
load rc if File.exist? rc
|
159
|
+
1
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Users may redefine this method in their ~/.drxrc
|
164
|
+
# to fine-tune the app.
|
165
|
+
def user_customizations
|
166
|
+
end
|
167
|
+
|
168
|
+
def vbox(*args); VBox.new(toplevel, args); end
|
169
|
+
def hbox(*args); HBox.new(toplevel, args); end
|
170
|
+
def separator; TkLabel.new(toplevel, :text => ' '); end
|
171
|
+
|
109
172
|
# Arrange the main widgets inside layout widgets.
|
110
173
|
def layout
|
111
174
|
main_frame = TkPanedwindow.new(toplevel, :orient => :vertical) {
|
@@ -114,29 +177,35 @@ module Drx
|
|
114
177
|
# We don't want them to obscure the main ones.
|
115
178
|
lower
|
116
179
|
}
|
117
|
-
main_frame.add
|
180
|
+
main_frame.add vbox(
|
118
181
|
[Scrolled.new(toplevel, @eval_result, :vertical => true), { :expand => true, :fill => 'both' } ],
|
119
182
|
TkLabel.new(toplevel, :anchor => 'w') {
|
120
|
-
text 'Type some code to eval; \'self\' is the object at
|
183
|
+
text 'Type some code to eval; \'self\' is the object at the base of the graph; prepend with "see" to inspect result.'
|
121
184
|
},
|
122
|
-
@eval_entry
|
123
|
-
|
185
|
+
@eval_entry
|
186
|
+
)
|
124
187
|
|
125
188
|
panes = TkPanedwindow.new(main_frame, :orient => :horizontal) {
|
126
189
|
lower
|
127
190
|
}
|
128
|
-
|
191
|
+
# Note the :weight's on the followings.
|
192
|
+
panes.add vbox(
|
129
193
|
TkLabel.new(toplevel, :text => 'Object graph (klass and super):', :anchor => 'w'),
|
130
|
-
[@im, { :expand => true, :fill => 'both' } ],
|
131
|
-
|
132
|
-
|
194
|
+
[Scrolled.new(toplevel, @im), { :expand => true, :fill => 'both' } ],
|
195
|
+
hbox(TkLabel.new(toplevel, :text => 'Size: '), @graph_size_menu,
|
196
|
+
separator,
|
197
|
+
TkLabel.new(toplevel, :text => 'Style: '), @graph_style_menu,
|
198
|
+
separator,
|
199
|
+
[@save_btn, { :pady => 5 }])
|
200
|
+
), :weight => 10
|
201
|
+
panes.add vbox(
|
133
202
|
TkLabel.new(toplevel, :text => 'Variables (iv_tbl):', :anchor => 'w'),
|
134
203
|
[Scrolled.new(toplevel, @varsbox), { :expand => true, :fill => 'both' } ]
|
135
|
-
|
136
|
-
panes.add
|
204
|
+
), :weight => 50
|
205
|
+
panes.add vbox(
|
137
206
|
TkLabel.new(toplevel, :text => 'Methods (m_tbl):', :anchor => 'w'),
|
138
207
|
[Scrolled.new(toplevel, @methodsbox), { :expand => true, :fill => 'both' } ]
|
139
|
-
|
208
|
+
), :weight => 10
|
140
209
|
|
141
210
|
main_frame.add(panes)
|
142
211
|
end
|
@@ -151,7 +220,7 @@ module Drx
|
|
151
220
|
|
152
221
|
def open_up_editor(filename, lineno)
|
153
222
|
command = sprintf(ENV['DRX_EDITOR_COMMAND'] || EDITOR_COMMAND, lineno, filename)
|
154
|
-
output "
|
223
|
+
output "Executing: #{command}...\n", 'info'
|
155
224
|
if !fork
|
156
225
|
if !Kernel.system(command)
|
157
226
|
output "Could not execute the command '#{command}'\n", 'error'
|
@@ -160,15 +229,15 @@ module Drx
|
|
160
229
|
end
|
161
230
|
end
|
162
231
|
|
163
|
-
def
|
164
|
-
|
165
|
-
if !
|
232
|
+
def locate_method(obj, method_name)
|
233
|
+
place = ObjInfo.new(obj).locate_method(method_name)
|
234
|
+
if !place
|
166
235
|
output "Method #{method_name} doesn't exist\n", 'info'
|
167
236
|
else
|
168
|
-
if
|
169
|
-
|
237
|
+
if place =~ /\A(\d+):(.*)/
|
238
|
+
open_up_editor($2, $1)
|
170
239
|
else
|
171
|
-
|
240
|
+
output "Can't locate method, because it's a: #{place}\n", 'info'
|
172
241
|
end
|
173
242
|
end
|
174
243
|
end
|
@@ -212,23 +281,35 @@ module Drx
|
|
212
281
|
|
213
282
|
# Fills the variables listbox with a list of the object's instance variables.
|
214
283
|
def display_variables(obj)
|
215
|
-
|
284
|
+
allowed_names = [/^@/, /^[A-Z]/, '__classpath__', '__tmp_classpath__', '__classid__', '__attached__']
|
285
|
+
@varsbox.clear
|
216
286
|
info = ObjInfo.new(obj)
|
217
287
|
if obj and info.has_iv_tbl?
|
218
288
|
vars = info.iv_tbl.keys.map do |v| v.to_s end.sort
|
219
289
|
# Get rid of gazillions of Tk classes:
|
220
290
|
vars = vars.reject { |v| v =~ /Tk|Ttk/ }
|
221
|
-
|
291
|
+
vars.each do |name|
|
292
|
+
value = if allowed_names.any? { |p| p === name }
|
293
|
+
info.__get_ivar(name).inspect
|
294
|
+
else
|
295
|
+
# We don't want to inspect ruby's internal variables (because
|
296
|
+
# they may not be Ruby values at all).
|
297
|
+
''
|
298
|
+
end
|
299
|
+
@varsbox.insert('', 'end', :text => name, :values => [ name, value ] )
|
300
|
+
end
|
222
301
|
end
|
223
302
|
end
|
224
303
|
|
225
304
|
# Fills the methods listbox with a list of the object's methods.
|
226
305
|
def display_methods(obj)
|
227
|
-
@methodsbox.
|
306
|
+
@methodsbox.clear
|
228
307
|
info = ObjInfo.new(obj)
|
229
308
|
if obj and info.class_like?
|
230
309
|
methods = info.m_tbl.keys.map do |v| v.to_s end.sort
|
231
|
-
|
310
|
+
methods.each do |name|
|
311
|
+
@methodsbox.insert('', 'end', :text => name, :values => [ name, File.basename(String(info.locate_method(name))) ] )
|
312
|
+
end
|
232
313
|
end
|
233
314
|
end
|
234
315
|
|
@@ -237,7 +318,7 @@ module Drx
|
|
237
318
|
require 'drx/tempfiles'
|
238
319
|
@objs = {}
|
239
320
|
Tempfiles.new do |files|
|
240
|
-
ObjInfo.new(obj).generate_diagram(files) do |info|
|
321
|
+
ObjInfo.new(obj).generate_diagram(files, @graph_opts) do |info|
|
241
322
|
@objs[info.dot_url] = info
|
242
323
|
end
|
243
324
|
@im.image = files['gif']
|
@@ -245,6 +326,17 @@ module Drx
|
|
245
326
|
end
|
246
327
|
end
|
247
328
|
|
329
|
+
# Saves the graph to a file.
|
330
|
+
def save_graph(obj)
|
331
|
+
require 'drx/tempfiles'
|
332
|
+
Tempfiles.new do |files|
|
333
|
+
ObjInfo.new(obj).generate_diagram(files, @graph_opts)
|
334
|
+
if (output = Tk.getSaveFile(:parent => toplevel, :defaultextension => '.gif')) != ''
|
335
|
+
FileUtils.cp(files['gif'], output)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
248
340
|
# Makes `obj` the primary object seen (the one which is the tip of the diagram).
|
249
341
|
def navigate_to(obj)
|
250
342
|
@current_object = obj
|
@@ -261,6 +353,11 @@ module Drx
|
|
261
353
|
@navigation_history.last
|
262
354
|
end
|
263
355
|
|
356
|
+
# Refreshes the display.
|
357
|
+
def refresh
|
358
|
+
navigate_to tip
|
359
|
+
end
|
360
|
+
|
264
361
|
# Make `obj` the selected object. That is, the one the variable and method boxes reflect.
|
265
362
|
def select_object(obj)
|
266
363
|
@current_object = obj
|
@@ -315,7 +412,7 @@ module Drx
|
|
315
412
|
@the_widget = the_widget
|
316
413
|
if opts[:vertical]
|
317
414
|
TkScrollbar.new(self) { |s|
|
318
|
-
|
415
|
+
grid(:row => 0, :column => 1, :rowspan => 1, :columnspan => 1, :sticky => 'news')
|
319
416
|
command { |*args| the_widget.yview *args }
|
320
417
|
the_widget.yscrollcommand { |first,last| s.set first,last }
|
321
418
|
}
|
@@ -323,13 +420,15 @@ module Drx
|
|
323
420
|
if opts[:horizontal]
|
324
421
|
TkScrollbar.new(self) { |s|
|
325
422
|
orient 'horizontal'
|
326
|
-
|
423
|
+
grid(:row => 1, :column => 0, :rowspan => 1, :columnspan => 1, :sticky => 'news')
|
327
424
|
command { |*args| the_widget.xview *args }
|
328
425
|
the_widget.xscrollcommand { |first,last| s.set first,last }
|
329
426
|
}
|
330
427
|
end
|
331
428
|
the_widget.raise # Since the frame is created after the widget, it obscures it by default.
|
332
|
-
the_widget.
|
429
|
+
the_widget.grid(:in => self, :row => 0, :column => 0, :rowspan => 1, :columnspan => 1, :sticky => 'news')
|
430
|
+
TkGrid.rowconfigure(self, 0, :weight => 1, :minsize => 0)
|
431
|
+
TkGrid.columnconfigure(self, 0, :weight => 1, :minsize => 0)
|
333
432
|
end
|
334
433
|
def raise
|
335
434
|
super
|
@@ -342,20 +441,35 @@ module Drx
|
|
342
441
|
def initialize(parent, widgets)
|
343
442
|
super(parent)
|
344
443
|
widgets.each { |w, layout|
|
345
|
-
layout = {}
|
346
|
-
layout = { :in => self, :side => 'top', :fill => 'x' }.merge layout
|
444
|
+
layout = default_layout.merge(layout || {})
|
347
445
|
w.raise
|
348
446
|
w.pack(layout)
|
349
447
|
}
|
350
448
|
end
|
449
|
+
def default_layout
|
450
|
+
{ :in => self, :side => 'top', :fill => 'x' }
|
451
|
+
end
|
452
|
+
def raise
|
453
|
+
pack_slaves.each {|w| w.raise }
|
454
|
+
end
|
351
455
|
end
|
352
456
|
|
353
|
-
|
457
|
+
# Arranges widgets one beside the other.
|
458
|
+
class HBox < VBox
|
459
|
+
def default_layout
|
460
|
+
{ :in => self, :side => 'left', :fill => 'none' }
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
class ::Tk::Tile::Treeview
|
354
465
|
def get_selection
|
355
|
-
|
466
|
+
selection[0].text
|
356
467
|
end
|
357
468
|
def has_selection?
|
358
|
-
not
|
469
|
+
not selection.empty?
|
470
|
+
end
|
471
|
+
def clear
|
472
|
+
children('').each { |i| delete i }
|
359
473
|
end
|
360
474
|
end
|
361
475
|
|
data/lib/drx/tk/imagemap.rb
CHANGED
@@ -2,12 +2,20 @@ class TkCanvas
|
|
2
2
|
def coords(evt)
|
3
3
|
return [canvasx(evt.x), canvasy(evt.y)]
|
4
4
|
end
|
5
|
+
|
6
|
+
# TkCanvas redefines raise() (and lower()) to operate on tags.
|
7
|
+
# But our layout classes (VBox and Scrolled) require a proper raise(),
|
8
|
+
# so we restore its behavior.
|
9
|
+
# @todo: find out the "correct" way to do it.
|
10
|
+
def raise
|
11
|
+
super
|
12
|
+
end
|
5
13
|
end
|
6
14
|
|
7
15
|
module TkImageMap
|
8
16
|
|
9
17
|
# A widget to show an image together with an HTML imagemap.
|
10
|
-
class ImageMap <
|
18
|
+
class ImageMap < TkCanvas
|
11
19
|
|
12
20
|
def select_command &block
|
13
21
|
@select_command = block
|
@@ -22,10 +30,12 @@ module TkImageMap
|
|
22
30
|
@hotspots.map { |h| h[:url] }
|
23
31
|
end
|
24
32
|
|
25
|
-
#
|
26
|
-
#
|
27
|
-
|
28
|
-
|
33
|
+
# I'm not yet sure I want ImageMap to be a TkCanvas directly (an
|
34
|
+
# alternative would be for it to control an external canvas). So,
|
35
|
+
# for future compatibility, I access the canvas through canvas()
|
36
|
+
# instead of assuming it's 'self'.
|
37
|
+
def canvas
|
38
|
+
self
|
29
39
|
end
|
30
40
|
|
31
41
|
def initialize(parent, *rest)
|
@@ -37,22 +47,16 @@ module TkImageMap
|
|
37
47
|
@select_command = proc {}
|
38
48
|
@double_select_command = proc {}
|
39
49
|
|
40
|
-
@canvas = TkCanvas.new self, :scrollregion => [0, 0, 2000, 2000]
|
41
|
-
|
42
|
-
v_scr = TkScrollbar.new(self, :orient => 'vertical').pack :side=> 'right', :fill => 'y'
|
43
|
-
h_scr = TkScrollbar.new(self, :orient => 'horizontal').pack :side=> 'bottom', :fill => 'x'
|
44
|
-
@canvas.xscrollbar(h_scr)
|
45
|
-
@canvas.yscrollbar(v_scr)
|
46
50
|
# Attach scrolling to middle button.
|
47
|
-
|
48
|
-
|
51
|
+
canvas.bind('2', proc { |x,y| canvas.scan_mark(x,y) }, '%x %y')
|
52
|
+
canvas.bind('B2-Motion', proc { |x,y| canvas.scan_dragto x, y }, '%x %y')
|
49
53
|
|
50
|
-
|
54
|
+
canvas.pack :expand => true, :fill => 'both'
|
51
55
|
|
52
56
|
['Motion','Control-Motion'].each { |sequence|
|
53
|
-
|
54
|
-
x, y =
|
55
|
-
spot =
|
57
|
+
canvas.bind sequence do |evt|
|
58
|
+
x, y = canvas.coords(evt)
|
59
|
+
spot = canvas.find('overlapping', x, y, x + 1, y + 1).first
|
56
60
|
if spot and spot.cget('tags').include? 'hotspot'
|
57
61
|
new_hover_region = spot.hotspot_region
|
58
62
|
else
|
@@ -68,31 +72,31 @@ module TkImageMap
|
|
68
72
|
end
|
69
73
|
end
|
70
74
|
}
|
71
|
-
|
72
|
-
x, y =
|
73
|
-
spot =
|
75
|
+
canvas.bind 'Button-1' do |evt|
|
76
|
+
x, y = canvas.coords(evt)
|
77
|
+
spot = canvas.find('overlapping', x, y, x+1, y+1).first
|
74
78
|
if spot and spot.cget('tags').include? 'hotspot'
|
75
79
|
self.active_region = spot.hotspot_region
|
76
80
|
else
|
77
81
|
self.active_region = nil
|
78
82
|
end
|
79
83
|
end
|
80
|
-
|
84
|
+
canvas.bind 'Double-Button-1' do
|
81
85
|
@double_select_command.call(active_url) if @active_region
|
82
86
|
end
|
83
87
|
# Middle button: vertical scrolling.
|
84
|
-
|
85
|
-
|
88
|
+
canvas.bind 'Button-4' do
|
89
|
+
canvas.yview_scroll(-1, 'units')
|
86
90
|
end
|
87
|
-
|
88
|
-
|
91
|
+
canvas.bind 'Button-5' do
|
92
|
+
canvas.yview_scroll(1, 'units')
|
89
93
|
end
|
90
94
|
# Middle button: horizontal scrolling.
|
91
|
-
|
92
|
-
|
95
|
+
canvas.bind 'Shift-Button-4' do
|
96
|
+
canvas.xview_scroll(-1, 'units')
|
93
97
|
end
|
94
|
-
|
95
|
-
|
98
|
+
canvas.bind 'Shift-Button-5' do
|
99
|
+
canvas.xview_scroll(1, 'units')
|
96
100
|
end
|
97
101
|
end
|
98
102
|
|
@@ -124,15 +128,15 @@ module TkImageMap
|
|
124
128
|
|
125
129
|
def image=(pathname)
|
126
130
|
@image = TkPhotoImage.new :file => pathname
|
127
|
-
|
131
|
+
canvas.configure :scrollregion => [0, 0, @image.width, @image.height]
|
128
132
|
end
|
129
133
|
|
130
134
|
# You must call this after image=()
|
131
135
|
def image_map=(pathname)
|
132
136
|
# Delete the previous spot widgets and the image widget.
|
133
|
-
|
134
|
-
|
135
|
-
|
137
|
+
canvas.find('all').each {|w| w.destroy }
|
138
|
+
canvas.xview_moveto(0)
|
139
|
+
canvas.yview_moveto(0)
|
136
140
|
|
137
141
|
@hotspots = Utils.parse_imap(pathname)
|
138
142
|
|
@@ -140,7 +144,7 @@ module TkImageMap
|
|
140
144
|
# Create the back spots.
|
141
145
|
#
|
142
146
|
@hotspots.each do |region|
|
143
|
-
args = [
|
147
|
+
args = [canvas, *(region[:args])]
|
144
148
|
opts = {
|
145
149
|
:fill => 'red',
|
146
150
|
:tags => ['hotspot']
|
@@ -156,13 +160,13 @@ module TkImageMap
|
|
156
160
|
end
|
157
161
|
end
|
158
162
|
|
159
|
-
@image_widget = TkcImage.new(
|
163
|
+
@image_widget = TkcImage.new(canvas,0,0, :image => @image, :anchor=> 'nw')
|
160
164
|
|
161
165
|
#
|
162
166
|
# Create the hover spots.
|
163
167
|
#
|
164
168
|
@hotspots.each do |region|
|
165
|
-
args = [
|
169
|
+
args = [canvas, *(region[:args])]
|
166
170
|
opts = {
|
167
171
|
:dash => '. ',
|
168
172
|
:width => 5,
|
@@ -187,9 +191,7 @@ module TkImageMap
|
|
187
191
|
# DOT outputs oval elements as polygons with many many sides. Since Tk doesn't
|
188
192
|
# draw them well, we convert such polygons to ovals.
|
189
193
|
def self.to_oval(coords)
|
190
|
-
|
191
|
-
#points = coords.each_slice(2).to_a # Doesn't work for older 1.8's.
|
192
|
-
points = coords.enum_for(:each_slice, 2).to_a
|
194
|
+
points = coords.enum_slice(2).to_a
|
193
195
|
if points.size < 10
|
194
196
|
# If there are few sides, we assume it's a real polygon.
|
195
197
|
return nil
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: drx
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mooffie
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-
|
12
|
+
date: 2010-03-28 00:00:00 +03:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -31,10 +31,9 @@ files:
|
|
31
31
|
- lib/drx.rb
|
32
32
|
- ext/extconf.rb
|
33
33
|
- ext/drx_core.c
|
34
|
-
- examples/
|
35
|
-
- examples/
|
36
|
-
- examples/
|
37
|
-
- examples/drx_datamapper19.rb
|
34
|
+
- examples/drx_datamapper.rb
|
35
|
+
- examples/drx_date.rb
|
36
|
+
- examples/drx_guitar.rb
|
38
37
|
has_rdoc: true
|
39
38
|
homepage: http://drx.rubyforge.org/
|
40
39
|
licenses: []
|
@@ -46,9 +45,9 @@ require_paths:
|
|
46
45
|
- lib
|
47
46
|
required_ruby_version: !ruby/object:Gem::Requirement
|
48
47
|
requirements:
|
49
|
-
- -
|
48
|
+
- - ~>
|
50
49
|
- !ruby/object:Gem::Version
|
51
|
-
version: 1.8
|
50
|
+
version: "1.8"
|
52
51
|
version:
|
53
52
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
53
|
requirements:
|
@@ -62,6 +61,6 @@ rubyforge_project: drx
|
|
62
61
|
rubygems_version: 1.3.5
|
63
62
|
signing_key:
|
64
63
|
specification_version: 3
|
65
|
-
summary:
|
64
|
+
summary: Inspect Ruby objects.
|
66
65
|
test_files: []
|
67
66
|
|
@@ -1,35 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'dm-core'
|
3
|
-
$: << File.join('/home/mooffie/_drx.git/lib')
|
4
|
-
require 'drx'
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
#
|
9
|
-
# This is part of a blogging website. Users write posts. A post
|
10
|
-
# belongs to a user.
|
11
|
-
#
|
12
|
-
|
13
|
-
class Post
|
14
|
-
include DataMapper::Resource
|
15
|
-
|
16
|
-
property :post_id, Serial
|
17
|
-
property :title, String
|
18
|
-
property :body, Text
|
19
|
-
|
20
|
-
belongs_to :user
|
21
|
-
end
|
22
|
-
|
23
|
-
class User
|
24
|
-
include DataMapper::Resource
|
25
|
-
|
26
|
-
property :user_uid, Serial
|
27
|
-
property :name, String
|
28
|
-
property :mail, String
|
29
|
-
end
|
30
|
-
|
31
|
-
post = Post.new
|
32
|
-
|
33
|
-
p post
|
34
|
-
require 'drx'
|
35
|
-
post.see
|