#!/usr/bin/env ruby require 'ptools' require 'rgl/adjacency' require 'rgl/dot' require 'rgl/rdot' class FunctionGraph < RGL::DirectedAdjacencyGraph attr_accessor :command attr_accessor :funx attr_accessor :lines def initialize super @@lines = nil @@funx = nil end def genFiles(graph, filelist, lang, exclude=[]) # test if the 'tobegenerated' files already exist if (File.exist?('all.php') or File.exist?('php-funx')) puts "Die Dateien 'all.php' oder 'php-funx' exitieren bereits." puts "Bitte loeschen Sie sie manuel" exit end # command to generate the necessary files command = 'cat ' + filelist + ' | ruby -ne \' print unless /^#.*$/\' | ruby -pe \'gsub(/#.*/," "); gsub(/".*"/,"")\' > all.php; ctags -x --php-kinds=f all.php | sort -n -k 3 | ruby -pe \'gsub(/ function /,"\t"); gsub(/all.php/,"\t")\' | cut -f 1,2 |ruby -pe \'gsub(/ {2,}/,"")\' > php-funx' system(command) funchash = Hash.new file = open('php-funx') file.read.split(/\n/).each {|f| thisfunc = f.split funchash.store(thisfunc.last.to_i, thisfunc.first) } graph.lines = Array.new graph.funx = Array.new funchash.sort.each {|f| next if exclude.include?(f.last) graph.lines << f.first graph.funx << f.last } $filesize = open('all.php').readlines.size end def fill(filelist,lang,exclude=[]) # generate the necessary files and return the size (in number of lines) # of the file with all definitions genFiles(self,filelist,lang,exclude) # scan the first function funcbody = File.middle('all.php', lines[0]+1, lines[1]-1).to_s add_vertex(funx.first) funx.each {|func| if funcbody =~ /#{func} *\(/ add_func("#{funx.first}","#{func}") end } # scan any other function except the last (1...funx.size-1).each {|index| add_vertex(funx[index]) funcbody = File.middle('all.php', lines[index]+1,lines[index+1]-1).to_s funx.each {|func| if funcbody =~ /#{func} *\(/ add_func("#{funx[index]}","#{func}") end } } # scan the last function funcbody = File.middle('all.php', lines.last+1 , $filesize).to_s add_vertex(funx.last) funx.each {|func| if funcbody =~ /#{func} *\(/ add_func("#{funx.last}","#{func}") end } system('rm all.php php-funx') # add fallow function's knodes funx.each {|func| if not has_vertex?(func) add_edge("UNUSED CODE",func) end } end def to_ps(filename) if File.exist?(filename) system("rm #{filename}") end if File.exist?(filename+".dot") system("rm #{filename}.dot") end # create dot file params = Hash['rankdir' => 'LR', 'ranksep' => '2.0', 'concentrate' => 'TRUE', 'fontsize' => '10'] File.open("#{filename}.dot","w") {|f| print_dotted_on(params,f) } system("dot -Tps -o #{filename} -Nshape=box #{filename}.dot") end def add_func(f,g) add_edge(f,g) end def display params = Hash['rankdir' => 'LR', 'ranksep' => '2.0', 'concentrate' => 'TRUE', 'label' => 'LCWA', 'fontsize' => '12'] dotty(params) end private :genFiles end class SingleFunctionGraph < FunctionGraph attr_accessor :func def initialize(func) @func = func super() end def fill(filelist,lang,exclude=[]) genFiles(self,filelist,lang,exclude) $scannedfunx = Array.new scan(self, func) system('rm all.php php-funx') end def scan(graph, f) if ($scannedfunx.include?(f)) else $scannedfunx << f beginbody = graph.lines[graph.funx.index(f)]+1 if (graph.lines.size <= graph.funx.index(f)+1) endbody = $filesize else endbody = graph.lines[graph.funx.index(f)+1]-1 end fbody = File.middle('all.php', beginbody, endbody).to_s graph.funx.each {|g| if fbody =~ /#{g} *\(/ graph.add_func(f,g) scan(graph,g) end } end end private :scan end # main script ################################################################# require 'getoptlong' def show_usage puts <<-END Usage: #{__FILE__}: --help guess what --source, -s take every $IMPSRC/lcwa/web_interface/*inc.php file to search in --local, -l take every $(pwd)/*.inc.php file for scanning --file-list, -F "" fill the graph with every function, that has a definiton inside the given files --function, -f take the given func as the root knode --exclude, -x "" exclude a list of functions from the graph --to-ps, -p create a postscript file from the graph END end # option setting --------------------------------------------------------------- options = GetoptLong.new( ['--help', '-h', GetoptLong::NO_ARGUMENT], ['--local', '-l', GetoptLong::NO_ARGUMENT], ['--source', '-s', GetoptLong::NO_ARGUMENT], ['--function', '-f', GetoptLong::REQUIRED_ARGUMENT], ['--file-list', '-F', GetoptLong::REQUIRED_ARGUMENT], ['--to-ps', '-p', GetoptLong::REQUIRED_ARGUMENT], ['--exclude', '-x', GetoptLong::REQUIRED_ARGUMENT] ) options.ordering = GetoptLong::RETURN_IN_ORDER # the default options ---------------------------------------------------------- filelist = '' mode = 'multiple' createPS = false filename = '' function = '' exclude = Array.new spaces = Array.new # option parsing --------------------------------------------------------------- options.each do |opt, arg| case opt when '--local' filelist = './*inc.php' spaces << filelist when '--source' filelist = '$IMPSRC/lcwa/web_interface/*inc.php' spaces << filelist when '--file-list' filelist = arg spaces << filelist when '--function' mode = 'single' function = arg when '--to-ps' createPS = true filename = arg when '--exclude' exclude = arg.split(/ /) else show_usage exit end end options.terminate # script controlling ########################################################### case mode when 'single' g = SingleFunctionGraph.new(function) else g = FunctionGraph.new end case spaces.size when 1 g.fill(filelist,'php',exclude) else puts "Benutzen Sie entweder --local, --source oder --file-list " show_usage exit end case createPS when true g.to_ps(filename) else g.display end