require 'object_browser_ui'
module ObjectBrowser
class Describer
attr_accessor :show_methods, :show_state, :show_empty_sections
def initialize(describers = ObjectBrowser.constants.grep(/Describer/).map{|cn| ObjectBrowser.const_get(cn)}.
select{|c| im = c.instance_methods;
(im.include?'applicable?') and (im.include?'importance') and (im.include?'describe')})
@describers = []
describers.each{ |d| register(d.new) }
self.show_methods = true
self.show_state = true
self.show_empty_sections = true
end
def register(describer)
@describers << describer
@describers = @describers.sort_by{|d| -d.importance}
end
def describe(object, description_factory)
@describers.each do | d |
return d.describe(self, object, description_factory) if d.applicable?(object)
end
raise "No applicable descriptor found"
end
def display_string(object, length = nil)
@describers.each do | d |
return d.display_string(self, object, length) if d.applicable?(object)
end
raise "No applicable descriptor found"
end
def short_description(object)
@describers.each do | d |
return d.short_description(self, object) if d.applicable?(object)
end
raise "No applicable descriptor found"
end
end
class ObjectDescriber
def importance() -100 end
def applicable?(object)
object.is_a?Object
end
def describe(options, object, description)
add_constants(options, object, description)
add_instance_variables(options, object, description)
add_class_variables(options, object, description)
add_methods(options, object, description)
add_modules(options, object, description)
end
def display_string(options, object, length = nil)
result = (object.respond_to?:to_s) ? object.to_s : object.inspect
return result[0..length-4] + '...' if length and result.length > length
return result
end
def short_description(options, object)
return "#{object.class.to_s} #{object.to_s}" if object.respond_to?(:to_s)
return object.class.to_s
end
protected
def add_methods(options, object, description)
return unless options.show_methods
[['Public Methods', :public_methods],
['Protected Methods', :protected_methods],
['Private Methods', :private_methods],
['Public Instance Methods', :public_instance_methods],
['Protected Instance Methods', :protected_instance_methods],
['Private Instance Methods', :private_instance_methods]].each do | title, list_method |
next unless object.respond_to?list_method
if options.show_empty_sections || !object.send(list_method, true).empty?
section = description.add_section(:h1, title)
methods = object.send(list_method, false).sort
if options.show_empty_sections || !methods.empty?
method_type_section = section.add_section(:h2, 'Own Methods')
methods.each do | method |
arity = (object.method(method).arity rescue nil) || (object.instance_eval("self.method('#{method}').arity") rescue nil) || -99
$stderr.puts "Could not evaluate self.method('#{method}').arity for #{object.inspect}." unless arity
method_type_section.add(:method, "#{method}(#{args_from_arity(arity)})")
end
end
methods = (object.send(list_method, true) - object.send(list_method, false)).sort
if options.show_empty_sections || !methods.empty?
method_type_section = section.add_section(:h2, 'Inherited Methods')
methods.each do | method |
arity = (object.method(method).arity rescue nil) || (object.instance_eval("self.method('#{method}').arity") rescue nil) || -99
$stderr.puts "Could not evaluate self.method('#{method}').arity for #{object.inspect}." unless arity
method_type_section.add(:method, "#{method}(#{args_from_arity(arity)})")
end
end
end
end
end
def add_instance_variables(options, object, description)
return unless options.show_state
return unless object.respond_to?:instance_variables
if options.show_empty_sections || !object.instance_variables.empty?
section = description.add_section(:h1, 'Instance Variables')
object.instance_variables.each do | varname |
value = object.instance_variable_get(varname)
section.add(:variable, "#{varname} = #{options.display_string(value)}", value, options.short_description(value))
end
end
end
def add_class_variables(options, object, description)
return unless options.show_state
return unless object.respond_to?:class_variables
if options.show_empty_sections || !object.class_variables.empty?
description.add_section(:h1, 'Class Variables')
object.class_variables.each do | varname |
value = object.class_eval(varname)
section.add(:variable, "#{varname} = #{options.display_string(value)}", value, options.short_description(value))
model.append(node)
end
end
end
def add_modules(options, object, description)
return unless options.show_methods
return unless object.respond_to?:included_modules
if options.show_empty_sections || !object.included_modules.empty?
section = description.add_section(:h1, 'Included Modules')
object.included_modules.each do | mod | section.add(:module, mod, mod) end
end
end
def add_constants(options, object, description)
return unless options.show_state
return unless object.respond_to?:constants
if options.show_empty_sections || !object.constants.empty?
section = description.add_section(:h1, 'Constants')
object.constants.each do | varname |
value = object.const_get(varname)
section.add(:constant, "#{varname} = #{options.display_string(value)}", value, options.short_description(value))
end
end
end
protected
def args_from_arity(arity)
arg = 'a'
args = []
if arity < 0
(-arity - 1).times do args << arg; arg = arg.succ end
args << '*'+arg
else
arity.times do args << arg; arg = arg.succ end
end
args.join(', ')
end
protected
def shorten(string, length)
return string if string.length <= length
return string[0..length-3] + '...'
end
protected
def to_simple_str(object)
object.respond_to?(:to_s) ? object.to_s : "#<#{object.id.to_s(16)}>"
end
end
class NumericDescriber < ObjectDescriber
def importance() -90 end
def applicable?(object)
object.is_a?Numeric
end
def display_string(options, object, length = nil)
object.to_s
end
end
class HashDescriber < ObjectDescriber
def importance() -80 end
def applicable?(object)
object.is_a?Hash
end
def display_string(options, object, length = nil)
if length
l = length -1
result = []
if object.each do | k, v |
s = "#{options.display_string(k, l)} => #{options.display_string(v, l)}"
l -= (s.length + 2)
break false if l < 0
result << s
end
'{' + result.join(', ') + '}'
else
'{' + result.join(', ') + '...}'
end
else
'{' + object.map{|k, v| "#{options.display_string(k)} => #{options.display_string(v)}"}.join(', ') + '}'
end
end
def short_description(options, object)
"#{object.class} #{object.inspect}"
end
def describe(options, object, description)
add_hash_values(options, object, description)
add_constants(options, object, description)
add_instance_variables(options, object, description)
add_class_variables(options, object, description)
add_methods(options, object, description)
add_modules(options, object, description)
end
protected
def add_hash_values(option, object, description)
return unless option.show_state
return unless object.respond_to?:each
section = description.add_section(:h1, 'Content')
object.each do | key, value |
cl = section.add(:variable, "#{option.display_string(key)} => #{option.display_string(value)}", nil,
"#{options.short_description(key)} => #{options.short_description(value)}")
cl.add(:variable, "Key: #{option.display_string(key)}", key, options.short_description(key))
cl.add(:variable, "Value: #{option.display_string(value)}", value, options.short_description(value))
end
end
end
class ArrayDescriber < ObjectDescriber
def importance() -80 end
def applicable?(object)
object.is_a?Array
end
def display_string(options, object, length = nil)
if length
l = length -1
result = []
if object.each do | v |
s = options.display_string(v, l)
l -= (s.length + 2)
break false if l < 0
result << s
end
'[' + result.join(', ') + ']'
else
'[' + result.join(', ') + '...]'
end
else
'[' + object.map{|k, v| options.display_string(v)}.join(', ') + ']'
end
end
def short_description(options, object)
"#{object.class} #{object.inspect}"
end
def describe(options, object, description)
add_array_values(options, object, description)
add_constants(options, object, description)
add_instance_variables(options, object, description)
add_class_variables(options, object, description)
add_methods(options, object, description)
add_modules(options, object, description)
end
protected
def add_array_values(option, object, description)
return unless option.show_state
return unless object.respond_to?:each
section = description.add_section(:h1, 'Content')
object.each_with_index do | value, key |
section.add(:variable, "#{key}: #{option.display_string(value)}", value, "#{option.short_description(value)}")
end
end
end
class SymbolDescriber < ObjectDescriber
def importance() -90 end
def applicable?(object)
object.is_a?Symbol
end
def short_description(options, object)
"Symbol #{object}"
end
def display_string(options, object, length = nil)
":#{object}"
end
end
class ClassDescriber < ObjectDescriber
def importance() -95 end
def applicable?(object)
object.is_a?Class
end
def short_description(options, object)
"Class #{object.ancestors.join(' < ')}"
end
def display_string(options, object, length = nil)
object.to_s
end
end
class ModuleDescriber < ObjectDescriber
def importance() -95 end
def applicable?(object)
object.is_a?Module
end
def short_description(options, object)
"Module #{object}"
end
def display_string(options, object, length = nil)
object.to_s
end
end
class ModuleDescriber < ObjectDescriber
def importance() -95 end
def applicable?(object)
object.is_a?(Proc) || object.is_a?(Method) || object.is_a?(UnboundMethod)
end
def short_description(options, object)
"#{object.class} #{object.to_s}(#{args_from_arity(object.arity)})"
end
def display_string(options, object, length = nil)
object.to_s
end
end
class ClassTreeNode
attr_accessor :klass, :subclasses, :objects
def initialize(klass)
@klass = klass
@subclasses = {}
@objects = []
end
def add_class(klass)
@subclasses[klass] ||= ClassTreeNode.new(klass)
end
def add_object(object)
@objects << object
self
end
end
def create_class_tree(classtree = ClassTreeNode.new(Kernel),
ignore = [ClassTreeNode, ObjectDescriber, ::ObjectBrowser,
::ObjectBrowser::UI, ::ObjectBrowser::UI::DescriptionFactory,
::ObjectBrowser::Describer])
ObjectSpace.each_object do | x |
next if ignore.inject(false) { | result, klass | result || x.is_a?(klass) }
x.class.ancestors.reverse[1..-1].inject(classtree) do | classnode, klass | classnode.add_class(klass) end.add_object(x)
end
classtree
end
extend self
end