February 5, 2006

Graphviz - a tool for the postmodern programmer

Graphviz is a great tool for the postmodern programmer. It's got a simple to integrate interface of text files and command line execution. Bjorn Freeman-Benson is visiting at the moment. He suggested we demonstrate graphviz using the dependencies between eclipse plugins. So here's the code that reads the eclipse manifest files and the resulting graph. This graph shows the dependencies between a significant subset of the standard eclipse 3.1 plugins. Bjorn and I have paired on this to bring you "postable" code that doesn't embarrass us too much.

graphsmall.PNG

import sys, time
from os import listdir, system
from os.path import join, isdir, exists
import os.path
from zipfile import is_zipfile, ZipFile

class DependencyGrapher:
    def run(self, pluginDirectory):
        files = [join(pluginDirectory, file) for file in listdir(pluginDirectory)]
        graphText = self.process_files(files)
        
        f=open("graph.txt","w")
        graphText = 'digraph G {\n "' +\
            "generated on " + time.asctime() + '";\n' +\
            graphText + "\n}"
        f.write(graphText)
        f.close()
        system("dot -Tpng -ograph.png graph.txt")
        
    def process_files(self, files):
        result = ""
        for file in files:
            if isdir(file):
                result += self.process_plugin_directory(file)
            if is_zipfile(file):
                result += self.process_plugin_jar(file)
        return result

    def process_plugin_directory(self, directory):
        propertiesFileName = join(directory,"META-INF","MANIFEST.MF")
        if exists(propertiesFileName):
                return self.process_properties(self.graphviz_friendly_name_from_plugin_filename(directory), open(propertiesFileName).read())
        return ""
                    
    def process_plugin_jar(self, jarName):
        try:
            jar = ZipFile(jarName)
            propertiesFile = jar.read("META-INF/MANIFEST.MF")
            return self.process_properties(self.graphviz_friendly_name_from_plugin_filename(jarName), propertiesFile)
        finally:
            jar.close()
        
    def process_properties(self, pluginName, properties):
        result = ""
        start = properties.find("Require-Bundle:")
        if start != -1:
                lines = [line.strip() for line in properties[start+len("Require-Bundle:"):].split('\n')]
                for line in [line for line in lines if len(line) > 0]:
                        lastLine = not line.endswith(',')
                        if not lastLine:
                                line = line[:-1]
                        parts = line.split(';')
                        line = parts[0]
                        result += self.graphviz_friendly_name(pluginName) + " -> " + self.graphviz_friendly_name(line) + ";\n"
                        if lastLine:
                                break
        return result
        
    def graphviz_friendly_name(self, line):
        return line.replace('.','_')
            
    def graphviz_friendly_name_from_plugin_filename(self, fileName):
        return self.graphviz_friendly_name(os.path.split(fileName)[-1].split('_')[0])

if __name__ == "__main__":
    if len(sys.argv) != 2:
            print "need plugin directory location"
            print 'e.g. python dependencies.py "C:\Program Files\eclipse\plugins"'
            sys.exit(1)
            
    pluginDirectory = sys.argv[1]
    DependencyGrapher().run(pluginDirectory)
Posted by ivan at February 5, 2006 7:32 PM
Copyright (c) 2004-2007 Ivan Moore