OLD | NEW |
1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. | 1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr | 2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
3 # | 3 # |
4 # This file is part of logilab-common. | 4 # This file is part of logilab-common. |
5 # | 5 # |
6 # logilab-common is free software: you can redistribute it and/or modify it unde
r | 6 # logilab-common is free software: you can redistribute it and/or modify it unde
r |
7 # the terms of the GNU Lesser General Public License as published by the Free | 7 # the terms of the GNU Lesser General Public License as published by the Free |
8 # Software Foundation, either version 2.1 of the License, or (at your option) an
y | 8 # Software Foundation, either version 2.1 of the License, or (at your option) an
y |
9 # later version. | 9 # later version. |
10 # | 10 # |
(...skipping 10 matching lines...) Expand all Loading... |
21 """ | 21 """ |
22 | 22 |
23 __docformat__ = "restructuredtext en" | 23 __docformat__ = "restructuredtext en" |
24 | 24 |
25 __metaclass__ = type | 25 __metaclass__ = type |
26 | 26 |
27 import os.path as osp | 27 import os.path as osp |
28 import os | 28 import os |
29 import sys | 29 import sys |
30 import tempfile | 30 import tempfile |
31 from logilab.common.compat import str_encode | 31 import codecs |
| 32 import errno |
32 | 33 |
33 def escape(value): | 34 def escape(value): |
34 """Make <value> usable in a dot file.""" | 35 """Make <value> usable in a dot file.""" |
35 lines = [line.replace('"', '\\"') for line in value.split('\n')] | 36 lines = [line.replace('"', '\\"') for line in value.split('\n')] |
36 data = '\\l'.join(lines) | 37 data = '\\l'.join(lines) |
37 return '\\n' + data | 38 return '\\n' + data |
38 | 39 |
39 def target_info_from_filename(filename): | 40 def target_info_from_filename(filename): |
40 """Transforms /some/path/foo.png into ('/some/path', 'foo.png', 'png').""" | 41 """Transforms /some/path/foo.png into ('/some/path', 'foo.png', 'png').""" |
41 basename = osp.basename(filename) | 42 basename = osp.basename(filename) |
(...skipping 14 matching lines...) Expand all Loading... |
56 if rankdir: | 57 if rankdir: |
57 self.emit('rankdir=%s' % rankdir) | 58 self.emit('rankdir=%s' % rankdir) |
58 if ratio: | 59 if ratio: |
59 self.emit('ratio=%s' % ratio) | 60 self.emit('ratio=%s' % ratio) |
60 if size: | 61 if size: |
61 self.emit('size="%s"' % size) | 62 self.emit('size="%s"' % size) |
62 if charset: | 63 if charset: |
63 assert charset.lower() in ('utf-8', 'iso-8859-1', 'latin1'), \ | 64 assert charset.lower() in ('utf-8', 'iso-8859-1', 'latin1'), \ |
64 'unsupported charset %s' % charset | 65 'unsupported charset %s' % charset |
65 self.emit('charset="%s"' % charset) | 66 self.emit('charset="%s"' % charset) |
66 for param in additionnal_param.iteritems(): | 67 for param in sorted(additionnal_param.items()): |
67 self.emit('='.join(param)) | 68 self.emit('='.join(param)) |
68 | 69 |
69 def get_source(self): | 70 def get_source(self): |
70 """returns self._source""" | 71 """returns self._source""" |
71 if self._source is None: | 72 if self._source is None: |
72 self.emit("}\n") | 73 self.emit("}\n") |
73 self._source = '\n'.join(self.lines) | 74 self._source = '\n'.join(self.lines) |
74 del self.lines | 75 del self.lines |
75 return self._source | 76 return self._source |
76 | 77 |
(...skipping 22 matching lines...) Expand all Loading... |
99 pdot, dot_sourcepath = tempfile.mkstemp(".dot", name) | 100 pdot, dot_sourcepath = tempfile.mkstemp(".dot", name) |
100 os.close(pdot) | 101 os.close(pdot) |
101 else: | 102 else: |
102 dot_sourcepath = osp.join(storedir, dotfile) | 103 dot_sourcepath = osp.join(storedir, dotfile) |
103 else: | 104 else: |
104 target = 'png' | 105 target = 'png' |
105 pdot, dot_sourcepath = tempfile.mkstemp(".dot", name) | 106 pdot, dot_sourcepath = tempfile.mkstemp(".dot", name) |
106 ppng, outputfile = tempfile.mkstemp(".png", name) | 107 ppng, outputfile = tempfile.mkstemp(".png", name) |
107 os.close(pdot) | 108 os.close(pdot) |
108 os.close(ppng) | 109 os.close(ppng) |
109 pdot = open(dot_sourcepath, 'w') | 110 pdot = codecs.open(dot_sourcepath, 'w', encoding='utf8') |
110 pdot.write(str_encode(self.source, 'utf8')) | 111 pdot.write(self.source) |
111 pdot.close() | 112 pdot.close() |
112 if target != 'dot': | 113 if target != 'dot': |
113 if sys.platform == 'win32': | 114 if sys.platform == 'win32': |
114 use_shell = True | 115 use_shell = True |
115 else: | 116 else: |
116 use_shell = False | 117 use_shell = False |
117 if mapfile: | 118 try: |
118 subprocess.call([self.renderer, '-Tcmapx', '-o', mapfile, '-T',
target, dot_sourcepath, '-o', outputfile], | 119 if mapfile: |
119 shell=use_shell) | 120 subprocess.call([self.renderer, '-Tcmapx', '-o', mapfile, '
-T', target, dot_sourcepath, '-o', outputfile], |
120 else: | 121 shell=use_shell) |
121 subprocess.call([self.renderer, '-T', target, | 122 else: |
122 dot_sourcepath, '-o', outputfile], | 123 subprocess.call([self.renderer, '-T', target, |
123 shell=use_shell) | 124 dot_sourcepath, '-o', outputfile], |
| 125 shell=use_shell) |
| 126 except OSError as e: |
| 127 if e.errno == errno.ENOENT: |
| 128 e.strerror = 'File not found: {0}'.format(self.renderer) |
| 129 raise |
124 os.unlink(dot_sourcepath) | 130 os.unlink(dot_sourcepath) |
125 return outputfile | 131 return outputfile |
126 | 132 |
127 def emit(self, line): | 133 def emit(self, line): |
128 """Adds <line> to final output.""" | 134 """Adds <line> to final output.""" |
129 self.lines.append(line) | 135 self.lines.append(line) |
130 | 136 |
131 def emit_edge(self, name1, name2, **props): | 137 def emit_edge(self, name1, name2, **props): |
132 """emit an edge from <name1> to <name2>. | 138 """emit an edge from <name1> to <name2>. |
133 edge properties: see http://www.graphviz.org/doc/info/attrs.html | 139 edge properties: see http://www.graphviz.org/doc/info/attrs.html |
134 """ | 140 """ |
135 attrs = ['%s="%s"' % (prop, value) for prop, value in props.items()] | 141 attrs = ['%s="%s"' % (prop, value) for prop, value in props.items()] |
136 n_from, n_to = normalize_node_id(name1), normalize_node_id(name2) | 142 n_from, n_to = normalize_node_id(name1), normalize_node_id(name2) |
137 self.emit('%s -> %s [%s];' % (n_from, n_to, ", ".join(attrs)) ) | 143 self.emit('%s -> %s [%s];' % (n_from, n_to, ', '.join(sorted(attrs))) ) |
138 | 144 |
139 def emit_node(self, name, **props): | 145 def emit_node(self, name, **props): |
140 """emit a node with given properties. | 146 """emit a node with given properties. |
141 node properties: see http://www.graphviz.org/doc/info/attrs.html | 147 node properties: see http://www.graphviz.org/doc/info/attrs.html |
142 """ | 148 """ |
143 attrs = ['%s="%s"' % (prop, value) for prop, value in props.items()] | 149 attrs = ['%s="%s"' % (prop, value) for prop, value in props.items()] |
144 self.emit('%s [%s];' % (normalize_node_id(name), ", ".join(attrs))) | 150 self.emit('%s [%s];' % (normalize_node_id(name), ', '.join(sorted(attrs)
))) |
145 | 151 |
146 def normalize_node_id(nid): | 152 def normalize_node_id(nid): |
147 """Returns a suitable DOT node id for `nid`.""" | 153 """Returns a suitable DOT node id for `nid`.""" |
148 return '"%s"' % nid | 154 return '"%s"' % nid |
149 | 155 |
150 class GraphGenerator: | 156 class GraphGenerator: |
151 def __init__(self, backend): | 157 def __init__(self, backend): |
152 # the backend is responsible to output the graph in a particular format | 158 # the backend is responsible to output the graph in a particular format |
153 self.backend = backend | 159 self.backend = backend |
154 | 160 |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
219 '''given a dictionary representing an ordered graph (i.e. key are vertices | 225 '''given a dictionary representing an ordered graph (i.e. key are vertices |
220 and values is a list of destination vertices representing edges), return a | 226 and values is a list of destination vertices representing edges), return a |
221 list of detected cycles | 227 list of detected cycles |
222 ''' | 228 ''' |
223 if not graph_dict: | 229 if not graph_dict: |
224 return () | 230 return () |
225 result = [] | 231 result = [] |
226 if vertices is None: | 232 if vertices is None: |
227 vertices = graph_dict.keys() | 233 vertices = graph_dict.keys() |
228 for vertice in vertices: | 234 for vertice in vertices: |
229 _get_cycles(graph_dict, vertice, [], result) | 235 _get_cycles(graph_dict, [], set(), result, vertice) |
230 return result | 236 return result |
231 | 237 |
232 def _get_cycles(graph_dict, vertice=None, path=None, result=None): | 238 def _get_cycles(graph_dict, path, visited, result, vertice): |
233 """recursive function doing the real work for get_cycles""" | 239 """recursive function doing the real work for get_cycles""" |
234 if vertice in path: | 240 if vertice in path: |
235 cycle = [vertice] | 241 cycle = [vertice] |
236 for node in path[::-1]: | 242 for node in path[::-1]: |
237 if node == vertice: | 243 if node == vertice: |
238 break | 244 break |
239 cycle.insert(0, node) | 245 cycle.insert(0, node) |
240 # make a canonical representation | 246 # make a canonical representation |
241 start_from = min(cycle) | 247 start_from = min(cycle) |
242 index = cycle.index(start_from) | 248 index = cycle.index(start_from) |
243 cycle = cycle[index:] + cycle[0:index] | 249 cycle = cycle[index:] + cycle[0:index] |
244 # append it to result if not already in | 250 # append it to result if not already in |
245 if not cycle in result: | 251 if not cycle in result: |
246 result.append(cycle) | 252 result.append(cycle) |
247 return | 253 return |
248 path.append(vertice) | 254 path.append(vertice) |
249 try: | 255 try: |
250 for node in graph_dict[vertice]: | 256 for node in graph_dict[vertice]: |
251 _get_cycles(graph_dict, node, path, result) | 257 # don't check already visited nodes again |
| 258 if node not in visited: |
| 259 _get_cycles(graph_dict, path, visited, result, node) |
| 260 visited.add(node) |
252 except KeyError: | 261 except KeyError: |
253 pass | 262 pass |
254 path.pop() | 263 path.pop() |
255 | 264 |
256 def has_path(graph_dict, fromnode, tonode, path=None): | 265 def has_path(graph_dict, fromnode, tonode, path=None): |
257 """generic function taking a simple graph definition as a dictionary, with | 266 """generic function taking a simple graph definition as a dictionary, with |
258 node has key associated to a list of nodes directly reachable from it. | 267 node has key associated to a list of nodes directly reachable from it. |
259 | 268 |
260 Return None if no path exists to go from `fromnode` to `tonode`, else the | 269 Return None if no path exists to go from `fromnode` to `tonode`, else the |
261 first path found (as a list including the destination node at last) | 270 first path found (as a list including the destination node at last) |
262 """ | 271 """ |
263 if path is None: | 272 if path is None: |
264 path = [] | 273 path = [] |
265 elif fromnode in path: | 274 elif fromnode in path: |
266 return None | 275 return None |
267 path.append(fromnode) | 276 path.append(fromnode) |
268 for destnode in graph_dict[fromnode]: | 277 for destnode in graph_dict[fromnode]: |
269 if destnode == tonode or has_path(graph_dict, destnode, tonode, path): | 278 if destnode == tonode or has_path(graph_dict, destnode, tonode, path): |
270 return path[1:] + [tonode] | 279 return path[1:] + [tonode] |
271 path.pop() | 280 path.pop() |
272 return None | 281 return None |
273 | 282 |
OLD | NEW |