Created
February 8, 2009 21:21
-
-
Save fairchild/60516 to your computer and use it in GitHub Desktop.
graphviz of ruby class heriarchy, modified from http://objectgraph.rubyforge.org/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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