File: dependencygraph.rb

Project: Rubylabor

#!/usr/bin/env ruby -w
# 
# construct dependency graph for C/C++ projects
#
# Copyright (c) 2004 by Karsten Meier, http://meier-online.com
# This program is free software.
# You can distribute/modify this program under the terms of
# the GNU General Public License version 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.
#
require 'find'
require 'optparse'

# $/ = "\r"   # uncomment for macintosh linebreaks

# get all source files under a directory.
# a source file is either a .c file or a .h file
def get_source_files(startpath)
  sources = []
  Find.find(startpath) do |filename|
    if /\.c[p]*$/.match(filename)
      sources.push filename
    end  
    if /\.h$/.match(filename)
      sources.push filename
    end  
  end
  sources
end

class IncludeAnalyser
  def initialize
    @dependend = Hash.new{Array.new}
  end
  
  def extract_dependency(filename)
    File.open(filename).each { |line|
      if /^\#include\s*\"([\w\.]+)\"/.match(line)
        add_dependency(filename, $1)
      end
      if /^\#include\s*\<([\w\.]+)\>/.match(line)
        add_dependency(filename, $1)
      end     
    }
  end

  def add_dependency(sourcepath, included)
      base = File.basename(sourcepath)
      if @dependend[base].empty?
        @dependend[base] = [included]
      else  
        @dependend[base].push(included) 
      end  
  end

  def print_all
    @dependend.each{|source, includes|
      print_childs(source, 0)
    }
  end

  def print_childs(source, level)
    puts("  " * level + source)
    for child in @dependend[source]
      print_childs(child, level + 1)
    end
  end

  def print_dot(stream)
      stream.puts "digraph G {"
      stream.puts "  rankdir=LR;"
      stream.puts "  node [shape=rect];"
      @dependend.each{|source, includes|
          for include in includes
              stream.puts "  #{dotname(source)} -> #{dotname(include)};"
          end
      }
      stream.puts "}"
  end

  def dotname(filename)
    filename.sub(/\./,'_')
  end

end

# ----- analysing of command line arguments
ARGV.options do |opts|
  opts.banner = "Usage: ruby #{$0} [-d DOTOUTPUTFILE] INPUTDIRECTORY"
  opts.on("-h", "--help", "show this message"){
    puts opts
    exit
  }
  opts.on("-d", "--dotoutput=FILE", String, "Create  dot output file"){
    |@dotoutputfile|
  }
  opts.parse!
end

# ----- main program
files = []
for path in ARGV
  files = files + get_source_files(path)
end  

analyser = IncludeAnalyser.new
for file in files
  analyser.extract_dependency(file)
end

if defined? @dotoutputfile
  File.open(@dotoutputfile, "w"){ |stream|
    analyser.print_dot(stream)
  }
else
  analyser.print_all
end

[ Index ]Generated by [ source2html ]