drx 0.4.2 → 0.4.3
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/examples/drx_activerecord.rb +44 -0
- data/examples/drx_sequel.rb +35 -0
- data/lib/drx/arguments.rb +103 -0
- data/lib/drx/graphviz.rb +55 -21
- data/lib/drx/objinfo.rb +8 -3
- data/lib/drx/tk/app.rb +117 -27
- data/lib/drx.rb +1 -0
- metadata +5 -3
- data/examples/drx_datamapper19.rb +0 -35
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'activerecord'
|
3
|
+
|
4
|
+
#
|
5
|
+
# This is part of a blogging website. Users write posts. A post
|
6
|
+
# belongs to a user.
|
7
|
+
#
|
8
|
+
|
9
|
+
ActiveRecord::Base.logger = Logger.new(STDERR)
|
10
|
+
ActiveRecord::Base.colorize_logging = false
|
11
|
+
|
12
|
+
ActiveRecord::Base.establish_connection(
|
13
|
+
:adapter => 'sqlite3',
|
14
|
+
:database => ':memory:'
|
15
|
+
)
|
16
|
+
|
17
|
+
ActiveRecord::Schema.define do
|
18
|
+
create_table :users do |table|
|
19
|
+
table.column :name, :string
|
20
|
+
table.column :mail, :string
|
21
|
+
end
|
22
|
+
create_table :posts do |table|
|
23
|
+
table.column :user_id, :integer
|
24
|
+
table.column :title, :string
|
25
|
+
table.column :body, :string
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class User < ActiveRecord::Base
|
30
|
+
has_many :posts
|
31
|
+
end
|
32
|
+
|
33
|
+
class Post < ActiveRecord::Base
|
34
|
+
belongs_to :users
|
35
|
+
end
|
36
|
+
|
37
|
+
david = User.create(:name => 'David')
|
38
|
+
david.posts.create(:title => 'Lies')
|
39
|
+
david.posts.create(:title => 'Truths')
|
40
|
+
|
41
|
+
post = User.find(1).posts.first
|
42
|
+
|
43
|
+
require 'drx'
|
44
|
+
post.see
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'sequel'
|
3
|
+
|
4
|
+
#
|
5
|
+
# This is part of a blogging website. Users write posts. A post
|
6
|
+
# belongs to a user.
|
7
|
+
#
|
8
|
+
|
9
|
+
DB = Sequel.sqlite(':memory:')
|
10
|
+
|
11
|
+
DB.create_table :posts do
|
12
|
+
primary_key :id
|
13
|
+
Integer :user_id
|
14
|
+
String :title
|
15
|
+
String :body
|
16
|
+
end
|
17
|
+
|
18
|
+
DB.create_table :users do
|
19
|
+
primary_key :id
|
20
|
+
String :name
|
21
|
+
String :email
|
22
|
+
end
|
23
|
+
|
24
|
+
class Post < Sequel::Model
|
25
|
+
many_to_one :user
|
26
|
+
end
|
27
|
+
|
28
|
+
class User < Sequel::Model
|
29
|
+
one_to_many :posts
|
30
|
+
end
|
31
|
+
|
32
|
+
post = Post.create(:title => 'Lies', :user => User.create(:name => 'David'))
|
33
|
+
|
34
|
+
require 'drx'
|
35
|
+
post.see
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# Adds method arguments detection to ObjInfo
|
2
|
+
|
3
|
+
module Drx
|
4
|
+
|
5
|
+
class ObjInfo
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# Whether to use the 'arguments' gem, if present.
|
9
|
+
# This isn't on by default because it's slow.
|
10
|
+
attr_accessor :use_arguments_gem
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns a Ruby 1.9.2-compatible array describing the arguments a method expects.
|
14
|
+
def method_arguments(method_name)
|
15
|
+
if ObjInfo.use_arguments_gem
|
16
|
+
_method_arguments__by_arguments_gem(method_name) || _method_arguments__by_arity(method_name)
|
17
|
+
else
|
18
|
+
_method_arguments__by_methopara(method_name) || _method_arguments__by_arity(method_name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Strategy: use the 'arguments' gem, which, in turn, uses either ParseTree (Ruby 1.8)
|
23
|
+
# or RubyParser (Ruby 1.9)
|
24
|
+
#
|
25
|
+
# pros: shows default values; works for 1.8 too.
|
26
|
+
# cons: very slow.
|
27
|
+
def _method_arguments__by_arguments_gem(method_name)
|
28
|
+
@@once__arguments ||= begin
|
29
|
+
begin
|
30
|
+
require 'arguments'
|
31
|
+
rescue LoadError
|
32
|
+
# Not installed.
|
33
|
+
end
|
34
|
+
1
|
35
|
+
end
|
36
|
+
return nil if not defined? Arguments
|
37
|
+
args = Arguments.names(the_object, method_name, false)
|
38
|
+
# Convert this to a Ruby 1.9.2 format:
|
39
|
+
return args.map do |arg|
|
40
|
+
if arg.size == 2
|
41
|
+
[:opt, arg[0], arg[1]]
|
42
|
+
else
|
43
|
+
name = arg[0].to_s
|
44
|
+
case name[0,1]
|
45
|
+
when '*' then [:rest, name[1..-1]]
|
46
|
+
when '&' then [:block, name[1..-1]]
|
47
|
+
else [:req, name]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
rescue SyntaxError => e
|
52
|
+
# We could just return nil here, to continue to the next strategy,
|
53
|
+
# but we want to inform the user of the suckiness of RubyParser.
|
54
|
+
raise
|
55
|
+
rescue Exception
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
# Strategy: use Method#parameters (for ruby 1.9 only).
|
60
|
+
#
|
61
|
+
# pros: fast.
|
62
|
+
# cons: doesn't show default values.
|
63
|
+
def _method_arguments__by_methopara(method_name)
|
64
|
+
@@once__methopara ||= begin
|
65
|
+
# For ruby 1.9.0 and 1.9.1, we need to use a gem.
|
66
|
+
begin
|
67
|
+
require 'methopara'
|
68
|
+
rescue LoadError
|
69
|
+
# Not installed.
|
70
|
+
end
|
71
|
+
1
|
72
|
+
end
|
73
|
+
method = the_object.instance_method(method_name)
|
74
|
+
if method.respond_to? :parameters
|
75
|
+
return method.parameters
|
76
|
+
end
|
77
|
+
rescue NotImplementedError
|
78
|
+
# For some methods #parameters raises an exception. We return nil
|
79
|
+
# to move on to the next strategy.
|
80
|
+
return nil
|
81
|
+
end
|
82
|
+
|
83
|
+
# Strategy: simulation via Method#arity.
|
84
|
+
#
|
85
|
+
# This is used as a fallback if any of the other strategies fail.
|
86
|
+
#
|
87
|
+
# pros: fast; work without any gems.
|
88
|
+
# cons: doesn't show argument names.
|
89
|
+
def _method_arguments__by_arity(method_name)
|
90
|
+
method = the_object.instance_method(method_name)
|
91
|
+
ary, rest = method.arity, false
|
92
|
+
if ary < 0
|
93
|
+
ary = -ary - 1
|
94
|
+
rest = true
|
95
|
+
end
|
96
|
+
args = [[:req]] * ary
|
97
|
+
args << [:rest] if rest
|
98
|
+
return args
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
data/lib/drx/graphviz.rb
CHANGED
@@ -8,7 +8,8 @@ module Drx
|
|
8
8
|
# - Windows' CMD.EXE too supports "2>&1"
|
9
9
|
# - We're generating GIF, not PNG, because that's a format Tk
|
10
10
|
# supports out of the box.
|
11
|
-
|
11
|
+
# - Each {extension} token is replaced by the filepath with that extension.
|
12
|
+
GRAPHVIZ_COMMAND = 'dot "{dot}" -Tgif -o "{gif}" -Tcmapx -o "{map}" 2>&1'
|
12
13
|
|
13
14
|
@@sizes = {
|
14
15
|
'100%' => "
|
@@ -128,7 +129,7 @@ module Drx
|
|
128
129
|
out
|
129
130
|
end
|
130
131
|
|
131
|
-
def dot_fragment(opts = {}, &block) # :yield:
|
132
|
+
def dot_fragment(opts = {}, ancestors = [], &block) # :yield:
|
132
133
|
out = ''
|
133
134
|
# Note: since 'obj' may be a T_ICLASS, it doesn't respond to many methods,
|
134
135
|
# including is_a?. So when we're querying things we're using Drx calls
|
@@ -147,9 +148,9 @@ module Drx
|
|
147
148
|
return '' if seen
|
148
149
|
|
149
150
|
if class_like?
|
150
|
-
if spr = self.super and display_super?(spr)
|
151
|
-
out << spr.dot_fragment(opts, &block)
|
152
|
-
if insignificant_super_arrow?(opts)
|
151
|
+
if spr = self.super and display_super?(spr, ancestors)
|
152
|
+
out << spr.dot_fragment(opts, ancestors + [self], &block)
|
153
|
+
if insignificant_super_arrow?(opts, ancestors)
|
153
154
|
# We don't want these relatively insignificant lines to clutter the display,
|
154
155
|
# so we paint them lightly and tell DOT they aren't to affect the layout (width=0).
|
155
156
|
out << "#{dot_id} -> #{spr.dot_id} [color=gray85, weight=0];" "\n"
|
@@ -172,6 +173,12 @@ module Drx
|
|
172
173
|
# (To see the effect of this, set the weight unconditionally to '0' and
|
173
174
|
# see the graph for DataMapper.)
|
174
175
|
weight = t_iclass? ? 1 : 0
|
176
|
+
|
177
|
+
# However, here's a special case. DOT seems to have a bug: it sometimes
|
178
|
+
# doesn't draw the arrows going out of Class and Module (to their
|
179
|
+
# singletons). Making their weight 1 makes DOT draw them.
|
180
|
+
weight = 1 if self == _Class or self == _Module
|
181
|
+
|
175
182
|
out << "#{dot_id} -> #{kls.dot_id} [style=dotted, weight=#{weight}];" "\n"
|
176
183
|
out << "{ rank=same; #{dot_id}; #{kls.dot_id}; }" "\n"
|
177
184
|
end
|
@@ -187,11 +194,13 @@ module Drx
|
|
187
194
|
# following method is to break the cycle. Normally we break the cycle at
|
188
195
|
# Module (and its singleton). When the user is examining a module, we
|
189
196
|
# instead break the cycle at Class (and its singleton).
|
190
|
-
def insignificant_super_arrow?(opts)
|
197
|
+
def insignificant_super_arrow?(opts, my_ancestors)
|
191
198
|
if opts[:base].t_module?
|
192
|
-
|
199
|
+
self == _ClassS or
|
200
|
+
(self.super == _Module and (my_ancestors + [self]).include? _Class)
|
193
201
|
else
|
194
|
-
|
202
|
+
self == _ModuleS or
|
203
|
+
(self.super == _Object and (my_ancestors + [self]).include? _Module)
|
195
204
|
end
|
196
205
|
end
|
197
206
|
|
@@ -203,7 +212,17 @@ module Drx
|
|
203
212
|
# Usually this means that the ICLASS has a singleton (see "Singletons
|
204
213
|
# of included modules" in display_super?()). We want to see this
|
205
214
|
# singleton.
|
206
|
-
|
215
|
+
|
216
|
+
# Unfortunately, here is a special treatment for the 'arguments' gem,
|
217
|
+
# which our GUI uses. That gem includes the 'Arguments' module in both
|
218
|
+
# Class and Module (this is redundant!) and having the singleton twice
|
219
|
+
# in our graph may break its nice rectangular structure. So we don't
|
220
|
+
# show its singleton.
|
221
|
+
if defined? ::Arguments
|
222
|
+
return false if ::Arguments == klass.the_object
|
223
|
+
end
|
224
|
+
|
225
|
+
return kls != _Module
|
207
226
|
else
|
208
227
|
# Displaying a singleton's klass is confusing and usually unneeded.
|
209
228
|
return !singleton?
|
@@ -211,16 +230,19 @@ module Drx
|
|
211
230
|
end
|
212
231
|
|
213
232
|
# Whether to display the super.
|
214
|
-
def display_super?(spr)
|
215
|
-
if
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
233
|
+
def display_super?(spr, my_ancestors)
|
234
|
+
if spr == _Module
|
235
|
+
# Many objects have Module as their super (e.g., singletones
|
236
|
+
# of included modules, or modules included in them). To prevent
|
237
|
+
# clutter we print the arrow to Module only if it comes from
|
238
|
+
# Class (or a module included in it).
|
239
|
+
return (my_ancestors + [self]).include? _Class
|
240
|
+
#
|
241
|
+
# A somewhat irrelevant note (I don't have a better place to put it):
|
242
|
+
#
|
243
|
+
# "Singletons of included modules" often exist solely for their
|
244
|
+
# #included method. For example, DataMapper#Resource has
|
245
|
+
# such a singleton.
|
224
246
|
end
|
225
247
|
return true
|
226
248
|
end
|
@@ -237,8 +259,19 @@ module Drx
|
|
237
259
|
end
|
238
260
|
end
|
239
261
|
|
262
|
+
# Shortcuts for some specific objects. An "S" suffix
|
263
|
+
# denotes a singleton.
|
264
|
+
protected
|
265
|
+
def _Module; ObjInfo.new(Module); end
|
266
|
+
def _ModuleS; ObjInfo.new(Module).klass; end
|
267
|
+
def _Object; ObjInfo.new(Object); end
|
268
|
+
def _ObjectS; ObjInfo.new(Object).klass; end
|
269
|
+
def _Class; ObjInfo.new(Class); end
|
270
|
+
def _ClassS; ObjInfo.new(Class).klass; end
|
271
|
+
public
|
272
|
+
|
240
273
|
# Generates a diagram of the inheritance hierarchy. It accepts a hash
|
241
|
-
# pointing to
|
274
|
+
# pointing to filepaths to write the result to. A Tempfiles hash
|
242
275
|
# can be used instead.
|
243
276
|
#
|
244
277
|
# the_object = "some object"
|
@@ -250,7 +283,8 @@ module Drx
|
|
250
283
|
def generate_diagram(files, opts = {}, &block)
|
251
284
|
source = self.dot_source(opts, &block)
|
252
285
|
File.open(files['dot'], 'w') { |f| f.write(source) }
|
253
|
-
|
286
|
+
# Replace {extension} tokens with their corresponding filepaths:
|
287
|
+
command = GRAPHVIZ_COMMAND.gsub(/\{([a-z]+)\}/) { files[$1] }
|
254
288
|
message = Kernel.`(command) # `
|
255
289
|
if $? != 0
|
256
290
|
error = <<-EOS % [command, message]
|
data/lib/drx/objinfo.rb
CHANGED
@@ -19,6 +19,10 @@ module Drx
|
|
19
19
|
Core::get_address(@obj)
|
20
20
|
end
|
21
21
|
|
22
|
+
def ==(other)
|
23
|
+
other.is_a?(ObjInfo) and address == other.address
|
24
|
+
end
|
25
|
+
|
22
26
|
def the_object
|
23
27
|
@obj
|
24
28
|
end
|
@@ -70,8 +74,9 @@ module Drx
|
|
70
74
|
Core::get_iv_tbl(@obj)
|
71
75
|
end
|
72
76
|
|
73
|
-
#
|
74
|
-
|
77
|
+
# Returns the value of an instance variable. Actually, of any sort
|
78
|
+
# of variable that's recorded in the variable-table.
|
79
|
+
def get_ivar(name)
|
75
80
|
if class_like? and name.to_s =~ /^[A-Z]/
|
76
81
|
# If it's a constant, it may be 'autoloaded'. We
|
77
82
|
# trigger the loading by calling const_get().
|
@@ -120,7 +125,7 @@ module Drx
|
|
120
125
|
if t_iclass?
|
121
126
|
'include ' + klass.repr
|
122
127
|
elsif singleton?
|
123
|
-
attached =
|
128
|
+
attached = get_ivar('__attached__') || self
|
124
129
|
attached.inspect + " 'S"
|
125
130
|
else
|
126
131
|
@obj.inspect
|
data/lib/drx/tk/app.rb
CHANGED
@@ -11,8 +11,23 @@ module Drx
|
|
11
11
|
|
12
12
|
class Application
|
13
13
|
|
14
|
+
# Loads ~/.drxrc.
|
15
|
+
#
|
16
|
+
# @see Application#user_customizations
|
17
|
+
def self.load_rc
|
18
|
+
@rc_loaded ||= begin
|
19
|
+
rc = File.join(ENV['HOME'] || Dir.pwd, '.drxrc')
|
20
|
+
load rc if File.exist? rc
|
21
|
+
1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the top-level frame in which to show ourselves.
|
14
26
|
def toplevel
|
15
|
-
@toplevel ||=
|
27
|
+
@toplevel ||= begin
|
28
|
+
# We're showing ourselves inside a TkRoot, unless one already exists.
|
29
|
+
Application.first_window? ? TkRoot.new : TkToplevel.new
|
30
|
+
end
|
16
31
|
end
|
17
32
|
|
18
33
|
def initialize
|
@@ -56,12 +71,16 @@ module Drx
|
|
56
71
|
show 'headings'
|
57
72
|
}
|
58
73
|
@methodsbox = Tk::Tile::Treeview.new(toplevel) {
|
59
|
-
columns 'name location'
|
74
|
+
columns 'name arguments location'
|
60
75
|
heading_configure('name', :text => 'Name')
|
76
|
+
heading_configure('arguments', :text => 'Arguments')
|
61
77
|
heading_configure('location', :text => 'Location')
|
62
78
|
column_configure('name', :stretch => false )
|
79
|
+
column_configure('arguments', :stretch => false )
|
63
80
|
column_configure('location', :stretch => false )
|
64
81
|
show 'headings'
|
82
|
+
# We want the layout manager to allocate space for two columns only:
|
83
|
+
displaycolumns 'name location'
|
65
84
|
}
|
66
85
|
|
67
86
|
@graph_size_menu = Tk::Tile::Combobox.new(toplevel) {
|
@@ -83,6 +102,15 @@ module Drx
|
|
83
102
|
save_graph tip
|
84
103
|
}
|
85
104
|
|
105
|
+
@show_arguments_chk = TkCheckbutton.new(toplevel) {
|
106
|
+
text 'Show arguments'
|
107
|
+
variable TkVariable.new(0)
|
108
|
+
}
|
109
|
+
@use_arguments_gem_chk = TkCheckbutton.new(toplevel) {
|
110
|
+
text "Use the 'arguments' gem (slower)"
|
111
|
+
variable TkVariable.new(0)
|
112
|
+
}
|
113
|
+
|
86
114
|
layout
|
87
115
|
|
88
116
|
@varsbox.bind('<TreeviewSelect>') {
|
@@ -141,30 +169,45 @@ module Drx
|
|
141
169
|
@graph_opts[:style] = @graph_style_menu.get
|
142
170
|
refresh
|
143
171
|
}
|
172
|
+
@show_arguments_chk.variable.trace('w') do |value,|
|
173
|
+
if value == 1
|
174
|
+
@use_arguments_gem_chk.raise
|
175
|
+
@methodsbox.displaycolumns 'name arguments location'
|
176
|
+
display_methods(current_object)
|
177
|
+
else
|
178
|
+
@use_arguments_gem_chk.lower
|
179
|
+
@methodsbox.displaycolumns 'name location'
|
180
|
+
end
|
181
|
+
end
|
182
|
+
@use_arguments_gem_chk.variable.trace('w') do |value,|
|
183
|
+
ObjInfo.use_arguments_gem = (value == 1)
|
184
|
+
display_methods(current_object)
|
185
|
+
end
|
186
|
+
@show_arguments_chk.variable.value = @show_arguments_chk.variable.value # Trigger the trace handler.
|
144
187
|
|
145
188
|
output "Please visit the homepage, http://drx.rubyforge.org/, for usage instructions.\n", 'info'
|
146
189
|
|
147
|
-
|
190
|
+
Application.load_rc
|
191
|
+
system_customizations
|
148
192
|
user_customizations
|
149
193
|
end
|
150
194
|
|
151
|
-
|
152
|
-
|
195
|
+
# Users may redefine this method in their ~/.drxrc
|
196
|
+
# to fine-tune the app.
|
197
|
+
def user_customizations
|
198
|
+
end
|
199
|
+
|
200
|
+
# The following are default customizations. They are subjective in
|
201
|
+
# nature and users may knock them out in their ~/.drxrc.
|
202
|
+
def system_customizations
|
203
|
+
if Application.first_window?
|
153
204
|
# Try to make the Unixy GUI less ugly.
|
154
205
|
if Tk.windowingsystem == 'x11' and Ttk.themes.include? 'clam'
|
155
206
|
Ttk.set_theme 'clam'
|
156
207
|
end
|
157
|
-
rc = File.join(ENV['HOME'] || Dir.pwd, '.drxrc')
|
158
|
-
load rc if File.exist? rc
|
159
|
-
1
|
160
208
|
end
|
161
209
|
end
|
162
210
|
|
163
|
-
# Users may redefine this method in their ~/.drxrc
|
164
|
-
# to fine-tune the app.
|
165
|
-
def user_customizations
|
166
|
-
end
|
167
|
-
|
168
211
|
def vbox(*args); VBox.new(toplevel, args); end
|
169
212
|
def hbox(*args); HBox.new(toplevel, args); end
|
170
213
|
def separator; TkLabel.new(toplevel, :text => ' '); end
|
@@ -204,7 +247,8 @@ module Drx
|
|
204
247
|
), :weight => 50
|
205
248
|
panes.add vbox(
|
206
249
|
TkLabel.new(toplevel, :text => 'Methods (m_tbl):', :anchor => 'w'),
|
207
|
-
[Scrolled.new(toplevel, @methodsbox), { :expand => true, :fill => 'both' } ]
|
250
|
+
[Scrolled.new(toplevel, @methodsbox), { :expand => true, :fill => 'both' } ],
|
251
|
+
hbox(@show_arguments_chk, separator, @use_arguments_gem_chk)
|
208
252
|
), :weight => 10
|
209
253
|
|
210
254
|
main_frame.add(panes)
|
@@ -221,11 +265,10 @@ module Drx
|
|
221
265
|
def open_up_editor(filename, lineno)
|
222
266
|
command = sprintf(ENV['DRX_EDITOR_COMMAND'] || EDITOR_COMMAND, lineno, filename)
|
223
267
|
output "Executing: #{command}...\n", 'info'
|
224
|
-
|
268
|
+
Thread.new do
|
225
269
|
if !Kernel.system(command)
|
226
270
|
output "Could not execute the command '#{command}'\n", 'error'
|
227
271
|
end
|
228
|
-
exit!
|
229
272
|
end
|
230
273
|
end
|
231
274
|
|
@@ -250,7 +293,7 @@ module Drx
|
|
250
293
|
end
|
251
294
|
|
252
295
|
def selected_var
|
253
|
-
ObjInfo.new(current_object).
|
296
|
+
ObjInfo.new(current_object).get_ivar(@varsbox.get_selection)
|
254
297
|
end
|
255
298
|
|
256
299
|
def eval_code(code)
|
@@ -289,14 +332,20 @@ module Drx
|
|
289
332
|
# Get rid of gazillions of Tk classes:
|
290
333
|
vars = vars.reject { |v| v =~ /Tk|Ttk/ }
|
291
334
|
vars.each do |name|
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
335
|
+
begin
|
336
|
+
value = if allowed_names.any? { |p| p === name }
|
337
|
+
info.get_ivar(name).inspect
|
338
|
+
else
|
339
|
+
# We don't want to inspect ruby's internal variables (because
|
340
|
+
# they may not be Ruby values at all).
|
341
|
+
''
|
342
|
+
end
|
343
|
+
@varsbox.insert('', 'end', :text => name, :values => [ name, value ] )
|
344
|
+
rescue NameError
|
345
|
+
# Referencing an autoloaded constant (in ObjInfo#get_ivar()) may
|
346
|
+
# raise a NameError. This happens when the source file autoloaded
|
347
|
+
# defines the moudle/class in the top-level. Example is Camping::Mab.
|
348
|
+
end
|
300
349
|
end
|
301
350
|
end
|
302
351
|
end
|
@@ -321,6 +370,27 @@ module Drx
|
|
321
370
|
end
|
322
371
|
end
|
323
372
|
|
373
|
+
# Returns a string describing a method's arguments, for use in GUIs.
|
374
|
+
def pretty_arguments(info, name)
|
375
|
+
args = info.method_arguments(name)
|
376
|
+
return args.map do |arg|
|
377
|
+
case arg[0]
|
378
|
+
when :req; (arg[1] || 'arg').to_s
|
379
|
+
when :opt; (arg[1] || 'arg').to_s + '=' + (arg[2] || '?')
|
380
|
+
when :rest; '*' + (arg[1] || 'args').to_s
|
381
|
+
when :block; '&' + (arg[1] || 'arg').to_s
|
382
|
+
end
|
383
|
+
end.join ', '
|
384
|
+
rescue NameError
|
385
|
+
return '---'
|
386
|
+
rescue SyntaxError => e
|
387
|
+
'SYNTAX ERROR: ' + e.to_s
|
388
|
+
end
|
389
|
+
|
390
|
+
def show_arguments?
|
391
|
+
@show_arguments_chk.variable == 1
|
392
|
+
end
|
393
|
+
|
324
394
|
# Fills the methods listbox with a list of the object's methods.
|
325
395
|
def display_methods(obj)
|
326
396
|
@methodsbox.clear
|
@@ -328,7 +398,11 @@ module Drx
|
|
328
398
|
if obj and info.class_like?
|
329
399
|
methods = info.m_tbl.keys.map do |v| v.to_s end.sort
|
330
400
|
methods.each do |name|
|
331
|
-
@methodsbox.insert('', 'end', :text => name, :values => [
|
401
|
+
@methodsbox.insert('', 'end', :text => name, :values => [
|
402
|
+
name,
|
403
|
+
show_arguments? ? pretty_arguments(info, name) : '-',
|
404
|
+
pretty_location(info, name)
|
405
|
+
])
|
332
406
|
end
|
333
407
|
end
|
334
408
|
end
|
@@ -357,11 +431,19 @@ module Drx
|
|
357
431
|
end
|
358
432
|
end
|
359
433
|
|
434
|
+
# Updates the window title (usually shown in the taskbar).
|
435
|
+
def update_title(obj)
|
436
|
+
toplevel.title = 'Drx: ' + begin
|
437
|
+
obj.is_a?(Module) ? obj.name : obj.class.name
|
438
|
+
end.to_s # In case of singletons, #name returns nil, so to_s enforces a string.
|
439
|
+
end
|
440
|
+
|
360
441
|
# Makes `obj` the primary object seen (the one which is the tip of the diagram).
|
361
442
|
def navigate_to(obj)
|
362
443
|
@current_object = obj
|
363
444
|
@navigation_history << obj
|
364
445
|
display_graph(obj)
|
446
|
+
update_title(obj)
|
365
447
|
# Trigger the update of the variables and methods tables by selecting this object
|
366
448
|
# in the imagemap.
|
367
449
|
@im.active_url = @im.urls.first
|
@@ -391,9 +473,17 @@ module Drx
|
|
391
473
|
navigate_to(current_object)
|
392
474
|
end
|
393
475
|
|
476
|
+
class << self
|
477
|
+
attr_accessor :in_loop
|
478
|
+
def first_window?; !in_loop; end
|
479
|
+
end
|
480
|
+
|
394
481
|
def run
|
395
|
-
|
482
|
+
return if Application.in_loop
|
483
|
+
# @todo Any other way to detect that Tk's mainloop is already running?
|
484
|
+
Application.in_loop = true
|
396
485
|
Tk.mainloop
|
486
|
+
Application.in_loop = false
|
397
487
|
Tk.restart # So that Tk doesn't complain 'can't invoke "frame" command: application has been destroyed' next time.
|
398
488
|
end
|
399
489
|
end
|
data/lib/drx.rb
CHANGED
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.
|
4
|
+
version: 0.4.3
|
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-04-
|
12
|
+
date: 2010-04-16 00:00:00 +03:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -27,6 +27,7 @@ files:
|
|
27
27
|
- lib/drx/tk/imagemap.rb
|
28
28
|
- lib/drx/tk/app.rb
|
29
29
|
- lib/drx/objinfo.rb
|
30
|
+
- lib/drx/arguments.rb
|
30
31
|
- lib/drx/tempfiles.rb
|
31
32
|
- lib/drx.rb
|
32
33
|
- ext/extconf.rb
|
@@ -34,7 +35,8 @@ files:
|
|
34
35
|
- examples/drx_datamapper.rb
|
35
36
|
- examples/drx_date.rb
|
36
37
|
- examples/drx_guitar.rb
|
37
|
-
- examples/
|
38
|
+
- examples/drx_activerecord.rb
|
39
|
+
- examples/drx_sequel.rb
|
38
40
|
has_rdoc: true
|
39
41
|
homepage: http://drx.rubyforge.org/
|
40
42
|
licenses: []
|
@@ -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
|