#!/usr/bin/env ruby # Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Gregoire Lejeune # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA require 'rubygems' require 'getoptlong' require 'graphviz' require 'graphviz/constants' require 'rbconfig' require 'mkmf' DEBUG = false class Rb2Gv REQUIRE = /^\s*require\s*("|')([^\1\s]*)(\1)/ def initialize( xGVPath, xUse = "dot", xStops = [] ) @oGraph = GraphViz::new( "G", :path => xGVPath, :use => xUse ) # @oGraph['size'] = '10,10' @hxNodes = Hash::new( ) @hxEdge = Array::new( ) @lxStops = xStops end public def parse( xFile ) @hxNodes[xFile] = gv_newNode( xFile, "box", "forestgreen" ) puts "+ Node #{xFile}" if DEBUG parseFile( xFile, nil, xFile ) end def out( xFormat = "dot", xFile = nil ) if xFile.nil? == true @oGraph.output( xFormat => String ) else @oGraph.output( xFormat => xFile ) end end private def gv_newNode( xNode, xShape = "box", xColor = nil ) xNodeName = xNode.gsub( /[^a-zA-Z0-9]/, "_" ) if xColor.nil? == true @oGraph.add_node( xNodeName, "label" => xNode, "shape" => xShape ) else @oGraph.add_node( xNodeName, "label" => xNode, "shape" => xShape, "style" => "filled", "color" => xColor ) end end def getLibraryPath( xLib, xExt = ["rb"] ) xPath = [ "libexecdir", "libdir", "sitedir", "rubylibdir", "sitelibdir", "archdir", "sitedir", "sitearchdir" ] xRbLib = with_config( xLib+'lib', xLib) if /\.(rb|so|bundle|dll)$/.match( xRbLib ) match = /^(.*)\.([^\.]*)$/.match(xRbLib) xRbFile, xExt = match[1], [match[2]] else xRbFile = xRbLib end xExt.each do |e| xRbFileWithExt = xRbFile + "." + e # Search in "standard" paths xPath.each do |xDir| xCurrentPath = Config::expand( Config::CONFIG[xDir] ) xFileFound = File.join( xCurrentPath, xRbFileWithExt ) if File.exist?( xFileFound ) return xFileFound end end # Search in "rubygems" :: This is not utile but... spec = Gem.searcher.find(xLib) unless spec.nil? spec.require_paths.unshift spec.bindir if spec.bindir xPath = spec.require_paths.map do |path| File.join spec.full_gem_path, path end xPath.each do |xCurrentPath| xFileFound = File.join( xCurrentPath, xRbFileWithExt ) if File.exist?( xFileFound ) return xFileFound end end end end return nil end def parseFile( xFile, xFromFile = nil, xLib = nil ) if xFromFile.nil? == false puts "Parse #{xFile} required in #{xFromFile} :" if DEBUG else puts "Parse #{xFile} :" if DEBUG end fp = open( xFile, 'r' ) xData = fp.read() fp.close xData.each do |xLine| if lxLineMatch = REQUIRE.match( xLine ) xRequiredLib = lxLineMatch[2].gsub( /\.(rb|so)$/, "" ) if @hxNodes.has_key?( xRequiredLib ) == false puts " + Node #{xRequiredLib}" if DEBUG xRequiredFile = getLibraryPath( xRequiredLib ) if xRequiredFile.nil? == false unless @lxStops.include?(xRequiredLib) @hxNodes[xRequiredLib] = gv_newNode( xRequiredLib ) parseFile( xRequiredFile, xFile, xRequiredLib ) else @hxNodes[xRequiredLib] = gv_newNode( xRequiredLib, "invhouse", "deepskyblue" ) end else if (v = getLibraryPath( xRequiredLib, ["so", "bundle", "dll"] )) == nil @hxNodes[xRequiredLib] = gv_newNode( xRequiredLib, "box", "red" ) else @hxNodes[xRequiredLib] = gv_newNode( xRequiredLib, "box", "grey" ) end end end puts " + Edge #{xLib} -> #{xRequiredLib}" if DEBUG unless @hxEdge.include?( "#{@hxNodes[xLib].id}-#{@hxNodes[xRequiredLib].id}" ) @oGraph.add_edge( @hxNodes[xLib], @hxNodes[xRequiredLib] ) @hxEdge << "#{@hxNodes[xLib].id}-#{@hxNodes[xRequiredLib].id}" end end end end end def usage puts "usage: ruby2gv [-Tformat] [-ofile] [-h] [-V] script" puts "-T, --output-format format Output format (default: PNG)" puts "-o, --output-file file Output file (default: STDOUT)" puts "-p, --path Graphviz path" puts "-u, --use PROGRAM Program to use (default: dot)" puts "-s, --stop LIB[,LIB, ...] Stop on libs" puts "-V, --version Show version" puts "-h, --help Show this usage message" end def version puts "Ruby2GraphViz v#{Constants::RGV_VERSION}, (c)2005, 2009, 2010 Gregoire Lejeune " puts "" puts "This program is free software; you can redistribute it and/or modify" puts "it under the terms of the GNU General Public License as published by" puts "the Free Software Foundation; either version 2 of the License, or" puts "(at your option) any later version." puts "" puts "This program is distributed in the hope that it will be useful," puts "but WITHOUT ANY WARRANTY; without even the implied warranty of" puts "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the" puts "GNU General Public License for more details." puts "" puts "You should have received a copy of the GNU General Public License" puts "along with this program; if not, write to the Free Software" puts "Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA" end xOutFormat = "png" xOutFile = nil xGVPath = "" xUse = "dot" xStops = [] oOpt = GetoptLong.new( ['--output-format', '-T', GetoptLong::REQUIRED_ARGUMENT], ['--output-file', '-o', GetoptLong::REQUIRED_ARGUMENT], ['--path', '-p', GetoptLong::REQUIRED_ARGUMENT], ['--use', '-u', GetoptLong::REQUIRED_ARGUMENT], ['--stop', '-s', GetoptLong::REQUIRED_ARGUMENT], ['--help', '-h', GetoptLong::NO_ARGUMENT], ['--version', '-V', GetoptLong::NO_ARGUMENT] ) begin oOpt.each_option do |xOpt, xValue| case xOpt when '--output-format' xOutFormat = xValue when '--output-file' xOutFile = xValue when '--path' xGVPath = xValue when '--use' xUse = xValue when '--stop' xStops = xValue.split( "," ).map{ |x| x.strip } when '--help' usage( ) exit when '--version' version( ) exit end end rescue GetoptLong::InvalidOption => e usage( ) exit end xFile = ARGV[0] if xFile.nil? == true usage( ) exit end o = Rb2Gv::new( xGVPath, xUse, xStops ) o.parse( xFile ) result = o.out( xOutFormat, xOutFile ) puts result unless result.nil?