Merge lp:~veger/ubuntu/trusty/pydot/fix-for-987934 into lp:ubuntu/trusty/pydot
- Trusty (14.04)
- fix-for-987934
- Merge into trusty
Status: | Needs review | ||||||||
---|---|---|---|---|---|---|---|---|---|
Proposed branch: | lp:~veger/ubuntu/trusty/pydot/fix-for-987934 | ||||||||
Merge into: | lp:ubuntu/trusty/pydot | ||||||||
Diff against target: |
1828 lines (+632/-405) 21 files modified
ChangeLog (+0/-64) PKG-INFO (+9/-8) debian/changelog (+29/-0) debian/control (+6/-7) debian/patches/01-setup-py-nodata.patch (+6/-5) debian/patches/02-use-setuptools.patch (+20/-0) debian/patches/03-support-pyparsing-v2.patch (+18/-0) debian/patches/series (+3/-0) debian/pycompat (+0/-1) debian/pyversions (+0/-1) debian/rules (+0/-3) debian/source/format (+1/-0) dot_parser.py (+82/-33) pydot.egg-info/PKG-INFO (+0/-35) pydot.egg-info/SOURCES.txt (+0/-11) pydot.egg-info/dependency_links.txt (+0/-1) pydot.egg-info/requires.txt (+0/-2) pydot.egg-info/top_level.txt (+0/-2) pydot.py (+451/-224) setup.cfg (+0/-5) setup.py (+7/-3) |
||||||||
To merge this branch: | bzr merge lp:~veger/ubuntu/trusty/pydot/fix-for-987934 | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Daniel Holbach (community) | Approve | ||
Review via email: mp+206584@code.launchpad.net |
Commit message
Description of the change
I have updated the pydot package to version 1.0.28, as the current version is quite outdated (1.0.2).
The update fixes the linked bug reports and additionally it fixes #490015 bug 2. (I do not know what is causing bug 1, but this update still shows the same behavior as described by the reporter)
As the package is old, I had to update the debian/* files quite heavily as well.
I have followed the debian packaging and python library packaging guidelines, so everything should be fine. (But then again, I am not very experienced, so I might have missed something).
Maarten Bezemer (veger) wrote : | # |
I just have forwarded the (slightly modified) patch to Debian: https:/
Unmerged revisions
- 4. By Maarten Bezemer
-
* New upstream release. (LP: #574428, #987934)
* debian/control updated to newest standards version:
- Removed DM-Upload-Allowed, obsolete
- Updated Vsc-Browser field
- Depend on python-setuptools
- Depend on pyparsing >= 2.0.1 to prevent incompability problems with
debian/patches/ 03-support- pyparsing- v2.patch
- Add ${misc:Depends} to binary package Depends
- Support dh-python
o Bumped cdbs version to 0.4.90
o Bumped python version to 2.6.6.3
o Removed XB-Python-Version field
o Added X-Python-Version field
o Removed ${python:Depends} from Depends
* debian/rules:
- do not include simple-patchsys.mk
- Support dh-python: Remove DEB_PYTHON_SYSTEM
* debian/pyversions: not required for dh-python
* debian/pycompat: not required for dh-python
* debian/source/ format: added and set to 3.0 (quilt)
* debian/patches/ 02-use- setuptools. patch: patched setup.py to use setuptools
to prevent "Unknown distribution option: 'install_requires'" warning
* debian/patches/ 03-support- pyparsing- v2.patch: add support for pyparsing
version 2.X, fixes "Couldn't import dot_parser" error
Preview Diff
1 | === removed file '._dot_parser.py' |
2 | Binary files ._dot_parser.py 2008-05-26 15:35:04 +0000 and ._dot_parser.py 1970-01-01 00:00:00 +0000 differ |
3 | === removed file '._pydot.py' |
4 | Binary files ._pydot.py 2008-05-26 15:35:04 +0000 and ._pydot.py 1970-01-01 00:00:00 +0000 differ |
5 | === removed file '._setup.py' |
6 | Binary files ._setup.py 2008-05-26 15:35:04 +0000 and ._setup.py 1970-01-01 00:00:00 +0000 differ |
7 | === removed file 'ChangeLog' |
8 | --- ChangeLog 2008-05-26 15:35:04 +0000 |
9 | +++ ChangeLog 1970-01-01 00:00:00 +0000 |
10 | @@ -1,64 +0,0 @@ |
11 | -2004-04-28 21:50 carrer |
12 | - |
13 | - * pydot.py: Some of the changes already made should allow pydot to |
14 | - run on OSX. Bumped version to 0.9.2 |
15 | - |
16 | -2004-04-24 17:52 carrer |
17 | - |
18 | - * setup.py: Added more metainformation to the distribution. |
19 | - |
20 | -2004-04-24 17:51 carrer |
21 | - |
22 | - * pydot.py: Added support for circo and fdp. Fixed piping mechanism |
23 | - to not to capture stderr. |
24 | - |
25 | -2004-04-24 13:26 carrer |
26 | - |
27 | - * ChangeLog, LICENSE, MANIFEST, README, setup.py: Adding |
28 | - suplementary files to the distribution to the CVS. |
29 | - |
30 | -2004-04-24 12:57 carrer |
31 | - |
32 | - * pydot.py: Bumped version to 0.9.1 |
33 | - |
34 | -2004-04-24 01:36 carrer |
35 | - |
36 | - * pydot.py: Implemented tweaks suggested by John B. Cole to handle |
37 | - non-str nodes, converting them to strings. |
38 | - |
39 | -2004-04-24 01:10 carrer |
40 | - |
41 | - * pydot.py: Applied patch for Windows support by Kent Johnson. |
42 | - |
43 | -2004-04-24 01:05 carrer |
44 | - |
45 | - * pydot.py: Fixed to properly handle unicode strings in attributes. |
46 | - |
47 | -2004-04-20 00:06 carrer |
48 | - |
49 | - * pydot.py: |
50 | - Fixed silly error in graph_from_edges. When pasting the function |
51 | - into the code, the references to the pydot module were not |
52 | - removed, which are no longer needed since we now are _in_ the |
53 | - module. |
54 | - |
55 | -2004-04-19 23:33 carrer |
56 | - |
57 | - * pydot.py: |
58 | - Added support to write files with the desired output format with |
59 | - write_[format] |
60 | - |
61 | -2004-04-19 22:53 carrer |
62 | - |
63 | - * pydot.py: |
64 | - Done some clean up, no major changes. |
65 | - |
66 | -2004-04-08 00:22 carrer |
67 | - |
68 | - * pydot.py: |
69 | - Initial revision. |
70 | - |
71 | -2004-04-08 00:22 carrer |
72 | - |
73 | - * pydot.py: Initial revision |
74 | - |
75 | |
76 | === modified file 'PKG-INFO' |
77 | --- PKG-INFO 2008-05-26 15:35:04 +0000 |
78 | +++ PKG-INFO 2014-02-15 16:49:34 +0000 |
79 | @@ -1,12 +1,12 @@ |
80 | Metadata-Version: 1.0 |
81 | Name: pydot |
82 | -Version: 1.0.2 |
83 | +Version: 1.0.28 |
84 | Summary: Python interface to Graphviz's Dot |
85 | Home-page: http://code.google.com/p/pydot/ |
86 | Author: Ero Carrera |
87 | Author-email: ero@dkbza.org |
88 | License: MIT |
89 | -Download-URL: http://pydot.googlecode.com/files/pydot-1.0.2.tar.gz |
90 | +Download-URL: http://pydot.googlecode.com/files/pydot-1.0.28.tar.gz |
91 | Description: Graphviz's dot language Python interface. |
92 | |
93 | This module provides with a full interface to create handle modify |
94 | @@ -14,12 +14,13 @@ |
95 | |
96 | References: |
97 | |
98 | - pydot Homepage: http://www.dkbza.org/pydot.html |
99 | - Graphviz: http://www.research.att.com/sw/tools/graphviz/ |
100 | - DOT Language: http://www.research.att.com/~erg/graphviz/info/lang.html |
101 | - |
102 | - Programmed and tested with Graphviz 1.16 and Python 2.3.4 on GNU/Linux |
103 | - by Ero Carrera (c) 2004 [ero@dkbza.org] |
104 | + pydot Homepage: http://code.google.com/p/pydot/ |
105 | + Graphviz: http://www.graphviz.org/ |
106 | + DOT Language: http://www.graphviz.org/doc/info/lang.html |
107 | + |
108 | + Programmed and tested with Graphviz 2.26.3 and Python 2.6 on OSX 10.6.4 |
109 | + |
110 | + Copyright (c) 2005-2011 Ero Carrera <ero.carrera@gmail.com> |
111 | |
112 | Distributed under MIT license [http://opensource.org/licenses/mit-license.html]. |
113 | |
114 | |
115 | === modified file 'debian/changelog' |
116 | --- debian/changelog 2008-05-26 15:35:04 +0000 |
117 | +++ debian/changelog 2014-02-15 16:49:34 +0000 |
118 | @@ -1,3 +1,32 @@ |
119 | +pydot (1.0.28-1) trusty; urgency=low |
120 | + |
121 | + * New upstream release. (LP: #574428, #987934) |
122 | + * debian/control updated to newest standards version: |
123 | + - Removed DM-Upload-Allowed, obsolete |
124 | + - Updated Vsc-Browser field |
125 | + - Depend on python-setuptools |
126 | + - Depend on pyparsing >= 2.0.1 to prevent incompability problems with |
127 | + debian/patches/03-support-pyparsing-v2.patch |
128 | + - Add ${misc:Depends} to binary package Depends |
129 | + - Support dh-python |
130 | + o Bumped cdbs version to 0.4.90 |
131 | + o Bumped python version to 2.6.6.3 |
132 | + o Removed XB-Python-Version field |
133 | + o Added X-Python-Version field |
134 | + o Removed ${python:Depends} from Depends |
135 | + * debian/rules: |
136 | + - do not include simple-patchsys.mk |
137 | + - Support dh-python: Remove DEB_PYTHON_SYSTEM |
138 | + * debian/pyversions: not required for dh-python |
139 | + * debian/pycompat: not required for dh-python |
140 | + * debian/source/format: added and set to 3.0 (quilt) |
141 | + * debian/patches/02-use-setuptools.patch: patched setup.py to use setuptools |
142 | + to prevent "Unknown distribution option: 'install_requires'" warning |
143 | + * debian/patches/03-support-pyparsing-v2.patch: add support for pyparsing |
144 | + version 2.X, fixes "Couldn't import dot_parser" error |
145 | + |
146 | + -- Maarten Bezemer <maarten.bezemer@gmail.com> Sat, 15 Feb 2014 14:01:16 +0100 |
147 | + |
148 | pydot (1.0.2-1) unstable; urgency=low |
149 | |
150 | * New upstream release. |
151 | |
152 | === modified file 'debian/control' |
153 | --- debian/control 2008-05-26 15:35:04 +0000 |
154 | +++ debian/control 2014-02-15 16:49:34 +0000 |
155 | @@ -3,19 +3,18 @@ |
156 | Priority: optional |
157 | Maintainer: Peter Collingbourne <pcc03@doc.ic.ac.uk> |
158 | Uploaders: Debian Python Modules Team <python-modules-team@lists.alioth.debian.org> |
159 | -Build-Depends: cdbs (>= 0.4.49), debhelper (>= 5.0.38), python (>= 2.4~), python-support (>= 0.5.3) |
160 | -Build-Depends-Indep: python-pyparsing (>= 1.4.10) |
161 | -Standards-Version: 3.7.3 |
162 | +Build-Depends: cdbs (>= 0.4.90~), debhelper (>= 5.0.38), python (>= 2.6.6.3~), dh-python, python-setuptools |
163 | +Build-Depends-Indep: python-pyparsing (>= 2.0.1) |
164 | +Standards-Version: 3.9.5 |
165 | Vcs-Svn: svn://svn.debian.org/python-modules/packages/pydot/trunk |
166 | -Vcs-Browser: http://svn.debian.org/wsvn/python-modules/packages/pydot/trunk/?op=log |
167 | -DM-Upload-Allowed: yes |
168 | +Vcs-Browser: http://svn.debian.org/wsvn/python-modules/packages/pydot/trunk |
169 | Homepage: http://dkbza.org/pydot.html |
170 | +X-Python-Version: >= 2.4 |
171 | |
172 | Package: python-pydot |
173 | Architecture: all |
174 | -Depends: ${python:Depends}, python-pyparsing (>= 1.4.10), graphviz |
175 | +Depends: ${misc:Depends}, ${python:Depends}, python-pyparsing (>= 2.0.1), graphviz |
176 | Conflicts: dot2tex (<< 2.8.0) |
177 | -XB-Python-Version: ${python:Versions} |
178 | Description: Python interface to Graphviz's dot |
179 | This package provides you with a full Python interface for creating, |
180 | handling, modifying and processing graphs in Graphviz's dot language. |
181 | |
182 | === modified file 'debian/patches/01-setup-py-nodata.patch' |
183 | --- debian/patches/01-setup-py-nodata.patch 2008-05-26 15:35:04 +0000 |
184 | +++ debian/patches/01-setup-py-nodata.patch 2014-02-15 16:49:34 +0000 |
185 | @@ -1,10 +1,11 @@ |
186 | -diff -Nur pydot-1.0.2/setup.py pydot-1.0.2.new/setup.py |
187 | ---- pydot-1.0.2/setup.py 2008-02-14 20:48:02.000000000 +0000 |
188 | -+++ pydot-1.0.2.new/setup.py 2008-02-22 03:08:11.000000000 +0000 |
189 | -@@ -27,5 +27,4 @@ |
190 | +Index: pydot/setup.py |
191 | +=================================================================== |
192 | +--- pydot.orig/setup.py 2014-02-15 14:10:12.113725634 +0100 |
193 | ++++ pydot/setup.py 2014-02-15 14:10:12.105725635 +0100 |
194 | +@@ -31,5 +31,4 @@ |
195 | 'Topic :: Software Development :: Libraries :: Python Modules'], |
196 | long_description = "\n".join(pydot.__doc__.split('\n')), |
197 | py_modules = ['pydot', 'dot_parser'], |
198 | - install_requires = ['pyparsing', 'setuptools'], |
199 | -- data_files = [('.', ['ChangeLog', 'LICENSE', 'README'])] ) |
200 | +- data_files = [('.', ['LICENSE', 'README'])] ) |
201 | + install_requires = ['pyparsing', 'setuptools'] ) |
202 | |
203 | === added file 'debian/patches/02-use-setuptools.patch' |
204 | --- debian/patches/02-use-setuptools.patch 1970-01-01 00:00:00 +0000 |
205 | +++ debian/patches/02-use-setuptools.patch 2014-02-15 16:49:34 +0000 |
206 | @@ -0,0 +1,20 @@ |
207 | +Fix Unknown distribution option: 'install_requires', information from: http://stackoverflow.com/q/9810603/246263 |
208 | +Author: Maarten Bezemer <maarten.bezemer@gmail.com> |
209 | +Last-Update: <2014-02-15> |
210 | + |
211 | +Index: pydot/setup.py |
212 | +=================================================================== |
213 | +--- pydot.orig/setup.py 2014-02-15 16:47:04.321340325 +0100 |
214 | ++++ pydot/setup.py 2014-02-15 16:47:04.317340325 +0100 |
215 | +@@ -1,9 +1,9 @@ |
216 | + #!/usr/bin/env python |
217 | + |
218 | + try: |
219 | +- from distutils.core import setup |
220 | +-except ImportError, excp: |
221 | + from setuptools import setup |
222 | ++except ImportError, excp: |
223 | ++ from distutils.core import setup |
224 | + |
225 | + import pydot |
226 | + import os |
227 | |
228 | === added file 'debian/patches/03-support-pyparsing-v2.patch' |
229 | --- debian/patches/03-support-pyparsing-v2.patch 1970-01-01 00:00:00 +0000 |
230 | +++ debian/patches/03-support-pyparsing-v2.patch 2014-02-15 16:49:34 +0000 |
231 | @@ -0,0 +1,18 @@ |
232 | +Support pyparsing version 2, information from http://code.google.com/p/pydot/issues/detail?id=81#c9 |
233 | +Author: Maarten Bezemer <maarten.bezemer@gmail.com> |
234 | +Last-Update: <2014-02-15> |
235 | +Index: pydot/dot_parser.py |
236 | +=================================================================== |
237 | +--- pydot.orig/dot_parser.py 2014-02-15 15:50:30.713479250 +0100 |
238 | ++++ pydot/dot_parser.py 2014-02-15 15:50:30.709479250 +0100 |
239 | +@@ -25,8 +25,9 @@ |
240 | + from pyparsing import ( nestedExpr, Literal, CaselessLiteral, Word, Upcase, OneOrMore, ZeroOrMore, |
241 | + Forward, NotAny, delimitedList, oneOf, Group, Optional, Combine, alphas, nums, |
242 | + restOfLine, cStyleComment, nums, alphanums, printables, empty, quotedString, |
243 | +- ParseException, ParseResults, CharsNotIn, _noncomma, dblQuotedString, QuotedString, ParserElement ) |
244 | ++ ParseException, ParseResults, CharsNotIn, dblQuotedString, QuotedString, ParserElement ) |
245 | + |
246 | ++_noncomma = "".join( [ c for c in printables if c != "," ] ) |
247 | + |
248 | + class P_AttrList: |
249 | + |
250 | |
251 | === added file 'debian/patches/series' |
252 | --- debian/patches/series 1970-01-01 00:00:00 +0000 |
253 | +++ debian/patches/series 2014-02-15 16:49:34 +0000 |
254 | @@ -0,0 +1,3 @@ |
255 | +01-setup-py-nodata.patch |
256 | +02-use-setuptools.patch |
257 | +03-support-pyparsing-v2.patch |
258 | |
259 | === removed file 'debian/pycompat' |
260 | --- debian/pycompat 2006-11-08 15:52:04 +0000 |
261 | +++ debian/pycompat 1970-01-01 00:00:00 +0000 |
262 | @@ -1,1 +0,0 @@ |
263 | -2 |
264 | |
265 | === removed file 'debian/pyversions' |
266 | --- debian/pyversions 2008-05-26 15:35:04 +0000 |
267 | +++ debian/pyversions 1970-01-01 00:00:00 +0000 |
268 | @@ -1,1 +0,0 @@ |
269 | -2.4- |
270 | |
271 | === modified file 'debian/rules' |
272 | --- debian/rules 2008-05-26 15:35:04 +0000 |
273 | +++ debian/rules 2014-02-15 16:49:34 +0000 |
274 | @@ -1,7 +1,4 @@ |
275 | #!/usr/bin/make -f |
276 | |
277 | -DEB_PYTHON_SYSTEM = pysupport |
278 | - |
279 | include /usr/share/cdbs/1/rules/debhelper.mk |
280 | include /usr/share/cdbs/1/class/python-distutils.mk |
281 | -include /usr/share/cdbs/1/rules/simple-patchsys.mk |
282 | |
283 | === added directory 'debian/source' |
284 | === added file 'debian/source/format' |
285 | --- debian/source/format 1970-01-01 00:00:00 +0000 |
286 | +++ debian/source/format 2014-02-15 16:49:34 +0000 |
287 | @@ -0,0 +1,1 @@ |
288 | +3.0 (quilt) |
289 | |
290 | === modified file 'dot_parser.py' |
291 | --- dot_parser.py 2008-05-26 15:35:04 +0000 |
292 | +++ dot_parser.py 2014-02-15 16:49:34 +0000 |
293 | @@ -4,9 +4,10 @@ |
294 | The dotparser parses graphviz files in dot and dot files and transforms them |
295 | into a class representation defined by pydot. |
296 | |
297 | -The module needs pyparsing (tested with version 1.2.2) and pydot (tested with 0.9.10) |
298 | +The module needs pyparsing (tested with version 1.2.2) and pydot |
299 | |
300 | Author: Michael Krause <michael@krause-software.de> |
301 | +Fixes by: Ero Carrera <ero@dkbza.org> |
302 | """ |
303 | |
304 | __author__ = ['Michael Krause', 'Ero Carrera'] |
305 | @@ -17,6 +18,7 @@ |
306 | import glob |
307 | import pydot |
308 | import re |
309 | +import codecs |
310 | |
311 | from pyparsing import __version__ as pyparsing_version |
312 | |
313 | @@ -26,7 +28,6 @@ |
314 | ParseException, ParseResults, CharsNotIn, _noncomma, dblQuotedString, QuotedString, ParserElement ) |
315 | |
316 | |
317 | - |
318 | class P_AttrList: |
319 | |
320 | def __init__(self, toks): |
321 | @@ -35,12 +36,17 @@ |
322 | i = 0 |
323 | |
324 | while i < len(toks): |
325 | - |
326 | attrname = toks[i] |
327 | - attrvalue = toks[i+1] |
328 | + if i+2 < len(toks) and toks[i+1] == '=': |
329 | + attrvalue = toks[i+2] |
330 | + i += 3 |
331 | + else: |
332 | + attrvalue = None |
333 | + i += 1 |
334 | + |
335 | self.attrs[attrname] = attrvalue |
336 | - i += 2 |
337 | - |
338 | + |
339 | + |
340 | def __repr__(self): |
341 | |
342 | return "%s(%r)" % (self.__class__.__name__, self.attrs) |
343 | @@ -107,7 +113,9 @@ |
344 | else: |
345 | raise ValueError, "Unknown element statement: %r " % element |
346 | |
347 | - |
348 | + |
349 | + for g in top_graphs: |
350 | + update_parent_graph_hierarchy(g) |
351 | |
352 | if len( top_graphs ) == 1: |
353 | return top_graphs[0] |
354 | @@ -115,6 +123,42 @@ |
355 | return top_graphs |
356 | |
357 | |
358 | +def update_parent_graph_hierarchy(g, parent_graph=None, level=0): |
359 | + |
360 | + |
361 | + if parent_graph is None: |
362 | + parent_graph = g |
363 | + |
364 | + for key_name in ('edges',): |
365 | + |
366 | + if isinstance(g, pydot.frozendict): |
367 | + item_dict = g |
368 | + else: |
369 | + item_dict = g.obj_dict |
370 | + |
371 | + if not item_dict.has_key( key_name ): |
372 | + continue |
373 | + |
374 | + for key, objs in item_dict[key_name].items(): |
375 | + for obj in objs: |
376 | + if 'parent_graph' in obj and obj['parent_graph'].get_parent_graph()==g: |
377 | + if obj['parent_graph'] is g: |
378 | + pass |
379 | + else: |
380 | + obj['parent_graph'].set_parent_graph(parent_graph) |
381 | + |
382 | + if key_name == 'edges' and len(key) == 2: |
383 | + for idx, vertex in enumerate( obj['points'] ): |
384 | + if isinstance( vertex, (pydot.Graph, pydot.Subgraph, pydot.Cluster)): |
385 | + vertex.set_parent_graph(parent_graph) |
386 | + if isinstance( vertex, pydot.frozendict): |
387 | + if vertex['parent_graph'] is g: |
388 | + pass |
389 | + else: |
390 | + vertex['parent_graph'].set_parent_graph(parent_graph) |
391 | + |
392 | + |
393 | + |
394 | def add_defaults(element, defaults): |
395 | |
396 | d = element.__dict__ |
397 | @@ -134,7 +178,7 @@ |
398 | defaults_edge = {} |
399 | |
400 | for elm_idx, element in enumerate(toks): |
401 | - |
402 | + |
403 | if isinstance(element, (pydot.Subgraph, pydot.Cluster)): |
404 | |
405 | add_defaults(element, defaults_graph) |
406 | @@ -191,14 +235,19 @@ |
407 | return g |
408 | |
409 | |
410 | -def push_subgraph_stmt(str, loc, toks): |
411 | +def push_subgraph_stmt(str, loc, toks): |
412 | |
413 | g = pydot.Subgraph('') |
414 | - |
415 | for e in toks: |
416 | if len(e)==3: |
417 | - g = e[2] |
418 | - g.set_name(e[1]) |
419 | + e[2].set_name(e[1]) |
420 | + if e[0] == 'subgraph': |
421 | + e[2].obj_dict['show_keyword'] = True |
422 | + return e[2] |
423 | + else: |
424 | + if e[0] == 'subgraph': |
425 | + e[1].obj_dict['show_keyword'] = True |
426 | + return e[1] |
427 | |
428 | return g |
429 | |
430 | @@ -208,7 +257,6 @@ |
431 | # The pydot class instances should be marked as |
432 | # default statements to be inherited by actual |
433 | # graphs, nodes and edges. |
434 | - # print "push_default_stmt", toks |
435 | # |
436 | default_type = toks[0][0] |
437 | if len(toks) > 1: |
438 | @@ -362,23 +410,21 @@ |
439 | |
440 | # token definitions |
441 | |
442 | - identifier = Word(alphanums + "_" ).setName("identifier") |
443 | + identifier = Word(alphanums + "_." ).setName("identifier") |
444 | |
445 | double_quoted_string = QuotedString('"', multiline=True, unquoteResults=False) # dblQuotedString |
446 | |
447 | - alphastring_ = OneOrMore(CharsNotIn(_noncomma)) |
448 | + alphastring_ = OneOrMore(CharsNotIn(_noncomma + ' ')) |
449 | |
450 | def parse_html(s, loc, toks): |
451 | - |
452 | return '<%s>' % ''.join(toks[0]) |
453 | |
454 | |
455 | opener = '<' |
456 | closer = '>' |
457 | html_text = nestedExpr( opener, closer, |
458 | - ( CharsNotIn( |
459 | - opener + closer ).setParseAction( lambda t:t[0] )) |
460 | - ).setParseAction(parse_html) |
461 | + ( CharsNotIn( opener + closer ) ) |
462 | + ).setParseAction(parse_html).leaveWhitespace() |
463 | |
464 | ID = ( identifier | html_text | |
465 | double_quoted_string | #.setParseAction(strip_quotes) | |
466 | @@ -399,16 +445,16 @@ |
467 | Group(port_angle + Optional(port_location))).setName("port") |
468 | |
469 | node_id = (ID + Optional(port)) |
470 | - a_list = OneOrMore(ID + Optional(equals.suppress() + righthand_id) + |
471 | + a_list = OneOrMore(ID + Optional(equals + righthand_id) + |
472 | Optional(comma.suppress())).setName("a_list") |
473 | - |
474 | + |
475 | attr_list = OneOrMore(lbrack.suppress() + Optional(a_list) + |
476 | rbrack.suppress()).setName("attr_list") |
477 | - |
478 | + |
479 | attr_stmt = (Group(graph_ | node_ | edge_) + attr_list).setName("attr_stmt") |
480 | - |
481 | + |
482 | edgeop = (Literal("--") | Literal("->")).setName("edgeop") |
483 | - |
484 | + |
485 | stmt_list = Forward() |
486 | graph_stmt = Group(lbrace.suppress() + Optional(stmt_list) + |
487 | rbrace.suppress() + Optional(semi.suppress()) ).setName("graph_stmt") |
488 | @@ -421,17 +467,17 @@ |
489 | |
490 | subgraph = Group(subgraph_ + Optional(ID) + graph_stmt).setName("subgraph") |
491 | |
492 | - edge_point << Group(subgraph | graph_stmt | node_id ) |
493 | - |
494 | + edge_point << Group( subgraph | graph_stmt | node_id ).setName('edge_point') |
495 | + |
496 | node_stmt = (node_id + Optional(attr_list) + Optional(semi.suppress())).setName("node_stmt") |
497 | |
498 | - assignment = (ID + equals.suppress() + righthand_id).setName("assignment") |
499 | + assignment = (ID + equals + righthand_id).setName("assignment") |
500 | stmt = (assignment | edge_stmt | attr_stmt | subgraph | graph_stmt | node_stmt).setName("stmt") |
501 | stmt_list << OneOrMore(stmt + Optional(semi.suppress())) |
502 | - |
503 | - graphparser = OneOrMore( (Optional(strict_) + Group((graph_ | digraph_)) + |
504 | + |
505 | + graphparser = OneOrMore( (Optional(strict_) + Group((graph_ | digraph_)) + |
506 | Optional(ID) + graph_stmt).setResultsName("graph") ) |
507 | - |
508 | + |
509 | singleLineComment = Group("//" + restOfLine) | Group("#" + restOfLine) |
510 | |
511 | |
512 | @@ -439,18 +485,18 @@ |
513 | |
514 | graphparser.ignore(singleLineComment) |
515 | graphparser.ignore(cStyleComment) |
516 | - |
517 | + |
518 | assignment.setParseAction(push_attr_list) |
519 | a_list.setParseAction(push_attr_list) |
520 | edge_stmt.setParseAction(push_edge_stmt) |
521 | node_stmt.setParseAction(push_node_stmt) |
522 | attr_stmt.setParseAction(push_default_stmt) |
523 | - |
524 | + |
525 | subgraph.setParseAction(push_subgraph_stmt) |
526 | graph_stmt.setParseAction(push_graph_stmt) |
527 | graphparser.setParseAction(push_top_graph_stmt) |
528 | - |
529 | |
530 | + |
531 | return graphparser |
532 | |
533 | |
534 | @@ -460,6 +506,9 @@ |
535 | |
536 | top_graphs = list() |
537 | |
538 | + if data.startswith(codecs.BOM_UTF8): |
539 | + data = data.decode( 'utf-8' ) |
540 | + |
541 | try: |
542 | |
543 | graphparser = graph_definition() |
544 | |
545 | === removed directory 'pydot.egg-info' |
546 | === removed file 'pydot.egg-info/PKG-INFO' |
547 | --- pydot.egg-info/PKG-INFO 2008-05-26 15:35:04 +0000 |
548 | +++ pydot.egg-info/PKG-INFO 1970-01-01 00:00:00 +0000 |
549 | @@ -1,35 +0,0 @@ |
550 | -Metadata-Version: 1.0 |
551 | -Name: pydot |
552 | -Version: 1.0.2 |
553 | -Summary: Python interface to Graphviz's Dot |
554 | -Home-page: http://code.google.com/p/pydot/ |
555 | -Author: Ero Carrera |
556 | -Author-email: ero@dkbza.org |
557 | -License: MIT |
558 | -Download-URL: http://pydot.googlecode.com/files/pydot-1.0.2.tar.gz |
559 | -Description: Graphviz's dot language Python interface. |
560 | - |
561 | - This module provides with a full interface to create handle modify |
562 | - and process graphs in Graphviz's dot language. |
563 | - |
564 | - References: |
565 | - |
566 | - pydot Homepage: http://www.dkbza.org/pydot.html |
567 | - Graphviz: http://www.research.att.com/sw/tools/graphviz/ |
568 | - DOT Language: http://www.research.att.com/~erg/graphviz/info/lang.html |
569 | - |
570 | - Programmed and tested with Graphviz 1.16 and Python 2.3.4 on GNU/Linux |
571 | - by Ero Carrera (c) 2004 [ero@dkbza.org] |
572 | - |
573 | - Distributed under MIT license [http://opensource.org/licenses/mit-license.html]. |
574 | - |
575 | -Keywords: graphviz dot graphs visualization |
576 | -Platform: any |
577 | -Classifier: Development Status :: 5 - Production/Stable |
578 | -Classifier: Intended Audience :: Science/Research |
579 | -Classifier: License :: OSI Approved :: MIT License |
580 | -Classifier: Natural Language :: English |
581 | -Classifier: Operating System :: OS Independent |
582 | -Classifier: Programming Language :: Python |
583 | -Classifier: Topic :: Scientific/Engineering :: Visualization |
584 | -Classifier: Topic :: Software Development :: Libraries :: Python Modules |
585 | |
586 | === removed file 'pydot.egg-info/SOURCES.txt' |
587 | --- pydot.egg-info/SOURCES.txt 2008-05-26 15:35:04 +0000 |
588 | +++ pydot.egg-info/SOURCES.txt 1970-01-01 00:00:00 +0000 |
589 | @@ -1,11 +0,0 @@ |
590 | -ChangeLog |
591 | -LICENSE |
592 | -README |
593 | -dot_parser.py |
594 | -pydot.py |
595 | -setup.py |
596 | -pydot.egg-info/PKG-INFO |
597 | -pydot.egg-info/SOURCES.txt |
598 | -pydot.egg-info/dependency_links.txt |
599 | -pydot.egg-info/requires.txt |
600 | -pydot.egg-info/top_level.txt |
601 | |
602 | === removed file 'pydot.egg-info/dependency_links.txt' |
603 | --- pydot.egg-info/dependency_links.txt 2008-05-26 15:35:04 +0000 |
604 | +++ pydot.egg-info/dependency_links.txt 1970-01-01 00:00:00 +0000 |
605 | @@ -1,1 +0,0 @@ |
606 | - |
607 | |
608 | === removed file 'pydot.egg-info/requires.txt' |
609 | --- pydot.egg-info/requires.txt 2008-05-26 15:35:04 +0000 |
610 | +++ pydot.egg-info/requires.txt 1970-01-01 00:00:00 +0000 |
611 | @@ -1,2 +0,0 @@ |
612 | -pyparsing |
613 | -setuptools |
614 | \ No newline at end of file |
615 | |
616 | === removed file 'pydot.egg-info/top_level.txt' |
617 | --- pydot.egg-info/top_level.txt 2008-05-26 15:35:04 +0000 |
618 | +++ pydot.egg-info/top_level.txt 1970-01-01 00:00:00 +0000 |
619 | @@ -1,2 +0,0 @@ |
620 | -dot_parser |
621 | -pydot |
622 | |
623 | === modified file 'pydot.py' |
624 | --- pydot.py 2008-05-26 15:35:04 +0000 |
625 | +++ pydot.py 2014-02-15 16:49:34 +0000 |
626 | @@ -6,18 +6,20 @@ |
627 | |
628 | References: |
629 | |
630 | -pydot Homepage: http://www.dkbza.org/pydot.html |
631 | -Graphviz: http://www.research.att.com/sw/tools/graphviz/ |
632 | -DOT Language: http://www.research.att.com/~erg/graphviz/info/lang.html |
633 | - |
634 | -Programmed and tested with Graphviz 1.16 and Python 2.3.4 on GNU/Linux |
635 | -by Ero Carrera (c) 2004 [ero@dkbza.org] |
636 | +pydot Homepage: http://code.google.com/p/pydot/ |
637 | +Graphviz: http://www.graphviz.org/ |
638 | +DOT Language: http://www.graphviz.org/doc/info/lang.html |
639 | + |
640 | +Programmed and tested with Graphviz 2.26.3 and Python 2.6 on OSX 10.6.4 |
641 | + |
642 | +Copyright (c) 2005-2011 Ero Carrera <ero.carrera@gmail.com> |
643 | |
644 | Distributed under MIT license [http://opensource.org/licenses/mit-license.html]. |
645 | """ |
646 | |
647 | +__revision__ = "$LastChangedRevision: 28 $" |
648 | __author__ = 'Ero Carrera' |
649 | -__version__ = '1.0.2' |
650 | +__version__ = '1.0.%d' % int( __revision__[21:-2] ) |
651 | __license__ = 'MIT' |
652 | |
653 | import os |
654 | @@ -32,51 +34,53 @@ |
655 | |
656 | |
657 | |
658 | -GRAPH_ATTRIBUTES = set( ['Damping', 'K', 'URL', 'bb', 'bgcolor', 'center', 'charset', |
659 | - 'clusterrank', 'colorscheme', 'comment', 'compound', 'concentrate', |
660 | - 'defaultdist', 'dim', 'diredgeconstraints', 'dpi', 'epsilon', 'esep', |
661 | - 'fontcolor', 'fontname', 'fontnames', 'fontpath', 'fontsize', 'label', |
662 | - 'labeljust', 'labelloc', 'landscape', 'layers', 'layersep', 'levelsgap', |
663 | - 'lp', 'margin', 'maxiter', 'mclimit', 'mindist', 'mode', 'model', |
664 | - 'mosek', 'nodesep', 'nojustify', 'normalize', 'nslimit', 'nslimit1', 'ordering', |
665 | - 'orientation', 'outputorder', 'overlap', 'pack', 'packmode', 'pad', |
666 | - 'page', 'pagedir', 'quantum', 'rankdir', 'ranksep', 'ratio', 'remincross', |
667 | - 'resolution', 'root', 'rotate', 'searchsize', 'sep', 'showboxes', 'size', |
668 | - 'splines', 'start', 'stylesheet', 'target', 'truecolor', 'viewport', |
669 | - 'voro_margin'] + ['rank'] ) |
670 | - |
671 | - |
672 | -EDGE_ATTRIBUTES = set( ['URL', 'arrowhead', 'arrowsize', 'arrowtail', 'color', |
673 | - 'colorscheme', 'comment', 'constraint', 'decorate', 'dir', |
674 | - 'edgeURL', 'edgehref', 'edgetarget', 'edgetooltip', |
675 | - 'fontcolor', 'fontname', 'fontsize', 'headURL', 'headclip', |
676 | - 'headhref', 'headlabel', 'headport', 'headtarget', |
677 | - 'headtooltip', 'href', 'label', 'labelURL', 'labelangle', |
678 | - 'labeldistance', 'labelfloat', 'labelfontcolor', |
679 | +GRAPH_ATTRIBUTES = set( ['Damping', 'K', 'URL', 'aspect', 'bb', 'bgcolor', |
680 | + 'center', 'charset', 'clusterrank', 'colorscheme', 'comment', 'compound', |
681 | + 'concentrate', 'defaultdist', 'dim', 'dimen', 'diredgeconstraints', |
682 | + 'dpi', 'epsilon', 'esep', 'fontcolor', 'fontname', 'fontnames', |
683 | + 'fontpath', 'fontsize', 'id', 'label', 'labeljust', 'labelloc', |
684 | + 'landscape', 'layers', 'layersep', 'layout', 'levels', 'levelsgap', |
685 | + 'lheight', 'lp', 'lwidth', 'margin', 'maxiter', 'mclimit', 'mindist', |
686 | + 'mode', 'model', 'mosek', 'nodesep', 'nojustify', 'normalize', 'nslimit', |
687 | + 'nslimit1', 'ordering', 'orientation', 'outputorder', 'overlap', |
688 | + 'overlap_scaling', 'pack', 'packmode', 'pad', 'page', 'pagedir', |
689 | + 'quadtree', 'quantum', 'rankdir', 'ranksep', 'ratio', 'remincross', |
690 | + 'repulsiveforce', 'resolution', 'root', 'rotate', 'searchsize', 'sep', |
691 | + 'showboxes', 'size', 'smoothing', 'sortv', 'splines', 'start', |
692 | + 'stylesheet', 'target', 'truecolor', 'viewport', 'voro_margin', |
693 | + # for subgraphs |
694 | + 'rank' ] ) |
695 | + |
696 | + |
697 | +EDGE_ATTRIBUTES = set( ['URL', 'arrowhead', 'arrowsize', 'arrowtail', |
698 | + 'color', 'colorscheme', 'comment', 'constraint', 'decorate', 'dir', |
699 | + 'edgeURL', 'edgehref', 'edgetarget', 'edgetooltip', 'fontcolor', |
700 | + 'fontname', 'fontsize', 'headURL', 'headclip', 'headhref', 'headlabel', |
701 | + 'headport', 'headtarget', 'headtooltip', 'href', 'id', 'label', |
702 | + 'labelURL', 'labelangle', 'labeldistance', 'labelfloat', 'labelfontcolor', |
703 | 'labelfontname', 'labelfontsize', 'labelhref', 'labeltarget', |
704 | - 'labeltooltip', 'layer', 'len', 'lhead', 'lp', 'ltail', |
705 | - 'minlen', 'nojustify', 'pos', 'samehead', 'sametail', |
706 | - 'showboxes', 'style', 'tailURL', 'tailclip', 'tailhref', |
707 | - 'taillabel', 'tailport', 'tailtarget', 'tailtooltip', |
708 | - 'target', 'tooltip', 'weight'] + ['rank'] ) |
709 | + 'labeltooltip', 'layer', 'len', 'lhead', 'lp', 'ltail', 'minlen', |
710 | + 'nojustify', 'penwidth', 'pos', 'samehead', 'sametail', 'showboxes', |
711 | + 'style', 'tailURL', 'tailclip', 'tailhref', 'taillabel', 'tailport', |
712 | + 'tailtarget', 'tailtooltip', 'target', 'tooltip', 'weight', |
713 | + 'rank' ] ) |
714 | |
715 | |
716 | NODE_ATTRIBUTES = set( ['URL', 'color', 'colorscheme', 'comment', |
717 | 'distortion', 'fillcolor', 'fixedsize', 'fontcolor', 'fontname', |
718 | - 'fontsize', 'group', 'height', 'image', 'imagescale', 'label', |
719 | - 'layer', 'margin', 'nojustify', 'orientation', 'peripheries', |
720 | - 'pin', 'pos', 'rects', 'regular', 'root', 'samplepoints', |
721 | - 'shape', 'shapefile', 'showboxes', 'sides', 'skew', 'style', |
722 | + 'fontsize', 'group', 'height', 'id', 'image', 'imagescale', 'label', |
723 | + 'labelloc', 'layer', 'margin', 'nojustify', 'orientation', 'penwidth', |
724 | + 'peripheries', 'pin', 'pos', 'rects', 'regular', 'root', 'samplepoints', |
725 | + 'shape', 'shapefile', 'showboxes', 'sides', 'skew', 'sortv', 'style', |
726 | 'target', 'tooltip', 'vertices', 'width', 'z', |
727 | - |
728 | # The following are attributes dot2tex |
729 | 'texlbl', 'texmode' ] ) |
730 | |
731 | |
732 | -CLUSTER_ATTRIBUTES = set( ['K', 'URL', 'bgcolor', 'color', 'colorscheme', 'fillcolor', |
733 | - 'fontcolor', 'fontname', 'fontsize', 'label', 'labeljust', |
734 | - 'labelloc', 'lp', 'nojustify', 'pencolor', 'peripheries', |
735 | - 'style', 'target', 'tooltip'] ) |
736 | +CLUSTER_ATTRIBUTES = set( ['K', 'URL', 'bgcolor', 'color', 'colorscheme', |
737 | + 'fillcolor', 'fontcolor', 'fontname', 'fontsize', 'label', 'labeljust', |
738 | + 'labelloc', 'lheight', 'lp', 'lwidth', 'nojustify', 'pencolor', |
739 | + 'penwidth', 'peripheries', 'sortv', 'style', 'target', 'tooltip'] ) |
740 | |
741 | |
742 | # |
743 | @@ -101,8 +105,10 @@ |
744 | for arg in args: |
745 | if isinstance(arg, dict): |
746 | arg = copy.copy(arg) |
747 | - for k, v in arg.items(): |
748 | - if isinstance(v, dict): |
749 | + for k, v in arg.iteritems(): |
750 | + if isinstance(v, frozendict): |
751 | + arg[k] = v |
752 | + elif isinstance(v, dict): |
753 | arg[k] = frozendict(v) |
754 | elif isinstance(v, list): |
755 | v_ = list() |
756 | @@ -126,7 +132,7 @@ |
757 | try: |
758 | return self._cached_hash |
759 | except AttributeError: |
760 | - h = self._cached_hash = hash(tuple(sorted(self.items()))) |
761 | + h = self._cached_hash = hash(tuple(sorted(self.iteritems()))) |
762 | return h |
763 | |
764 | def __repr__(self): |
765 | @@ -135,11 +141,12 @@ |
766 | |
767 | dot_keywords = ['graph', 'subgraph', 'digraph', 'node', 'edge', 'strict'] |
768 | |
769 | -id_re_alpha_nums = re.compile('^[_a-zA-Z][a-zA-Z0-9_:,]*$') |
770 | -id_re_num = re.compile('^[0-9]+$') |
771 | -id_re_with_port = re.compile('^.*:([^"]+|[^"]*\"[^"]*\"[^"]*)$') |
772 | -id_re_dbl_quoted = re.compile('^\".*\"$', re.S) |
773 | -id_re_html = re.compile('^<.*>$', re.S) |
774 | +id_re_alpha_nums = re.compile('^[_a-zA-Z][a-zA-Z0-9_,]*$', re.UNICODE) |
775 | +id_re_alpha_nums_with_ports = re.compile('^[_a-zA-Z][a-zA-Z0-9_,:\"]*[a-zA-Z0-9_,\"]+$', re.UNICODE) |
776 | +id_re_num = re.compile('^[0-9,]+$', re.UNICODE) |
777 | +id_re_with_port = re.compile('^([^:]*):([^:]*)$', re.UNICODE) |
778 | +id_re_dbl_quoted = re.compile('^\".*\"$', re.S|re.UNICODE) |
779 | +id_re_html = re.compile('^<.*>$', re.S|re.UNICODE) |
780 | |
781 | |
782 | def needs_quotes( s ): |
783 | @@ -148,40 +155,54 @@ |
784 | It will check whether the string is solely composed |
785 | by the characters allowed in an ID or not. |
786 | If the string is one of the reserved keywords it will |
787 | - need quotes too. |
788 | + need quotes too but the user will need to add them |
789 | + manually. |
790 | """ |
791 | - |
792 | + |
793 | + # If the name is a reserved keyword it will need quotes but pydot |
794 | + # can't tell when it's being used as a keyword or when it's simply |
795 | + # a name. Hence the user needs to supply the quotes when an element |
796 | + # would use a reserved keyword as name. This function will return |
797 | + # false indicating that a keyword string, if provided as-is, won't |
798 | + # need quotes. |
799 | if s in dot_keywords: |
800 | return False |
801 | |
802 | chars = [ord(c) for c in s if ord(c)>0x7f or ord(c)==0] |
803 | - if chars: |
804 | - return False |
805 | + if chars and not id_re_dbl_quoted.match(s) and not id_re_html.match(s): |
806 | + return True |
807 | |
808 | - res = id_re_alpha_nums.match(s) |
809 | - if not res: |
810 | - res = id_re_num.match(s) |
811 | - if not res: |
812 | - res = id_re_dbl_quoted.match(s) |
813 | - if not res: |
814 | - res = id_re_html.match(s) |
815 | - if not res: |
816 | - res = id_re_with_port.match(s) |
817 | - |
818 | - if not res: |
819 | - return True |
820 | - |
821 | - return False |
822 | - |
823 | + for test_re in [id_re_alpha_nums, id_re_num, id_re_dbl_quoted, id_re_html, id_re_alpha_nums_with_ports]: |
824 | + if test_re.match(s): |
825 | + return False |
826 | + |
827 | + m = id_re_with_port.match(s) |
828 | + if m: |
829 | + return needs_quotes(m.group(1)) or needs_quotes(m.group(2)) |
830 | + |
831 | + return True |
832 | |
833 | |
834 | def quote_if_necessary(s): |
835 | |
836 | + if isinstance(s, bool): |
837 | + if s is True: |
838 | + return 'True' |
839 | + return 'False' |
840 | + |
841 | if not isinstance( s, basestring ): |
842 | return s |
843 | |
844 | + if not s: |
845 | + return s |
846 | + |
847 | if needs_quotes(s): |
848 | - |
849 | + replace = {'"' : r'\"', |
850 | + "\n" : r'\n', |
851 | + "\r" : r'\r'} |
852 | + for (a,b) in replace.items(): |
853 | + s = s.replace(a, b) |
854 | + |
855 | return '"' + s + '"' |
856 | |
857 | return s |
858 | @@ -233,8 +254,18 @@ |
859 | graph = Dot(graph_type='graph') |
860 | |
861 | for edge in edge_list: |
862 | + |
863 | + if isinstance(edge[0], str): |
864 | + src = node_prefix + edge[0] |
865 | + else: |
866 | + src = node_prefix + str(edge[0]) |
867 | + |
868 | + if isinstance(edge[1], str): |
869 | + dst = node_prefix + edge[1] |
870 | + else: |
871 | + dst = node_prefix + str(edge[1]) |
872 | |
873 | - e = Edge( node_prefix + edge[0], node_prefix + edge[1] ) |
874 | + e = Edge( src, dst ) |
875 | graph.add_edge(e) |
876 | |
877 | return graph |
878 | @@ -328,7 +359,7 @@ |
879 | """ |
880 | |
881 | success = False |
882 | - progs = {'dot': '', 'twopi': '', 'neato': '', 'circo': '', 'fdp': ''} |
883 | + progs = {'dot': '', 'twopi': '', 'neato': '', 'circo': '', 'fdp': '', 'sfdp': ''} |
884 | |
885 | was_quoted = False |
886 | path = path.strip() |
887 | @@ -338,7 +369,7 @@ |
888 | |
889 | if os.path.isdir(path) : |
890 | |
891 | - for prg in progs.keys(): |
892 | + for prg in progs.iterkeys(): |
893 | |
894 | if progs[prg]: |
895 | continue |
896 | @@ -399,30 +430,89 @@ |
897 | # Method 1 (Windows only) |
898 | # |
899 | if os.sys.platform == 'win32': |
900 | - |
901 | + |
902 | + HKEY_LOCAL_MACHINE = 0x80000002 |
903 | + KEY_QUERY_VALUE = 0x0001 |
904 | + |
905 | + RegOpenKeyEx = None |
906 | + RegQueryValueEx = None |
907 | + RegCloseKey = None |
908 | + |
909 | try: |
910 | import win32api, win32con |
911 | + RegOpenKeyEx = win32api.RegOpenKeyEx |
912 | + RegQueryValueEx = win32api.RegQueryValueEx |
913 | + RegCloseKey = win32api.RegCloseKey |
914 | + |
915 | + except ImportError: |
916 | + # Print a messaged suggesting they install these? |
917 | + # |
918 | + pass |
919 | + |
920 | + try: |
921 | + import ctypes |
922 | + |
923 | + def RegOpenKeyEx(key, subkey, opt, sam): |
924 | + result = ctypes.c_uint(0) |
925 | + ctypes.windll.advapi32.RegOpenKeyExA(key, subkey, opt, sam, ctypes.byref(result)) |
926 | + return result.value |
927 | + |
928 | + def RegQueryValueEx( hkey, valuename ): |
929 | + data_type = ctypes.c_uint(0) |
930 | + data_len = ctypes.c_uint(1024) |
931 | + data = ctypes.create_string_buffer( 1024 ) |
932 | + |
933 | + res = ctypes.windll.advapi32.RegQueryValueExA(hkey, valuename, 0, |
934 | + ctypes.byref(data_type), data, ctypes.byref(data_len)) |
935 | + |
936 | + return data.value |
937 | + |
938 | + RegCloseKey = ctypes.windll.advapi32.RegCloseKey |
939 | + |
940 | + except ImportError: |
941 | + # Print a messaged suggesting they install these? |
942 | + # |
943 | + pass |
944 | + |
945 | + if RegOpenKeyEx is not None: |
946 | |
947 | # Get the GraphViz install path from the registry |
948 | # |
949 | - hkey = win32api.RegOpenKeyEx( win32con.HKEY_LOCAL_MACHINE, |
950 | - "SOFTWARE\ATT\Graphviz", 0, win32con.KEY_QUERY_VALUE ) |
951 | - |
952 | - path = win32api.RegQueryValueEx( hkey, "InstallPath" )[0] |
953 | - win32api.RegCloseKey( hkey ) |
954 | - |
955 | - # Now append the "bin" subdirectory: |
956 | - # |
957 | - path = os.path.join(path, "bin") |
958 | - progs = __find_executables(path) |
959 | - if progs is not None : |
960 | - #print "Used Windows registry" |
961 | - return progs |
962 | - |
963 | - except ImportError : |
964 | - # Print a messaged suggesting they install these? |
965 | - # |
966 | - pass |
967 | + hkey = None |
968 | + potentialKeys = [ |
969 | + "SOFTWARE\\ATT\\Graphviz", |
970 | + "SOFTWARE\\AT&T Research Labs\\Graphviz", |
971 | + ] |
972 | + for potentialKey in potentialKeys: |
973 | + |
974 | + try: |
975 | + hkey = RegOpenKeyEx( HKEY_LOCAL_MACHINE, |
976 | + potentialKey, 0, KEY_QUERY_VALUE ) |
977 | + |
978 | + if hkey is not None: |
979 | + path = RegQueryValueEx( hkey, "InstallPath" ) |
980 | + RegCloseKey( hkey ) |
981 | + |
982 | + # The regitry variable might exist, left by old installations |
983 | + # but with no value, in those cases we keep searching... |
984 | + if not path: |
985 | + continue |
986 | + |
987 | + # Now append the "bin" subdirectory: |
988 | + # |
989 | + path = os.path.join(path, "bin") |
990 | + progs = __find_executables(path) |
991 | + if progs is not None : |
992 | + #print "Used Windows registry" |
993 | + return progs |
994 | + |
995 | + except Exception, excp: |
996 | + #raise excp |
997 | + pass |
998 | + else: |
999 | + break |
1000 | + |
1001 | + |
1002 | |
1003 | # Method 2 (Linux, Windows etc) |
1004 | # |
1005 | @@ -464,6 +554,7 @@ |
1006 | |
1007 | for path in ( |
1008 | '/usr/bin', '/usr/local/bin', |
1009 | + '/opt/local/bin', |
1010 | '/opt/bin', '/sw/bin', '/usr/share', |
1011 | '/Applications/Graphviz.app/Contents/MacOS/' ): |
1012 | |
1013 | @@ -487,20 +578,14 @@ |
1014 | |
1015 | def __getstate__(self): |
1016 | |
1017 | - dict = copy.copy(self.__dict__) |
1018 | - for attr in self.attributes.keys(): |
1019 | - |
1020 | - del dict['set_'+attr] |
1021 | - del dict['get_'+attr] |
1022 | + dict = copy.copy(self.obj_dict) |
1023 | |
1024 | return dict |
1025 | |
1026 | |
1027 | def __setstate__(self, state): |
1028 | |
1029 | - for k, v in state.items(): |
1030 | - |
1031 | - self.__setattr__(k, v) |
1032 | + self.obj_dict = state |
1033 | |
1034 | |
1035 | def __get_attribute__(self, attr): |
1036 | @@ -517,9 +602,25 @@ |
1037 | if default_node_name in ('subgraph', 'digraph', 'cluster'): |
1038 | default_node_name = 'graph' |
1039 | |
1040 | - defaults = self.get_parent_graph().get_node( default_node_name ) |
1041 | - if defaults: |
1042 | - attr_val = defaults.obj_dict['attributes'].get(attr, None) |
1043 | + g = self.get_parent_graph() |
1044 | + if g is not None: |
1045 | + defaults = g.get_node( default_node_name ) |
1046 | + else: |
1047 | + return None |
1048 | + |
1049 | + # Multiple defaults could be set by having repeated 'graph [...]' |
1050 | + # 'node [...]', 'edge [...]' statements. In such case, if the |
1051 | + # same attribute is set in different statements, only the first |
1052 | + # will be returned. In order to get all, one would call the |
1053 | + # get_*_defaults() methods and handle those. Or go node by node |
1054 | + # (of the ones specifying defaults) and modify the attributes |
1055 | + # individually. |
1056 | + # |
1057 | + if not isinstance(defaults, (list, tuple)): |
1058 | + defaults = [defaults] |
1059 | + |
1060 | + for default in defaults: |
1061 | + attr_val = default.obj_dict['attributes'].get(attr, None) |
1062 | if attr_val: |
1063 | return attr_val |
1064 | else: |
1065 | @@ -549,14 +650,7 @@ |
1066 | which are defined for all the existing attributes. |
1067 | """ |
1068 | |
1069 | - if self.obj_dict['attributes'].has_key(name): |
1070 | - self.obj_dict['attributes'][name] = value |
1071 | - return True |
1072 | - |
1073 | - # Attribute is not known |
1074 | - # |
1075 | - return False |
1076 | - |
1077 | + self.obj_dict['attributes'][name] = value |
1078 | |
1079 | |
1080 | def get(self, name): |
1081 | @@ -664,7 +758,7 @@ |
1082 | port = None |
1083 | if isinstance(name, basestring) and not name.startswith('"'): |
1084 | idx = name.find(':') |
1085 | - if idx > 0: |
1086 | + if idx > 0 and idx+1 < len(name): |
1087 | name, port = name[:idx], name[idx:] |
1088 | |
1089 | if isinstance(name, (long, int)): |
1090 | @@ -718,8 +812,11 @@ |
1091 | |
1092 | node_attr = list() |
1093 | |
1094 | - for attr, value in self.obj_dict['attributes'].items(): |
1095 | - node_attr.append( attr + '=' + quote_if_necessary(value) ) |
1096 | + for attr, value in self.obj_dict['attributes'].iteritems(): |
1097 | + if value is not None: |
1098 | + node_attr.append( '%s=%s' % (attr, quote_if_necessary(value) ) ) |
1099 | + else: |
1100 | + node_attr.append( attr ) |
1101 | |
1102 | |
1103 | # No point in having nodes setting any defaults if the don't set |
1104 | @@ -754,15 +851,22 @@ |
1105 | |
1106 | set_[attribute name], i.e. set_label, set_fontname |
1107 | |
1108 | - or using the instance's attributes: |
1109 | + or directly by using the instance's special dictionary: |
1110 | |
1111 | - Edge.[attribute name], i.e. edge_instance.label, edge_instance.fontname |
1112 | + Edge.obj_dict['attributes'][attribute name], i.e. |
1113 | + |
1114 | + edge_instance.obj_dict['attributes']['label'] |
1115 | + edge_instance.obj_dict['attributes']['fontname'] |
1116 | + |
1117 | """ |
1118 | |
1119 | |
1120 | |
1121 | def __init__(self, src='', dst='', obj_dict=None, **attrs): |
1122 | |
1123 | + if isinstance(src, (list, tuple)) and dst == '': |
1124 | + src, dst = src |
1125 | + |
1126 | if obj_dict is not None: |
1127 | |
1128 | self.obj_dict = obj_dict |
1129 | @@ -804,6 +908,11 @@ |
1130 | return self.obj_dict['points'][1] |
1131 | |
1132 | |
1133 | + def __hash__(self): |
1134 | + |
1135 | + return hash( hash(self.get_source()) + hash(self.get_destination()) ) |
1136 | + |
1137 | + |
1138 | def __eq__(self, edge): |
1139 | """Compare two edges. |
1140 | |
1141 | @@ -823,8 +932,8 @@ |
1142 | # If the graph is undirected, the edge has neither |
1143 | # source nor destination. |
1144 | # |
1145 | - if ( ( self.get_source()==edge.get_source() and self.get_destination()==edge.get_destination() ) or |
1146 | - ( edge.get_source() == get_destination() and self.get_destination() == edge.get_source() ) ): |
1147 | + if ( ( self.get_source() == edge.get_source() and self.get_destination() == edge.get_destination() ) or |
1148 | + ( edge.get_source() == self.get_destination() and edge.get_destination() == self.get_source() ) ): |
1149 | return True |
1150 | |
1151 | else: |
1152 | @@ -841,7 +950,7 @@ |
1153 | if not isinstance(node_str, str): |
1154 | return node_str |
1155 | |
1156 | - if node_str.startswith('"') and node_str.endswith('"') and node_str.count('"') % 2 != 0: |
1157 | + if node_str.startswith('"') and node_str.endswith('"'): |
1158 | |
1159 | return node_str |
1160 | |
1161 | @@ -874,36 +983,42 @@ |
1162 | |
1163 | if isinstance(src, frozendict): |
1164 | edge = [ Subgraph(obj_dict=src).to_string() ] |
1165 | + elif isinstance(src, (int, long)): |
1166 | + edge = [ str(src) ] |
1167 | else: |
1168 | edge = [ src ] |
1169 | |
1170 | if (self.get_parent_graph() and |
1171 | self.get_parent_graph().get_top_graph_type() and |
1172 | self.get_parent_graph().get_top_graph_type() == 'digraph' ): |
1173 | - |
1174 | + |
1175 | edge.append( '->' ) |
1176 | |
1177 | else: |
1178 | - |
1179 | edge.append( '--' ) |
1180 | |
1181 | if isinstance(dst, frozendict): |
1182 | edge.append( Subgraph(obj_dict=dst).to_string() ) |
1183 | + elif isinstance(dst, (int, long)): |
1184 | + edge.append( str(dst) ) |
1185 | else: |
1186 | edge.append( dst ) |
1187 | |
1188 | |
1189 | edge_attr = list() |
1190 | |
1191 | - for attr, value in self.obj_dict['attributes'].items(): |
1192 | + for attr, value in self.obj_dict['attributes'].iteritems(): |
1193 | |
1194 | - edge_attr.append( attr + '=' + quote_if_necessary(value) ) |
1195 | + if value is not None: |
1196 | + edge_attr.append( '%s=%s' % (attr, quote_if_necessary(value) ) ) |
1197 | + else: |
1198 | + edge_attr.append( attr ) |
1199 | |
1200 | edge_attr = ', '.join(edge_attr) |
1201 | |
1202 | if edge_attr: |
1203 | edge.append( ' [' + edge_attr + ']' ) |
1204 | - |
1205 | + |
1206 | return ' '.join(edge) + ';' |
1207 | |
1208 | |
1209 | @@ -940,7 +1055,10 @@ |
1210 | |
1211 | or using the instance's attributes: |
1212 | |
1213 | - Graph.[attribute name], i.e. graph_instance.label, graph_instance.fontname |
1214 | + Graph.obj_dict['attributes'][attribute name], i.e. |
1215 | + |
1216 | + graph_instance.obj_dict['attributes']['label'] |
1217 | + graph_instance.obj_dict['attributes']['fontname'] |
1218 | """ |
1219 | |
1220 | |
1221 | @@ -960,7 +1078,7 @@ |
1222 | raise Error, 'Invalid type "%s". Accepted graph types are: graph, digraph, subgraph' % graph_type |
1223 | |
1224 | |
1225 | - self.obj_dict['name'] = graph_name |
1226 | + self.obj_dict['name'] = quote_if_necessary(graph_name) |
1227 | self.obj_dict['type'] = graph_type |
1228 | |
1229 | self.obj_dict['strict'] = strict |
1230 | @@ -1003,8 +1121,12 @@ |
1231 | def get_graph_defaults(self, **attrs): |
1232 | |
1233 | graph_nodes = self.get_node('graph') |
1234 | - |
1235 | - return [ node.get_attributes() for node in graph_nodes ] |
1236 | + |
1237 | + if isinstance( graph_nodes, (list, tuple)): |
1238 | + return [ node.get_attributes() for node in graph_nodes ] |
1239 | + |
1240 | + return graph_nodes.get_attributes() |
1241 | + |
1242 | |
1243 | |
1244 | def set_node_defaults(self, **attrs): |
1245 | @@ -1017,7 +1139,10 @@ |
1246 | |
1247 | graph_nodes = self.get_node('node') |
1248 | |
1249 | - return [ node.get_attributes() for node in graph_nodes ] |
1250 | + if isinstance( graph_nodes, (list, tuple)): |
1251 | + return [ node.get_attributes() for node in graph_nodes ] |
1252 | + |
1253 | + return graph_nodes.get_attributes() |
1254 | |
1255 | |
1256 | def set_edge_defaults(self, **attrs): |
1257 | @@ -1030,7 +1155,10 @@ |
1258 | |
1259 | graph_nodes = self.get_node('edge') |
1260 | |
1261 | - return [ node.get_attributes() for node in graph_nodes ] |
1262 | + if isinstance( graph_nodes, (list, tuple)): |
1263 | + return [ node.get_attributes() for node in graph_nodes ] |
1264 | + |
1265 | + return graph_nodes.get_attributes() |
1266 | |
1267 | |
1268 | |
1269 | @@ -1106,7 +1234,7 @@ |
1270 | def set_suppress_disconnected(self, val): |
1271 | """Suppress disconnected nodes in the output graph. |
1272 | |
1273 | - This option will skip nodes in the graph with no incoming or outgoing |
1274 | + This option will skip nodes in the graph with no incoming or outgoing |
1275 | edges. This option works also for subgraphs and has effect only in the |
1276 | current graph/subgraph. |
1277 | """ |
1278 | @@ -1133,6 +1261,7 @@ |
1279 | return seq |
1280 | |
1281 | |
1282 | + |
1283 | def add_node(self, graph_node): |
1284 | """Adds a node object to the graph. |
1285 | |
1286 | @@ -1141,7 +1270,7 @@ |
1287 | """ |
1288 | |
1289 | if not isinstance(graph_node, Node): |
1290 | - raise TypeError('add_node() received a non node class object') |
1291 | + raise TypeError('add_node() received a non node class object: ' + str(graph_node)) |
1292 | |
1293 | |
1294 | node = self.get_node(graph_node.get_name()) |
1295 | @@ -1161,16 +1290,48 @@ |
1296 | |
1297 | |
1298 | |
1299 | + def del_node(self, name, index=None): |
1300 | + """Delete a node from the graph. |
1301 | + |
1302 | + Given a node's name all node(s) with that same name |
1303 | + will be deleted if 'index' is not specified or set |
1304 | + to None. |
1305 | + If there are several nodes with that same name and |
1306 | + 'index' is given, only the node in that position |
1307 | + will be deleted. |
1308 | + |
1309 | + 'index' should be an integer specifying the position |
1310 | + of the node to delete. If index is larger than the |
1311 | + number of nodes with that name, no action is taken. |
1312 | + |
1313 | + If nodes are deleted it returns True. If no action |
1314 | + is taken it returns False. |
1315 | + """ |
1316 | + |
1317 | + if isinstance(name, Node): |
1318 | + name = name.get_name() |
1319 | + |
1320 | + if self.obj_dict['nodes'].has_key(name): |
1321 | + |
1322 | + if index is not None and index < len(self.obj_dict['nodes'][name]): |
1323 | + del self.obj_dict['nodes'][name][index] |
1324 | + return True |
1325 | + else: |
1326 | + del self.obj_dict['nodes'][name] |
1327 | + return True |
1328 | + |
1329 | + return False |
1330 | + |
1331 | + |
1332 | def get_node(self, name): |
1333 | - """Retrieved a node from the graph. |
1334 | + """Retrieve a node from the graph. |
1335 | |
1336 | Given a node's name the corresponding Node |
1337 | instance will be returned. |
1338 | |
1339 | - If multiple nodes exist with that name, a list of |
1340 | + If one or more nodes exist with that name a list of |
1341 | Node instances is returned. |
1342 | - If only one node exists, the instance is returned. |
1343 | - None is returned otherwise. |
1344 | + An empty list is returned otherwise. |
1345 | """ |
1346 | |
1347 | match = list() |
1348 | @@ -1179,14 +1340,11 @@ |
1349 | |
1350 | match.extend( [ Node( obj_dict = obj_dict ) for obj_dict in self.obj_dict['nodes'][name] ]) |
1351 | |
1352 | - if len(match)==1: |
1353 | - return match[0] |
1354 | - |
1355 | return match |
1356 | |
1357 | |
1358 | def get_nodes(self): |
1359 | - """Return an iterator.""" |
1360 | + """Get the list of Node instances.""" |
1361 | |
1362 | return self.get_node_list() |
1363 | |
1364 | @@ -1200,7 +1358,7 @@ |
1365 | |
1366 | node_objs = list() |
1367 | |
1368 | - for node, obj_dict_list in self.obj_dict['nodes'].items(): |
1369 | + for node, obj_dict_list in self.obj_dict['nodes'].iteritems(): |
1370 | node_objs.extend( [ Node( obj_dict = obj_d ) for obj_d in obj_dict_list ] ) |
1371 | |
1372 | return node_objs |
1373 | @@ -1215,56 +1373,98 @@ |
1374 | """ |
1375 | |
1376 | if not isinstance(graph_edge, Edge): |
1377 | - raise TypeError('add_edge() received a non edge class object') |
1378 | - |
1379 | + raise TypeError('add_edge() received a non edge class object: ' + str(graph_edge)) |
1380 | + |
1381 | edge_points = ( graph_edge.get_source(), graph_edge.get_destination() ) |
1382 | |
1383 | if self.obj_dict['edges'].has_key(edge_points): |
1384 | - |
1385 | + |
1386 | edge_list = self.obj_dict['edges'][edge_points] |
1387 | edge_list.append(graph_edge.obj_dict) |
1388 | - |
1389 | + |
1390 | else: |
1391 | - |
1392 | + |
1393 | self.obj_dict['edges'][edge_points] = [ graph_edge.obj_dict ] |
1394 | - |
1395 | + |
1396 | + |
1397 | graph_edge.set_sequence( self.get_next_sequence_number() ) |
1398 | - |
1399 | + |
1400 | graph_edge.set_parent_graph( self.get_parent_graph() ) |
1401 | - |
1402 | - |
1403 | - |
1404 | - |
1405 | - def get_edge(self, src, dst): |
1406 | + |
1407 | + |
1408 | + |
1409 | + def del_edge(self, src_or_list, dst=None, index=None): |
1410 | + """Delete an edge from the graph. |
1411 | + |
1412 | + Given an edge's (source, destination) node names all |
1413 | + matching edges(s) will be deleted if 'index' is not |
1414 | + specified or set to None. |
1415 | + If there are several matching edges and 'index' is |
1416 | + given, only the edge in that position will be deleted. |
1417 | + |
1418 | + 'index' should be an integer specifying the position |
1419 | + of the edge to delete. If index is larger than the |
1420 | + number of matching edges, no action is taken. |
1421 | + |
1422 | + If edges are deleted it returns True. If no action |
1423 | + is taken it returns False. |
1424 | + """ |
1425 | + |
1426 | + if isinstance( src_or_list, (list, tuple)): |
1427 | + if dst is not None and isinstance(dst, (int, long)): |
1428 | + index = dst |
1429 | + src, dst = src_or_list |
1430 | + else: |
1431 | + src, dst = src_or_list, dst |
1432 | + |
1433 | + if isinstance(src, Node): |
1434 | + src = src.get_name() |
1435 | + |
1436 | + if isinstance(dst, Node): |
1437 | + dst = dst.get_name() |
1438 | + |
1439 | + if self.obj_dict['edges'].has_key( (src, dst) ): |
1440 | + |
1441 | + if index is not None and index < len(self.obj_dict['edges'][(src, dst)]): |
1442 | + del self.obj_dict['edges'][(src, dst)][index] |
1443 | + return True |
1444 | + else: |
1445 | + del self.obj_dict['edges'][(src, dst)] |
1446 | + return True |
1447 | + |
1448 | + return False |
1449 | + |
1450 | + |
1451 | + def get_edge(self, src_or_list, dst=None): |
1452 | """Retrieved an edge from the graph. |
1453 | |
1454 | Given an edge's source and destination the corresponding |
1455 | - Edge instance will be returned. |
1456 | + Edge instance(s) will be returned. |
1457 | |
1458 | - If multiple edges exist with that source and destination, |
1459 | + If one or more edges exist with that source and destination |
1460 | a list of Edge instances is returned. |
1461 | - If only one edge exists, the instance is returned. |
1462 | - None is returned otherwise. |
1463 | + An empty list is returned otherwise. |
1464 | """ |
1465 | |
1466 | - edge_points = (src, dst) |
1467 | - |
1468 | + if isinstance( src_or_list, (list, tuple)) and dst is None: |
1469 | + edge_points = tuple(src_or_list) |
1470 | + edge_points_reverse = (edge_points[1], edge_points[0]) |
1471 | + else: |
1472 | + edge_points = (src_or_list, dst) |
1473 | + edge_points_reverse = (dst, src_or_list) |
1474 | |
1475 | match = list() |
1476 | |
1477 | - if self.obj_dict['edges'].has_key( (src, dst) ) or ( |
1478 | - self.get_top_graph_type() == 'graph' and self.obj_dict['edges'].has_key( (dst, src) )): |
1479 | + if self.obj_dict['edges'].has_key( edge_points ) or ( |
1480 | + self.get_top_graph_type() == 'graph' and self.obj_dict['edges'].has_key( edge_points_reverse )): |
1481 | |
1482 | edges_obj_dict = self.obj_dict['edges'].get( |
1483 | - (src, dst), |
1484 | - self.obj_dict['edges'].get( (dst, src), None )) |
1485 | + edge_points, |
1486 | + self.obj_dict['edges'].get( edge_points_reverse, None )) |
1487 | |
1488 | for edge_obj_dict in edges_obj_dict: |
1489 | match.append( Edge( edge_points[0], edge_points[1], obj_dict = edge_obj_dict ) ) |
1490 | - |
1491 | - if len(match)==1: |
1492 | - return match[0] |
1493 | - |
1494 | + |
1495 | return match |
1496 | |
1497 | |
1498 | @@ -1281,7 +1481,7 @@ |
1499 | |
1500 | edge_objs = list() |
1501 | |
1502 | - for edge, obj_dict_list in self.obj_dict['edges'].items(): |
1503 | + for edge, obj_dict_list in self.obj_dict['edges'].iteritems(): |
1504 | edge_objs.extend( [ Edge( obj_dict = obj_d ) for obj_d in obj_dict_list ] ) |
1505 | |
1506 | return edge_objs |
1507 | @@ -1296,7 +1496,7 @@ |
1508 | """ |
1509 | |
1510 | if not isinstance(sgraph, Subgraph) and not isinstance(sgraph, Cluster): |
1511 | - raise TypeError('add_subgraph() received a non subgraph class object') |
1512 | + raise TypeError('add_subgraph() received a non subgraph class object:' + str(sgraph)) |
1513 | |
1514 | if self.obj_dict['subgraphs'].has_key(sgraph.get_name()): |
1515 | |
1516 | @@ -1319,30 +1519,27 @@ |
1517 | Given a subgraph's name the corresponding |
1518 | Subgraph instance will be returned. |
1519 | |
1520 | - If multiple subgraphs exist with the same name, a list of |
1521 | + If one or more subgraphs exist with the same name, a list of |
1522 | Subgraph instances is returned. |
1523 | - If only one Subgraph exists, the instance is returned. |
1524 | - None is returned otherwise. |
1525 | + An empty list is returned otherwise. |
1526 | """ |
1527 | |
1528 | - match = None |
1529 | - |
1530 | - if self.obj_dict['subgraphs'].has_key( sgraph.get_name() ): |
1531 | - |
1532 | - sgraphs_obj_dict = self.obj_dict['subgraphs'].get( sgraph.get_name() ) |
1533 | + match = list() |
1534 | + |
1535 | + if self.obj_dict['subgraphs'].has_key( name ): |
1536 | + |
1537 | + sgraphs_obj_dict = self.obj_dict['subgraphs'].get( name ) |
1538 | |
1539 | for obj_dict_list in sgraphs_obj_dict: |
1540 | - match = [ Subgraph( obj_dict = obj_d ) for obj_d in obj_dict_list ] |
1541 | + #match.extend( Subgraph( obj_dict = obj_d ) for obj_d in obj_dict_list ) |
1542 | + match.append( Subgraph( obj_dict = obj_dict_list ) ) |
1543 | |
1544 | - if len(match)==1: |
1545 | - return match[0] |
1546 | - |
1547 | return match |
1548 | |
1549 | |
1550 | def get_subgraphs(self): |
1551 | |
1552 | - return get_subgraph_list() |
1553 | + return self.get_subgraph_list() |
1554 | |
1555 | |
1556 | def get_subgraph_list(self): |
1557 | @@ -1354,7 +1551,7 @@ |
1558 | |
1559 | sgraph_objs = list() |
1560 | |
1561 | - for sgraph, obj_dict_list in self.obj_dict['subgraphs'].items(): |
1562 | + for sgraph, obj_dict_list in self.obj_dict['subgraphs'].iteritems(): |
1563 | sgraph_objs.extend( [ Subgraph( obj_dict = obj_d ) for obj_d in obj_dict_list ] ) |
1564 | |
1565 | return sgraph_objs |
1566 | @@ -1365,15 +1562,15 @@ |
1567 | |
1568 | self.obj_dict['parent_graph'] = parent_graph |
1569 | |
1570 | - for obj_list in self.obj_dict['nodes'].values(): |
1571 | - for obj in obj_list: |
1572 | - obj['parent_graph'] = parent_graph |
1573 | - |
1574 | - for obj_list in self.obj_dict['edges'].values(): |
1575 | - for obj in obj_list: |
1576 | - obj['parent_graph'] = parent_graph |
1577 | - |
1578 | - for obj_list in self.obj_dict['subgraphs'].values(): |
1579 | + for obj_list in self.obj_dict['nodes'].itervalues(): |
1580 | + for obj in obj_list: |
1581 | + obj['parent_graph'] = parent_graph |
1582 | + |
1583 | + for obj_list in self.obj_dict['edges'].itervalues(): |
1584 | + for obj in obj_list: |
1585 | + obj['parent_graph'] = parent_graph |
1586 | + |
1587 | + for obj_list in self.obj_dict['subgraphs'].itervalues(): |
1588 | for obj in obj_list: |
1589 | Graph(obj_dict=obj).set_parent_graph(parent_graph) |
1590 | |
1591 | @@ -1393,21 +1590,25 @@ |
1592 | if self==self.get_parent_graph() and self.obj_dict['strict']: |
1593 | |
1594 | graph.append('strict ') |
1595 | - |
1596 | + |
1597 | if self.obj_dict['name'] == '': |
1598 | - graph.append( '{\n' ) |
1599 | + if 'show_keyword' in self.obj_dict and self.obj_dict['show_keyword']: |
1600 | + graph.append( 'subgraph {\n' ) |
1601 | + else: |
1602 | + graph.append( '{\n' ) |
1603 | else: |
1604 | graph.append( '%s %s {\n' % (self.obj_dict['type'], self.obj_dict['name']) ) |
1605 | |
1606 | |
1607 | - for attr in self.obj_dict['attributes'].keys(): |
1608 | + for attr in self.obj_dict['attributes'].iterkeys(): |
1609 | |
1610 | if self.obj_dict['attributes'].get(attr, None) is not None: |
1611 | |
1612 | - graph.append( '%s=' % attr ) |
1613 | val = self.obj_dict['attributes'].get(attr) |
1614 | - |
1615 | - graph.append( quote_if_necessary(val) ) |
1616 | + if val is not None: |
1617 | + graph.append( '%s=%s' % (attr, quote_if_necessary(val)) ) |
1618 | + else: |
1619 | + graph.append( attr ) |
1620 | |
1621 | graph.append( ';\n' ) |
1622 | |
1623 | @@ -1415,7 +1616,7 @@ |
1624 | edges_done = set() |
1625 | |
1626 | edge_obj_dicts = list() |
1627 | - for e in self.obj_dict['edges'].values(): |
1628 | + for e in self.obj_dict['edges'].itervalues(): |
1629 | edge_obj_dicts.extend(e) |
1630 | |
1631 | if edge_obj_dicts: |
1632 | @@ -1425,11 +1626,11 @@ |
1633 | edge_src_set, edge_dst_set = set(), set() |
1634 | |
1635 | node_obj_dicts = list() |
1636 | - for e in self.obj_dict['nodes'].values(): |
1637 | + for e in self.obj_dict['nodes'].itervalues(): |
1638 | node_obj_dicts.extend(e) |
1639 | |
1640 | sgraph_obj_dicts = list() |
1641 | - for sg in self.obj_dict['subgraphs'].values(): |
1642 | + for sg in self.obj_dict['subgraphs'].itervalues(): |
1643 | sgraph_obj_dicts.extend(sg) |
1644 | |
1645 | |
1646 | @@ -1455,7 +1656,7 @@ |
1647 | |
1648 | edge = Edge(obj_dict=obj) |
1649 | |
1650 | - if self.obj_dict.get('simplify', False) and elm in edges_done: |
1651 | + if self.obj_dict.get('simplify', False) and edge in edges_done: |
1652 | continue |
1653 | |
1654 | graph.append( edge.to_string() + '\n' ) |
1655 | @@ -1496,8 +1697,10 @@ |
1656 | |
1657 | or using the instance's attributes: |
1658 | |
1659 | - Subgraph.[attribute name], i.e. |
1660 | - subgraph_instance.label, subgraph_instance.fontname |
1661 | + Subgraph.obj_dict['attributes'][attribute name], i.e. |
1662 | + |
1663 | + subgraph_instance.obj_dict['attributes']['label'] |
1664 | + subgraph_instance.obj_dict['attributes']['fontname'] |
1665 | """ |
1666 | |
1667 | |
1668 | @@ -1541,8 +1744,10 @@ |
1669 | |
1670 | or using the instance's attributes: |
1671 | |
1672 | - Cluster.[attribute name], i.e. |
1673 | - cluster_instance.color, cluster_instance.fontname |
1674 | + Cluster.obj_dict['attributes'][attribute name], i.e. |
1675 | + |
1676 | + cluster_instance.obj_dict['attributes']['label'] |
1677 | + cluster_instance.obj_dict['attributes']['fontname'] |
1678 | """ |
1679 | |
1680 | |
1681 | @@ -1557,6 +1762,8 @@ |
1682 | self.obj_dict['type'] = 'subgraph' |
1683 | self.obj_dict['name'] = 'cluster_'+graph_name |
1684 | |
1685 | + self.create_attribute_methods(CLUSTER_ATTRIBUTES) |
1686 | + |
1687 | |
1688 | |
1689 | |
1690 | @@ -1603,23 +1810,19 @@ |
1691 | |
1692 | f = self.__dict__['write_'+frmt] |
1693 | f.__doc__ = '''Refer to the docstring accompanying the 'write' method for more information.''' |
1694 | - |
1695 | + |
1696 | + |
1697 | |
1698 | - |
1699 | def __getstate__(self): |
1700 | - |
1701 | - dict = copy.copy(self.__dict__) |
1702 | - for attr in self.attributes: |
1703 | - del dict['set_'+attr] |
1704 | - del dict['get_'+attr] |
1705 | - |
1706 | - for k in [ x for x in dict.keys() if |
1707 | - x.startswith('write_') or x.startswith('create_') ]: |
1708 | - |
1709 | - del dict[k] |
1710 | - |
1711 | + |
1712 | + dict = copy.copy(self.obj_dict) |
1713 | + |
1714 | return dict |
1715 | |
1716 | + def __setstate__(self, state): |
1717 | + |
1718 | + self.obj_dict = state |
1719 | + |
1720 | |
1721 | def set_shape_files(self, file_paths): |
1722 | """Add the paths of the required image files. |
1723 | @@ -1691,7 +1894,19 @@ |
1724 | |
1725 | dot_fd = file(path, "w+b") |
1726 | if format == 'raw': |
1727 | - dot_fd.write(self.to_string()) |
1728 | + data = self.to_string() |
1729 | + if isinstance(data, basestring): |
1730 | + if not isinstance(data, unicode): |
1731 | + try: |
1732 | + data = unicode(data, 'utf-8') |
1733 | + except: |
1734 | + pass |
1735 | + |
1736 | + try: |
1737 | + data = data.encode('utf-8') |
1738 | + except: |
1739 | + pass |
1740 | + dot_fd.write(data) |
1741 | else: |
1742 | dot_fd.write(self.create(prog, format)) |
1743 | dot_fd.close() |
1744 | @@ -1715,11 +1930,22 @@ |
1745 | |
1746 | which are automatically defined for all the supported formats. |
1747 | [create_ps(), create_gif(), create_dia(), ...] |
1748 | + |
1749 | + If 'prog' is a list instead of a string the fist item is expected |
1750 | + to be the program name, followed by any optional command-line |
1751 | + arguments for it: |
1752 | + |
1753 | + [ 'twopi', '-Tdot', '-s10' ] |
1754 | """ |
1755 | |
1756 | if prog is None: |
1757 | prog = self.prog |
1758 | |
1759 | + if isinstance(prog, (list, tuple)): |
1760 | + prog, args = prog[0], prog[1:] |
1761 | + else: |
1762 | + args = [] |
1763 | + |
1764 | if self.progs is None: |
1765 | self.progs = find_graphviz() |
1766 | if self.progs is None: |
1767 | @@ -1755,9 +1981,11 @@ |
1768 | f = file( os.path.join( tmp_dir, os.path.basename(img) ), 'wb' ) |
1769 | f.write(f_data) |
1770 | f.close() |
1771 | - |
1772 | + |
1773 | + cmdline = [self.progs[prog], '-T'+format, tmp_name] + args |
1774 | + |
1775 | p = subprocess.Popen( |
1776 | - (self.progs[prog], '-T'+format, tmp_name), |
1777 | + cmdline, |
1778 | cwd=tmp_dir, |
1779 | stderr=subprocess.PIPE, stdout=subprocess.PIPE) |
1780 | |
1781 | @@ -1772,8 +2000,7 @@ |
1782 | stdout_output.append(data) |
1783 | stdout.close() |
1784 | |
1785 | - if stdout_output: |
1786 | - stdout_output = ''.join(stdout_output) |
1787 | + stdout_output = ''.join(stdout_output) |
1788 | |
1789 | if not stderr.closed: |
1790 | stderr_output = list() |
1791 | |
1792 | === removed file 'setup.cfg' |
1793 | --- setup.cfg 2008-05-26 15:35:04 +0000 |
1794 | +++ setup.cfg 1970-01-01 00:00:00 +0000 |
1795 | @@ -1,5 +0,0 @@ |
1796 | -[egg_info] |
1797 | -tag_build = |
1798 | -tag_date = 0 |
1799 | -tag_svn_revision = 0 |
1800 | - |
1801 | |
1802 | === modified file 'setup.py' |
1803 | --- setup.py 2008-05-26 15:35:04 +0000 |
1804 | +++ setup.py 2014-02-15 16:49:34 +0000 |
1805 | @@ -1,11 +1,15 @@ |
1806 | #!/usr/bin/env python |
1807 | |
1808 | try: |
1809 | + from distutils.core import setup |
1810 | +except ImportError, excp: |
1811 | from setuptools import setup |
1812 | -except ImportError, excp: |
1813 | - from distutils.core import setup |
1814 | |
1815 | import pydot |
1816 | +import os |
1817 | + |
1818 | +os.environ['COPY_EXTENDED_ATTRIBUTES_DISABLE'] = 'true' |
1819 | +os.environ['COPYFILE_DISABLE'] = 'true' |
1820 | |
1821 | setup( name = 'pydot', |
1822 | version = pydot.__version__, |
1823 | @@ -28,4 +32,4 @@ |
1824 | long_description = "\n".join(pydot.__doc__.split('\n')), |
1825 | py_modules = ['pydot', 'dot_parser'], |
1826 | install_requires = ['pyparsing', 'setuptools'], |
1827 | - data_files = [('.', ['ChangeLog', 'LICENSE', 'README'])] ) |
1828 | + data_files = [('.', ['LICENSE', 'README'])] ) |
Thanks a lot for your work on this. Uploaded with two small modifications: /wiki.ubuntu. com/DebianMaint ainerField
- changed version number to 1.0.28-0ubuntu1 (1.0.28-1 would indicate that it was in Debian already)
- ran the "update-maintainer" script to adhere to https:/
Can you please forward your changes to Debian as well?