Class: Drx::ObjInfo
- Defined in:
- lib/drx/objinfo.rb,
lib/drx/graphviz.rb,
lib/drx/arguments.rb
Overview
An object orieneted wrapper around the DrX::Core functions.
This object lets you query various properties of an object.
info = ObjInfo.new("foo")
info.has_iv_tbl?
info.klass
Constant Summary collapse
- GRAPHVIZ_COMMAND =
Notes:
-
Windows’ CMD.EXE too supports “2>&1”
-
We’re generating GIF, not PNG, because that’s a format Tk supports out of the box.
-
Each extension token is replaced by the filepath with that extension.
-
'dot "{dot}" -Tgif -o "{gif}" -Tcmapx -o "{map}" 2>&1'
- @@sizes =
{ '100%' => " node[fontsize=10] ", '90%' => " node[fontsize=10] ranksep=0.4 edge[arrowsize=0.8] ", '80%' => " node[fontsize=10] ranksep=0.3 nodesep=0.2 node[height=0.4] edge[arrowsize=0.6] ", '60%' => " node[fontsize=8] ranksep=0.18 nodesep=0.2 node[height=0] edge[arrowsize=0.5] " }
Class Attribute Summary collapse
-
.use_arguments_gem ⇒ Object
Whether to use the ‘arguments’ gem, if present.
Instance Method Summary collapse
- #==(other) ⇒ Object
-
#_method_arguments__by_arguments_gem(method_name) ⇒ Object
Strategy: use the ‘arguments’ gem, which, in turn, uses either ParseTree (Ruby 1.8) or RubyParser (Ruby 1.9).
-
#_method_arguments__by_arity(method_name) ⇒ Object
Strategy: simulation via Method#arity.
-
#_method_arguments__by_methopara(method_name) ⇒ Object
Strategy: use Method#parameters (for ruby 1.9 only).
- #address ⇒ Object
-
#class_like? ⇒ Boolean
Returns true if this object is either a class or a module.
-
#display_klass?(kls) ⇒ Boolean
Whether to display the klass.
-
#display_super?(spr, my_ancestors) ⇒ Boolean
Whether to display the super.
-
#dot_fragment(opts = {}, ancestors = []) {|_self| ... } ⇒ Object
:yield:.
-
#dot_id ⇒ Object
Create an ID for the DOT node representing this object.
-
#dot_label(max = 20) ⇒ Object
Returns the DOT label for the node.
-
#dot_quote(s) ⇒ Object
Quotes a string to be used in DOT source.
-
#dot_source(opts = {}, &block) ⇒ Object
Builds the DOT source for the diagram.
-
#dot_style__crazy ⇒ Object
Returns the DOT style for the node.
-
#dot_style__default ⇒ Object
Returns the DOT style for the node.
-
#dot_url ⇒ Object
Creates a pseudo URL for the HTML imagemap.
-
#effective_klass ⇒ Object
Like klass(), but without surprises.
-
#examine(level = 0, title = '', &block) ⇒ Object
A utility function to print the inheritance hierarchy of an object.
-
#generate_diagram(files, opts = {}, &block) ⇒ Object
Generates a diagram of the inheritance hierarchy.
-
#get_ivar(name) ⇒ Object
Returns the value of an instance variable.
- #has_iv_tbl? ⇒ Boolean
-
#initialize(obj) ⇒ ObjInfo
constructor
A new instance of ObjInfo.
-
#insignificant_super_arrow?(opts, my_ancestors) ⇒ Boolean
Whether the ‘super’ arrow is infignificant and must not affect the DOT layout.
-
#iv_tbl ⇒ Object
Returns the variable-table of an object.
-
#klass ⇒ Object
Note: the klass of an iclass is the included module.
-
#locate_method(method_name) ⇒ Object
Returns the source-code position where a method is defined.
-
#m_tbl ⇒ Object
Returns the method-table of an object.
-
#method_arguments(method_name) ⇒ Object
Returns a Ruby 1.9.2-compatible array describing the arguments a method expects.
-
#repr ⇒ Object
Returns a string representation of the object.
- #singleton? ⇒ Boolean
-
#super ⇒ Object
Returns the ‘super’ of a class-like object.
- #t_class? ⇒ Boolean
- #t_iclass? ⇒ Boolean
- #t_module? ⇒ Boolean
- #t_object? ⇒ Boolean
- #the_object ⇒ Object
Constructor Details
Class Attribute Details
.use_arguments_gem ⇒ Object
Whether to use the ‘arguments’ gem, if present. This isn’t on by default because it’s slow.
10 11 12 |
# File 'lib/drx/arguments.rb', line 10 def use_arguments_gem @use_arguments_gem end |
Instance Method Details
#==(other) ⇒ Object
22 23 24 |
# File 'lib/drx/objinfo.rb', line 22 def ==(other) other.is_a?(ObjInfo) and address == other.address end |
#_method_arguments__by_arguments_gem(method_name) ⇒ Object
Strategy: use the ‘arguments’ gem, which, in turn, uses either ParseTree (Ruby 1.8) or RubyParser (Ruby 1.9)
pros: shows default values; works for 1.8 too. cons: very slow.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/drx/arguments.rb', line 27 def _method_arguments__by_arguments_gem(method_name) @@once__arguments ||= begin begin require 'arguments' rescue LoadError # Not installed. end 1 end return nil if not defined? Arguments args = Arguments.names(the_object, method_name, false) # Convert this to a Ruby 1.9.2 format: return args.map do |arg| if arg.size == 2 [:opt, arg[0], arg[1]] else name = arg[0].to_s case name[0,1] when '*' then [:rest, name[1..-1]] when '&' then [:block, name[1..-1]] else [:req, name] end end end rescue SyntaxError => e # We could just return nil here, to continue to the next strategy, # but we want to inform the user of the suckiness of RubyParser. raise rescue Exception nil end |
#_method_arguments__by_arity(method_name) ⇒ Object
Strategy: simulation via Method#arity.
This is used as a fallback if any of the other strategies fail.
pros: fast; work without any gems. cons: doesn’t show argument names.
89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/drx/arguments.rb', line 89 def _method_arguments__by_arity(method_name) method = the_object.instance_method(method_name) ary, rest = method.arity, false if ary < 0 ary = -ary - 1 rest = true end args = [[:req]] * ary args << [:rest] if rest return args end |
#_method_arguments__by_methopara(method_name) ⇒ Object
Strategy: use Method#parameters (for ruby 1.9 only).
pros: fast. cons: doesn’t show default values.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/drx/arguments.rb', line 63 def _method_arguments__by_methopara(method_name) @@once__methopara ||= begin # For ruby 1.9.0 and 1.9.1, we need to use a gem. begin require 'methopara' rescue LoadError # Not installed. end 1 end method = the_object.instance_method(method_name) if method.respond_to? :parameters return method.parameters end rescue NotImplementedError # For some methods #parameters raises an exception. We return nil # to move on to the next strategy. return nil end |
#address ⇒ Object
18 19 20 |
# File 'lib/drx/objinfo.rb', line 18 def address Core::get_address(@obj) end |
#class_like? ⇒ Boolean
Returns true if this object is either a class or a module. When true, you know it has ‘m_tbl’ and ‘super’.
32 33 34 |
# File 'lib/drx/objinfo.rb', line 32 def class_like? [Core::T_CLASS, Core::T_ICLASS, Core::T_MODULE].include? @type end |
#display_klass?(kls) ⇒ Boolean
Whether to display the klass.
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/drx/graphviz.rb', line 208 def display_klass?(kls) if t_iclass? # We're interested in an ICLASS's klass only if it isn't Module. # # Usually this means that the ICLASS has a singleton (see "Singletons # of included modules" in display_super?()). We want to see this # singleton. # Unfortunately, here is a special treatment for the 'arguments' gem, # which our GUI uses. That gem includes the 'Arguments' module in both # Class and Module (this is redundant!) and having the singleton twice # in our graph may break its nice rectangular structure. So we don't # show its singleton. if defined? ::Arguments return false if ::Arguments == klass.the_object end return kls != _Module else # Displaying a singleton's klass is confusing and usually unneeded. return !singleton? end end |
#display_super?(spr, my_ancestors) ⇒ Boolean
Whether to display the super.
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/drx/graphviz.rb', line 233 def display_super?(spr, my_ancestors) if spr == _Module # Many objects have Module as their super (e.g., singletones # of included modules, or modules included in them). To prevent # clutter we print the arrow to Module only if it comes from # Class (or a module included in it). return (my_ancestors + [self]).include?(_Class) # # A somewhat irrelevant note (I don't have a better place to put it): # # "Singletons of included modules" often exist solely for their # #included method. For example, DataMapper#Resource has # such a singleton. end return true end |
#dot_fragment(opts = {}, ancestors = []) {|_self| ... } ⇒ Object
:yield:
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/drx/graphviz.rb', line 132 def dot_fragment(opts = {}, ancestors = [], &block) # :yield: out = '' # Note: since 'obj' may be a T_ICLASS, it doesn't respond to many methods, # including is_a?. So when we're querying things we're using Drx calls # instead. seen = @@seen[address] @@seen[address] = true if not seen dot_style = method('dot_style__' + (opts[:style] || 'default')).call out << "#{dot_id} [#{dot_style}, label=#{dot_quote dot_label}, URL=#{dot_quote dot_url}];" "\n" end yield self if block_given? return '' if seen if class_like? if spr = self.super and display_super?(spr, ancestors) out << spr.dot_fragment(opts, ancestors + [self], &block) if insignificant_super_arrow?(opts, ancestors) # We don't want these relatively insignificant lines to clutter the display, # so we paint them lightly and tell DOT they aren't to affect the layout (width=0). out << "#{dot_id} -> #{spr.dot_id} [color=gray85, weight=0];" "\n" else out << "#{dot_id} -> #{spr.dot_id};" "\n" end end end kls = effective_klass if display_klass?(kls) out << kls.dot_fragment(opts, &block) # Recall that in Ruby there are two main inheritance groups: the class # inheritance and the singleton inheritance. # # When an ICLASS has a singleton, we want this singleton to appear close # to the ICLASS, because we want to keep the two groups visually distinct. # We do this by setting the arrow's weight to 1.0. # # (To see the effect of this, set the weight unconditionally to '0' and # see the graph for DataMapper.) weight = t_iclass? ? 1 : 0 # However, here's a special case. DOT seems to have a bug: it sometimes # doesn't draw the arrows going out of Class and Module (to their # singletons). Making their weight 1 makes DOT draw them. weight = 1 if self == _Class or self == _Module out << "#{dot_id} -> #{kls.dot_id} [style=dotted, weight=#{weight}];" "\n" out << "{ rank=same; #{dot_id}; #{kls.dot_id}; }" "\n" end out end |
#dot_id ⇒ Object
Create an ID for the DOT node representing this object.
40 41 42 43 44 45 46 |
# File 'lib/drx/graphviz.rb', line 40 def dot_id ('o' + address.to_s).sub('-', '_') # Tip: when examining the DOT output you may wish to # uncomment the following line. It will show you which # ruby object the DOT node represents. #('o' + address.to_s).sub('-', '_') + " /* #{repr} */ " end |
#dot_label(max = 20) ⇒ Object
Returns the DOT label for the node.
The representation may be quite big, so we trim it.
103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/drx/graphviz.rb', line 103 def dot_label(max = 20) if class_like? # Let's be more lenient when trimming a class/module name. # We want to show The::Last::Component and possibly a singleton's # trailing 'S. max = 60 if max < 60 end r = repr if r.length > max r[0, max] + ' ...' else r end end |
#dot_quote(s) ⇒ Object
Quotes a string to be used in DOT source.
54 55 56 |
# File 'lib/drx/graphviz.rb', line 54 def dot_quote(s) '"' + s.gsub('\\') { '\\\\' }.gsub('"', '\\"').gsub("\n", '\\n') + '"' end |
#dot_source(opts = {}, &block) ⇒ Object
Builds the DOT source for the diagram. if you’re only interested in the output image, use generate_diagram() instead.
120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/drx/graphviz.rb', line 120 def dot_source(opts = {}, &block) # :yield: opts = opts.dup opts[:base] = self @@seen = {} out = 'digraph {' "\n" out << @@sizes[opts[:size] || '100%'] out << dot_fragment(opts, &block) out << '}' "\n" out end |
#dot_style__crazy ⇒ Object
Returns the DOT style for the node.
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/drx/graphviz.rb', line 81 def dot_style__crazy craze = "distortion=#{2*rand-1},skew=#{2*rand-1},orientation=#{360*rand}" crazy_oval = "shape=polygon,sides=25," + craze crazy_rect = "shape=polygon,sides=#{4+rand(3)}," + craze if singleton? # A singleton class "#{crazy_oval},color=palevioletred3,style=filled,fontcolor=white,peripheries=3" elsif t_class? # A class "#{crazy_oval},color=palevioletred1,style=filled" elsif t_iclass? or t_module? # A module "#{crazy_rect},color=peachpuff1,style=filled" else # Else: a "normal" object, or an immediate. "shape=house,color=pink,style=filled" end end |
#dot_style__default ⇒ Object
Returns the DOT style for the node.
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/drx/graphviz.rb', line 59 def dot_style__default if singleton? # A singleton class "shape=oval,color=skyblue1,style=filled" elsif t_class? # A class "shape=oval,color=lightblue1,style=filled" elsif t_iclass? or t_module? # A module if repr['#'] # Paint anonymous modules only lightly. "shape=box,style=filled,color=\"#D9FFF2\",fontcolor=gray60" else "shape=box,style=filled,color=aquamarine" end else # Else: a "normal" object, or an immediate. "shape=house,color=wheat1,style=filled" end end |
#dot_url ⇒ Object
Creates a pseudo URL for the HTML imagemap.
49 50 51 |
# File 'lib/drx/graphviz.rb', line 49 def dot_url "http://ruby/object/#{dot_id}" end |
#effective_klass ⇒ Object
Like klass(), but without surprises.
Since the klass of an ICLASS is the module itself, we need to invoke klass() twice.
254 255 256 257 258 259 260 |
# File 'lib/drx/graphviz.rb', line 254 def effective_klass if t_iclass? klass.klass else klass end end |
#examine(level = 0, title = '', &block) ⇒ Object
A utility function to print the inheritance hierarchy of an object.
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/drx/objinfo.rb', line 136 def examine(level = 0, title = '', &block) # :yield: # Note: since '@obj' may be a T_ICLASS, it doesn't repond to may methods, # including is_a?. So when we're querying things we're using Drx calls # instead. @@seen = {} if level.zero? line = (' ' * level) + title + ' ' + repr seen = @@seen[address] @@seen[address] = true if seen line += " [seen]" end if block_given? yield line, self else puts line end return if seen if class_like? if spr = self.super spr.examine(level + 1, '[super]', &block) end end # Displaying a T_ICLASS's klass isn't very useful, because the data # is already mirrored in the m_tbl and iv_tvl of the T_ICLASS itself. if not t_iclass? klass.examine(level + 1, '[klass]', &block) end end |
#generate_diagram(files, opts = {}, &block) ⇒ Object
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
# File 'lib/drx/graphviz.rb', line 283 def generate_diagram(files, opts = {}, &block) source = self.dot_source(opts, &block) File.open(files['dot'], 'w') { |f| f.write(source) } # Replace {extension} tokens with their corresponding filepaths: command = GRAPHVIZ_COMMAND.gsub(/\{([a-z]+)\}/) { files[$1] } = Kernel.`(command) # ` if $? != 0 error = <<-EOS % [command, ] ERROR: Failed to run the 'dot' command. Make sure you have the GraphViz package installed and that its bin folder appears in your PATH. The command I tried to execute is this: %s And the response I got is this: %s EOS raise error end end |
#get_ivar(name) ⇒ Object
Returns the value of an instance variable. Actually, of any sort of variable that’s recorded in the variable-table.
79 80 81 82 83 84 85 86 |
# File 'lib/drx/objinfo.rb', line 79 def get_ivar(name) if class_like? and name.to_s =~ /^[A-Z]/ # If it's a constant, it may be 'autoloaded'. We # trigger the loading by calling const_get(). @obj.const_get(name) end Core::get_ivar(@obj, name) end |
#has_iv_tbl? ⇒ Boolean
67 68 69 |
# File 'lib/drx/objinfo.rb', line 67 def has_iv_tbl? t_object? || class_like? end |
#insignificant_super_arrow?(opts, my_ancestors) ⇒ Boolean
Whether the ‘super’ arrow is infignificant and must not affect the DOT layout
A Ruby object graph is cyclic. We don’t want to feed DOT a cyclic graph because it will ruin our nice “rectangular” layout. The purpose of the following method is to break the cycle. Normally we break the cycle at Module (and its singleton). When the user is examining a module, we instead break the cycle at Class (and its singleton).
197 198 199 200 201 202 203 204 205 |
# File 'lib/drx/graphviz.rb', line 197 def insignificant_super_arrow?(opts, my_ancestors) if opts[:base].t_module? self == _ClassS or (self.super == _Module and (my_ancestors + [self]).include? _Class) else self == _ModuleS or (self.super == _Object and (my_ancestors + [self]).include? _Module) end end |
#iv_tbl ⇒ Object
Returns the variable-table of an object.
72 73 74 75 |
# File 'lib/drx/objinfo.rb', line 72 def iv_tbl return nil if not has_iv_tbl? Core::get_iv_tbl(@obj) end |
#klass ⇒ Object
Note: the klass of an iclass is the included module.
109 110 111 |
# File 'lib/drx/objinfo.rb', line 109 def klass ObjInfo.new Core::get_klass(@obj) end |
#locate_method(method_name) ⇒ Object
Returns the source-code position where a method is defined.
Returns one of:
- [ 'file.rb', 12 ]
- "<c>", "<undef>", "<alias>", etc., if not a ruby code (for Ruby 1.9,
returns only "<c>").
- nil, if functionality not implemented.
- raises NameError if method not found.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/drx/objinfo.rb', line 49 def locate_method(method_name) if Core.respond_to? :locate_method # Ruby 1.8 Core::locate_method(@obj, method_name) elsif Method.method_defined? :source_location # Ruby 1.9 location = @obj.instance_method(method_name).source_location if location location else '<c>' end else # Some version of ruby that doesn't have Method#source_loction nil end end |
#m_tbl ⇒ Object
Returns the method-table of an object.
37 38 39 |
# File 'lib/drx/objinfo.rb', line 37 def m_tbl Core::get_m_tbl(@obj) end |
#method_arguments(method_name) ⇒ Object
Returns a Ruby 1.9.2-compatible array describing the arguments a method expects.
14 15 16 17 18 19 20 |
# File 'lib/drx/arguments.rb', line 14 def method_arguments(method_name) if ObjInfo.use_arguments_gem _method_arguments__by_arguments_gem(method_name) || _method_arguments__by_arity(method_name) else _method_arguments__by_methopara(method_name) || _method_arguments__by_arity(method_name) end end |
#repr ⇒ Object
Returns a string representation of the object. Similar to Object#inspect.
124 125 126 127 128 129 130 131 132 133 |
# File 'lib/drx/objinfo.rb', line 124 def repr if t_iclass? 'include ' + klass.repr elsif singleton? attached = get_ivar('__attached__') || self attached.inspect + " 'S" else @obj.inspect end end |
#singleton? ⇒ Boolean
88 89 90 |
# File 'lib/drx/objinfo.rb', line 88 def singleton? class_like? && (Core::get_flags(@obj) & Core::FL_SINGLETON).nonzero? end |
#super ⇒ Object
Returns the ‘super’ of a class-like object. Returns nil for end of chain.
Examples: Kernel has a NULL super. Modules too have NULL super, unless when ‘include’ing.
117 118 119 120 121 |
# File 'lib/drx/objinfo.rb', line 117 def super spr = Core::get_super(@obj) # Note: we can't do 'if spr.nil?' because T_ICLASS doesn't "have" #nil. spr ? ObjInfo.new(spr) : nil end |
#t_class? ⇒ Boolean
96 97 98 |
# File 'lib/drx/objinfo.rb', line 96 def t_class? @type == Core::T_CLASS end |
#t_iclass? ⇒ Boolean
92 93 94 |
# File 'lib/drx/objinfo.rb', line 92 def t_iclass? @type == Core::T_ICLASS end |
#t_module? ⇒ Boolean
104 105 106 |
# File 'lib/drx/objinfo.rb', line 104 def t_module? @type == Core::T_MODULE end |
#t_object? ⇒ Boolean
100 101 102 |
# File 'lib/drx/objinfo.rb', line 100 def t_object? @type == Core::T_OBJECT end |
#the_object ⇒ Object
26 27 28 |
# File 'lib/drx/objinfo.rb', line 26 def the_object @obj end |