Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jteneycke/d5a5faf40542173fe99df9f039990b08 to your computer and use it in GitHub Desktop.
Save jteneycke/d5a5faf40542173fe99df9f039990b08 to your computer and use it in GitHub Desktop.
graphviz of ruby class heriarchy, modified from http://objectgraph.rubyforge.org/
# jEdit :folding=explicit:collapseFolds=1:indentSize=2:tabSize=2:
# add requires here #{{{
#}}}
# handle command line #{{{
def usage #{{{
puts <<-USAGE
Ruby Class Inheritance Graph, version 1.0.1.
Generates a png and an HTML map or the ruby
ruby [-r libs] graph.rb <layout> <options>
Use the -r lib to require extra libraries into the namespace (or edit the
script file).
'layout' is at least one of the following GraphViz layout engines:
-neato neato engine ('spring' model layout. recommended :)
-dot dot engine (hierarchical tree)
-circo circo engine (circular layout. generates large pics)
-twopi twopi engine (oval layout)
-all generate graphs with all layouts
Refer to http://www.research.att.com/sw/tools/graphviz/ for more details.
'options' are:
-skip-errno Does not show the Errno classes
-font <font> Uses <font> in the nodes texts.
-output <fmt> Output format as suppoted by GraphViz (defaults to PNG).
-base-class <ClassName> Display only classes that inherit from <ClassName>.
Cannot be use for inheritance trees based in class Module.
-name-space <namespace> Display only classes in <namespace>
-regex <regular expression> Display only objects whose name matches <regex>
-out-name <name> Ouput file name. Only useful is only one format is specified.
USAGE
end #}}}
# manual handling to avoid requiring extra libs.
$genDot = ARGV.include? "-dot"
$genNeato = ARGV.include? "-neato"
$genTwopi = ARGV.include? "-twopi"
$genCirco = ARGV.include? "-circo"
$genDot = $genNeato = $genTwopi = $genCirco = true if ARGV.include? "-all"
if not ($genDot or $genNeato or $genTwopi or $genCirco) or not (ARGV & ["-help", "-h", "-?"]).empty?
usage
exit(-1)
end
$skipErrno = ARGV.include? "-skip-errno"
$font = ""
$font = ARGV[ARGV.index("-font")+1] if ARGV.include? "-font"
$outfmt = "png"
$outfmt = ARGV[ARGV.index("-output")+1] if ARGV.include? "-output"
$outname = ""
$outname = ARGV[ARGV.index("-out-name")+1] if ARGV.include? "-out-name"
$regex = 'PoolParty' #Default to Poolparty regex
$regex = ARGV[ARGV.index("-regex")+1] if ARGV.include? "-regex"
$useBaseClass = ARGV.include? "-base-class"
if $useBaseClass
baseClassName = ARGV[ARGV.index("-base-class")+1]
$baseClass = ObjectSpace.each_object(Class) { |k| break k if k.name == baseClassName }
end
$useNamespace = ARGV.include? "-name-space"
if $useNamespace
$nameSpace = ARGV[ARGV.index("-name-space")+1]
end
#}}}
# node and link formating utility #{{{
# Core classes docos: {{{
# This is ugly, but i can't think of another way short of checking the site.
$core_classes = %w{
ArgumentError Array Benchmark Benchmark::Job Benchmark::Report
Benchmark::Tms Bignum Binding CGI CGI::Cookie CGI::HtmlExtension CGI::QueryExtension
CGI::QueryExtension::Value Class Comparable Complex ConditionVariable Continuation
Data Date DateTime Dir EOFError Enumerable Errno Exception ExceptionForMatrix FalseClass
File File::Constants File::Stat FileTest FileUtils FileUtils::NoWrite FileUtils::Verbose
Find Fixnum Float FloatDomainError GC Generator Hash IO IOError IndexError Integer
Interrupt Kernel LoadError LocalJumpError Logger Logger::Application Logger::Error
Logger::LogDevice Logger::Severity Logger::ShiftingError Marshal MatchData Math
Matrix Method Module Mutex NameError NilClass NoMemoryError NoMethodError NotImplementedError
Numeric Object ObjectSpace Observable Pathname Precision Proc Process Process::GID Process::Status
Process::Sys Process::UID Queue Range RangeError Regexp RegexpError RuntimeError
ScriptError SecurityError Set Shellwords Signal SignalException Singleton Singleton
SingletonClassMethods SizedQueue SortedSet StandardError String Struct Symbol SyncEnumerator
SyntaxError SystemCallError SystemExit SystemStackError Tempfile Test Test::Unit
Thread ThreadError ThreadGroup ThreadsWait Time TrueClass TypeError UnboundMethod
Vector YAML ZeroDivisionError} #}}}
def get_class_url(klass)
if $core_classes.include? klass.name
"http://www.ruby-doc.org/docs/rdoc/1.9/classes/#{klass.name.sub(/::/,'/')}.html"
else
# mod = klass.name[0...(klass.name.index('::') || klass.name.length)].downcase
# "http://www.ruby-doc.org/stdlib/libdoc/#{mod}/rdoc/classes/#{klass.name.sub(/::/,'/')}.html"
# until we can better guess the module:
"http://www.ruby-doc.org/stdlib/"
end
end
def print_info(fmt, klass, inverse=false, options={})
# mask StringIO if it's undefined {{{
# TODO: make this more efficient
s = nil
if defined? StringIO
s = StringIO.new
else
s = String.new
class << s
def puts(str)
self.replace(self + str + "\n")
end
def string
self.to_s
end
end
end
#}}}
begin
oname = klass.name.gsub(/:/, '_')
sname = klass.superclass.name.gsub(/:/, '_')
rescue
return
end
url = get_class_url(klass)
node_options= {'URL' => url, 'label'=>klass.name, 'color'=>'black'}.merge(options)
node_format_string = node_options.collect {|k,v| "#{k.to_s}=\"#{v}\""}.join(',')
s.puts "#{oname} [#{node_format_string}];"
names = [oname, sname]
names.reverse! if inverse
s.puts fmt % names
s.string
end #}}}
# Main loop over the object space {{{
def objectspace_loop(fmt, io, inverse)
ObjectSpace.each_object(Class) do |klass|
next if $skipErrno and klass.name =~ /^Errno/
next if $regex and klass.to_s.match(/#{$regex}/).nil?
begin
if $useBaseClass
# do not :allocate objects of class Class
next if [Object, $baseClass, Class, Module].include? klass
next unless klass.allocate.kind_of? $baseClass
end
if $useNamespace
next unless klass.name =~ /^#{$nameSpace}/
end
rescue => detail
next if detail.message =~ /allocator undefined/
end
# klass.kind_of?(Module) ? color = 'red' : color = 'blue'
puts "#{klass}"
# puts( print_info(fmt, klass, inverse, {'color'=>'blue'}) )
require "rubygems"; require "ruby-debug"; debugger
mods=klass.included_modules.collect{|m| m.to_s }.sort
included_modules = mods.select{|m| m.match(/#{$regex}/)}
color = 'black'
io.puts( print_info(fmt, klass, inverse, {'color'=>color}) )
end
end #}}}
# Graph generation routine #{{{
def genGraph(prog, graphParams = "", inverse=false)
require 'poolparty'
baseName = ($outname == "" ? prog : $outname + "_" + prog)
graphFile = baseName + '.ObjectGraph'
picFile = baseName + 'OG.' + $outfmt
htmlFile = baseName + 'OG.html'
htmlMapFile = baseName + 'OG.map'
# create graph file:
File.open(graphFile, "w") do |file|
# Graph properties:
file.puts "digraph G {"
file.puts 'concentrate = true;'
file.puts "node [fontsize=8,fontname=\"#{$font}\",height=0.2];"
# Page Special nodes properties:
file.puts graphParams
if $useBaseClass
url = get_class_url($baseClass)
file.puts "#{$baseClass.name.gsub(/:/, '_')} [color=green,style=bold,label=\"#{$baseClass.name}\",URL=\"#{url}\"];"
else
url = get_class_url(Object)
file.puts "Object [color=green,style=bold,label=\"Object\",URL=\"#{url}\"];"
end
# Links and node properties:
objectspace_loop("%s -> %s;", file, inverse)
file.puts "}"
end
# call GraphViz:
system("#{prog} -Tcmap #{graphFile} -o #{htmlMapFile}") # genereate html map
system("#{prog} -T#$outfmt #{graphFile} -o #{picFile}") # generate png (or other output)
# generate HTML file:
File.open(htmlFile, "w") do |html|
html.puts '<html><head></head><body>'
html.puts "<img src='#{picFile}') usemap='#graph.map'>"
html.puts '<map name="graph.map">'
html.puts File.read(htmlMapFile)
html.puts '</map></body></html>'
end
end #}}}
# Create graphs and HTML files #{{{
# sizes are optimised for a full screen view at 1280 x 1024.
genGraph('dot', "edge [dir=back];", true) if $genDot
genGraph('neato', 'edge [len=2.0];') if $genNeato
genGraph('twopi', 'ranksep=2.0;') if $genTwopi
genGraph('circo', 'size="12.5,12.5";') if $genCirco
#}}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment