# jEdit :folding=explicit:collapseFolds=1:indentSize=2:tabSize=2: objectgraph = true # add requires here #{{{ require "startup.rb" #}}} # 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> -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" $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) # 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) s.puts "#{oname} [label=\"#{klass.name}\",URL=\"#{url}\"];" 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/ 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 io.puts( print_info(fmt, klass, inverse) ) end end #}}} # Graph generation routine #{{{ def genGraph(prog, graphParams = "", inverse=false) 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 #}}}