root / logilab.pylintinstaller / logilab / common / graph.py

Revision 202:d67e86292521, 5.7 kB (checked in by tziade@…, 9 months ago)

added logilab.pylintinstaller

Line 
1"""some various graph manipuliation utilities
2
3(dot generation adapted from pypy/translator/tool/make_dot.py)
4
5:organization: Logilab
6:copyright: 2003-2007 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
7:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
8"""
9
10__docformat__ = "restructuredtext en"
11__metaclass__ = type
12
13import os.path as osp
14import os
15
16def escape(value):
17    """make <value> usable in a dot file"""
18    lines = [line.replace('"', '\\"') for line in value.split('\n')]
19    data = '\\l'.join(lines)
20    return '\\n' + data
21
22def target_info_from_filename(filename):
23    """transforms /some/path/foo.png into ('/some/path', 'foo.png', 'png')"""
24    abspath = osp.abspath(filename)
25    basename = osp.basename(filename)
26    storedir = osp.dirname(abspath)
27    target = filename.split('.')[-1]
28    return storedir, basename, target
29
30
31class DotBackend:
32    """Dot File backend"""
33    def __init__(self, graphname, rankdir=None, size=None, ratio=None, charset='utf-8'):
34        self.graphname = graphname
35        self.lines = []
36        self._source = None
37        self.emit("digraph %s {" % normalize_node_id(graphname))
38        if rankdir:
39            self.emit('rankdir=%s' % rankdir)
40        if ratio:
41            self.emit('ratio=%s' % ratio)
42        if size:
43            self.emit('size="%s"' % size)
44        if charset:
45            assert charset.lower() in ('utf-8', 'iso-8859-1', 'latin1'), \
46                   'unsupported charset %s' % charset
47            self.emit('charset="%s"' % charset)
48
49    def get_source(self):
50        """returns self._source"""
51        if self._source is None:
52            self.emit("}")
53            self._source = '\n'.join(self.lines)
54            del self.lines
55        return self._source
56
57    source = property(get_source)
58   
59    def generate(self, outputfile=None, dotfile=None):
60        """generates a graph file
61        :param target: output format ('png', 'ps', etc.). If None,
62                       the raw dot source will be returned
63        :return: a path to the generated file
64        """
65        if outputfile is not None:
66            storedir, basename, target = target_info_from_filename(outputfile)
67        else:
68            storedir = '/tmp'
69            basename = '%s.png' % (self.graphname)
70            target = 'png'
71            outputfile = osp.join(storedir, basename)
72        dotfile = dotfile or ('%s.dot' % self.graphname)
73        dot_sourcepath = osp.join(storedir, dotfile)
74        pdot = file(dot_sourcepath, 'w')
75        if isinstance(self.source, unicode):
76            pdot.write(self.source.encode('UTF8'))
77        else:
78            pdot.write(self.source)
79        pdot.close()
80        if target != 'dot':
81            os.system('dot -T%s %s -o%s' % (target, dot_sourcepath, outputfile))
82            os.unlink(dot_sourcepath)
83        return outputfile
84
85    def emit(self, line):
86        """adds <line> to final output"""
87        self.lines.append(line)
88
89    def emit_edge(self, name1, name2, **props):
90        """emits edge from <name1> to <name2>
91       
92        authorized props: see http://www.graphviz.org/doc/info/attrs.html
93        """
94        attrs = ['%s="%s"' % (prop, value) for prop, value in props.items()]
95        self.emit('edge [%s];' % ", ".join(attrs))
96        self.emit('%s -> %s' % (normalize_node_id(name1), normalize_node_id(name2)))
97
98    def emit_node(self, name, **props):
99        """authorized props: see http://www.graphviz.org/doc/info/attrs.html
100        """
101        attrs = ['%s="%s"' % (prop, value) for prop, value in props.items()]
102        self.emit('%s [%s];' % (normalize_node_id(name), ", ".join(attrs)))
103
104def normalize_node_id(nid):
105    """returns a suitable DOT node id for `nid`"""
106    return '"%s"' % nid
107
108class GraphGenerator:
109    def __init__(self, backend):
110        # the backend is responsible to output the graph is a particular format
111        self.backend = backend
112
113    def generate(self, visitor, propshdlr, outputfile=None):
114        # the visitor
115        # the properties handler is used to get nodes and edges properties
116        # according to the graph and to the backend
117        self.propshdlr = propshdlr
118        for nodeid, node in visitor.nodes():
119            props = propshdlr.node_properties(node)
120            self.backend.emit_node(nodeid, **props)
121        for subjnode, objnode, edge in visitor.edges():
122            props = propshdlr.edge_properties(edge, subjnode, objnode)
123            self.backend.emit_edge(subjnode, objnode, **props)
124        return self.backend.generate(outputfile)
125
126
127
128def get_cycles(graph_dict, vertices=None):
129    '''given a dictionnary representing an ordered graph (i.e. key are vertices
130    and values is a list of destination vertices representing edges), return a
131    list of detected cycles
132    '''
133    if not graph_dict:
134        return ()
135    result = []
136    if vertices is None:
137        vertices = graph_dict.keys()
138    for vertice in vertices:
139        _get_cycles(graph_dict, vertice, [], result)
140    return result
141
142def _get_cycles(graph_dict, vertice=None, path=None, result=None):
143    """recursive function doing the real work for get_cycles"""
144    if vertice in path:
145        cycle = [vertice]
146        for i in range(len(path)-1, 0, -1):
147            node = path[i]
148            if node == vertice:
149                break
150            cycle.insert(0, node)
151        # make a canonical representation
152        start_from = min(cycle)
153        index = cycle.index(start_from)
154        cycle = cycle[index:] + cycle[0:index]
155        # append it to result if not already in
156        if not cycle in result:
157            result.append(cycle)
158        return
159    path.append(vertice)
160    try:
161        for node in graph_dict[vertice]:
162            _get_cycles(graph_dict, node, path, result)
163    except KeyError:
164        pass
165    path.pop()
Note: See TracBrowser for help on using the browser.