| Index: third_party/logilab/common/graph.py
 | 
| ===================================================================
 | 
| --- third_party/logilab/common/graph.py	(revision 293047)
 | 
| +++ third_party/logilab/common/graph.py	(working copy)
 | 
| @@ -28,7 +28,8 @@
 | 
|  import os
 | 
|  import sys
 | 
|  import tempfile
 | 
| -from logilab.common.compat import str_encode
 | 
| +import codecs
 | 
| +import errno
 | 
|  
 | 
|  def escape(value):
 | 
|      """Make <value> usable in a dot file."""
 | 
| @@ -63,7 +64,7 @@
 | 
|              assert charset.lower() in ('utf-8', 'iso-8859-1', 'latin1'), \
 | 
|                     'unsupported charset %s' % charset
 | 
|              self.emit('charset="%s"' % charset)
 | 
| -        for param in additionnal_param.iteritems():
 | 
| +        for param in sorted(additionnal_param.items()):
 | 
|              self.emit('='.join(param))
 | 
|  
 | 
|      def get_source(self):
 | 
| @@ -106,8 +107,8 @@
 | 
|              ppng, outputfile = tempfile.mkstemp(".png", name)
 | 
|              os.close(pdot)
 | 
|              os.close(ppng)
 | 
| -        pdot = open(dot_sourcepath, 'w')
 | 
| -        pdot.write(str_encode(self.source, 'utf8'))
 | 
| +        pdot = codecs.open(dot_sourcepath, 'w', encoding='utf8')
 | 
| +        pdot.write(self.source)
 | 
|          pdot.close()
 | 
|          if target != 'dot':
 | 
|              if sys.platform == 'win32':
 | 
| @@ -114,13 +115,18 @@
 | 
|                  use_shell = True
 | 
|              else:
 | 
|                  use_shell = False
 | 
| -            if mapfile:
 | 
| -                subprocess.call([self.renderer,  '-Tcmapx', '-o', mapfile, '-T', target, dot_sourcepath, '-o', outputfile],
 | 
| -                                shell=use_shell)
 | 
| -            else:
 | 
| -                subprocess.call([self.renderer, '-T',  target,
 | 
| -                                 dot_sourcepath, '-o',  outputfile],
 | 
| -                                shell=use_shell)
 | 
| +            try:
 | 
| +                if mapfile:
 | 
| +                    subprocess.call([self.renderer,  '-Tcmapx', '-o', mapfile, '-T', target, dot_sourcepath, '-o', outputfile],
 | 
| +                                    shell=use_shell)
 | 
| +                else:
 | 
| +                    subprocess.call([self.renderer, '-T',  target,
 | 
| +                                     dot_sourcepath, '-o',  outputfile],
 | 
| +                                    shell=use_shell)
 | 
| +            except OSError as e:
 | 
| +                if e.errno == errno.ENOENT:
 | 
| +                    e.strerror = 'File not found: {0}'.format(self.renderer)
 | 
| +                    raise
 | 
|              os.unlink(dot_sourcepath)
 | 
|          return outputfile
 | 
|  
 | 
| @@ -134,7 +140,7 @@
 | 
|          """
 | 
|          attrs = ['%s="%s"' % (prop, value) for prop, value in props.items()]
 | 
|          n_from, n_to = normalize_node_id(name1), normalize_node_id(name2)
 | 
| -        self.emit('%s -> %s [%s];' % (n_from, n_to, ", ".join(attrs)) )
 | 
| +        self.emit('%s -> %s [%s];' % (n_from, n_to, ', '.join(sorted(attrs))) )
 | 
|  
 | 
|      def emit_node(self, name, **props):
 | 
|          """emit a node with given properties.
 | 
| @@ -141,7 +147,7 @@
 | 
|          node properties: see http://www.graphviz.org/doc/info/attrs.html
 | 
|          """
 | 
|          attrs = ['%s="%s"' % (prop, value) for prop, value in props.items()]
 | 
| -        self.emit('%s [%s];' % (normalize_node_id(name), ", ".join(attrs)))
 | 
| +        self.emit('%s [%s];' % (normalize_node_id(name), ', '.join(sorted(attrs))))
 | 
|  
 | 
|  def normalize_node_id(nid):
 | 
|      """Returns a suitable DOT node id for `nid`."""
 | 
| @@ -226,10 +232,10 @@
 | 
|      if vertices is None:
 | 
|          vertices = graph_dict.keys()
 | 
|      for vertice in vertices:
 | 
| -        _get_cycles(graph_dict, vertice, [], result)
 | 
| +        _get_cycles(graph_dict, [], set(), result, vertice)
 | 
|      return result
 | 
|  
 | 
| -def _get_cycles(graph_dict, vertice=None, path=None, result=None):
 | 
| +def _get_cycles(graph_dict, path, visited, result, vertice):
 | 
|      """recursive function doing the real work for get_cycles"""
 | 
|      if vertice in path:
 | 
|          cycle = [vertice]
 | 
| @@ -248,7 +254,10 @@
 | 
|      path.append(vertice)
 | 
|      try:
 | 
|          for node in graph_dict[vertice]:
 | 
| -            _get_cycles(graph_dict, node, path, result)
 | 
| +            # don't check already visited nodes again
 | 
| +            if node not in visited:
 | 
| +                _get_cycles(graph_dict, path, visited, result, node)
 | 
| +                visited.add(node)
 | 
|      except KeyError:
 | 
|          pass
 | 
|      path.pop()
 | 
| 
 |