• [^] # php2dot

    Posté par . En réponse au message graphe des liaisons entre fichiers. Évalué à 2.

    #!/usr/bin/python
    # -*- coding: iso-8859-1 -*-
    #
    # Copyright (C) 2004 Nicolas Girard < bobert at dlfp dot org >
    #
    # 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, 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.
    import os,sys,re
    from os.path import join
    from itertools import ifilter
    from sets import Set
    class Finder:
     """Parcourt une hiérarchie de répertoires à partir de startDirectory"""
     def __init__(self,startDirectory):
     self.walker = os.walk(startDirectory)
     def walk(self):
     while 1:
     root, dirs, files = self.walker.next()
     for file in files:
     yield (root,file)
     def phpFiles(self):
     isPhp = lambda (r,f):f.endswith('.php') or f.endswith('.php3')
     return ifilter(isPhp,self.walk())
     
    def find(dir):
     return Finder(dir)
     
    class PhpFile:
     include = re.compile(".*include\s*\(['\"](\S+)['\"]\)\s*;")
     def __init__(self,dir,name):
     (self.dir,self.name)=(dir,name)
     def includes(self):
     f=open(join(self.dir,self.name),'r')
     lines=f.readlines()
     f.close()
     for line in lines:
     if PhpFile.include.match(line):
     yield PhpFile.include.match(line).group(1).split('/')[-1]
     
    class Node:
     def __init__(self,name,labels=[],children=Set()):
     (self.name,self.labels,self.children,self.nodes)=(name,labels,children,None)
     def mergeWith(self,node):
     self.labels+=node.labels
     def label(self):
     return "\\n".join(self.labels)
     def edges(self):
     return "\n".join(map(lambda child,s=self:"%s -> %s"%(s.name,child.name),self.children))
     def __eq__(self,other):
     return self.name==other.name
     def __str__(self):
     return "%s [label=\"%s\"]\n%s"%(self.name,self.label(),self.edges())
     def rank(self):
     """calcule le nombre de parents du noeud"""
     return sum(map(lambda o,s=self,:s in o.children,self.nodes))
     def isExcluded(self):
     return self.rank()==0 and len(self.children)==0
     
    class Graph:
     def __init__(self,ranksep=2,nodesep=0.5,minlen=1.5,concentrate=True,nodes=[]):
     self.nodes=nodes
     self.head="""
    digraph Schema {
     node[shape=box];
     concentrate=%s;
     ranksep=%f;
     nodesep=%f;
     edge[minlen=%f];\n"""%(concentrate,ranksep,nodesep,minlen)
     self.foot="}\n"
     def append(self,node):
     node.nodes=self.nodes
     self.nodes.append(node)
     def __getitem__(self,nodeName):
     res=filter(lambda node,n=nodeName:node.name==n,self.nodes)
     if len(res)==1:
     return res[0]
     else:
     return None
     def checkChildren(self):
     """On supprime les fichiers inclus ne correspondant pas à un fichier trouvé
     et les doublons"""
     for node in self.nodes:
     node.children = filter(lambda ch,fath=node:ch and fath!=ch,
     map(lambda c,s=self:self[c],node.children))
     def removeExcluded(self):
     """supprime les noeuds exclus du graphe"""
     for excluded in filter(Node.isExcluded,self.nodes):
     self.nodes.remove(excluded)
     def clusterize(self):
     """Regroupe les fichers non-inclus par d'autres, et incluant les mêmes fichiers"""
     nodes = filter(lambda n:n.rank()==0,self.nodes)
     for node in nodes:
     (node.clustered,node.removed)=(False,False)
     for node in nodes:
     ch = node.children[:]
     for other in nodes:
     if other == node or other.clustered or other.removed:
     continue
     if ch==other.children:
     node.mergeWith(other)
     node.clustered=True
     other.removed=True
     for node in filter(lambda n:n.removed,nodes):
     self.nodes.remove(node)
     def __str__(self):
     for i in range(len(self.nodes)):
     self.nodes[i].name="node%d"%i
     return self.head+"\n".join(map(str,self.nodes))+self.foot 
     
     
    usage="""
    php2dot - Génère un script dot des relations d'inclusion entre fichiers PHP
     
    Syntaxe: php2dot startDir
     
     startDir : répertoire de recherche des fichiers PHP.
     La recherche est récursive.
     
    Synopsis:
     php2dot startDir > graph.dot
     dot -Tpng -ograph.png graph.dot
     
    Limitation:
     Les noms des fichiers PHP trouvés doivent être distincts
     
    """
     
    if __name__=="__main__":
     if len(sys.argv)==1:
     print usage
     sys.exit(0)
     startDir=sys.argv[1]
     graph = Graph()
     for (dir,name) in find(startDir).phpFiles():
     phpFile = PhpFile(dir,name)
     if not graph[name]:
     graph.append(Node(name,[name],phpFile.includes()))
     graph.checkChildren()
     graph.removeExcluded()
     graph.clusterize()
     print graph