[blockdiag] 14/29: Import Upstream version 1.3.2

Andreas Tille tille at debian.org
Tue Jan 10 21:35:58 UTC 2017


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository blockdiag.

commit 8f8eaf3aa06d7c6b898b15b7d8b9158cf2cbba1a
Author: Andreas Tille <tille at debian.org>
Date:   Tue Jan 10 11:08:04 2017 +0100

    Import Upstream version 1.3.2
---
 MANIFEST.in                                        |  17 +-
 PKG-INFO                                           | 992 +++++++++++----------
 src/README.txt => README.rst                       | 966 ++++++++++----------
 bootstrap.py                                       | 234 ++---
 buildout.cfg                                       |  94 +-
 examples/blockdiagrc                               |   2 +
 setup.cfg                                          |   3 +
 setup.py                                           | 231 +++--
 src/TODO.txt                                       |  14 -
 src/blockdiag.egg-info/PKG-INFO                    | 992 +++++++++++----------
 src/blockdiag.egg-info/SOURCES.txt                 |  11 +-
 src/blockdiag.egg-info/entry_points.txt            |  64 +-
 src/blockdiag.egg-info/requires.txt                |   5 +-
 src/blockdiag/__init__.py                          |  32 +-
 src/blockdiag/builder.py                           |  80 +-
 src/blockdiag/command.py                           |  14 +-
 src/blockdiag/drawer.py                            |   8 +-
 src/blockdiag/elements.py                          |   5 +-
 src/blockdiag/imagedraw/base.py                    |   5 +-
 src/blockdiag/imagedraw/filters/__init__.py        |  28 +-
 src/blockdiag/imagedraw/filters/linejump.py        |  13 +-
 src/blockdiag/imagedraw/pdf.py                     |   9 +-
 src/blockdiag/imagedraw/png.py                     |  62 +-
 src/blockdiag/imagedraw/simplesvg.py               |  32 +-
 src/blockdiag/imagedraw/svg.py                     |  31 +-
 src/blockdiag/imagedraw/textfolder.py              |  20 +-
 src/blockdiag/imagedraw/utils/__init__.py          |  65 +-
 src/blockdiag/imagedraw/utils/ellipse.py           |   4 +-
 src/blockdiag/metrics.py                           | 272 +++---
 src/blockdiag/noderenderer/__init__.py             |  11 +-
 src/blockdiag/noderenderer/actor.py                |  13 +-
 src/blockdiag/noderenderer/circle.py               |   5 +-
 src/blockdiag/noderenderer/cloud.py                |  23 +-
 src/blockdiag/noderenderer/diamond.py              |   9 +-
 src/blockdiag/noderenderer/flowchart/database.py   |  11 +-
 src/blockdiag/noderenderer/flowchart/input.py      |   6 +-
 src/blockdiag/noderenderer/flowchart/loopin.py     |   7 +-
 src/blockdiag/noderenderer/flowchart/loopout.py    |   7 +-
 src/blockdiag/noderenderer/flowchart/terminator.py |   2 +-
 src/blockdiag/noderenderer/roundedbox.py           |   2 +-
 src/blockdiag/noderenderer/square.py               |   5 +-
 src/blockdiag/noderenderer/textbox.py              |   5 +-
 src/blockdiag/parser.py                            | 223 +++--
 src/blockdiag/tests/diagrams/branched.diag         |  10 +-
 src/blockdiag/tests/diagrams/circular_ref.diag     |  10 +-
 .../tests/diagrams/circular_ref_to_root.diag       |  10 +-
 .../tests/diagrams/circular_skipped_edge.diag      |  10 +-
 src/blockdiag/tests/diagrams/edge_attribute.diag   |  10 +-
 src/blockdiag/tests/diagrams/edge_styles.diag      |  18 +-
 src/blockdiag/tests/diagrams/empty_group.diag      |  10 +-
 .../tests/diagrams/empty_nested_group.diag         |  14 +-
 .../diagrams/errors/belongs_to_two_groups.diag     |  18 +-
 .../tests/diagrams/errors/group_follows_node.diag  |  12 +-
 .../tests/diagrams/errors/node_follows_group.diag  |  12 +-
 src/blockdiag/tests/diagrams/flowable_node.diag    |  10 +-
 src/blockdiag/tests/diagrams/folded_edge.diag      |  12 +-
 .../group_id_and_node_id_are_not_conflicted.diag   |  14 +-
 .../tests/diagrams/group_works_node_decorator.diag |  18 +-
 .../tests/diagrams/large_group_and_node.diag       |  20 +-
 .../tests/diagrams/large_group_and_node2.diag      |  14 +-
 .../tests/diagrams/large_group_and_two_nodes.diag  |  22 +-
 src/blockdiag/tests/diagrams/multiple_groups.diag  |  32 +-
 .../tests/diagrams/multiple_nested_groups.diag     |  28 +-
 src/blockdiag/tests/diagrams/nested_groups.diag    |  18 +-
 .../tests/diagrams/nested_groups_and_edges.diag    |  22 +-
 .../nested_groups_work_node_decorator.diag         |  22 +-
 .../tests/diagrams/nested_skipped_circular.diag    |  14 +-
 src/blockdiag/tests/diagrams/node_attribute.diag   |  24 +-
 .../diagrams/node_in_group_follows_outer_node.diag |  14 +-
 src/blockdiag/tests/diagrams/node_styles.diag      |  10 +-
 .../diagrams/outer_node_follows_node_in_group.diag |  14 +-
 .../tests/diagrams/reverse_multiple_groups.diag    |  32 +-
 src/blockdiag/tests/diagrams/self_ref.diag         |   8 +-
 src/blockdiag/tests/diagrams/simple_group.diag     |  14 +-
 src/blockdiag/tests/diagrams/single_edge.diag      |   6 +-
 src/blockdiag/tests/diagrams/single_node.diag      |   6 +-
 src/blockdiag/tests/diagrams/skipped_circular.diag |  12 +-
 src/blockdiag/tests/diagrams/skipped_edge.diag     |  10 +-
 src/blockdiag/tests/diagrams/triple_branched.diag  |  12 +-
 .../tests/diagrams/twin_circular_ref_to_root.diag  |  10 +-
 src/blockdiag/tests/diagrams/two_edges.diag        |   6 +-
 src/blockdiag/tests/diagrams/white.gif             | Bin 0 -> 43 bytes
 src/blockdiag/tests/test_boot_params.py            | 203 ++---
 src/blockdiag/tests/test_builder.py                | 317 ++++---
 src/blockdiag/tests/test_builder_edge.py           | 294 +++---
 src/blockdiag/tests/test_builder_errors.py         | 192 ++--
 src/blockdiag/tests/test_builder_group.py          | 419 ++++-----
 src/blockdiag/tests/test_builder_node.py           | 273 +++---
 src/blockdiag/tests/test_builder_separate.py       |  80 +-
 src/blockdiag/tests/test_generate_diagram.py       | 169 ++--
 src/blockdiag/tests/test_imagedraw_textfolder.py   |  33 +-
 src/blockdiag/tests/test_imagedraw_utils.py        |  64 ++
 src/blockdiag/tests/test_parser.py                 | 283 +++---
 src/blockdiag/tests/test_pep8.py                   | 105 +--
 src/blockdiag/tests/test_rst_directives.py         | 236 ++---
 src/blockdiag/tests/test_utils_fontmap.py          | 101 ++-
 src/blockdiag/tests/utils.py                       | 133 ++-
 src/blockdiag/utils/__init__.py                    |  38 +-
 src/blockdiag/utils/bootstrap.py                   |  56 +-
 src/blockdiag/utils/collections.py                 |  39 -
 src/blockdiag/utils/compat.py                      |  60 ++
 src/blockdiag/utils/config.py                      |  13 +-
 src/blockdiag/utils/fontmap.py                     |   5 +-
 src/blockdiag/utils/functools.py                   |  39 -
 src/blockdiag/utils/images.py                      |  74 +-
 src/blockdiag/utils/jpeg.py                        |   5 +-
 src/blockdiag/utils/myitertools.py                 |  18 +-
 src/blockdiag/utils/namedtuple.py                  |  18 -
 src/blockdiag/utils/rst/directives.py              |  62 +-
 src/blockdiag/utils/urlutil.py                     |   5 +-
 src/blockdiag/utils/uuid.py                        |   4 +-
 src/blockdiag_sphinxhelper.py                      |  20 +-
 tox.ini                                            |  20 +
 113 files changed, 4184 insertions(+), 4386 deletions(-)

diff --git a/MANIFEST.in b/MANIFEST.in
index 3e2ed84..02701f8 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,8 +1,9 @@
-include buildout.cfg
-include bootstrap.py
-include MANIFEST.in
-include LICENSE
-include blockdiag.1
-recursive-include examples *.diag *.png *.svg
-recursive-include src *.py *.txt *.diag
-
+include buildout.cfg
+include bootstrap.py
+include MANIFEST.in
+include README.rst
+include LICENSE
+include blockdiag.1
+include tox.ini
+recursive-include examples blockdiagrc *.diag *.png *.svg
+recursive-include src *.py *.diag *.gif
diff --git a/PKG-INFO b/PKG-INFO
index 74aa02e..be630f3 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,509 +1,513 @@
-Metadata-Version: 1.1
+Metadata-Version: 1.0
 Name: blockdiag
-Version: 1.2.4
-Summary: blockdiag generate block-diagram image file from spec-text file.
+Version: 1.3.2
+Summary: blockdiag generates block-diagram image from text
 Home-page: http://blockdiag.com/
 Author: Takeshi Komiya
 Author-email: i.tkomiya at gmail.com
 License: Apache License 2.0
 Download-URL: http://pypi.python.org/pypi/blockdiag
-Description: `blockdiag` generate block-diagram image file from spec-text file.
-        
-        Features
-        ========
-        * Generate block-diagram from dot like text (basic feature).
-        * Multilingualization for node-label (utf-8 only).
-        
-        You can get some examples and generated images on 
-        `blockdiag.com <http://blockdiag.com/blockdiag/build/html/index.html>`_ .
-        
-        Setup
-        =====
-        
-        by easy_install
-        ----------------
-        Make environment::
-        
-           $ easy_install blockdiag
-        
-        If you want to export as PDF format, give pdf arguments::
-        
-           $ easy_install "blockdiag[pdf]"
-        
-        by buildout
-        ------------
-        Make environment::
-        
-           $ hg clone http://bitbucket.org/tk0miya/blockdiag
-           $ cd blockdiag
-           $ python bootstrap.py
-           $ bin/buildout
-        
-        Copy and modify ini file. example::
-        
-           $ cp <blockdiag installed path>/blockdiag/examples/simple.diag .
-           $ vi simple.diag
-        
-        Please refer to `spec-text setting sample`_ section for the format of the
-        `simpla.diag` configuration file.
-        
-        spec-text setting sample
-        ========================
-        Few examples are available.
-        You can get more examples at
-        `blockdiag.com <http://blockdiag.com/blockdiag/build/html/index.html>`_ .
-        
-        simple.diag
-        ------------
-        simple.diag is simply define nodes and transitions by dot-like text format::
-        
-            diagram admin {
-              top_page -> config -> config_edit -> config_confirm -> top_page;
-            }
-        
-        screen.diag
-        ------------
-        screen.diag is more complexly sample. diaglam nodes have a alternative label
-        and some transitions::
-        
-            diagram admin {
-              top_page [label = "Top page"];
-        
-              foo_index [label = "List of FOOs"];
-              foo_detail [label = "Detail FOO"];
-              foo_add [label = "Add FOO"];
-              foo_add_confirm [label = "Add FOO (confirm)"];
-              foo_edit [label = "Edit FOO"];
-              foo_edit_confirm [label = "Edit FOO (confirm)"];
-              foo_delete_confirm [label = "Delete FOO (confirm)"];
-        
-              bar_detail [label = "Detail of BAR"];
-              bar_edit [label = "Edit BAR"];
-              bar_edit_confirm [label = "Edit BAR (confirm)"];
-        
-              logout;
-        
-              top_page -> foo_index;
-              top_page -> bar_detail;
-        
-              foo_index -> foo_detail;
-                           foo_detail -> foo_edit;
-                           foo_detail -> foo_delete_confirm;
-              foo_index -> foo_add -> foo_add_confirm -> foo_index;
-              foo_index -> foo_edit -> foo_edit_confirm -> foo_index;
-              foo_index -> foo_delete_confirm -> foo_index;
-        
-              bar_detail -> bar_edit -> bar_edit_confirm -> bar_detail;
-            }
-        
-        
-        Usage
-        =====
-        Execute blockdiag command::
-        
-           $ blockdiag simple.diag
-           $ ls simple.png
-           simple.png
-        
-        
-        Requirements
-        ============
-        * Python 2.4 or later (not support 3.x)
-        * Python Imaging Library 1.1.5 or later.
-        * funcparserlib 0.3.4 or later.
-        * setuptools or distribute.
-        
-        
-        License
-        =======
-        Apache License 2.0
-        
-        
-        History
-        =======
-        
-        1.2.4 (2012-11-21)
-        ------------------
-        * Fix bugs
-        
-        1.2.3 (2012-11-05)
-        ------------------
-        * Fix bugs
-        
-        1.2.2 (2012-10-28)
-        ------------------
-        * Fix bugs
-        
-        1.2.1 (2012-10-28)
-        ------------------
-        * Add external imagedraw plugin supports
-        * Add node attribute: label_orientation*
-        * Fix bugs
-        
-        1.2.0 (2012-10-22)
-        ------------------
-        * Optimize algorithm for rendering shadow
-        * Add options to docutils directive
-        * Fix bugs
-        
-        1.1.8 (2012-09-28)
-        ------------------
-        * Add --ignore-pil option
-        * Fix bugs
-        
-        1.1.7 (2012-09-20)
-        ------------------
-        * Add diagram attribute: shadow_style
-        * Add font path for centos 6.2
-        * Add a setting 'antialias' in the configuration file
-        * Fix bugs
-        
-        1.1.6 (2012-06-06)
-        ------------------
-        * Support for readthedocs.org
-        * reST directive supports :caption: option
-        * Fix bugs
-        
-        1.1.5 (2012-04-22)
-        ------------------
-        * Embed source code to SVG document as description
-        * Fix bugs
-        
-        1.1.4 (2012-03-15)
-        ------------------
-        * Add new edge.hstyles: oneone, onemany, manyone, manymany
-        * Add edge attribute: description (for build description-tables)
-        * Fix bugs
-        
-        1.1.3 (2012-02-13)
-        ------------------
-        * Add new edge type for data-models (thanks to David Lang)
-        * Add --no-transparency option
-        * Fix bugs
-        
-        1.1.2 (2011-12-26)
-        ------------------
-        * Support font-index for TrueType Font Collections (.ttc file)
-        * Allow to use reST syntax in descriptions of nodes
-        * Fix bugs
-        
-        1.1.1 (2011-11-27)
-        ------------------
-        * Add node attribute: href (thanks to @r_rudi!)
-        * Fix bugs
-        
-        1.1.0 (2011-11-19)
-        ------------------
-        * Add shape: square and circle
-        * Add fontfamily attribute for switching fontface
-        * Fix bugs
-        
-        1.0.3 (2011-11-13)
-        ------------------
-        * Add plugin: attributes
-        * Change plugin syntax; (cf. plugin attributes [attr = value, attr, value])
-        * Fix bugs
-        
-        1.0.2 (2011-11-07)
-        ------------------
-        * Fix bugs
-        
-        1.0.1 (2011-11-06)
-        ------------------
-        * Add group attribute: shape
-        * Fix bugs
-        
-        1.0.0 (2011-11-04)
-        ------------------
-        * Add node attribute: linecolor
-        * Rename diagram attributes:
-           * fontsize -> default_fontsize
-           * default_line_color -> default_linecolor
-           * default_text_color -> default_textcolor
-        * Add docutils extention
-        * Fix bugs
-        
-        0.9.7 (2011-11-01)
-        ------------------
-        * Add node attribute: fontsize
-        * Add edge attributes: thick, fontsize
-        * Add group attribute: fontsize
-        * Change color of shadow in PDF mode
-        * Add class feature (experimental)
-        * Add handler-plugin framework (experimental)
-        
-        0.9.6 (2011-10-22)
-        ------------------
-        * node.style supports dashed_array format style
-        * Fix bugs
-        
-        0.9.5 (2011-10-19)
-        ------------------
-        * Add node attributes: width and height
-        * Fix bugs
-        
-        0.9.4 (2011-10-07)
-        ------------------
-        * Fix bugs
-        
-        0.9.3 (2011-10-06)
-        ------------------
-        * Replace SVG core by original's (simplesvg.py)
-        * Refactored
-        * Fix bugs
-        
-        0.9.2 (2011-09-30)
-        ------------------
-        * Add node attribute: textcolor
-        * Add group attribute: textcolor
-        * Add edge attribute: textcolor
-        * Add diagram attributes: default_text_attribute
-        * Fix beginpoint shape and endpoint shape were reversed
-        * Fix bugs
-        
-        0.9.1 (2011-09-26)
-        ------------------
-        * Add diagram attributes: default_node_color, default_group_color and default_line_color
-        * Fix bugs
-        
-        0.9.0 (2011-09-25)
-        ------------------
-        * Add icon attribute to node
-        * Make transparency to background of PNG images 
-        * Fix bugs
-        
-        0.8.9 (2011-08-09)
-        ------------------
-        * Fix bugs
-        
-        0.8.8 (2011-08-08)
-        ------------------
-        * Fix bugs
-        
-        0.8.7 (2011-08-06)
-        ------------------
-        * Fix bugs
-        
-        0.8.6 (2011-08-01)
-        ------------------
-        * Support Pillow as replacement of PIL (experimental)
-        * Fix bugs
-        
-        0.8.5 (2011-07-31)
-        ------------------
-        * Allow dot characters in node_id
-        * Fix bugs
-        
-        0.8.4 (2011-07-05)
-        ------------------
-        * Fix bugs
-        
-        0.8.3 (2011-07-03)
-        ------------------
-        * Support input from stdin
-        * Fix bugs
-        
-        0.8.2 (2011-06-29)
-        ------------------
-        * Add node.stacked
-        * Add node shapes: dots, none
-        * Add hiragino-font to font search list
-        * Support background image fetching from web
-        * Add diagram.edge_layout (experimental)
-        * Fix bugs
-        
-        0.8.1 (2011-05-14)
-        ------------------
-        * Change license to Apache License 2.0
-        * Fix bugs
-        
-        0.8.0 (2011-05-04)
-        ------------------
-        * Add --separate option and --version option
-        * Fix bugs
-        
-        0.7.8 (2011-04-19)
-        ------------------
-        * Update layout engine
-        * Update requirements: PIL >= 1.1.5
-        * Update parser for tokenize performance
-        * Add --nodoctype option
-        * Fix bugs
-        * Add many testcases
-        
-        0.7.7 (2011-03-29)
-        ------------------
-        * Fix bugs
-        
-        0.7.6 (2011-03-26)
-        ------------------
-        * Add new layout manager for portrait edges
-        * Fix bugs
-        
-        0.7.5 (2011-03-20)
-        ------------------
-        * Support multiple nodes relations (cf. A -> B, C)
-        * Support node group declaration at attribute of nodes
-        * Fix bugs
-        
-        0.7.4 (2011-03-08)
-        ------------------
-        * Fix bugs
-        
-        0.7.3 (2011-03-02)
-        ------------------
-        * Use UTF-8 characters as Name token (by @swtw7466)
-        * Fix htmlentities included in labels was not escaped on SVG images
-        * Fix bugs
-        
-        0.7.2 (2011-02-28)
-        ------------------
-        * Add default_shape attribute to diagram
-        
-        0.7.1 (2011-02-27)
-        ------------------
-        * Fix edge has broken with antialias option
-        
-        0.7.0 (2011-02-25)
-        ------------------
-        * Support node shape
-        
-        0.6.7 (2011-02-12)
-        ------------------
-        * Change noderenderer interface to new style
-        * Render dashed ellipse more clearly (contributed by @cocoatomo)
-        * Support PDF exporting
-        
-        0.6.6 (2011-01-31)
-        ------------------
-        * Support diagram.shape_namespace
-        * Add new node shapes; mail, cloud, beginpoint, endpoint, minidiamond, actor
-        * Support plug-in structure to install node shapes
-        * Fix bugs
-        
-        0.6.5 (2011-01-18)
-        ------------------
-        * Support node shape (experimental)
-        
-        0.6.4 (2011-01-17)
-        ------------------
-        * Fix bugs
-        
-        0.6.3 (2011-01-15)
-        ------------------
-        * Fix bugs
-        
-        0.6.2 (2011-01-08)
-        ------------------
-        * Fix bugs
-        
-        0.6.1 (2011-01-07)
-        ------------------
-        * Implement 'folded' attribute for edge
-        * Refactor layout engine
-        
-        0.6 (2011-01-02)
-        ------------------
-        * Support nested groups.
-        
-        0.5.5 (2010-12-24)
-        ------------------
-        * Specify direction of edges as syntax (->, --, <-, <->)
-        * Fix bugs.
-        
-        0.5.4 (2010-12-23)
-        ------------------
-        * Remove debug codes.
-        
-        0.5.3 (2010-12-23)
-        ------------------
-        * Support NodeGroup.label.
-        * Implement --separate option (experimental)
-        * Fix right-up edge overrapped on other nodes.
-        * Support configration file: .blockdiagrc
-        
-        0.5.2 (2010-11-06)
-        ------------------
-        * Fix unicode errors for UTF-8'ed SVG exportion.
-        * Refactoring codes for running on GAE.
-        
-        0.5.1 (2010-10-26)
-        ------------------
-        * Fix license text on diagparser.py
-        * Update layout engine.
-        
-        0.5 (2010-10-15)
-        ------------------
-        * Support background-image of node (SVG)
-        * Support labels for edge.
-        * Fix bugs.
-        
-        0.4.2 (2010-10-10)
-        ------------------
-        * Support background-color of node groups.
-        * Draw edge has jumped at edge's cross-points.
-        * Fix bugs.
-        
-        0.4.1 (2010-10-07)
-        ------------------
-        * Fix bugs.
-        
-        0.4 (2010-10-07)
-        ------------------
-        * Support SVG exporting.
-        * Support dashed edge drawing.
-        * Support background image of nodes (PNG only)
-        
-        0.3.1 (2010-09-29)
-        ------------------
-        * Fasten anti-alias process.
-        * Fix text was broken on windows.
-        
-        0.3 (2010-09-26)
-        ------------------
-        * Add --antialias option.
-        * Fix bugs.
-        
-        0.2.2 (2010-09-25)
-        ------------------
-        * Fix edge bugs.
-        
-        0.2.1 (2010-09-25)
-        ------------------
-        * Fix bugs.
-        * Fix package style.
-        
-        0.2 (2010-09-23)
-        ------------------
-        * Update layout engine.
-        * Support group { ... } sentence for create Node-Groups.
-        * Support numbered badge on node (cf. A [numbered = 5])
-        
-        0.1 (2010-09-20)
-        -----------------
-        * first release
-        
-        Todos
-        ======
-        
-        Functionals
-        ------------
-        * Reimplement --separate option
-        * Support diagram legends
-        * Support other block diagram structure
-        
-        Known Issues
-        -------------
-        * Fix some experimental features.
-        * PDF renderer does not support blur shadow
-        * PDF renderer does not support path rendering
+Description: `blockdiag` generate block-diagram image file from spec-text file.
+        
+        Features
+        ========
+        * Generate block-diagram from dot like text (basic feature).
+        * Multilingualization for node-label (utf-8 only).
+        
+        You can get some examples and generated images on 
+        `blockdiag.com <http://blockdiag.com/blockdiag/build/html/index.html>`_ .
+        
+        Setup
+        =====
+        
+        by easy_install
+        ----------------
+        Make environment::
+        
+           $ easy_install blockdiag
+        
+        If you want to export as PDF format, give pdf arguments::
+        
+           $ easy_install "blockdiag[pdf]"
+        
+        by buildout
+        ------------
+        Make environment::
+        
+           $ hg clone http://bitbucket.org/tk0miya/blockdiag
+           $ cd blockdiag
+           $ python bootstrap.py
+           $ bin/buildout
+        
+        Copy and modify ini file. example::
+        
+           $ cp <blockdiag installed path>/blockdiag/examples/simple.diag .
+           $ vi simple.diag
+        
+        Please refer to `spec-text setting sample`_ section for the format of the
+        `simpla.diag` configuration file.
+        
+        spec-text setting sample
+        ========================
+        Few examples are available.
+        You can get more examples at
+        `blockdiag.com <http://blockdiag.com/blockdiag/build/html/index.html>`_ .
+        
+        simple.diag
+        ------------
+        simple.diag is simply define nodes and transitions by dot-like text format::
+        
+            diagram admin {
+              top_page -> config -> config_edit -> config_confirm -> top_page;
+            }
+        
+        screen.diag
+        ------------
+        screen.diag is more complexly sample. diaglam nodes have a alternative label
+        and some transitions::
+        
+            diagram admin {
+              top_page [label = "Top page"];
+        
+              foo_index [label = "List of FOOs"];
+              foo_detail [label = "Detail FOO"];
+              foo_add [label = "Add FOO"];
+              foo_add_confirm [label = "Add FOO (confirm)"];
+              foo_edit [label = "Edit FOO"];
+              foo_edit_confirm [label = "Edit FOO (confirm)"];
+              foo_delete_confirm [label = "Delete FOO (confirm)"];
+        
+              bar_detail [label = "Detail of BAR"];
+              bar_edit [label = "Edit BAR"];
+              bar_edit_confirm [label = "Edit BAR (confirm)"];
+        
+              logout;
+        
+              top_page -> foo_index;
+              top_page -> bar_detail;
+        
+              foo_index -> foo_detail;
+                           foo_detail -> foo_edit;
+                           foo_detail -> foo_delete_confirm;
+              foo_index -> foo_add -> foo_add_confirm -> foo_index;
+              foo_index -> foo_edit -> foo_edit_confirm -> foo_index;
+              foo_index -> foo_delete_confirm -> foo_index;
+        
+              bar_detail -> bar_edit -> bar_edit_confirm -> bar_detail;
+            }
+        
+        
+        Usage
+        =====
+        Execute blockdiag command::
+        
+           $ blockdiag simple.diag
+           $ ls simple.png
+           simple.png
+        
+        
+        Requirements
+        ============
+        * Python 2.6, 2.7, 3.2, 3.3
+        * Pillow 2.2.1
+        * funcparserlib 0.3.6
+        * setuptools
+        
+        
+        License
+        =======
+        Apache License 2.0
+        
+        
+        History
+        =======
+        
+        1.3.2 (2013-11-19)
+        ------------------
+        * Fix bugs
+        
+        1.3.1 (2013-10-22)
+        ------------------
+        * Fix bugs
+        
+        1.3.0 (2013-10-05)
+        ------------------
+        * Support python 3.2 and 3.3 (thanks to @masayuko)
+        * Drop supports for python 2.4 and 2.5
+        * Replace dependency: PIL -> Pillow
+        
+        1.2.4 (2012-11-21)
+        ------------------
+        * Fix bugs
+        
+        1.2.3 (2012-11-05)
+        ------------------
+        * Fix bugs
+        
+        1.2.2 (2012-10-28)
+        ------------------
+        * Fix bugs
+        
+        1.2.1 (2012-10-28)
+        ------------------
+        * Add external imagedraw plugin supports
+        * Add node attribute: label_orientation*
+        * Fix bugs
+        
+        1.2.0 (2012-10-22)
+        ------------------
+        * Optimize algorithm for rendering shadow
+        * Add options to docutils directive
+        * Fix bugs
+        
+        1.1.8 (2012-09-28)
+        ------------------
+        * Add --ignore-pil option
+        * Fix bugs
+        
+        1.1.7 (2012-09-20)
+        ------------------
+        * Add diagram attribute: shadow_style
+        * Add font path for centos 6.2
+        * Add a setting 'antialias' in the configuration file
+        * Fix bugs
+        
+        1.1.6 (2012-06-06)
+        ------------------
+        * Support for readthedocs.org
+        * reST directive supports :caption: option
+        * Fix bugs
+        
+        1.1.5 (2012-04-22)
+        ------------------
+        * Embed source code to SVG document as description
+        * Fix bugs
+        
+        1.1.4 (2012-03-15)
+        ------------------
+        * Add new edge.hstyles: oneone, onemany, manyone, manymany
+        * Add edge attribute: description (for build description-tables)
+        * Fix bugs
+        
+        1.1.3 (2012-02-13)
+        ------------------
+        * Add new edge type for data-models (thanks to David Lang)
+        * Add --no-transparency option
+        * Fix bugs
+        
+        1.1.2 (2011-12-26)
+        ------------------
+        * Support font-index for TrueType Font Collections (.ttc file)
+        * Allow to use reST syntax in descriptions of nodes
+        * Fix bugs
+        
+        1.1.1 (2011-11-27)
+        ------------------
+        * Add node attribute: href (thanks to @r_rudi!)
+        * Fix bugs
+        
+        1.1.0 (2011-11-19)
+        ------------------
+        * Add shape: square and circle
+        * Add fontfamily attribute for switching fontface
+        * Fix bugs
+        
+        1.0.3 (2011-11-13)
+        ------------------
+        * Add plugin: attributes
+        * Change plugin syntax; (cf. plugin attributes [attr = value, attr, value])
+        * Fix bugs
+        
+        1.0.2 (2011-11-07)
+        ------------------
+        * Fix bugs
+        
+        1.0.1 (2011-11-06)
+        ------------------
+        * Add group attribute: shape
+        * Fix bugs
+        
+        1.0.0 (2011-11-04)
+        ------------------
+        * Add node attribute: linecolor
+        * Rename diagram attributes:
+           * fontsize -> default_fontsize
+           * default_line_color -> default_linecolor
+           * default_text_color -> default_textcolor
+        * Add docutils extention
+        * Fix bugs
+        
+        0.9.7 (2011-11-01)
+        ------------------
+        * Add node attribute: fontsize
+        * Add edge attributes: thick, fontsize
+        * Add group attribute: fontsize
+        * Change color of shadow in PDF mode
+        * Add class feature (experimental)
+        * Add handler-plugin framework (experimental)
+        
+        0.9.6 (2011-10-22)
+        ------------------
+        * node.style supports dashed_array format style
+        * Fix bugs
+        
+        0.9.5 (2011-10-19)
+        ------------------
+        * Add node attributes: width and height
+        * Fix bugs
+        
+        0.9.4 (2011-10-07)
+        ------------------
+        * Fix bugs
+        
+        0.9.3 (2011-10-06)
+        ------------------
+        * Replace SVG core by original's (simplesvg.py)
+        * Refactored
+        * Fix bugs
+        
+        0.9.2 (2011-09-30)
+        ------------------
+        * Add node attribute: textcolor
+        * Add group attribute: textcolor
+        * Add edge attribute: textcolor
+        * Add diagram attributes: default_text_attribute
+        * Fix beginpoint shape and endpoint shape were reversed
+        * Fix bugs
+        
+        0.9.1 (2011-09-26)
+        ------------------
+        * Add diagram attributes: default_node_color, default_group_color and default_line_color
+        * Fix bugs
+        
+        0.9.0 (2011-09-25)
+        ------------------
+        * Add icon attribute to node
+        * Make transparency to background of PNG images 
+        * Fix bugs
+        
+        0.8.9 (2011-08-09)
+        ------------------
+        * Fix bugs
+        
+        0.8.8 (2011-08-08)
+        ------------------
+        * Fix bugs
+        
+        0.8.7 (2011-08-06)
+        ------------------
+        * Fix bugs
+        
+        0.8.6 (2011-08-01)
+        ------------------
+        * Support Pillow as replacement of PIL (experimental)
+        * Fix bugs
+        
+        0.8.5 (2011-07-31)
+        ------------------
+        * Allow dot characters in node_id
+        * Fix bugs
+        
+        0.8.4 (2011-07-05)
+        ------------------
+        * Fix bugs
+        
+        0.8.3 (2011-07-03)
+        ------------------
+        * Support input from stdin
+        * Fix bugs
+        
+        0.8.2 (2011-06-29)
+        ------------------
+        * Add node.stacked
+        * Add node shapes: dots, none
+        * Add hiragino-font to font search list
+        * Support background image fetching from web
+        * Add diagram.edge_layout (experimental)
+        * Fix bugs
+        
+        0.8.1 (2011-05-14)
+        ------------------
+        * Change license to Apache License 2.0
+        * Fix bugs
+        
+        0.8.0 (2011-05-04)
+        ------------------
+        * Add --separate option and --version option
+        * Fix bugs
+        
+        0.7.8 (2011-04-19)
+        ------------------
+        * Update layout engine
+        * Update requirements: PIL >= 1.1.5
+        * Update parser for tokenize performance
+        * Add --nodoctype option
+        * Fix bugs
+        * Add many testcases
+        
+        0.7.7 (2011-03-29)
+        ------------------
+        * Fix bugs
+        
+        0.7.6 (2011-03-26)
+        ------------------
+        * Add new layout manager for portrait edges
+        * Fix bugs
+        
+        0.7.5 (2011-03-20)
+        ------------------
+        * Support multiple nodes relations (cf. A -> B, C)
+        * Support node group declaration at attribute of nodes
+        * Fix bugs
+        
+        0.7.4 (2011-03-08)
+        ------------------
+        * Fix bugs
+        
+        0.7.3 (2011-03-02)
+        ------------------
+        * Use UTF-8 characters as Name token (by @swtw7466)
+        * Fix htmlentities included in labels was not escaped on SVG images
+        * Fix bugs
+        
+        0.7.2 (2011-02-28)
+        ------------------
+        * Add default_shape attribute to diagram
+        
+        0.7.1 (2011-02-27)
+        ------------------
+        * Fix edge has broken with antialias option
+        
+        0.7.0 (2011-02-25)
+        ------------------
+        * Support node shape
+        
+        0.6.7 (2011-02-12)
+        ------------------
+        * Change noderenderer interface to new style
+        * Render dashed ellipse more clearly (contributed by @cocoatomo)
+        * Support PDF exporting
+        
+        0.6.6 (2011-01-31)
+        ------------------
+        * Support diagram.shape_namespace
+        * Add new node shapes; mail, cloud, beginpoint, endpoint, minidiamond, actor
+        * Support plug-in structure to install node shapes
+        * Fix bugs
+        
+        0.6.5 (2011-01-18)
+        ------------------
+        * Support node shape (experimental)
+        
+        0.6.4 (2011-01-17)
+        ------------------
+        * Fix bugs
+        
+        0.6.3 (2011-01-15)
+        ------------------
+        * Fix bugs
+        
+        0.6.2 (2011-01-08)
+        ------------------
+        * Fix bugs
+        
+        0.6.1 (2011-01-07)
+        ------------------
+        * Implement 'folded' attribute for edge
+        * Refactor layout engine
+        
+        0.6 (2011-01-02)
+        ------------------
+        * Support nested groups.
+        
+        0.5.5 (2010-12-24)
+        ------------------
+        * Specify direction of edges as syntax (->, --, <-, <->)
+        * Fix bugs.
+        
+        0.5.4 (2010-12-23)
+        ------------------
+        * Remove debug codes.
+        
+        0.5.3 (2010-12-23)
+        ------------------
+        * Support NodeGroup.label.
+        * Implement --separate option (experimental)
+        * Fix right-up edge overrapped on other nodes.
+        * Support configration file: .blockdiagrc
+        
+        0.5.2 (2010-11-06)
+        ------------------
+        * Fix unicode errors for UTF-8'ed SVG exportion.
+        * Refactoring codes for running on GAE.
+        
+        0.5.1 (2010-10-26)
+        ------------------
+        * Fix license text on diagparser.py
+        * Update layout engine.
+        
+        0.5 (2010-10-15)
+        ------------------
+        * Support background-image of node (SVG)
+        * Support labels for edge.
+        * Fix bugs.
+        
+        0.4.2 (2010-10-10)
+        ------------------
+        * Support background-color of node groups.
+        * Draw edge has jumped at edge's cross-points.
+        * Fix bugs.
+        
+        0.4.1 (2010-10-07)
+        ------------------
+        * Fix bugs.
+        
+        0.4 (2010-10-07)
+        ------------------
+        * Support SVG exporting.
+        * Support dashed edge drawing.
+        * Support background image of nodes (PNG only)
+        
+        0.3.1 (2010-09-29)
+        ------------------
+        * Fasten anti-alias process.
+        * Fix text was broken on windows.
+        
+        0.3 (2010-09-26)
+        ------------------
+        * Add --antialias option.
+        * Fix bugs.
+        
+        0.2.2 (2010-09-25)
+        ------------------
+        * Fix edge bugs.
+        
+        0.2.1 (2010-09-25)
+        ------------------
+        * Fix bugs.
+        * Fix package style.
+        
+        0.2 (2010-09-23)
+        ------------------
+        * Update layout engine.
+        * Support group { ... } sentence for create Node-Groups.
+        * Support numbered badge on node (cf. A [numbered = 5])
+        
+        0.1 (2010-09-20)
+        -----------------
+        * first release
+        
         
 Keywords: diagram,generator
 Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
+Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: System Administrators
 Classifier: License :: OSI Approved :: Apache Software License
 Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.2
+Classifier: Programming Language :: Python :: 3.3
 Classifier: Topic :: Software Development
 Classifier: Topic :: Software Development :: Documentation
 Classifier: Topic :: Text Processing :: Markup
diff --git a/src/README.txt b/README.rst
similarity index 94%
rename from src/README.txt
rename to README.rst
index 132832d..fa950b3 100644
--- a/src/README.txt
+++ b/README.rst
@@ -1,476 +1,490 @@
-`blockdiag` generate block-diagram image file from spec-text file.
-
-Features
-========
-* Generate block-diagram from dot like text (basic feature).
-* Multilingualization for node-label (utf-8 only).
-
-You can get some examples and generated images on 
-`blockdiag.com <http://blockdiag.com/blockdiag/build/html/index.html>`_ .
-
-Setup
-=====
-
-by easy_install
-----------------
-Make environment::
-
-   $ easy_install blockdiag
-
-If you want to export as PDF format, give pdf arguments::
-
-   $ easy_install "blockdiag[pdf]"
-
-by buildout
-------------
-Make environment::
-
-   $ hg clone http://bitbucket.org/tk0miya/blockdiag
-   $ cd blockdiag
-   $ python bootstrap.py
-   $ bin/buildout
-
-Copy and modify ini file. example::
-
-   $ cp <blockdiag installed path>/blockdiag/examples/simple.diag .
-   $ vi simple.diag
-
-Please refer to `spec-text setting sample`_ section for the format of the
-`simpla.diag` configuration file.
-
-spec-text setting sample
-========================
-Few examples are available.
-You can get more examples at
-`blockdiag.com <http://blockdiag.com/blockdiag/build/html/index.html>`_ .
-
-simple.diag
-------------
-simple.diag is simply define nodes and transitions by dot-like text format::
-
-    diagram admin {
-      top_page -> config -> config_edit -> config_confirm -> top_page;
-    }
-
-screen.diag
-------------
-screen.diag is more complexly sample. diaglam nodes have a alternative label
-and some transitions::
-
-    diagram admin {
-      top_page [label = "Top page"];
-
-      foo_index [label = "List of FOOs"];
-      foo_detail [label = "Detail FOO"];
-      foo_add [label = "Add FOO"];
-      foo_add_confirm [label = "Add FOO (confirm)"];
-      foo_edit [label = "Edit FOO"];
-      foo_edit_confirm [label = "Edit FOO (confirm)"];
-      foo_delete_confirm [label = "Delete FOO (confirm)"];
-
-      bar_detail [label = "Detail of BAR"];
-      bar_edit [label = "Edit BAR"];
-      bar_edit_confirm [label = "Edit BAR (confirm)"];
-
-      logout;
-
-      top_page -> foo_index;
-      top_page -> bar_detail;
-
-      foo_index -> foo_detail;
-                   foo_detail -> foo_edit;
-                   foo_detail -> foo_delete_confirm;
-      foo_index -> foo_add -> foo_add_confirm -> foo_index;
-      foo_index -> foo_edit -> foo_edit_confirm -> foo_index;
-      foo_index -> foo_delete_confirm -> foo_index;
-
-      bar_detail -> bar_edit -> bar_edit_confirm -> bar_detail;
-    }
-
-
-Usage
-=====
-Execute blockdiag command::
-
-   $ blockdiag simple.diag
-   $ ls simple.png
-   simple.png
-
-
-Requirements
-============
-* Python 2.4 or later (not support 3.x)
-* Python Imaging Library 1.1.5 or later.
-* funcparserlib 0.3.4 or later.
-* setuptools or distribute.
-
-
-License
-=======
-Apache License 2.0
-
-
-History
-=======
-
-1.2.4 (2012-11-21)
-------------------
-* Fix bugs
-
-1.2.3 (2012-11-05)
-------------------
-* Fix bugs
-
-1.2.2 (2012-10-28)
-------------------
-* Fix bugs
-
-1.2.1 (2012-10-28)
-------------------
-* Add external imagedraw plugin supports
-* Add node attribute: label_orientation*
-* Fix bugs
-
-1.2.0 (2012-10-22)
-------------------
-* Optimize algorithm for rendering shadow
-* Add options to docutils directive
-* Fix bugs
-
-1.1.8 (2012-09-28)
-------------------
-* Add --ignore-pil option
-* Fix bugs
-
-1.1.7 (2012-09-20)
-------------------
-* Add diagram attribute: shadow_style
-* Add font path for centos 6.2
-* Add a setting 'antialias' in the configuration file
-* Fix bugs
-
-1.1.6 (2012-06-06)
-------------------
-* Support for readthedocs.org
-* reST directive supports :caption: option
-* Fix bugs
-
-1.1.5 (2012-04-22)
-------------------
-* Embed source code to SVG document as description
-* Fix bugs
-
-1.1.4 (2012-03-15)
-------------------
-* Add new edge.hstyles: oneone, onemany, manyone, manymany
-* Add edge attribute: description (for build description-tables)
-* Fix bugs
-
-1.1.3 (2012-02-13)
-------------------
-* Add new edge type for data-models (thanks to David Lang)
-* Add --no-transparency option
-* Fix bugs
-
-1.1.2 (2011-12-26)
-------------------
-* Support font-index for TrueType Font Collections (.ttc file)
-* Allow to use reST syntax in descriptions of nodes
-* Fix bugs
-
-1.1.1 (2011-11-27)
-------------------
-* Add node attribute: href (thanks to @r_rudi!)
-* Fix bugs
-
-1.1.0 (2011-11-19)
-------------------
-* Add shape: square and circle
-* Add fontfamily attribute for switching fontface
-* Fix bugs
-
-1.0.3 (2011-11-13)
-------------------
-* Add plugin: attributes
-* Change plugin syntax; (cf. plugin attributes [attr = value, attr, value])
-* Fix bugs
-
-1.0.2 (2011-11-07)
-------------------
-* Fix bugs
-
-1.0.1 (2011-11-06)
-------------------
-* Add group attribute: shape
-* Fix bugs
-
-1.0.0 (2011-11-04)
-------------------
-* Add node attribute: linecolor
-* Rename diagram attributes:
-   * fontsize -> default_fontsize
-   * default_line_color -> default_linecolor
-   * default_text_color -> default_textcolor
-* Add docutils extention
-* Fix bugs
-
-0.9.7 (2011-11-01)
-------------------
-* Add node attribute: fontsize
-* Add edge attributes: thick, fontsize
-* Add group attribute: fontsize
-* Change color of shadow in PDF mode
-* Add class feature (experimental)
-* Add handler-plugin framework (experimental)
-
-0.9.6 (2011-10-22)
-------------------
-* node.style supports dashed_array format style
-* Fix bugs
-
-0.9.5 (2011-10-19)
-------------------
-* Add node attributes: width and height
-* Fix bugs
-
-0.9.4 (2011-10-07)
-------------------
-* Fix bugs
-
-0.9.3 (2011-10-06)
-------------------
-* Replace SVG core by original's (simplesvg.py)
-* Refactored
-* Fix bugs
-
-0.9.2 (2011-09-30)
-------------------
-* Add node attribute: textcolor
-* Add group attribute: textcolor
-* Add edge attribute: textcolor
-* Add diagram attributes: default_text_attribute
-* Fix beginpoint shape and endpoint shape were reversed
-* Fix bugs
-
-0.9.1 (2011-09-26)
-------------------
-* Add diagram attributes: default_node_color, default_group_color and default_line_color
-* Fix bugs
-
-0.9.0 (2011-09-25)
-------------------
-* Add icon attribute to node
-* Make transparency to background of PNG images 
-* Fix bugs
-
-0.8.9 (2011-08-09)
-------------------
-* Fix bugs
-
-0.8.8 (2011-08-08)
-------------------
-* Fix bugs
-
-0.8.7 (2011-08-06)
-------------------
-* Fix bugs
-
-0.8.6 (2011-08-01)
-------------------
-* Support Pillow as replacement of PIL (experimental)
-* Fix bugs
-
-0.8.5 (2011-07-31)
-------------------
-* Allow dot characters in node_id
-* Fix bugs
-
-0.8.4 (2011-07-05)
-------------------
-* Fix bugs
-
-0.8.3 (2011-07-03)
-------------------
-* Support input from stdin
-* Fix bugs
-
-0.8.2 (2011-06-29)
-------------------
-* Add node.stacked
-* Add node shapes: dots, none
-* Add hiragino-font to font search list
-* Support background image fetching from web
-* Add diagram.edge_layout (experimental)
-* Fix bugs
-
-0.8.1 (2011-05-14)
-------------------
-* Change license to Apache License 2.0
-* Fix bugs
-
-0.8.0 (2011-05-04)
-------------------
-* Add --separate option and --version option
-* Fix bugs
-
-0.7.8 (2011-04-19)
-------------------
-* Update layout engine
-* Update requirements: PIL >= 1.1.5
-* Update parser for tokenize performance
-* Add --nodoctype option
-* Fix bugs
-* Add many testcases
-
-0.7.7 (2011-03-29)
-------------------
-* Fix bugs
-
-0.7.6 (2011-03-26)
-------------------
-* Add new layout manager for portrait edges
-* Fix bugs
-
-0.7.5 (2011-03-20)
-------------------
-* Support multiple nodes relations (cf. A -> B, C)
-* Support node group declaration at attribute of nodes
-* Fix bugs
-
-0.7.4 (2011-03-08)
-------------------
-* Fix bugs
-
-0.7.3 (2011-03-02)
-------------------
-* Use UTF-8 characters as Name token (by @swtw7466)
-* Fix htmlentities included in labels was not escaped on SVG images
-* Fix bugs
-
-0.7.2 (2011-02-28)
-------------------
-* Add default_shape attribute to diagram
-
-0.7.1 (2011-02-27)
-------------------
-* Fix edge has broken with antialias option
-
-0.7.0 (2011-02-25)
-------------------
-* Support node shape
-
-0.6.7 (2011-02-12)
-------------------
-* Change noderenderer interface to new style
-* Render dashed ellipse more clearly (contributed by @cocoatomo)
-* Support PDF exporting
-
-0.6.6 (2011-01-31)
-------------------
-* Support diagram.shape_namespace
-* Add new node shapes; mail, cloud, beginpoint, endpoint, minidiamond, actor
-* Support plug-in structure to install node shapes
-* Fix bugs
-
-0.6.5 (2011-01-18)
-------------------
-* Support node shape (experimental)
-
-0.6.4 (2011-01-17)
-------------------
-* Fix bugs
-
-0.6.3 (2011-01-15)
-------------------
-* Fix bugs
-
-0.6.2 (2011-01-08)
-------------------
-* Fix bugs
-
-0.6.1 (2011-01-07)
-------------------
-* Implement 'folded' attribute for edge
-* Refactor layout engine
-
-0.6 (2011-01-02)
-------------------
-* Support nested groups.
-
-0.5.5 (2010-12-24)
-------------------
-* Specify direction of edges as syntax (->, --, <-, <->)
-* Fix bugs.
-
-0.5.4 (2010-12-23)
-------------------
-* Remove debug codes.
-
-0.5.3 (2010-12-23)
-------------------
-* Support NodeGroup.label.
-* Implement --separate option (experimental)
-* Fix right-up edge overrapped on other nodes.
-* Support configration file: .blockdiagrc
-
-0.5.2 (2010-11-06)
-------------------
-* Fix unicode errors for UTF-8'ed SVG exportion.
-* Refactoring codes for running on GAE.
-
-0.5.1 (2010-10-26)
-------------------
-* Fix license text on diagparser.py
-* Update layout engine.
-
-0.5 (2010-10-15)
-------------------
-* Support background-image of node (SVG)
-* Support labels for edge.
-* Fix bugs.
-
-0.4.2 (2010-10-10)
-------------------
-* Support background-color of node groups.
-* Draw edge has jumped at edge's cross-points.
-* Fix bugs.
-
-0.4.1 (2010-10-07)
-------------------
-* Fix bugs.
-
-0.4 (2010-10-07)
-------------------
-* Support SVG exporting.
-* Support dashed edge drawing.
-* Support background image of nodes (PNG only)
-
-0.3.1 (2010-09-29)
-------------------
-* Fasten anti-alias process.
-* Fix text was broken on windows.
-
-0.3 (2010-09-26)
-------------------
-* Add --antialias option.
-* Fix bugs.
-
-0.2.2 (2010-09-25)
-------------------
-* Fix edge bugs.
-
-0.2.1 (2010-09-25)
-------------------
-* Fix bugs.
-* Fix package style.
-
-0.2 (2010-09-23)
-------------------
-* Update layout engine.
-* Support group { ... } sentence for create Node-Groups.
-* Support numbered badge on node (cf. A [numbered = 5])
-
-0.1 (2010-09-20)
------------------
-* first release
-
+`blockdiag` generate block-diagram image file from spec-text file.
+
+Features
+========
+* Generate block-diagram from dot like text (basic feature).
+* Multilingualization for node-label (utf-8 only).
+
+You can get some examples and generated images on 
+`blockdiag.com <http://blockdiag.com/blockdiag/build/html/index.html>`_ .
+
+Setup
+=====
+
+by easy_install
+----------------
+Make environment::
+
+   $ easy_install blockdiag
+
+If you want to export as PDF format, give pdf arguments::
+
+   $ easy_install "blockdiag[pdf]"
+
+by buildout
+------------
+Make environment::
+
+   $ hg clone http://bitbucket.org/tk0miya/blockdiag
+   $ cd blockdiag
+   $ python bootstrap.py
+   $ bin/buildout
+
+Copy and modify ini file. example::
+
+   $ cp <blockdiag installed path>/blockdiag/examples/simple.diag .
+   $ vi simple.diag
+
+Please refer to `spec-text setting sample`_ section for the format of the
+`simpla.diag` configuration file.
+
+spec-text setting sample
+========================
+Few examples are available.
+You can get more examples at
+`blockdiag.com <http://blockdiag.com/blockdiag/build/html/index.html>`_ .
+
+simple.diag
+------------
+simple.diag is simply define nodes and transitions by dot-like text format::
+
+    diagram admin {
+      top_page -> config -> config_edit -> config_confirm -> top_page;
+    }
+
+screen.diag
+------------
+screen.diag is more complexly sample. diaglam nodes have a alternative label
+and some transitions::
+
+    diagram admin {
+      top_page [label = "Top page"];
+
+      foo_index [label = "List of FOOs"];
+      foo_detail [label = "Detail FOO"];
+      foo_add [label = "Add FOO"];
+      foo_add_confirm [label = "Add FOO (confirm)"];
+      foo_edit [label = "Edit FOO"];
+      foo_edit_confirm [label = "Edit FOO (confirm)"];
+      foo_delete_confirm [label = "Delete FOO (confirm)"];
+
+      bar_detail [label = "Detail of BAR"];
+      bar_edit [label = "Edit BAR"];
+      bar_edit_confirm [label = "Edit BAR (confirm)"];
+
+      logout;
+
+      top_page -> foo_index;
+      top_page -> bar_detail;
+
+      foo_index -> foo_detail;
+                   foo_detail -> foo_edit;
+                   foo_detail -> foo_delete_confirm;
+      foo_index -> foo_add -> foo_add_confirm -> foo_index;
+      foo_index -> foo_edit -> foo_edit_confirm -> foo_index;
+      foo_index -> foo_delete_confirm -> foo_index;
+
+      bar_detail -> bar_edit -> bar_edit_confirm -> bar_detail;
+    }
+
+
+Usage
+=====
+Execute blockdiag command::
+
+   $ blockdiag simple.diag
+   $ ls simple.png
+   simple.png
+
+
+Requirements
+============
+* Python 2.6, 2.7, 3.2, 3.3
+* Pillow 2.2.1
+* funcparserlib 0.3.6
+* setuptools
+
+
+License
+=======
+Apache License 2.0
+
+
+History
+=======
+
+1.3.2 (2013-11-19)
+------------------
+* Fix bugs
+
+1.3.1 (2013-10-22)
+------------------
+* Fix bugs
+
+1.3.0 (2013-10-05)
+------------------
+* Support python 3.2 and 3.3 (thanks to @masayuko)
+* Drop supports for python 2.4 and 2.5
+* Replace dependency: PIL -> Pillow
+
+1.2.4 (2012-11-21)
+------------------
+* Fix bugs
+
+1.2.3 (2012-11-05)
+------------------
+* Fix bugs
+
+1.2.2 (2012-10-28)
+------------------
+* Fix bugs
+
+1.2.1 (2012-10-28)
+------------------
+* Add external imagedraw plugin supports
+* Add node attribute: label_orientation*
+* Fix bugs
+
+1.2.0 (2012-10-22)
+------------------
+* Optimize algorithm for rendering shadow
+* Add options to docutils directive
+* Fix bugs
+
+1.1.8 (2012-09-28)
+------------------
+* Add --ignore-pil option
+* Fix bugs
+
+1.1.7 (2012-09-20)
+------------------
+* Add diagram attribute: shadow_style
+* Add font path for centos 6.2
+* Add a setting 'antialias' in the configuration file
+* Fix bugs
+
+1.1.6 (2012-06-06)
+------------------
+* Support for readthedocs.org
+* reST directive supports :caption: option
+* Fix bugs
+
+1.1.5 (2012-04-22)
+------------------
+* Embed source code to SVG document as description
+* Fix bugs
+
+1.1.4 (2012-03-15)
+------------------
+* Add new edge.hstyles: oneone, onemany, manyone, manymany
+* Add edge attribute: description (for build description-tables)
+* Fix bugs
+
+1.1.3 (2012-02-13)
+------------------
+* Add new edge type for data-models (thanks to David Lang)
+* Add --no-transparency option
+* Fix bugs
+
+1.1.2 (2011-12-26)
+------------------
+* Support font-index for TrueType Font Collections (.ttc file)
+* Allow to use reST syntax in descriptions of nodes
+* Fix bugs
+
+1.1.1 (2011-11-27)
+------------------
+* Add node attribute: href (thanks to @r_rudi!)
+* Fix bugs
+
+1.1.0 (2011-11-19)
+------------------
+* Add shape: square and circle
+* Add fontfamily attribute for switching fontface
+* Fix bugs
+
+1.0.3 (2011-11-13)
+------------------
+* Add plugin: attributes
+* Change plugin syntax; (cf. plugin attributes [attr = value, attr, value])
+* Fix bugs
+
+1.0.2 (2011-11-07)
+------------------
+* Fix bugs
+
+1.0.1 (2011-11-06)
+------------------
+* Add group attribute: shape
+* Fix bugs
+
+1.0.0 (2011-11-04)
+------------------
+* Add node attribute: linecolor
+* Rename diagram attributes:
+   * fontsize -> default_fontsize
+   * default_line_color -> default_linecolor
+   * default_text_color -> default_textcolor
+* Add docutils extention
+* Fix bugs
+
+0.9.7 (2011-11-01)
+------------------
+* Add node attribute: fontsize
+* Add edge attributes: thick, fontsize
+* Add group attribute: fontsize
+* Change color of shadow in PDF mode
+* Add class feature (experimental)
+* Add handler-plugin framework (experimental)
+
+0.9.6 (2011-10-22)
+------------------
+* node.style supports dashed_array format style
+* Fix bugs
+
+0.9.5 (2011-10-19)
+------------------
+* Add node attributes: width and height
+* Fix bugs
+
+0.9.4 (2011-10-07)
+------------------
+* Fix bugs
+
+0.9.3 (2011-10-06)
+------------------
+* Replace SVG core by original's (simplesvg.py)
+* Refactored
+* Fix bugs
+
+0.9.2 (2011-09-30)
+------------------
+* Add node attribute: textcolor
+* Add group attribute: textcolor
+* Add edge attribute: textcolor
+* Add diagram attributes: default_text_attribute
+* Fix beginpoint shape and endpoint shape were reversed
+* Fix bugs
+
+0.9.1 (2011-09-26)
+------------------
+* Add diagram attributes: default_node_color, default_group_color and default_line_color
+* Fix bugs
+
+0.9.0 (2011-09-25)
+------------------
+* Add icon attribute to node
+* Make transparency to background of PNG images 
+* Fix bugs
+
+0.8.9 (2011-08-09)
+------------------
+* Fix bugs
+
+0.8.8 (2011-08-08)
+------------------
+* Fix bugs
+
+0.8.7 (2011-08-06)
+------------------
+* Fix bugs
+
+0.8.6 (2011-08-01)
+------------------
+* Support Pillow as replacement of PIL (experimental)
+* Fix bugs
+
+0.8.5 (2011-07-31)
+------------------
+* Allow dot characters in node_id
+* Fix bugs
+
+0.8.4 (2011-07-05)
+------------------
+* Fix bugs
+
+0.8.3 (2011-07-03)
+------------------
+* Support input from stdin
+* Fix bugs
+
+0.8.2 (2011-06-29)
+------------------
+* Add node.stacked
+* Add node shapes: dots, none
+* Add hiragino-font to font search list
+* Support background image fetching from web
+* Add diagram.edge_layout (experimental)
+* Fix bugs
+
+0.8.1 (2011-05-14)
+------------------
+* Change license to Apache License 2.0
+* Fix bugs
+
+0.8.0 (2011-05-04)
+------------------
+* Add --separate option and --version option
+* Fix bugs
+
+0.7.8 (2011-04-19)
+------------------
+* Update layout engine
+* Update requirements: PIL >= 1.1.5
+* Update parser for tokenize performance
+* Add --nodoctype option
+* Fix bugs
+* Add many testcases
+
+0.7.7 (2011-03-29)
+------------------
+* Fix bugs
+
+0.7.6 (2011-03-26)
+------------------
+* Add new layout manager for portrait edges
+* Fix bugs
+
+0.7.5 (2011-03-20)
+------------------
+* Support multiple nodes relations (cf. A -> B, C)
+* Support node group declaration at attribute of nodes
+* Fix bugs
+
+0.7.4 (2011-03-08)
+------------------
+* Fix bugs
+
+0.7.3 (2011-03-02)
+------------------
+* Use UTF-8 characters as Name token (by @swtw7466)
+* Fix htmlentities included in labels was not escaped on SVG images
+* Fix bugs
+
+0.7.2 (2011-02-28)
+------------------
+* Add default_shape attribute to diagram
+
+0.7.1 (2011-02-27)
+------------------
+* Fix edge has broken with antialias option
+
+0.7.0 (2011-02-25)
+------------------
+* Support node shape
+
+0.6.7 (2011-02-12)
+------------------
+* Change noderenderer interface to new style
+* Render dashed ellipse more clearly (contributed by @cocoatomo)
+* Support PDF exporting
+
+0.6.6 (2011-01-31)
+------------------
+* Support diagram.shape_namespace
+* Add new node shapes; mail, cloud, beginpoint, endpoint, minidiamond, actor
+* Support plug-in structure to install node shapes
+* Fix bugs
+
+0.6.5 (2011-01-18)
+------------------
+* Support node shape (experimental)
+
+0.6.4 (2011-01-17)
+------------------
+* Fix bugs
+
+0.6.3 (2011-01-15)
+------------------
+* Fix bugs
+
+0.6.2 (2011-01-08)
+------------------
+* Fix bugs
+
+0.6.1 (2011-01-07)
+------------------
+* Implement 'folded' attribute for edge
+* Refactor layout engine
+
+0.6 (2011-01-02)
+------------------
+* Support nested groups.
+
+0.5.5 (2010-12-24)
+------------------
+* Specify direction of edges as syntax (->, --, <-, <->)
+* Fix bugs.
+
+0.5.4 (2010-12-23)
+------------------
+* Remove debug codes.
+
+0.5.3 (2010-12-23)
+------------------
+* Support NodeGroup.label.
+* Implement --separate option (experimental)
+* Fix right-up edge overrapped on other nodes.
+* Support configration file: .blockdiagrc
+
+0.5.2 (2010-11-06)
+------------------
+* Fix unicode errors for UTF-8'ed SVG exportion.
+* Refactoring codes for running on GAE.
+
+0.5.1 (2010-10-26)
+------------------
+* Fix license text on diagparser.py
+* Update layout engine.
+
+0.5 (2010-10-15)
+------------------
+* Support background-image of node (SVG)
+* Support labels for edge.
+* Fix bugs.
+
+0.4.2 (2010-10-10)
+------------------
+* Support background-color of node groups.
+* Draw edge has jumped at edge's cross-points.
+* Fix bugs.
+
+0.4.1 (2010-10-07)
+------------------
+* Fix bugs.
+
+0.4 (2010-10-07)
+------------------
+* Support SVG exporting.
+* Support dashed edge drawing.
+* Support background image of nodes (PNG only)
+
+0.3.1 (2010-09-29)
+------------------
+* Fasten anti-alias process.
+* Fix text was broken on windows.
+
+0.3 (2010-09-26)
+------------------
+* Add --antialias option.
+* Fix bugs.
+
+0.2.2 (2010-09-25)
+------------------
+* Fix edge bugs.
+
+0.2.1 (2010-09-25)
+------------------
+* Fix bugs.
+* Fix package style.
+
+0.2 (2010-09-23)
+------------------
+* Update layout engine.
+* Support group { ... } sentence for create Node-Groups.
+* Support numbered badge on node (cf. A [numbered = 5])
+
+0.1 (2010-09-20)
+-----------------
+* first release
+
diff --git a/bootstrap.py b/bootstrap.py
index 716795f..1b28969 100644
--- a/bootstrap.py
+++ b/bootstrap.py
@@ -18,75 +18,14 @@ The script accepts buildout command-line options, so you can
 use the -c option to specify an alternate configuration file.
 """
 
-import os, shutil, sys, tempfile, urllib, urllib2, subprocess
-from optparse import OptionParser
-
-if sys.platform == 'win32':
-    def quote(c):
-        if ' ' in c:
-            return '"%s"' % c  # work around spawn lamosity on windows
-        else:
-            return c
-else:
-    quote = str
-
-# See zc.buildout.easy_install._has_broken_dash_S for motivation and comments.
-stdout, stderr = subprocess.Popen(
-    [sys.executable, '-Sc',
-     'try:\n'
-     '    import ConfigParser\n'
-     'except ImportError:\n'
-     '    print 1\n'
-     'else:\n'
-     '    print 0\n'],
-    stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
-has_broken_dash_S = bool(int(stdout.strip()))
-
-# In order to be more robust in the face of system Pythons, we want to
-# run without site-packages loaded.  This is somewhat tricky, in
-# particular because Python 2.6's distutils imports site, so starting
-# with the -S flag is not sufficient.  However, we'll start with that:
-if not has_broken_dash_S and 'site' in sys.modules:
-    # We will restart with python -S.
-    args = sys.argv[:]
-    args[0:0] = [sys.executable, '-S']
-    args = map(quote, args)
-    os.execv(sys.executable, args)
-# Now we are running with -S.  We'll get the clean sys.path, import site
-# because distutils will do it later, and then reset the path and clean
-# out any namespace packages from site-packages that might have been
-# loaded by .pth files.
-clean_path = sys.path[:]
-import site  # imported because of its side effects
-sys.path[:] = clean_path
-for k, v in sys.modules.items():
-    if k in ('setuptools', 'pkg_resources') or (
-        hasattr(v, '__path__') and
-        len(v.__path__) == 1 and
-        not os.path.exists(os.path.join(v.__path__[0], '__init__.py'))):
-        # This is a namespace package.  Remove it.
-        sys.modules.pop(k)
-
-is_jython = sys.platform.startswith('java')
-
-setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py'
-distribute_source = 'http://python-distribute.org/distribute_setup.py'
+import os
+import shutil
+import sys
+import tempfile
 
+from optparse import OptionParser
 
-# parsing arguments
-def normalize_to_url(option, opt_str, value, parser):
-    if value:
-        if '://' not in value:  # It doesn't smell like a URL.
-            value = 'file://%s' % (
-                urllib.pathname2url(
-                    os.path.abspath(os.path.expanduser(value))),)
-        if opt_str == '--download-base' and not value.endswith('/'):
-            # Download base needs a trailing slash to make the world happy.
-            value += '/'
-    else:
-        value = None
-    name = opt_str[2:].replace('-', '_')
-    setattr(parser.values, name, value)
+tmpeggs = tempfile.mkdtemp()
 
 usage = '''\
 [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
@@ -96,31 +35,13 @@ Bootstraps a buildout-based project.
 Simply run this script in a directory containing a buildout.cfg, using the
 Python that you want bin/buildout to use.
 
-Note that by using --setup-source and --download-base to point to
-local resources, you can keep this script from going over the network.
+Note that by using --find-links to point to local resources, you can keep 
+this script from going over the network.
 '''
 
 parser = OptionParser(usage=usage)
-parser.add_option("-v", "--version", dest="version",
-                          help="use a specific zc.buildout version")
-parser.add_option("-d", "--distribute",
-                   action="store_true", dest="use_distribute", default=False,
-                   help="Use Distribute rather than Setuptools.")
-parser.add_option("--setup-source", action="callback", dest="setup_source",
-                  callback=normalize_to_url, nargs=1, type="string",
-                  help=("Specify a URL or file location for the setup file. "
-                        "If you use Setuptools, this will default to " +
-                        setuptools_source + "; if you use Distribute, this "
-                        "will default to " + distribute_source + "."))
-parser.add_option("--download-base", action="callback", dest="download_base",
-                  callback=normalize_to_url, nargs=1, type="string",
-                  help=("Specify a URL or directory for downloading "
-                        "zc.buildout and either Setuptools or Distribute. "
-                        "Defaults to PyPI."))
-parser.add_option("--eggs",
-                  help=("Specify a directory for storing eggs.  Defaults to "
-                        "a temporary directory that is deleted when the "
-                        "bootstrap script completes."))
+parser.add_option("-v", "--version", help="use a specific zc.buildout version")
+
 parser.add_option("-t", "--accept-buildout-test-releases",
                   dest='accept_buildout_test_releases',
                   action="store_true", default=False,
@@ -130,50 +51,38 @@ parser.add_option("-t", "--accept-buildout-test-releases",
                         "extensions for you.  If you use this flag, "
                         "bootstrap and buildout will get the newest releases "
                         "even if they are alphas or betas."))
-parser.add_option("-c", None, action="store", dest="config_file",
-                   help=("Specify the path to the buildout configuration "
-                         "file to be used."))
-
-options, orig_args = parser.parse_args()
-
-args = []
-
-# if -c was provided, we push it back into args for buildout's main function
-if options.config_file is not None:
-    args += ['-c', options.config_file]
+parser.add_option("-c", "--config-file",
+                  help=("Specify the path to the buildout configuration "
+                        "file to be used."))
+parser.add_option("-f", "--find-links",
+                  help=("Specify a URL to search for buildout releases"))
 
-if options.eggs:
-    eggs_dir = os.path.abspath(os.path.expanduser(options.eggs))
-else:
-    eggs_dir = tempfile.mkdtemp()
 
-if options.setup_source is None:
-    if options.use_distribute:
-        options.setup_source = distribute_source
-    else:
-        options.setup_source = setuptools_source
+options, args = parser.parse_args()
 
-if options.accept_buildout_test_releases:
-    args.append('buildout:accept-buildout-test-releases=true')
+######################################################################
+# load/install setuptools
 
+to_reload = False
 try:
     import pkg_resources
-    import setuptools  # A flag.  Sometimes pkg_resources is installed alone.
-    if not hasattr(pkg_resources, '_distribute'):
-        raise ImportError
+    import setuptools
 except ImportError:
-    ez_code = urllib2.urlopen(
-        options.setup_source).read().replace('\r\n', '\n')
     ez = {}
-    exec ez_code in ez
-    setup_args = dict(to_dir=eggs_dir, download_delay=0)
-    if options.download_base:
-        setup_args['download_base'] = options.download_base
-    if options.use_distribute:
-        setup_args['no_fake'] = True
+
+    try:
+        from urllib.request import urlopen
+    except ImportError:
+        from urllib2 import urlopen
+
+    # XXX use a more permanent ez_setup.py URL when available.
+    exec(urlopen('https://bitbucket.org/pypa/setuptools/raw/0.7.2/ez_setup.py'
+                ).read(), ez)
+    setup_args = dict(to_dir=tmpeggs, download_delay=0)
     ez['use_setuptools'](**setup_args)
-    if 'pkg_resources' in sys.modules:
-        reload(sys.modules['pkg_resources'])
+
+    if to_reload:
+        reload(pkg_resources)
     import pkg_resources
     # This does not (always?) update the default working set.  We will
     # do it.
@@ -181,31 +90,26 @@ except ImportError:
         if path not in pkg_resources.working_set.entries:
             pkg_resources.working_set.add_entry(path)
 
-cmd = [quote(sys.executable),
-       '-c',
-       quote('from setuptools.command.easy_install import main; main()'),
-       '-mqNxd',
-       quote(eggs_dir)]
+######################################################################
+# Install buildout
 
-if not has_broken_dash_S:
-    cmd.insert(1, '-S')
+ws = pkg_resources.working_set
+
+cmd = [sys.executable, '-c',
+       'from setuptools.command.easy_install import main; main()',
+       '-mZqNxd', tmpeggs]
 
-find_links = options.download_base
-if not find_links:
-    find_links = os.environ.get('bootstrap-testing-find-links')
+find_links = os.environ.get(
+    'bootstrap-testing-find-links',
+    options.find_links or
+    ('http://downloads.buildout.org/'
+     if options.accept_buildout_test_releases else None)
+    )
 if find_links:
-    cmd.extend(['-f', quote(find_links)])
+    cmd.extend(['-f', find_links])
 
-if options.use_distribute:
-    setup_requirement = 'distribute'
-else:
-    setup_requirement = 'setuptools'
-ws = pkg_resources.working_set
-setup_requirement_path = ws.find(
-    pkg_resources.Requirement.parse(setup_requirement)).location
-env = dict(
-    os.environ,
-    PYTHONPATH=setup_requirement_path)
+setuptools_path = ws.find(
+    pkg_resources.Requirement.parse('setuptools')).location
 
 requirement = 'zc.buildout'
 version = options.version
@@ -220,7 +124,7 @@ if version is None and not options.accept_buildout_test_releases:
                 return False
         return True
     index = setuptools.package_index.PackageIndex(
-        search_path=[setup_requirement_path])
+        search_path=[setuptools_path])
     if find_links:
         index.add_find_links((find_links,))
     req = pkg_resources.Requirement.parse(requirement)
@@ -242,25 +146,25 @@ if version:
     requirement = '=='.join((requirement, version))
 cmd.append(requirement)
 
-if is_jython:
-    import subprocess
-    exitcode = subprocess.Popen(cmd, env=env).wait()
-else:  # Windows prefers this, apparently; otherwise we would prefer subprocess
-    exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env]))
-if exitcode != 0:
-    sys.stdout.flush()
-    sys.stderr.flush()
-    print ("An error occurred when trying to install zc.buildout. "
-           "Look above this message for any errors that "
-           "were output by easy_install.")
-    sys.exit(exitcode)
+import subprocess
+if subprocess.call(cmd, env=dict(os.environ, PYTHONPATH=setuptools_path)) != 0:
+    raise Exception(
+        "Failed to execute command:\n%s",
+        repr(cmd)[1:-1])
 
-ws.add_entry(eggs_dir)
+######################################################################
+# Import and run buildout
+
+ws.add_entry(tmpeggs)
 ws.require(requirement)
 import zc.buildout.buildout
-if orig_args:
-    # run buildout with commands passed to bootstrap.py, then actually bootstrap
-    zc.buildout.buildout.main(args + orig_args)
-zc.buildout.buildout.main(args + ['bootstrap'])
-if not options.eggs:  # clean up temporary egg directory
-    shutil.rmtree(eggs_dir)
+
+if not [a for a in args if '=' not in a]:
+    args.append('bootstrap')
+
+# if -c was provided, we push it back into args for buildout' main function
+if options.config_file is not None:
+    args[0:0] = ['-c', options.config_file]
+
+zc.buildout.buildout.main(args)
+shutil.rmtree(tmpeggs)
diff --git a/buildout.cfg b/buildout.cfg
index cb44b9f..69df527 100644
--- a/buildout.cfg
+++ b/buildout.cfg
@@ -1,49 +1,45 @@
-[buildout]
-parts = blockdiag test coverage pyflakes pylint
-
-develop = .
-
-[blockdiag]
-recipe = zc.recipe.egg
-eggs = blockdiag[rst]
-interpreter = py
-
-[test]
-recipe = pbp.recipe.noserunner
-eggs =
-    blockdiag[rst]
-    blockdiag[test]
-    coverage
-    unittest-xml-reporting
-
-[coverage]
-recipe = zc.recipe.egg
-eggs = coverage
-
-[pyflakes]
-recipe = zc.recipe.egg
-eggs = pyflakes
-scripts = pyflakes
-entry-points = pyflakes=pyflakes.scripts.pyflakes:main
-
-[pylint]
-recipe = zc.recipe.egg
-eggs =
-    pylint
-    blockdiag[test]
-scripts = pylint
-
-[test-extra]
-recipe = iw.recipe.cmd:py
-on_install = true
-cmds =
-   >>> url = "http://sourceforge.jp/frs/redir.php?m=jaist&f=%2Fvlgothic%2F46966%2FVLGothic-20100416.zip"
-   >>> buildout_dir = buildout.get('directory', '.')
-   >>> path = os.path.join(buildout_dir, 'src/blockdiag/tests/truetype')
-   >>> if not os.path.exists(path):
-   ...     os.makedirs(path)
-   ...     import cStringIO, urllib2, zipfile
-   ...     archive = urllib2.urlopen(url).read()
-   ...     zip = zipfile.ZipFile(cStringIO.StringIO(archive))
-   ...     ttf = zip.read('VLGothic/VL-PGothic-Regular.ttf')
-   ...     open(os.path.join(path, 'VL-PGothic-Regular.ttf'), 'wb').write(ttf)
+[buildout]
+parts = blockdiag test tox static_analysis
+
+develop = .
+
+[blockdiag]
+recipe = zc.recipe.egg
+eggs = blockdiag[rst]
+interpreter = py
+
+[test]
+recipe = pbp.recipe.noserunner
+eggs =
+    blockdiag[rst]
+    blockdiag[test]
+    coverage
+    unittest-xml-reporting
+
+[tox]
+recipe = zc.recipe.egg
+eggs =
+    tox
+    detox
+
+[static_analysis]
+recipe = zc.recipe.egg
+eggs =
+    coverage
+    flake8
+    pylint
+
+[test-extra]
+recipe = iw.recipe.cmd:py
+on_install = true
+cmds =
+   >>> url = "http://sourceforge.jp/frs/redir.php?m=jaist&f=%2Fvlgothic%2F46966%2FVLGothic-20100416.zip"
+   >>> buildout_dir = buildout.get('directory', '.')
+   >>> path = os.path.join(buildout_dir, 'src/blockdiag/tests/truetype')
+   >>> if not os.path.exists(path):
+   ...     os.makedirs(path)
+   ...     import cStringIO, urllib2, zipfile
+   ...     archive = urllib2.urlopen(url).read()
+   ...     zip = zipfile.ZipFile(cStringIO.StringIO(archive))
+   ...     ttf = zip.read('VLGothic/VL-PGothic-Regular.ttf')
+   ...     open(os.path.join(path, 'VL-PGothic-Regular.ttf'), 'wb').write(ttf)
diff --git a/examples/blockdiagrc b/examples/blockdiagrc
new file mode 100644
index 0000000..6572995
--- /dev/null
+++ b/examples/blockdiagrc
@@ -0,0 +1,2 @@
+[blockdiag]
+fontpath = /usr/share/fonts/truetype/kochi/kochi-mincho.ttf
diff --git a/setup.cfg b/setup.cfg
index be3189c..18b023c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -9,3 +9,6 @@ build-base = _build
 [sdist]
 formats = gztar
 
+[flake8]
+ignore = _
+
diff --git a/setup.py b/setup.py
index 1f72f01..0a0f1a7 100644
--- a/setup.py
+++ b/setup.py
@@ -1,129 +1,102 @@
-# -*- coding: utf-8 -*-
-from setuptools import setup, find_packages
-import os, sys
-import pkg_resources
-
-sys.path.insert(0, 'src')
-import blockdiag
-
-
-def is_installed(name):
-    try:
-        pkg_resources.get_distribution(name)
-        return True
-    except:
-        return False
-
-
-long_description = \
-        open(os.path.join("src","README.txt")).read() + \
-        open(os.path.join("src","TODO.txt")).read()
-
-classifiers = [
-    "Development Status :: 4 - Beta",
-    "Intended Audience :: System Administrators",
-    "License :: OSI Approved :: Apache Software License",
-    "Programming Language :: Python",
-    "Topic :: Software Development",
-    "Topic :: Software Development :: Documentation",
-    "Topic :: Text Processing :: Markup",
-]
-
-requires = ['setuptools',
-            'funcparserlib',
-            'webcolors']
-deplinks = []
-
-# For readthedocs.org
-# http://read-the-docs.readthedocs.org/en/latest/faq.html#how-do-i-change-behavior-for-read-the-docs
-# Find imaging libraries
-
-if 'READTHEDOCS' in os.environ:
-    requires.append('Pillow')
-elif is_installed('PIL'):
-    requires.append('PIL')
-elif is_installed('Pillow'):
-    requires.append('Pillow')
-elif sys.platform == 'win32':
-    requires.append('Pillow')
-else:
-    requires.append('PIL')
-
-
-# only for Python2.6
-if sys.version_info > (2, 6) and sys.version_info < (2, 7):
-    requires.append('OrderedDict')
-
-
-setup(
-     name='blockdiag',
-     version=blockdiag.__version__,
-     description='blockdiag generate block-diagram image file from spec-text file.',
-     long_description=long_description,
-     classifiers=classifiers,
-     keywords=['diagram','generator'],
-     author='Takeshi Komiya',
-     author_email='i.tkomiya at gmail.com',
-     url='http://blockdiag.com/',
-     download_url='http://pypi.python.org/pypi/blockdiag',
-     license='Apache License 2.0',
-     py_modules=['blockdiag_sphinxhelper'],
-     packages=find_packages('src'),
-     package_dir={'': 'src'},
-     package_data = {'': ['buildout.cfg']},
-     include_package_data=True,
-     install_requires=requires,
-     extras_require=dict(
-         test=[
-             'Nose',
-             'pep8>=1.3',
-             'unittest2',
-         ],
-         pdf=[
-             'reportlab',
-         ],
-         rst=[
-             'docutils',
-         ],
-     ),
-     dependency_links=deplinks,
-     test_suite='nose.collector',
-     tests_require=['Nose','pep8'],
-     entry_points="""
-        [console_scripts]
-        blockdiag = blockdiag.command:main
-
-        [blockdiag_noderenderer]
-        box = blockdiag.noderenderer.box
-        square = blockdiag.noderenderer.square
-        roundedbox = blockdiag.noderenderer.roundedbox
-        diamond = blockdiag.noderenderer.diamond
-        minidiamond = blockdiag.noderenderer.minidiamond
-        mail = blockdiag.noderenderer.mail
-        note = blockdiag.noderenderer.note
-        cloud = blockdiag.noderenderer.cloud
-        circle = blockdiag.noderenderer.circle
-        ellipse = blockdiag.noderenderer.ellipse
-        beginpoint = blockdiag.noderenderer.beginpoint
-        endpoint = blockdiag.noderenderer.endpoint
-        actor = blockdiag.noderenderer.actor
-        flowchart.database = blockdiag.noderenderer.flowchart.database
-        flowchart.input = blockdiag.noderenderer.flowchart.input
-        flowchart.loopin = blockdiag.noderenderer.flowchart.loopin
-        flowchart.loopout = blockdiag.noderenderer.flowchart.loopout
-        flowchart.terminator = blockdiag.noderenderer.flowchart.terminator
-        textbox = blockdiag.noderenderer.textbox
-        dots = blockdiag.noderenderer.dots
-        none = blockdiag.noderenderer.none
-
-        [blockdiag_plugins]
-        attributes = blockdiag.plugins.attributes
-        autoclass = blockdiag.plugins.autoclass
-
-        [blockdiag_imagedrawers]
-        imagedraw_png = blockdiag.imagedraw.png
-        imagedraw_svg = blockdiag.imagedraw.svg
-        imagedraw_pdf = blockdiag.imagedraw.pdf
-     """,
-)
-
+# -*- coding: utf-8 -*-
+import os
+import sys
+from setuptools import setup, find_packages
+
+sys.path.insert(0, 'src')
+import blockdiag
+
+classifiers = [
+    "Development Status :: 5 - Production/Stable",
+    "Intended Audience :: System Administrators",
+    "License :: OSI Approved :: Apache Software License",
+    "Programming Language :: Python",
+    "Programming Language :: Python :: 2.6",
+    "Programming Language :: Python :: 2.7",
+    "Programming Language :: Python :: 3.2",
+    "Programming Language :: Python :: 3.3",
+    "Topic :: Software Development",
+    "Topic :: Software Development :: Documentation",
+    "Topic :: Text Processing :: Markup",
+]
+
+requires = ['setuptools',
+            'funcparserlib',
+            'webcolors',
+            'Pillow']
+test_requires = ['Nose',
+                 'pep8>=1.3']
+
+
+# only for Python2.6
+if sys.version_info > (2, 6) and sys.version_info < (2, 7):
+    requires.append('OrderedDict')
+    test_requires.append('unittest2')
+
+
+setup(
+    name='blockdiag',
+    version=blockdiag.__version__,
+    description='blockdiag generates block-diagram image from text',
+    long_description=open("README.rst").read(),
+    classifiers=classifiers,
+    keywords=['diagram', 'generator'],
+    author='Takeshi Komiya',
+    author_email='i.tkomiya at gmail.com',
+    url='http://blockdiag.com/',
+    download_url='http://pypi.python.org/pypi/blockdiag',
+    license='Apache License 2.0',
+    py_modules=['blockdiag_sphinxhelper'],
+    packages=find_packages('src'),
+    package_dir={'': 'src'},
+    package_data={'': ['buildout.cfg']},
+    include_package_data=True,
+    install_requires=requires,
+    extras_require=dict(
+        test=test_requires,
+        pdf=[
+            'reportlab',
+        ],
+        rst=[
+            'docutils',
+        ],
+    ),
+    test_suite='nose.collector',
+    tests_require=test_requires,
+    entry_points="""
+       [console_scripts]
+       blockdiag = blockdiag.command:main
+
+       [blockdiag_noderenderer]
+       box = blockdiag.noderenderer.box
+       square = blockdiag.noderenderer.square
+       roundedbox = blockdiag.noderenderer.roundedbox
+       diamond = blockdiag.noderenderer.diamond
+       minidiamond = blockdiag.noderenderer.minidiamond
+       mail = blockdiag.noderenderer.mail
+       note = blockdiag.noderenderer.note
+       cloud = blockdiag.noderenderer.cloud
+       circle = blockdiag.noderenderer.circle
+       ellipse = blockdiag.noderenderer.ellipse
+       beginpoint = blockdiag.noderenderer.beginpoint
+       endpoint = blockdiag.noderenderer.endpoint
+       actor = blockdiag.noderenderer.actor
+       flowchart.database = blockdiag.noderenderer.flowchart.database
+       flowchart.input = blockdiag.noderenderer.flowchart.input
+       flowchart.loopin = blockdiag.noderenderer.flowchart.loopin
+       flowchart.loopout = blockdiag.noderenderer.flowchart.loopout
+       flowchart.terminator = blockdiag.noderenderer.flowchart.terminator
+       textbox = blockdiag.noderenderer.textbox
+       dots = blockdiag.noderenderer.dots
+       none = blockdiag.noderenderer.none
+
+       [blockdiag_plugins]
+       attributes = blockdiag.plugins.attributes
+       autoclass = blockdiag.plugins.autoclass
+
+       [blockdiag_imagedrawers]
+       imagedraw_png = blockdiag.imagedraw.png
+       imagedraw_svg = blockdiag.imagedraw.svg
+       imagedraw_pdf = blockdiag.imagedraw.pdf
+    """,
+)
diff --git a/src/TODO.txt b/src/TODO.txt
deleted file mode 100644
index b28943a..0000000
--- a/src/TODO.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-Todos
-======
-
-Functionals
-------------
-* Reimplement --separate option
-* Support diagram legends
-* Support other block diagram structure
-
-Known Issues
--------------
-* Fix some experimental features.
-* PDF renderer does not support blur shadow
-* PDF renderer does not support path rendering
diff --git a/src/blockdiag.egg-info/PKG-INFO b/src/blockdiag.egg-info/PKG-INFO
index 74aa02e..be630f3 100644
--- a/src/blockdiag.egg-info/PKG-INFO
+++ b/src/blockdiag.egg-info/PKG-INFO
@@ -1,509 +1,513 @@
-Metadata-Version: 1.1
+Metadata-Version: 1.0
 Name: blockdiag
-Version: 1.2.4
-Summary: blockdiag generate block-diagram image file from spec-text file.
+Version: 1.3.2
+Summary: blockdiag generates block-diagram image from text
 Home-page: http://blockdiag.com/
 Author: Takeshi Komiya
 Author-email: i.tkomiya at gmail.com
 License: Apache License 2.0
 Download-URL: http://pypi.python.org/pypi/blockdiag
-Description: `blockdiag` generate block-diagram image file from spec-text file.
-        
-        Features
-        ========
-        * Generate block-diagram from dot like text (basic feature).
-        * Multilingualization for node-label (utf-8 only).
-        
-        You can get some examples and generated images on 
-        `blockdiag.com <http://blockdiag.com/blockdiag/build/html/index.html>`_ .
-        
-        Setup
-        =====
-        
-        by easy_install
-        ----------------
-        Make environment::
-        
-           $ easy_install blockdiag
-        
-        If you want to export as PDF format, give pdf arguments::
-        
-           $ easy_install "blockdiag[pdf]"
-        
-        by buildout
-        ------------
-        Make environment::
-        
-           $ hg clone http://bitbucket.org/tk0miya/blockdiag
-           $ cd blockdiag
-           $ python bootstrap.py
-           $ bin/buildout
-        
-        Copy and modify ini file. example::
-        
-           $ cp <blockdiag installed path>/blockdiag/examples/simple.diag .
-           $ vi simple.diag
-        
-        Please refer to `spec-text setting sample`_ section for the format of the
-        `simpla.diag` configuration file.
-        
-        spec-text setting sample
-        ========================
-        Few examples are available.
-        You can get more examples at
-        `blockdiag.com <http://blockdiag.com/blockdiag/build/html/index.html>`_ .
-        
-        simple.diag
-        ------------
-        simple.diag is simply define nodes and transitions by dot-like text format::
-        
-            diagram admin {
-              top_page -> config -> config_edit -> config_confirm -> top_page;
-            }
-        
-        screen.diag
-        ------------
-        screen.diag is more complexly sample. diaglam nodes have a alternative label
-        and some transitions::
-        
-            diagram admin {
-              top_page [label = "Top page"];
-        
-              foo_index [label = "List of FOOs"];
-              foo_detail [label = "Detail FOO"];
-              foo_add [label = "Add FOO"];
-              foo_add_confirm [label = "Add FOO (confirm)"];
-              foo_edit [label = "Edit FOO"];
-              foo_edit_confirm [label = "Edit FOO (confirm)"];
-              foo_delete_confirm [label = "Delete FOO (confirm)"];
-        
-              bar_detail [label = "Detail of BAR"];
-              bar_edit [label = "Edit BAR"];
-              bar_edit_confirm [label = "Edit BAR (confirm)"];
-        
-              logout;
-        
-              top_page -> foo_index;
-              top_page -> bar_detail;
-        
-              foo_index -> foo_detail;
-                           foo_detail -> foo_edit;
-                           foo_detail -> foo_delete_confirm;
-              foo_index -> foo_add -> foo_add_confirm -> foo_index;
-              foo_index -> foo_edit -> foo_edit_confirm -> foo_index;
-              foo_index -> foo_delete_confirm -> foo_index;
-        
-              bar_detail -> bar_edit -> bar_edit_confirm -> bar_detail;
-            }
-        
-        
-        Usage
-        =====
-        Execute blockdiag command::
-        
-           $ blockdiag simple.diag
-           $ ls simple.png
-           simple.png
-        
-        
-        Requirements
-        ============
-        * Python 2.4 or later (not support 3.x)
-        * Python Imaging Library 1.1.5 or later.
-        * funcparserlib 0.3.4 or later.
-        * setuptools or distribute.
-        
-        
-        License
-        =======
-        Apache License 2.0
-        
-        
-        History
-        =======
-        
-        1.2.4 (2012-11-21)
-        ------------------
-        * Fix bugs
-        
-        1.2.3 (2012-11-05)
-        ------------------
-        * Fix bugs
-        
-        1.2.2 (2012-10-28)
-        ------------------
-        * Fix bugs
-        
-        1.2.1 (2012-10-28)
-        ------------------
-        * Add external imagedraw plugin supports
-        * Add node attribute: label_orientation*
-        * Fix bugs
-        
-        1.2.0 (2012-10-22)
-        ------------------
-        * Optimize algorithm for rendering shadow
-        * Add options to docutils directive
-        * Fix bugs
-        
-        1.1.8 (2012-09-28)
-        ------------------
-        * Add --ignore-pil option
-        * Fix bugs
-        
-        1.1.7 (2012-09-20)
-        ------------------
-        * Add diagram attribute: shadow_style
-        * Add font path for centos 6.2
-        * Add a setting 'antialias' in the configuration file
-        * Fix bugs
-        
-        1.1.6 (2012-06-06)
-        ------------------
-        * Support for readthedocs.org
-        * reST directive supports :caption: option
-        * Fix bugs
-        
-        1.1.5 (2012-04-22)
-        ------------------
-        * Embed source code to SVG document as description
-        * Fix bugs
-        
-        1.1.4 (2012-03-15)
-        ------------------
-        * Add new edge.hstyles: oneone, onemany, manyone, manymany
-        * Add edge attribute: description (for build description-tables)
-        * Fix bugs
-        
-        1.1.3 (2012-02-13)
-        ------------------
-        * Add new edge type for data-models (thanks to David Lang)
-        * Add --no-transparency option
-        * Fix bugs
-        
-        1.1.2 (2011-12-26)
-        ------------------
-        * Support font-index for TrueType Font Collections (.ttc file)
-        * Allow to use reST syntax in descriptions of nodes
-        * Fix bugs
-        
-        1.1.1 (2011-11-27)
-        ------------------
-        * Add node attribute: href (thanks to @r_rudi!)
-        * Fix bugs
-        
-        1.1.0 (2011-11-19)
-        ------------------
-        * Add shape: square and circle
-        * Add fontfamily attribute for switching fontface
-        * Fix bugs
-        
-        1.0.3 (2011-11-13)
-        ------------------
-        * Add plugin: attributes
-        * Change plugin syntax; (cf. plugin attributes [attr = value, attr, value])
-        * Fix bugs
-        
-        1.0.2 (2011-11-07)
-        ------------------
-        * Fix bugs
-        
-        1.0.1 (2011-11-06)
-        ------------------
-        * Add group attribute: shape
-        * Fix bugs
-        
-        1.0.0 (2011-11-04)
-        ------------------
-        * Add node attribute: linecolor
-        * Rename diagram attributes:
-           * fontsize -> default_fontsize
-           * default_line_color -> default_linecolor
-           * default_text_color -> default_textcolor
-        * Add docutils extention
-        * Fix bugs
-        
-        0.9.7 (2011-11-01)
-        ------------------
-        * Add node attribute: fontsize
-        * Add edge attributes: thick, fontsize
-        * Add group attribute: fontsize
-        * Change color of shadow in PDF mode
-        * Add class feature (experimental)
-        * Add handler-plugin framework (experimental)
-        
-        0.9.6 (2011-10-22)
-        ------------------
-        * node.style supports dashed_array format style
-        * Fix bugs
-        
-        0.9.5 (2011-10-19)
-        ------------------
-        * Add node attributes: width and height
-        * Fix bugs
-        
-        0.9.4 (2011-10-07)
-        ------------------
-        * Fix bugs
-        
-        0.9.3 (2011-10-06)
-        ------------------
-        * Replace SVG core by original's (simplesvg.py)
-        * Refactored
-        * Fix bugs
-        
-        0.9.2 (2011-09-30)
-        ------------------
-        * Add node attribute: textcolor
-        * Add group attribute: textcolor
-        * Add edge attribute: textcolor
-        * Add diagram attributes: default_text_attribute
-        * Fix beginpoint shape and endpoint shape were reversed
-        * Fix bugs
-        
-        0.9.1 (2011-09-26)
-        ------------------
-        * Add diagram attributes: default_node_color, default_group_color and default_line_color
-        * Fix bugs
-        
-        0.9.0 (2011-09-25)
-        ------------------
-        * Add icon attribute to node
-        * Make transparency to background of PNG images 
-        * Fix bugs
-        
-        0.8.9 (2011-08-09)
-        ------------------
-        * Fix bugs
-        
-        0.8.8 (2011-08-08)
-        ------------------
-        * Fix bugs
-        
-        0.8.7 (2011-08-06)
-        ------------------
-        * Fix bugs
-        
-        0.8.6 (2011-08-01)
-        ------------------
-        * Support Pillow as replacement of PIL (experimental)
-        * Fix bugs
-        
-        0.8.5 (2011-07-31)
-        ------------------
-        * Allow dot characters in node_id
-        * Fix bugs
-        
-        0.8.4 (2011-07-05)
-        ------------------
-        * Fix bugs
-        
-        0.8.3 (2011-07-03)
-        ------------------
-        * Support input from stdin
-        * Fix bugs
-        
-        0.8.2 (2011-06-29)
-        ------------------
-        * Add node.stacked
-        * Add node shapes: dots, none
-        * Add hiragino-font to font search list
-        * Support background image fetching from web
-        * Add diagram.edge_layout (experimental)
-        * Fix bugs
-        
-        0.8.1 (2011-05-14)
-        ------------------
-        * Change license to Apache License 2.0
-        * Fix bugs
-        
-        0.8.0 (2011-05-04)
-        ------------------
-        * Add --separate option and --version option
-        * Fix bugs
-        
-        0.7.8 (2011-04-19)
-        ------------------
-        * Update layout engine
-        * Update requirements: PIL >= 1.1.5
-        * Update parser for tokenize performance
-        * Add --nodoctype option
-        * Fix bugs
-        * Add many testcases
-        
-        0.7.7 (2011-03-29)
-        ------------------
-        * Fix bugs
-        
-        0.7.6 (2011-03-26)
-        ------------------
-        * Add new layout manager for portrait edges
-        * Fix bugs
-        
-        0.7.5 (2011-03-20)
-        ------------------
-        * Support multiple nodes relations (cf. A -> B, C)
-        * Support node group declaration at attribute of nodes
-        * Fix bugs
-        
-        0.7.4 (2011-03-08)
-        ------------------
-        * Fix bugs
-        
-        0.7.3 (2011-03-02)
-        ------------------
-        * Use UTF-8 characters as Name token (by @swtw7466)
-        * Fix htmlentities included in labels was not escaped on SVG images
-        * Fix bugs
-        
-        0.7.2 (2011-02-28)
-        ------------------
-        * Add default_shape attribute to diagram
-        
-        0.7.1 (2011-02-27)
-        ------------------
-        * Fix edge has broken with antialias option
-        
-        0.7.0 (2011-02-25)
-        ------------------
-        * Support node shape
-        
-        0.6.7 (2011-02-12)
-        ------------------
-        * Change noderenderer interface to new style
-        * Render dashed ellipse more clearly (contributed by @cocoatomo)
-        * Support PDF exporting
-        
-        0.6.6 (2011-01-31)
-        ------------------
-        * Support diagram.shape_namespace
-        * Add new node shapes; mail, cloud, beginpoint, endpoint, minidiamond, actor
-        * Support plug-in structure to install node shapes
-        * Fix bugs
-        
-        0.6.5 (2011-01-18)
-        ------------------
-        * Support node shape (experimental)
-        
-        0.6.4 (2011-01-17)
-        ------------------
-        * Fix bugs
-        
-        0.6.3 (2011-01-15)
-        ------------------
-        * Fix bugs
-        
-        0.6.2 (2011-01-08)
-        ------------------
-        * Fix bugs
-        
-        0.6.1 (2011-01-07)
-        ------------------
-        * Implement 'folded' attribute for edge
-        * Refactor layout engine
-        
-        0.6 (2011-01-02)
-        ------------------
-        * Support nested groups.
-        
-        0.5.5 (2010-12-24)
-        ------------------
-        * Specify direction of edges as syntax (->, --, <-, <->)
-        * Fix bugs.
-        
-        0.5.4 (2010-12-23)
-        ------------------
-        * Remove debug codes.
-        
-        0.5.3 (2010-12-23)
-        ------------------
-        * Support NodeGroup.label.
-        * Implement --separate option (experimental)
-        * Fix right-up edge overrapped on other nodes.
-        * Support configration file: .blockdiagrc
-        
-        0.5.2 (2010-11-06)
-        ------------------
-        * Fix unicode errors for UTF-8'ed SVG exportion.
-        * Refactoring codes for running on GAE.
-        
-        0.5.1 (2010-10-26)
-        ------------------
-        * Fix license text on diagparser.py
-        * Update layout engine.
-        
-        0.5 (2010-10-15)
-        ------------------
-        * Support background-image of node (SVG)
-        * Support labels for edge.
-        * Fix bugs.
-        
-        0.4.2 (2010-10-10)
-        ------------------
-        * Support background-color of node groups.
-        * Draw edge has jumped at edge's cross-points.
-        * Fix bugs.
-        
-        0.4.1 (2010-10-07)
-        ------------------
-        * Fix bugs.
-        
-        0.4 (2010-10-07)
-        ------------------
-        * Support SVG exporting.
-        * Support dashed edge drawing.
-        * Support background image of nodes (PNG only)
-        
-        0.3.1 (2010-09-29)
-        ------------------
-        * Fasten anti-alias process.
-        * Fix text was broken on windows.
-        
-        0.3 (2010-09-26)
-        ------------------
-        * Add --antialias option.
-        * Fix bugs.
-        
-        0.2.2 (2010-09-25)
-        ------------------
-        * Fix edge bugs.
-        
-        0.2.1 (2010-09-25)
-        ------------------
-        * Fix bugs.
-        * Fix package style.
-        
-        0.2 (2010-09-23)
-        ------------------
-        * Update layout engine.
-        * Support group { ... } sentence for create Node-Groups.
-        * Support numbered badge on node (cf. A [numbered = 5])
-        
-        0.1 (2010-09-20)
-        -----------------
-        * first release
-        
-        Todos
-        ======
-        
-        Functionals
-        ------------
-        * Reimplement --separate option
-        * Support diagram legends
-        * Support other block diagram structure
-        
-        Known Issues
-        -------------
-        * Fix some experimental features.
-        * PDF renderer does not support blur shadow
-        * PDF renderer does not support path rendering
+Description: `blockdiag` generate block-diagram image file from spec-text file.
+        
+        Features
+        ========
+        * Generate block-diagram from dot like text (basic feature).
+        * Multilingualization for node-label (utf-8 only).
+        
+        You can get some examples and generated images on 
+        `blockdiag.com <http://blockdiag.com/blockdiag/build/html/index.html>`_ .
+        
+        Setup
+        =====
+        
+        by easy_install
+        ----------------
+        Make environment::
+        
+           $ easy_install blockdiag
+        
+        If you want to export as PDF format, give pdf arguments::
+        
+           $ easy_install "blockdiag[pdf]"
+        
+        by buildout
+        ------------
+        Make environment::
+        
+           $ hg clone http://bitbucket.org/tk0miya/blockdiag
+           $ cd blockdiag
+           $ python bootstrap.py
+           $ bin/buildout
+        
+        Copy and modify ini file. example::
+        
+           $ cp <blockdiag installed path>/blockdiag/examples/simple.diag .
+           $ vi simple.diag
+        
+        Please refer to `spec-text setting sample`_ section for the format of the
+        `simpla.diag` configuration file.
+        
+        spec-text setting sample
+        ========================
+        Few examples are available.
+        You can get more examples at
+        `blockdiag.com <http://blockdiag.com/blockdiag/build/html/index.html>`_ .
+        
+        simple.diag
+        ------------
+        simple.diag is simply define nodes and transitions by dot-like text format::
+        
+            diagram admin {
+              top_page -> config -> config_edit -> config_confirm -> top_page;
+            }
+        
+        screen.diag
+        ------------
+        screen.diag is more complexly sample. diaglam nodes have a alternative label
+        and some transitions::
+        
+            diagram admin {
+              top_page [label = "Top page"];
+        
+              foo_index [label = "List of FOOs"];
+              foo_detail [label = "Detail FOO"];
+              foo_add [label = "Add FOO"];
+              foo_add_confirm [label = "Add FOO (confirm)"];
+              foo_edit [label = "Edit FOO"];
+              foo_edit_confirm [label = "Edit FOO (confirm)"];
+              foo_delete_confirm [label = "Delete FOO (confirm)"];
+        
+              bar_detail [label = "Detail of BAR"];
+              bar_edit [label = "Edit BAR"];
+              bar_edit_confirm [label = "Edit BAR (confirm)"];
+        
+              logout;
+        
+              top_page -> foo_index;
+              top_page -> bar_detail;
+        
+              foo_index -> foo_detail;
+                           foo_detail -> foo_edit;
+                           foo_detail -> foo_delete_confirm;
+              foo_index -> foo_add -> foo_add_confirm -> foo_index;
+              foo_index -> foo_edit -> foo_edit_confirm -> foo_index;
+              foo_index -> foo_delete_confirm -> foo_index;
+        
+              bar_detail -> bar_edit -> bar_edit_confirm -> bar_detail;
+            }
+        
+        
+        Usage
+        =====
+        Execute blockdiag command::
+        
+           $ blockdiag simple.diag
+           $ ls simple.png
+           simple.png
+        
+        
+        Requirements
+        ============
+        * Python 2.6, 2.7, 3.2, 3.3
+        * Pillow 2.2.1
+        * funcparserlib 0.3.6
+        * setuptools
+        
+        
+        License
+        =======
+        Apache License 2.0
+        
+        
+        History
+        =======
+        
+        1.3.2 (2013-11-19)
+        ------------------
+        * Fix bugs
+        
+        1.3.1 (2013-10-22)
+        ------------------
+        * Fix bugs
+        
+        1.3.0 (2013-10-05)
+        ------------------
+        * Support python 3.2 and 3.3 (thanks to @masayuko)
+        * Drop supports for python 2.4 and 2.5
+        * Replace dependency: PIL -> Pillow
+        
+        1.2.4 (2012-11-21)
+        ------------------
+        * Fix bugs
+        
+        1.2.3 (2012-11-05)
+        ------------------
+        * Fix bugs
+        
+        1.2.2 (2012-10-28)
+        ------------------
+        * Fix bugs
+        
+        1.2.1 (2012-10-28)
+        ------------------
+        * Add external imagedraw plugin supports
+        * Add node attribute: label_orientation*
+        * Fix bugs
+        
+        1.2.0 (2012-10-22)
+        ------------------
+        * Optimize algorithm for rendering shadow
+        * Add options to docutils directive
+        * Fix bugs
+        
+        1.1.8 (2012-09-28)
+        ------------------
+        * Add --ignore-pil option
+        * Fix bugs
+        
+        1.1.7 (2012-09-20)
+        ------------------
+        * Add diagram attribute: shadow_style
+        * Add font path for centos 6.2
+        * Add a setting 'antialias' in the configuration file
+        * Fix bugs
+        
+        1.1.6 (2012-06-06)
+        ------------------
+        * Support for readthedocs.org
+        * reST directive supports :caption: option
+        * Fix bugs
+        
+        1.1.5 (2012-04-22)
+        ------------------
+        * Embed source code to SVG document as description
+        * Fix bugs
+        
+        1.1.4 (2012-03-15)
+        ------------------
+        * Add new edge.hstyles: oneone, onemany, manyone, manymany
+        * Add edge attribute: description (for build description-tables)
+        * Fix bugs
+        
+        1.1.3 (2012-02-13)
+        ------------------
+        * Add new edge type for data-models (thanks to David Lang)
+        * Add --no-transparency option
+        * Fix bugs
+        
+        1.1.2 (2011-12-26)
+        ------------------
+        * Support font-index for TrueType Font Collections (.ttc file)
+        * Allow to use reST syntax in descriptions of nodes
+        * Fix bugs
+        
+        1.1.1 (2011-11-27)
+        ------------------
+        * Add node attribute: href (thanks to @r_rudi!)
+        * Fix bugs
+        
+        1.1.0 (2011-11-19)
+        ------------------
+        * Add shape: square and circle
+        * Add fontfamily attribute for switching fontface
+        * Fix bugs
+        
+        1.0.3 (2011-11-13)
+        ------------------
+        * Add plugin: attributes
+        * Change plugin syntax; (cf. plugin attributes [attr = value, attr, value])
+        * Fix bugs
+        
+        1.0.2 (2011-11-07)
+        ------------------
+        * Fix bugs
+        
+        1.0.1 (2011-11-06)
+        ------------------
+        * Add group attribute: shape
+        * Fix bugs
+        
+        1.0.0 (2011-11-04)
+        ------------------
+        * Add node attribute: linecolor
+        * Rename diagram attributes:
+           * fontsize -> default_fontsize
+           * default_line_color -> default_linecolor
+           * default_text_color -> default_textcolor
+        * Add docutils extention
+        * Fix bugs
+        
+        0.9.7 (2011-11-01)
+        ------------------
+        * Add node attribute: fontsize
+        * Add edge attributes: thick, fontsize
+        * Add group attribute: fontsize
+        * Change color of shadow in PDF mode
+        * Add class feature (experimental)
+        * Add handler-plugin framework (experimental)
+        
+        0.9.6 (2011-10-22)
+        ------------------
+        * node.style supports dashed_array format style
+        * Fix bugs
+        
+        0.9.5 (2011-10-19)
+        ------------------
+        * Add node attributes: width and height
+        * Fix bugs
+        
+        0.9.4 (2011-10-07)
+        ------------------
+        * Fix bugs
+        
+        0.9.3 (2011-10-06)
+        ------------------
+        * Replace SVG core by original's (simplesvg.py)
+        * Refactored
+        * Fix bugs
+        
+        0.9.2 (2011-09-30)
+        ------------------
+        * Add node attribute: textcolor
+        * Add group attribute: textcolor
+        * Add edge attribute: textcolor
+        * Add diagram attributes: default_text_attribute
+        * Fix beginpoint shape and endpoint shape were reversed
+        * Fix bugs
+        
+        0.9.1 (2011-09-26)
+        ------------------
+        * Add diagram attributes: default_node_color, default_group_color and default_line_color
+        * Fix bugs
+        
+        0.9.0 (2011-09-25)
+        ------------------
+        * Add icon attribute to node
+        * Make transparency to background of PNG images 
+        * Fix bugs
+        
+        0.8.9 (2011-08-09)
+        ------------------
+        * Fix bugs
+        
+        0.8.8 (2011-08-08)
+        ------------------
+        * Fix bugs
+        
+        0.8.7 (2011-08-06)
+        ------------------
+        * Fix bugs
+        
+        0.8.6 (2011-08-01)
+        ------------------
+        * Support Pillow as replacement of PIL (experimental)
+        * Fix bugs
+        
+        0.8.5 (2011-07-31)
+        ------------------
+        * Allow dot characters in node_id
+        * Fix bugs
+        
+        0.8.4 (2011-07-05)
+        ------------------
+        * Fix bugs
+        
+        0.8.3 (2011-07-03)
+        ------------------
+        * Support input from stdin
+        * Fix bugs
+        
+        0.8.2 (2011-06-29)
+        ------------------
+        * Add node.stacked
+        * Add node shapes: dots, none
+        * Add hiragino-font to font search list
+        * Support background image fetching from web
+        * Add diagram.edge_layout (experimental)
+        * Fix bugs
+        
+        0.8.1 (2011-05-14)
+        ------------------
+        * Change license to Apache License 2.0
+        * Fix bugs
+        
+        0.8.0 (2011-05-04)
+        ------------------
+        * Add --separate option and --version option
+        * Fix bugs
+        
+        0.7.8 (2011-04-19)
+        ------------------
+        * Update layout engine
+        * Update requirements: PIL >= 1.1.5
+        * Update parser for tokenize performance
+        * Add --nodoctype option
+        * Fix bugs
+        * Add many testcases
+        
+        0.7.7 (2011-03-29)
+        ------------------
+        * Fix bugs
+        
+        0.7.6 (2011-03-26)
+        ------------------
+        * Add new layout manager for portrait edges
+        * Fix bugs
+        
+        0.7.5 (2011-03-20)
+        ------------------
+        * Support multiple nodes relations (cf. A -> B, C)
+        * Support node group declaration at attribute of nodes
+        * Fix bugs
+        
+        0.7.4 (2011-03-08)
+        ------------------
+        * Fix bugs
+        
+        0.7.3 (2011-03-02)
+        ------------------
+        * Use UTF-8 characters as Name token (by @swtw7466)
+        * Fix htmlentities included in labels was not escaped on SVG images
+        * Fix bugs
+        
+        0.7.2 (2011-02-28)
+        ------------------
+        * Add default_shape attribute to diagram
+        
+        0.7.1 (2011-02-27)
+        ------------------
+        * Fix edge has broken with antialias option
+        
+        0.7.0 (2011-02-25)
+        ------------------
+        * Support node shape
+        
+        0.6.7 (2011-02-12)
+        ------------------
+        * Change noderenderer interface to new style
+        * Render dashed ellipse more clearly (contributed by @cocoatomo)
+        * Support PDF exporting
+        
+        0.6.6 (2011-01-31)
+        ------------------
+        * Support diagram.shape_namespace
+        * Add new node shapes; mail, cloud, beginpoint, endpoint, minidiamond, actor
+        * Support plug-in structure to install node shapes
+        * Fix bugs
+        
+        0.6.5 (2011-01-18)
+        ------------------
+        * Support node shape (experimental)
+        
+        0.6.4 (2011-01-17)
+        ------------------
+        * Fix bugs
+        
+        0.6.3 (2011-01-15)
+        ------------------
+        * Fix bugs
+        
+        0.6.2 (2011-01-08)
+        ------------------
+        * Fix bugs
+        
+        0.6.1 (2011-01-07)
+        ------------------
+        * Implement 'folded' attribute for edge
+        * Refactor layout engine
+        
+        0.6 (2011-01-02)
+        ------------------
+        * Support nested groups.
+        
+        0.5.5 (2010-12-24)
+        ------------------
+        * Specify direction of edges as syntax (->, --, <-, <->)
+        * Fix bugs.
+        
+        0.5.4 (2010-12-23)
+        ------------------
+        * Remove debug codes.
+        
+        0.5.3 (2010-12-23)
+        ------------------
+        * Support NodeGroup.label.
+        * Implement --separate option (experimental)
+        * Fix right-up edge overrapped on other nodes.
+        * Support configration file: .blockdiagrc
+        
+        0.5.2 (2010-11-06)
+        ------------------
+        * Fix unicode errors for UTF-8'ed SVG exportion.
+        * Refactoring codes for running on GAE.
+        
+        0.5.1 (2010-10-26)
+        ------------------
+        * Fix license text on diagparser.py
+        * Update layout engine.
+        
+        0.5 (2010-10-15)
+        ------------------
+        * Support background-image of node (SVG)
+        * Support labels for edge.
+        * Fix bugs.
+        
+        0.4.2 (2010-10-10)
+        ------------------
+        * Support background-color of node groups.
+        * Draw edge has jumped at edge's cross-points.
+        * Fix bugs.
+        
+        0.4.1 (2010-10-07)
+        ------------------
+        * Fix bugs.
+        
+        0.4 (2010-10-07)
+        ------------------
+        * Support SVG exporting.
+        * Support dashed edge drawing.
+        * Support background image of nodes (PNG only)
+        
+        0.3.1 (2010-09-29)
+        ------------------
+        * Fasten anti-alias process.
+        * Fix text was broken on windows.
+        
+        0.3 (2010-09-26)
+        ------------------
+        * Add --antialias option.
+        * Fix bugs.
+        
+        0.2.2 (2010-09-25)
+        ------------------
+        * Fix edge bugs.
+        
+        0.2.1 (2010-09-25)
+        ------------------
+        * Fix bugs.
+        * Fix package style.
+        
+        0.2 (2010-09-23)
+        ------------------
+        * Update layout engine.
+        * Support group { ... } sentence for create Node-Groups.
+        * Support numbered badge on node (cf. A [numbered = 5])
+        
+        0.1 (2010-09-20)
+        -----------------
+        * first release
+        
         
 Keywords: diagram,generator
 Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
+Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: System Administrators
 Classifier: License :: OSI Approved :: Apache Software License
 Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.2
+Classifier: Programming Language :: Python :: 3.3
 Classifier: Topic :: Software Development
 Classifier: Topic :: Software Development :: Documentation
 Classifier: Topic :: Text Processing :: Markup
diff --git a/src/blockdiag.egg-info/SOURCES.txt b/src/blockdiag.egg-info/SOURCES.txt
index 2fe42fc..e78203b 100644
--- a/src/blockdiag.egg-info/SOURCES.txt
+++ b/src/blockdiag.egg-info/SOURCES.txt
@@ -1,10 +1,13 @@
 LICENSE
 MANIFEST.in
+README.rst
 blockdiag.1
 bootstrap.py
 buildout.cfg
 setup.cfg
 setup.py
+tox.ini
+examples/blockdiagrc
 examples/group.diag
 examples/group.png
 examples/group.svg
@@ -20,8 +23,6 @@ examples/screen.svg
 examples/simple.diag
 examples/simple.png
 examples/simple.svg
-src/README.txt
-src/TODO.txt
 src/blockdiag_sphinxhelper.py
 src/blockdiag/__init__.py
 src/blockdiag/builder.py
@@ -83,6 +84,7 @@ src/blockdiag/tests/test_builder_node.py
 src/blockdiag/tests/test_builder_separate.py
 src/blockdiag/tests/test_generate_diagram.py
 src/blockdiag/tests/test_imagedraw_textfolder.py
+src/blockdiag/tests/test_imagedraw_utils.py
 src/blockdiag/tests/test_parser.py
 src/blockdiag/tests/test_pep8.py
 src/blockdiag/tests/test_rst_directives.py
@@ -203,6 +205,7 @@ src/blockdiag/tests/diagrams/twin_circular_ref_to_root.diag
 src/blockdiag/tests/diagrams/twin_forked.diag
 src/blockdiag/tests/diagrams/twin_multiple_parent_node.diag
 src/blockdiag/tests/diagrams/two_edges.diag
+src/blockdiag/tests/diagrams/white.gif
 src/blockdiag/tests/diagrams/errors/belongs_to_two_groups.diag
 src/blockdiag/tests/diagrams/errors/group_follows_node.diag
 src/blockdiag/tests/diagrams/errors/lexer_error.diag
@@ -224,14 +227,12 @@ src/blockdiag/tests/diagrams/errors/unknown_node_style.diag
 src/blockdiag/tests/diagrams/errors/unknown_plugin.diag
 src/blockdiag/utils/__init__.py
 src/blockdiag/utils/bootstrap.py
-src/blockdiag/utils/collections.py
+src/blockdiag/utils/compat.py
 src/blockdiag/utils/config.py
 src/blockdiag/utils/fontmap.py
-src/blockdiag/utils/functools.py
 src/blockdiag/utils/images.py
 src/blockdiag/utils/jpeg.py
 src/blockdiag/utils/myitertools.py
-src/blockdiag/utils/namedtuple.py
 src/blockdiag/utils/urlutil.py
 src/blockdiag/utils/uuid.py
 src/blockdiag/utils/rst/__init__.py
diff --git a/src/blockdiag.egg-info/entry_points.txt b/src/blockdiag.egg-info/entry_points.txt
index 07a8580..6a48927 100644
--- a/src/blockdiag.egg-info/entry_points.txt
+++ b/src/blockdiag.egg-info/entry_points.txt
@@ -1,36 +1,36 @@
 
-        [console_scripts]
-        blockdiag = blockdiag.command:main
+       [console_scripts]
+       blockdiag = blockdiag.command:main
 
-        [blockdiag_noderenderer]
-        box = blockdiag.noderenderer.box
-        square = blockdiag.noderenderer.square
-        roundedbox = blockdiag.noderenderer.roundedbox
-        diamond = blockdiag.noderenderer.diamond
-        minidiamond = blockdiag.noderenderer.minidiamond
-        mail = blockdiag.noderenderer.mail
-        note = blockdiag.noderenderer.note
-        cloud = blockdiag.noderenderer.cloud
-        circle = blockdiag.noderenderer.circle
-        ellipse = blockdiag.noderenderer.ellipse
-        beginpoint = blockdiag.noderenderer.beginpoint
-        endpoint = blockdiag.noderenderer.endpoint
-        actor = blockdiag.noderenderer.actor
-        flowchart.database = blockdiag.noderenderer.flowchart.database
-        flowchart.input = blockdiag.noderenderer.flowchart.input
-        flowchart.loopin = blockdiag.noderenderer.flowchart.loopin
-        flowchart.loopout = blockdiag.noderenderer.flowchart.loopout
-        flowchart.terminator = blockdiag.noderenderer.flowchart.terminator
-        textbox = blockdiag.noderenderer.textbox
-        dots = blockdiag.noderenderer.dots
-        none = blockdiag.noderenderer.none
+       [blockdiag_noderenderer]
+       box = blockdiag.noderenderer.box
+       square = blockdiag.noderenderer.square
+       roundedbox = blockdiag.noderenderer.roundedbox
+       diamond = blockdiag.noderenderer.diamond
+       minidiamond = blockdiag.noderenderer.minidiamond
+       mail = blockdiag.noderenderer.mail
+       note = blockdiag.noderenderer.note
+       cloud = blockdiag.noderenderer.cloud
+       circle = blockdiag.noderenderer.circle
+       ellipse = blockdiag.noderenderer.ellipse
+       beginpoint = blockdiag.noderenderer.beginpoint
+       endpoint = blockdiag.noderenderer.endpoint
+       actor = blockdiag.noderenderer.actor
+       flowchart.database = blockdiag.noderenderer.flowchart.database
+       flowchart.input = blockdiag.noderenderer.flowchart.input
+       flowchart.loopin = blockdiag.noderenderer.flowchart.loopin
+       flowchart.loopout = blockdiag.noderenderer.flowchart.loopout
+       flowchart.terminator = blockdiag.noderenderer.flowchart.terminator
+       textbox = blockdiag.noderenderer.textbox
+       dots = blockdiag.noderenderer.dots
+       none = blockdiag.noderenderer.none
 
-        [blockdiag_plugins]
-        attributes = blockdiag.plugins.attributes
-        autoclass = blockdiag.plugins.autoclass
+       [blockdiag_plugins]
+       attributes = blockdiag.plugins.attributes
+       autoclass = blockdiag.plugins.autoclass
 
-        [blockdiag_imagedrawers]
-        imagedraw_png = blockdiag.imagedraw.png
-        imagedraw_svg = blockdiag.imagedraw.svg
-        imagedraw_pdf = blockdiag.imagedraw.pdf
-     
\ No newline at end of file
+       [blockdiag_imagedrawers]
+       imagedraw_png = blockdiag.imagedraw.png
+       imagedraw_svg = blockdiag.imagedraw.svg
+       imagedraw_pdf = blockdiag.imagedraw.pdf
+    
\ No newline at end of file
diff --git a/src/blockdiag.egg-info/requires.txt b/src/blockdiag.egg-info/requires.txt
index 3dc7b6e..b054c00 100644
--- a/src/blockdiag.egg-info/requires.txt
+++ b/src/blockdiag.egg-info/requires.txt
@@ -1,7 +1,7 @@
 setuptools
 funcparserlib
 webcolors
-PIL
+Pillow
 
 [rst]
 docutils
@@ -11,5 +11,4 @@ reportlab
 
 [test]
 Nose
-pep8>=1.3
-unittest2
\ No newline at end of file
+pep8>=1.3
\ No newline at end of file
diff --git a/src/blockdiag/__init__.py b/src/blockdiag/__init__.py
index 4343b09..09ae5dc 100644
--- a/src/blockdiag/__init__.py
+++ b/src/blockdiag/__init__.py
@@ -1,16 +1,16 @@
-# -*- coding: utf-8 -*-
-#  Copyright 2011 Takeshi KOMIYA
-#
-#  Licensed under the Apache License, Version 2.0 (the "License");
-#  you may not use this file except in compliance with the License.
-#  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-__version__ = '1.2.4'
+# -*- coding: utf-8 -*-
+#  Copyright 2011 Takeshi KOMIYA
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+__version__ = '1.3.2'
diff --git a/src/blockdiag/builder.py b/src/blockdiag/builder.py
index f632e15..7ab63cf 100644
--- a/src/blockdiag/builder.py
+++ b/src/blockdiag/builder.py
@@ -16,6 +16,7 @@
 from blockdiag import parser
 from blockdiag.elements import Diagram, DiagramNode, NodeGroup, DiagramEdge
 from blockdiag.utils import unquote, XY
+from blockdiag.utils.compat import cmp_to_key
 
 
 class DiagramTreeBuilder:
@@ -67,7 +68,7 @@ class DiagramTreeBuilder:
 
     def instantiate(self, group, tree):
         for stmt in tree.stmts:
-            # Translate Node having group attribute to SubGraph
+            # Translate Node having group attribute to Group
             if isinstance(stmt, parser.Node):
                 group_attr = [a for a in stmt.attrs if a.name == 'group']
                 if group_attr:
@@ -75,7 +76,7 @@ class DiagramTreeBuilder:
                     stmt.attrs.remove(group_id)
 
                     if group_id.value != group.id:
-                        stmt = parser.SubGraph(group_id.value, [stmt])
+                        stmt = parser.Group(group_id.value, [stmt])
 
             # Instantiate statements
             if isinstance(stmt, parser.Node):
@@ -84,42 +85,33 @@ class DiagramTreeBuilder:
                 self.belong_to(node, group)
 
             elif isinstance(stmt, parser.Edge):
-                nodes = stmt.nodes.pop(0)
-                edge_from = [DiagramNode.get(n) for n in nodes]
-                for node in edge_from:
-                    self.belong_to(node, group)
-
-                while len(stmt.nodes):
-                    edge_type, edge_to = stmt.nodes.pop(0)
-                    edge_to = [DiagramNode.get(n) for n in edge_to]
-                    for node in edge_to:
-                        self.belong_to(node, group)
+                from_nodes = [DiagramNode.get(n) for n in stmt.from_nodes]
+                to_nodes = [DiagramNode.get(n) for n in stmt.to_nodes]
 
-                    for node1 in edge_from:
-                        for node2 in edge_to:
-                            edge = DiagramEdge.get(node1, node2)
-                            if edge_type:
-                                attrs = [parser.Attr('dir', edge_type)]
-                                edge.set_attributes(attrs)
-                            edge.set_attributes(stmt.attrs)
+                for node in from_nodes + to_nodes:
+                    self.belong_to(node, group)
 
-                    edge_from = edge_to
+                for node1 in from_nodes:
+                    for node2 in to_nodes:
+                        edge = DiagramEdge.get(node1, node2)
+                        edge.set_dir(stmt.edge_type)
+                        edge.set_attributes(stmt.attrs)
 
-            elif isinstance(stmt, parser.SubGraph):
+            elif isinstance(stmt, parser.Group):
                 subgroup = NodeGroup.get(stmt.id)
                 subgroup.level = group.level + 1
                 self.belong_to(subgroup, group)
                 self.instantiate(subgroup, stmt)
 
-            elif isinstance(stmt, parser.DefAttrs):
-                group.set_attributes(stmt.attrs)
-
-            elif isinstance(stmt, parser.AttrClass):
-                name = unquote(stmt.name)
-                Diagram.classes[name] = stmt
+            elif isinstance(stmt, parser.Attr):
+                group.set_attribute(stmt)
 
-            elif isinstance(stmt, parser.AttrPlugin):
-                self.diagram.set_plugin(stmt.name, stmt.attrs)
+            elif isinstance(stmt, parser.Extension):
+                if stmt.type == 'class':
+                    name = unquote(stmt.name)
+                    Diagram.classes[name] = stmt
+                elif stmt.type == 'plugin':
+                    self.diagram.set_plugin(stmt.name, stmt.attrs)
 
             elif isinstance(stmt, parser.Statements):
                 self.instantiate(group, stmt)
@@ -201,7 +193,7 @@ class DiagramLayoutManager:
             else:
                 related.append(uniq_node)
 
-        related.sort(lambda x, y: cmp(x.order, y.order))
+        related.sort(key=lambda x: x.order)
         return related
 
     def get_parent_nodes(self, node):
@@ -250,9 +242,7 @@ class DiagramLayoutManager:
                         if not parent in circular:
                             parents.append(parent)
 
-                parents.sort(lambda x, y: cmp(x.order, y.order))
-
-                for parent in parents:
+                for parent in sorted(parents, key=lambda x: x.order):
                     children = self.get_child_nodes(parent)
                     if node1 in children and node2 in children:
                         if circular.index(node1) > circular.index(node2):
@@ -364,11 +354,17 @@ class DiagramLayoutManager:
                 x.node1 = x.node1.group
                 y.node1 = y.node1.group
 
-            return cmp(x.node1.order, y.node1.order)
+            # cmp x.node1.order and y.node1.order
+            if x.node1.order < y.node1.order:
+                return -1
+            elif x.node1.order == y.node1.order:
+                return 0
+            else:
+                return 1
 
         edges = (DiagramEdge.find(parent, node1) +
                  DiagramEdge.find(parent, node2))
-        edges.sort(compare)
+        edges.sort(key=cmp_to_key(compare))
         if len(edges) == 0:
             return 0
         elif edges[0].node2 == node2:
@@ -390,9 +386,17 @@ class DiagramLayoutManager:
         node.xy = XY(node.xy.x, height)
         self.mark_xy(node.xy, node.colwidth, node.colheight)
 
+        def cmp(x, y):
+            if x.xy.x < y.xy.y:
+                return -1
+            elif x.xy.x == y.xy.y:
+                return 0
+            else:
+                return 1
+
         count = 0
         children = self.get_child_nodes(node)
-        children.sort(lambda x, y: cmp(x.xy.x, y.xy.y))
+        children.sort(key=cmp_to_key(cmp))
 
         grandchild = 0
         for child in children:
@@ -710,9 +714,9 @@ class SeparateDiagramBuilder(ScreenNodeBuilder):
 
             # pick up nodes to base diagram
             nodes1 = [e.node1 for e in DiagramEdge.find(None, group)]
-            nodes1.sort(lambda x, y: cmp(x.order, y.order))
+            nodes1.sort(key=lambda x: x.order)
             nodes2 = [e.node2 for e in DiagramEdge.find(group, None)]
-            nodes2.sort(lambda x, y: cmp(x.order, y.order))
+            nodes2.sort(key=lambda x: x.order)
 
             nodes = nodes1 + [group] + nodes2
             for i, n in enumerate(nodes):
diff --git a/src/blockdiag/command.py b/src/blockdiag/command.py
index 6563fdb..830aece 100644
--- a/src/blockdiag/command.py
+++ b/src/blockdiag/command.py
@@ -14,6 +14,7 @@
 #  limitations under the License.
 
 import re
+import sys
 import blockdiag
 import blockdiag.builder
 import blockdiag.drawer
@@ -21,11 +22,6 @@ import blockdiag.parser
 from blockdiag.utils.bootstrap import Application, Options
 
 
-# FIXME: for compatibility
-from blockdiag.utils.bootstrap import detectfont
-detectfont
-
-
 class BlockdiagOptions(Options):
     def build_parser(self):
         super(BlockdiagOptions, self).build_parser()
@@ -38,8 +34,8 @@ class BlockdiagOptions(Options):
 class BlockdiagApp(Application):
     module = blockdiag
 
-    def parse_options(self):
-        self.options = BlockdiagOptions(self.module).parse()
+    def parse_options(self, args):
+        self.options = BlockdiagOptions(self.module).parse(args)
 
     def build_diagram(self, tree):
         if not self.options.separate:
@@ -62,5 +58,5 @@ class BlockdiagApp(Application):
             return 0
 
 
-def main():
-    return BlockdiagApp().run()
+def main(args=sys.argv[1:]):
+    return BlockdiagApp().run(args)
diff --git a/src/blockdiag/drawer.py b/src/blockdiag/drawer.py
index 1a62aab..cae5d15 100644
--- a/src/blockdiag/drawer.py
+++ b/src/blockdiag/drawer.py
@@ -13,13 +13,12 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+from collections import defaultdict
 from blockdiag import imagedraw, noderenderer
 from blockdiag.metrics import AutoScaler, DiagramMetrics
-from blockdiag.utils.collections import defaultdict
 
 
 class DiagramDraw(object):
-    MetricsClass = None  # TODO: obsoleted interface
     shadow_colors = defaultdict(lambda: (0, 0, 0))
     shadow_colors['PNG'] = (64, 64, 64)
     shadow_colors['PDF'] = (144, 144, 144)
@@ -52,10 +51,7 @@ class DiagramDraw(object):
         self.drawer.set_options(jump_radius=self.metrics.cellsize / 2)
 
     def create_metrics(self, *args, **kwargs):
-        if self.MetricsClass:
-            return self.MetricsClass(*args, **kwargs)
-        else:
-            return DiagramMetrics(*args, **kwargs)
+        return DiagramMetrics(*args, **kwargs)
 
     @property
     def nodes(self):
diff --git a/src/blockdiag/elements.py b/src/blockdiag/elements.py
index f46919f..bc67124 100644
--- a/src/blockdiag/elements.py
+++ b/src/blockdiag/elements.py
@@ -18,6 +18,7 @@ import re
 import sys
 import copy
 from blockdiag.utils import images, unquote, urlutil, uuid, XY
+from blockdiag.utils.compat import u
 from blockdiag import noderenderer, plugins
 
 
@@ -224,7 +225,7 @@ class DiagramNode(Element):
         for name in self.desctable:
             value = getattr(self, name)
             if value is None:
-                attrs.append(u"")
+                attrs.append(u(""))
             else:
                 attrs.append(value)
 
@@ -645,7 +646,7 @@ class Diagram(NodeGroup):
     def set_edge_layout(self, value):
         value = value.lower()
         if value in ('normal', 'flowchart'):
-            msg = "WARNING: edge_layout is very experimental feature!\n"
+            msg = u("WARNING: edge_layout is very experimental feature!\n")
             sys.stderr.write(msg)
 
             self.edge_layout = value
diff --git a/src/blockdiag/imagedraw/base.py b/src/blockdiag/imagedraw/base.py
index 030c401..f53b940 100644
--- a/src/blockdiag/imagedraw/base.py
+++ b/src/blockdiag/imagedraw/base.py
@@ -13,9 +13,9 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+from functools import partial
 from blockdiag.imagedraw import textfolder
 from blockdiag.utils import Box
-from blockdiag.utils.functools import partial
 
 
 class ImageDraw(object):
@@ -72,8 +72,5 @@ class ImageDraw(object):
     def image(self, box, url):
         pass
 
-    def loadImage(self, filename, box):  # TODO: obsoleted
-        return self.image(box, filename)
-
     def save(self, filename, size, _format):
         pass
diff --git a/src/blockdiag/imagedraw/filters/__init__.py b/src/blockdiag/imagedraw/filters/__init__.py
index 5c383c2..bd36e96 100644
--- a/src/blockdiag/imagedraw/filters/__init__.py
+++ b/src/blockdiag/imagedraw/filters/__init__.py
@@ -1,14 +1,14 @@
-# -*- coding: utf-8 -*-
-#  Copyright 2011 Takeshi KOMIYA
-#
-#  Licensed under the Apache License, Version 2.0 (the "License");
-#  you may not use this file except in compliance with the License.
-#  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
+# -*- coding: utf-8 -*-
+#  Copyright 2011 Takeshi KOMIYA
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
diff --git a/src/blockdiag/imagedraw/filters/linejump.py b/src/blockdiag/imagedraw/filters/linejump.py
index d52c009..c3c41fe 100644
--- a/src/blockdiag/imagedraw/filters/linejump.py
+++ b/src/blockdiag/imagedraw/filters/linejump.py
@@ -13,10 +13,11 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-from blockdiag.utils import functools, Box, XY
+import functools
+from blockdiag.utils import Box, XY
 
 
-class LazyReciever(object):
+class LazyReceiver(object):
     def __init__(self, target):
         self.target = target
         self.calls = []
@@ -31,9 +32,9 @@ class LazyReciever(object):
         def _(*args, **kwargs):
             if name in self.target.self_generative_methods:
                 ret = method(self.target, *args, **kwargs)
-                reciever = LazyReciever(ret)
-                self.nested.append(reciever)
-                return reciever
+                receiver = LazyReceiver(ret)
+                self.nested.append(receiver)
+                return receiver
             else:
                 self.calls.append((method, args, kwargs))
                 return self
@@ -59,7 +60,7 @@ class LazyReciever(object):
             method(self.target, *args, **kwargs)
 
 
-class LineJumpDrawFilter(LazyReciever):
+class LineJumpDrawFilter(LazyReceiver):
     def __init__(self, target, jump_radius):
         super(LineJumpDrawFilter, self).__init__(target)
         self.ytree = []
diff --git a/src/blockdiag/imagedraw/pdf.py b/src/blockdiag/imagedraw/pdf.py
index 0daaa53..a474072 100644
--- a/src/blockdiag/imagedraw/pdf.py
+++ b/src/blockdiag/imagedraw/pdf.py
@@ -23,7 +23,7 @@ from blockdiag.imagedraw import base
 from blockdiag.imagedraw.utils import cached
 from blockdiag.utils import urlutil, Box, Size
 from blockdiag.utils.fontmap import parse_fontpath
-from blockdiag.utils.functools import partial
+from blockdiag.utils.compat import string_types
 
 
 class PDFImageDraw(base.ImageDraw):
@@ -34,6 +34,8 @@ class PDFImageDraw(base.ImageDraw):
         self.canvas = None
         self.fonts = {}
 
+        self.set_canvas_size(Size(1, 1))  # This line make textsize() workable
+
     def set_canvas_size(self, size):
         self.canvas = canvas.Canvas(self.filename, pagesize=size)
         self.size = size
@@ -89,7 +91,7 @@ class PDFImageDraw(base.ImageDraw):
             self.canvas.setDash()
 
     def set_stroke_color(self, color="black"):
-        if isinstance(color, basestring):
+        if isinstance(color, string_types):
             self.canvas.setStrokeColor(color)
         elif color:
             rgb = (color[0] / 256.0, color[1] / 256.0, color[2] / 256.0)
@@ -98,7 +100,7 @@ class PDFImageDraw(base.ImageDraw):
             self.set_stroke_color()
 
     def set_fill_color(self, color="white"):
-        if isinstance(color, basestring):
+        if isinstance(color, string_types):
             if color != 'none':
                 self.canvas.setFillColor(color)
         elif color:
@@ -128,6 +130,7 @@ class PDFImageDraw(base.ImageDraw):
 
     @cached
     def textlinesize(self, string, font):
+        self.set_font(font)
         width = self.canvas.stringWidth(string, font.path, font.size)
         return Size(int(math.ceil(width)), font.size)
 
diff --git a/src/blockdiag/imagedraw/png.py b/src/blockdiag/imagedraw/png.py
index bfcb8c1..e7529a6 100644
--- a/src/blockdiag/imagedraw/png.py
+++ b/src/blockdiag/imagedraw/png.py
@@ -13,42 +13,38 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+from __future__ import division
 import re
+import sys
 import math
-from itertools import izip, tee
+from itertools import tee
+try:
+    from future_builtins import zip
+except ImportError:
+    pass
+from functools import partial, wraps
+from PIL import Image, ImageDraw, ImageFont, ImageFilter
 from blockdiag.imagedraw import base
-from blockdiag.imagedraw.utils import cached, ellipse
+from blockdiag.imagedraw.utils import cached
 from blockdiag.imagedraw.utils.ellipse import dots as ellipse_dots
 from blockdiag.utils import urlutil, Box, Size, XY
 from blockdiag.utils.fontmap import parse_fontpath, FontMap
-from blockdiag.utils.functools import partial, wraps
 from blockdiag.utils.myitertools import istep, stepslice
 
-try:
-    from PIL import Image
-    from PIL import ImageDraw
-    from PIL import ImageFont
-    from PIL import ImageFilter
-except ImportError:
-    import Image
-    import ImageDraw
-    import ImageFont
-    import ImageFilter
-
 
 def point_pairs(xylist):
     iterable = iter(xylist)
     for pt in iterable:
         if isinstance(pt, int):
-            yield (pt, iterable.next())
+            yield (pt, next(iterable))
         else:
             yield pt
 
 
 def line_segments(xylist):
     p1, p2 = tee(point_pairs(xylist))
-    p2.next()
-    return izip(p1, p2)
+    next(p2)
+    return zip(p1, p2)
 
 
 def dashize_line(line, length):
@@ -57,7 +53,7 @@ def dashize_line(line, length):
         if pt1[1] > pt2[1]:
             pt2, pt1 = line
 
-        r = stepslice(xrange(pt1[1], pt2[1]), length)
+        r = stepslice(range(pt1[1], pt2[1]), length)
         for y1, y2 in istep(n for n in r):
             yield [(pt1[0], y1), (pt1[0], y2)]
 
@@ -65,7 +61,7 @@ def dashize_line(line, length):
         if pt1[0] > pt2[0]:
             pt2, pt1 = line
 
-        r = stepslice(xrange(pt1[0], pt2[0]), length)
+        r = stepslice(range(pt1[0], pt2[0]), length)
         for x1, x2 in istep(n for n in r):
             yield [(x1, pt1[1]), (x2, pt1[1])]
     else:  # diagonal
@@ -171,7 +167,7 @@ class ImageDrawExBase(base.ImageDraw):
             for pt in ellipse_dots(box, cycle, start, end):
                 self.draw.line([pt, pt], fill=kwargs['fill'])
         else:
-            self.draw.arc(box, start, end, **kwargs)
+            self.draw.arc(box.to_integer_point(), start, end, **kwargs)
 
     def ellipse(self, box, **kwargs):
         if 'filter' in kwargs:
@@ -199,7 +195,7 @@ class ImageDrawExBase(base.ImageDraw):
             if kwargs.get('fill') == 'none':
                 del kwargs['fill']
 
-            self.draw.ellipse(box, **kwargs)
+            self.draw.ellipse(box.to_integer_point(), **kwargs)
 
     def line(self, xy, **kwargs):
         if 'jump' in kwargs:
@@ -351,17 +347,20 @@ class ImageDrawExBase(base.ImageDraw):
             rendered = True
 
         if not rendered and font.size > 0:
-            font.size = int(font.size * 0.8)
-            self.textarea(box, string, font, **kwargs)
+            _font = font.duplicate()
+            _font.size = int(font.size * 0.8)
+            self.textarea(box, string, _font, **kwargs)
 
     def image(self, box, url):
         if urlutil.isurl(url):
-            import cStringIO
+            try:
+                from io import StringIO
+            except ImportError:
+                from cStringIO import StringIO
             import urllib
             try:
-                url = cStringIO.StringIO(urllib.urlopen(url).read())
+                url = StringIO(urllib.urlopen(url).read())
             except:
-                import sys
                 msg = "WARNING: Could not retrieve: %s\n" % url
                 sys.stderr.write(msg)
                 return
@@ -375,12 +374,12 @@ class ImageDrawExBase(base.ImageDraw):
         # centering image.
         w, h = image.size
         if box.width > w:
-            x = box[0] + (box.width - w) / 2
+            x = box[0] + (box.width - w) // 2
         else:
             x = box[0]
 
         if box.height > h:
-            y = box[1] + (box.height - h) / 2
+            y = box[1] + (box.height - h) // 2
         else:
             y = box[1]
 
@@ -401,8 +400,11 @@ class ImageDrawExBase(base.ImageDraw):
             self._image.save(self.filename, _format)
             image = None
         else:
-            import cStringIO
-            tmp = cStringIO.StringIO()
+            try:
+                from io import StringIO
+            except ImportError:
+                from cStringIO import StringIO
+            tmp = StringIO()
             self._image.save(tmp, _format)
             image = tmp.getvalue()
 
diff --git a/src/blockdiag/imagedraw/simplesvg.py b/src/blockdiag/imagedraw/simplesvg.py
index 273a297..fd5fe0b 100644
--- a/src/blockdiag/imagedraw/simplesvg.py
+++ b/src/blockdiag/imagedraw/simplesvg.py
@@ -14,11 +14,15 @@
 #  limitations under the License.
 
 import re
-import cStringIO
+from blockdiag.utils.compat import u, string_types
+try:
+    from io import StringIO
+except ImportError:
+    from cStringIO import StringIO
 
 
 def _escape(s):
-    if not isinstance(s, (str, unicode)):
+    if not isinstance(s, string_types):
         s = str(s)
     return s.replace("&", "&").replace("<", "<").replace(">", ">")
 
@@ -53,28 +57,26 @@ class base(object):
         clsname = self.__class__.__name__
         indent = '  ' * level
 
-        io.write('%s<%s' % (indent, clsname))
+        io.write(u('%s<%s') % (indent, clsname))
         for key in sorted(self.attributes):
             value = self.attributes[key]
             if value is not None:
-                io.write(' %s=%s' % (_escape(key), _quote(value)))
+                io.write(u(' %s=%s') % (_escape(key), _quote(value)))
 
         if self.elements == []:
             if self.text is not None:
-                text = _escape(self.text).encode('utf-8')
-                io.write(">%s</%s>\n" % (text, clsname))
+                io.write(u(">%s</%s>\n") % (_escape(self.text), clsname))
             else:
-                io.write(" />\n")
+                io.write(u(" />\n"))
         elif self.elements:
             if self.text is not None:
-                text = _escape(self.text).encode('utf-8')
-                io.write(">%s\n" % (text,))
+                io.write(u(">%s\n") % (_escape(self.text),))
             else:
-                io.write(">\n")
+                io.write(u(">\n"))
 
             for e in self.elements:
                 e.to_xml(io, level + 1)
-            io.write('%s</%s>\n' % (indent, clsname))
+            io.write(u('%s</%s>\n') % (indent, clsname))
 
 
 class element(base):
@@ -100,13 +102,13 @@ class svg(base):
         self.add_attribute('xmlns', 'http://www.w3.org/2000/svg')
 
     def to_xml(self):
-        io = cStringIO.StringIO()
+        io = StringIO()
 
         if not self.nodoctype:
             url = "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"
-            io.write("<?xml version='1.0' encoding='UTF-8'?>\n")
-            io.write('<!DOCTYPE svg PUBLIC '
-                     '"-//W3C//DTD SVG 1.0//EN" "%s">\n' % url)
+            io.write(u("<?xml version='1.0' encoding='UTF-8'?>\n"))
+            io.write(u('<!DOCTYPE svg PUBLIC ') +
+                     u('"-//W3C//DTD SVG 1.0//EN" "%s">\n') % url)
 
         super(svg, self).to_xml(io)
 
diff --git a/src/blockdiag/imagedraw/svg.py b/src/blockdiag/imagedraw/svg.py
index 1168572..c2a159f 100644
--- a/src/blockdiag/imagedraw/svg.py
+++ b/src/blockdiag/imagedraw/svg.py
@@ -16,11 +16,13 @@
 import re
 import base64
 from blockdiag.imagedraw import base as _base
-from blockdiag.imagedraw.simplesvg import *
+from blockdiag.imagedraw.simplesvg import (
+    svg, svgclass, filter, title, desc, defs, g, a, text,
+    rect, polygon, ellipse, path, pathdata, image
+)
 from blockdiag.imagedraw.utils import cached
 from blockdiag.imagedraw.utils.ellipse import endpoints as ellipse_endpoints
-from blockdiag.utils import urlutil, Box, XY
-from blockdiag.utils.functools import partial
+from blockdiag.utils import urlutil, Box, XY, is_Pillow_available
 
 feGaussianBlur = svgclass('feGaussianBlur')
 
@@ -82,10 +84,6 @@ class SVGImageDrawElement(_base.ImageDraw):
 
     def __init__(self, svg, parent=None):
         self.svg = svg
-        if parent and parent.ignore_pil:
-            self.ignore_pil = True
-        else:
-            self.ignore_pil = False
 
     def path(self, pd, **kwargs):
         fill = kwargs.get('fill')
@@ -107,15 +105,15 @@ class SVGImageDrawElement(_base.ImageDraw):
 
     @cached
     def textlinesize(self, string, font, **kwargs):
-        if kwargs.get('ignore_pil', self.ignore_pil):
-            from blockdiag.imagedraw.utils import textsize
-            return textsize(string, font)
-        else:
+        if is_Pillow_available():
             if not hasattr(self, '_pil_drawer'):
                 from blockdiag.imagedraw import png
                 self._pil_drawer = png.ImageDrawEx(None)
 
             return self._pil_drawer.textlinesize(string, font)
+        else:
+            from blockdiag.imagedraw.utils import textsize
+            return textsize(string, font)
 
     def text(self, point, string, font, **kwargs):
         fill = kwargs.get('fill')
@@ -141,8 +139,9 @@ class SVGImageDrawElement(_base.ImageDraw):
                 rendered = True
 
             if not rendered and font.size > 0:
-                font.size = int(font.size * 0.8)
-                self.textarea(box, string, font, **kwargs)
+                _font = font.duplicate()
+                _font.size = int(font.size * 0.8)
+                self.textarea(box, string, _font, **kwargs)
 
     def rotated_textarea(self, box, string, font, **kwargs):
         angle = int(kwargs['rotate']) % 360
@@ -182,7 +181,6 @@ class SVGImageDrawElement(_base.ImageDraw):
         self.svg.addElement(p)
 
     def arc(self, box, start, end, **kwargs):
-        thick = kwargs.get('thick')
         fill = kwargs.get('fill')
 
         w = box.width / 2
@@ -231,7 +229,7 @@ class SVGImageDrawElement(_base.ImageDraw):
     def image(self, box, url):
         if not urlutil.isurl(url):
             string = open(url, 'rb').read()
-            url = "data:;base64," + base64.b64encode(string)
+            url = "data:;base64," + str(base64.b64encode(string))
 
         im = image(url, box.x1, box.y1, box.width, box.height)
         self.svg.addElement(im)
@@ -256,7 +254,6 @@ class SVGImageDraw(SVGImageDrawElement):
 
         self.filename = filename
         self.options = kwargs
-        self.ignore_pil = kwargs.get('ignore_pil')
         self.set_canvas_size((0, 0))
 
     def set_canvas_size(self, size):
@@ -293,7 +290,7 @@ class SVGImageDraw(SVGImageDrawElement):
         image = self.svg.to_xml()
 
         if self.filename:
-            open(self.filename, 'w').write(image)
+            open(self.filename, 'wb').write(image.encode('utf-8'))
 
         return image
 
diff --git a/src/blockdiag/imagedraw/textfolder.py b/src/blockdiag/imagedraw/textfolder.py
index 83a1d8d..803cd77 100644
--- a/src/blockdiag/imagedraw/textfolder.py
+++ b/src/blockdiag/imagedraw/textfolder.py
@@ -15,6 +15,7 @@
 
 import re
 from blockdiag.utils import Box, Size, XY
+from blockdiag.utils.compat import u, string_types
 
 
 def splitlabel(string):
@@ -24,8 +25,9 @@ def splitlabel(string):
     """
     string = re.sub('^\s*', '', string)
     string = re.sub('\s*$', '', string)
-    string = re.sub('(?:\xa5|\\\\){2}', '\x00', string)
-    string = re.sub('(?:\xa5|\\\\)n', '\n', string)
+    string = re.sub('\xa5', '\\\\', string)
+    string = re.sub('(\\\\){2}', '\x00', string)
+    string = re.sub('\\\\n', '\n', string)
     for line in string.splitlines():
         yield re.sub('\x00', '\\\\', line).strip()
 
@@ -33,7 +35,7 @@ def splitlabel(string):
 def splittext(metrics, text, bound, measure='width'):
     folded = []
     if text == '':
-        folded.append(u' ')
+        folded.append(u(' '))
 
     for i in range(len(text), 0, -1):
         textsize = metrics.textsize(text[0:i])
@@ -84,7 +86,7 @@ class VerticalTextFolder(object):
         self._result = self._lines()
 
     def textsize(self, text, scaled=False):
-        if isinstance(text, (str, unicode)):
+        if isinstance(text, string_types):
             size = [self.drawer.textlinesize(c, self.font) for c in text]
             width = max(s.width for s in size)
             height = (sum(s.height for s in size) +
@@ -206,7 +208,7 @@ class HorizontalTextFolder(object):
         self._result = self._lines()
 
     def textsize(self, text, scaled=False):
-        if isinstance(text, (str, unicode)):
+        if isinstance(text, string_types):
             textsize = self.drawer.textlinesize(text, self.font)
         else:
             if text:
@@ -286,9 +288,11 @@ class HorizontalTextFolder(object):
                 if height + textsize.height + self.line_spacing < maxheight:
                     lines.append(folded)
                     height += textsize.height + self.line_spacing
-                elif len(lines) > 0:
-                    lines[-1] = truncate_text(self, lines[-1],
-                                              maxwidth, measure)
+                else:
+                    if len(lines) > 0:
+                        lines[-1] = truncate_text(self, lines[-1],
+                                                  maxwidth, measure)
+
                     finished = True
                     break
 
diff --git a/src/blockdiag/imagedraw/utils/__init__.py b/src/blockdiag/imagedraw/utils/__init__.py
index 27af44c..364c6d3 100644
--- a/src/blockdiag/imagedraw/utils/__init__.py
+++ b/src/blockdiag/imagedraw/utils/__init__.py
@@ -16,82 +16,37 @@
 import math
 import unicodedata
 from blockdiag.utils import Size
+from blockdiag.utils.compat import u
 
 
 def is_zenkaku(char):
-    u"""Detect given character is Japanese ZENKAKU character
-
-        >>> is_zenkaku(u"A")
-        False
-        >>> is_zenkaku(u"あ")
-        True
-    """
+    """Detect given character is Japanese ZENKAKU character"""
     char_width = unicodedata.east_asian_width(char)
-    return char_width in u"WFA"
+    return char_width in u("WFA")
 
 
 def zenkaku_len(string):
-    u"""
-    Count Japanese ZENKAKU characters from string
-
-    >>> zenkaku_len(u"abc")
-    0
-    >>> zenkaku_len(u"あいう")
-    3
-    >>> zenkaku_len(u"あいc")
-    2
-    """
+    """Count Japanese ZENKAKU characters from string"""
     return len([x for x in string if is_zenkaku(x)])
 
 
 def hankaku_len(string):
-    u"""Count non Japanese ZENKAKU characters from string
-
-        >>> hankaku_len(u"abc")
-        3
-        >>> hankaku_len(u"あいう")
-        0
-        >>> hankaku_len(u"あいc")
-        1
-    """
+    """Count non Japanese ZENKAKU characters from string"""
     return len([x for x in string if not is_zenkaku(x)])
 
 
 def string_width(string):
-    u"""Measure rendering width of string.
-        Count ZENKAKU-character as 2-point and non ZENKAKU-character as 1-point
-
-        >>> string_width(u"abc")
-        3
-        >>> string_width(u"あいう")
-        6
-        >>> string_width(u"あいc")
-        5
+    """Measure rendering width of string.
+       Count ZENKAKU-character as 2-point and non ZENKAKU-character as 1-point
     """
     widthmap = {'Na': 1, 'N': 1, 'H': 1, 'W': 2, 'F': 2, 'A': 2}
     return sum(widthmap[unicodedata.east_asian_width(c)] for c in string)
 
 
 def textsize(string, font):
-    u"""Measure rendering size (width and height) of line.
-        Returned size will not be exactly as rendered text size,
-        Because this method does not use fonts to measure size.
-
-        >>> from blockdiag.utils.fontmap import FontInfo
-        >>> box = [0, 0, 100, 50]
-        >>> font = FontInfo('serif', None, 11)
-        >>> textsize(u"abc", font)
-        Size(width=19, height=11)
-        >>> textsize(u"あいう", font)
-        Size(width=33, height=11)
-        >>> textsize(u"あいc", font)
-        Size(width=29, height=11)
-        >>> font = FontInfo('serif', None, 24)
-        >>> textsize(u"abc", font)
-        Size(width=40, height=24)
-        >>> font = FontInfo('serif', None, 18)
-        >>> textsize(u"あいう", font)
-        Size(width=54, height=18)
+    """Measure rendering size (width and height) of line.
+       Returned size will not be exactly as rendered text size,
+       Because this method does not use fonts to measure size.
     """
     width = (zenkaku_len(string) * font.size +
              hankaku_len(string) * font.size * 0.55)
diff --git a/src/blockdiag/imagedraw/utils/ellipse.py b/src/blockdiag/imagedraw/utils/ellipse.py
index c3d48a8..a362076 100644
--- a/src/blockdiag/imagedraw/utils/ellipse.py
+++ b/src/blockdiag/imagedraw/utils/ellipse.py
@@ -34,8 +34,8 @@ def _coordinates(du, a, b, start, end):
 
 
 def endpoints(du, a, b, start, end):
-    pt1 = iter(_coordinates(du, a, b, start, start + 1)).next()
-    pt2 = iter(_coordinates(du, a, b, end, end + 1)).next()
+    pt1 = next(iter(_coordinates(du, a, b, start, start + 1)))
+    pt2 = next(iter(_coordinates(du, a, b, end, end + 1)))
 
     return [XY(*pt1), XY(*pt2)]
 
diff --git a/src/blockdiag/metrics.py b/src/blockdiag/metrics.py
index ac64e62..dcb671d 100644
--- a/src/blockdiag/metrics.py
+++ b/src/blockdiag/metrics.py
@@ -13,12 +13,13 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+from __future__ import division
 import copy
+from collections import defaultdict
 from blockdiag import noderenderer
 from blockdiag.elements import DiagramNode
 from blockdiag.utils import Box, Size, XY
 from blockdiag.utils.fontmap import FontInfo, FontMap
-from blockdiag.utils.collections import defaultdict
 
 cellsize = 8
 
@@ -234,6 +235,10 @@ class DiagramMetrics(object):
 
 class SubMetrics(object):
     def __getattr__(self, name):
+        # avoid recursion-error on Python 2.6
+        if 'metrics' not in self.__dict__:
+            raise AttributeError()
+
         return getattr(self.metrics, name)
 
 
@@ -289,12 +294,12 @@ class SpreadSheetMetrics(SubMetrics):
 
         if use_padding:
             width = node.width or self.metrics.node_width
-            xdiff = (self.node_width[x] - width) / 2
+            xdiff = (self.node_width[x] - width) // 2
             if xdiff < 0:
                 xdiff = 0
 
             height = node.height or self.metrics.node_height
-            ydiff = (self.node_height[y] - height) / 2
+            ydiff = (self.node_height[y] - height) // 2
             if ydiff < 0:
                 ydiff = 0
         else:
@@ -319,12 +324,12 @@ class SpreadSheetMetrics(SubMetrics):
 
         if use_padding:
             width = node.width or self.metrics.node_width
-            xdiff = (self.node_width[x] - width) / 2
+            xdiff = (self.node_width[x] - width) // 2
             if xdiff < 0:
                 xdiff = 0
 
             height = node.height or self.metrics.node_height
-            ydiff = (self.node_height[y] - height) / 2
+            ydiff = (self.node_height[y] - height) // 2
             if ydiff < 0:
                 ydiff = 0
         else:
@@ -369,10 +374,10 @@ class NodeMetrics(SubMetrics):
 
     @property
     def marginbox(self):
-        return Box(self._box.x1 - self.span_width / 8,
-                   self._box.y1 - self.span_height / 4,
-                   self._box.x2 + self.span_width / 8,
-                   self._box.y2 + self.span_height / 4)
+        return Box(self._box.x1 - self.span_width // 8,
+                   self._box.y1 - self.span_height // 4,
+                   self._box.x2 + self.span_width // 8,
+                   self._box.y2 + self.span_height // 4)
 
     @property
     def corebox(self):
@@ -383,7 +388,7 @@ class NodeMetrics(SubMetrics):
 
     @property
     def grouplabelbox(self):
-        return Box(self._box.x1, self._box.y1 - self.span_height / 2,
+        return Box(self._box.x1, self._box.y1 - self.span_height // 2,
                    self._box.x2, self._box.y1)
 
 
@@ -421,30 +426,30 @@ class EdgeMetrics(SubMetrics):
         if direct == 'up':
             xy = node.bottom
             head.append(XY(xy.x, xy.y + 1))
-            head.append(XY(xy.x - cell / 2, xy.y + cell))
+            head.append(XY(xy.x - cell // 2, xy.y + cell))
             head.append(XY(xy.x, xy.y + cell * 2))
-            head.append(XY(xy.x + cell / 2, xy.y + cell))
+            head.append(XY(xy.x + cell // 2, xy.y + cell))
             head.append(XY(xy.x, xy.y + 1))
         elif direct == 'down':
             xy = node.top
             head.append(XY(xy.x, xy.y - 1))
-            head.append(XY(xy.x - cell / 2, xy.y - cell))
+            head.append(XY(xy.x - cell // 2, xy.y - cell))
             head.append(XY(xy.x, xy.y - cell * 2))
-            head.append(XY(xy.x + cell / 2, xy.y - cell))
+            head.append(XY(xy.x + cell // 2, xy.y - cell))
             head.append(XY(xy.x, xy.y - 1))
         elif direct == 'right':
             xy = node.left
             head.append(XY(xy.x - 1, xy.y))
-            head.append(XY(xy.x - cell, xy.y - cell / 2))
+            head.append(XY(xy.x - cell, xy.y - cell // 2))
             head.append(XY(xy.x - cell * 2, xy.y))
-            head.append(XY(xy.x - cell, xy.y + cell / 2))
+            head.append(XY(xy.x - cell, xy.y + cell // 2))
             head.append(XY(xy.x - 1, xy.y))
         elif direct == 'left':
             xy = node.right
             head.append(XY(xy.x + 1, xy.y))
-            head.append(XY(xy.x + cell, xy.y - cell / 2))
+            head.append(XY(xy.x + cell, xy.y - cell // 2))
             head.append(XY(xy.x + cell * 2, xy.y))
-            head.append(XY(xy.x + cell, xy.y + cell / 2))
+            head.append(XY(xy.x + cell, xy.y + cell // 2))
             head.append(XY(xy.x + 1, xy.y))
         elif direct == 'rup':
             xy = node.bottom
@@ -587,12 +592,12 @@ class LandscapeEdgeMetrics(EdgeMetrics):
             shaft.moveTo(node1.right)
 
             if self.edge.skipped:
-                shaft.lineTo(cell1.right.x + span.x / 2, cell1.right.y)
-                shaft.lineTo(cell1.right.x + span.x / 2,
-                             cell1.bottomright.y + span.y / 2)
-                shaft.lineTo(cell2.left.x - span.x / 4,
-                             cell2.bottomright.y + span.y / 2)
-                shaft.lineTo(cell2.left.x - span.x / 4, cell2.left.y)
+                shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y)
+                shaft.lineTo(cell1.right.x + span.x // 2,
+                             cell1.bottomright.y + span.y // 2)
+                shaft.lineTo(cell2.left.x - span.x // 4,
+                             cell2.bottomright.y + span.y // 2)
+                shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y)
 
             shaft.lineTo(node2.left)
 
@@ -600,40 +605,40 @@ class LandscapeEdgeMetrics(EdgeMetrics):
             shaft.moveTo(node1.right)
 
             if self.edge.skipped:
-                shaft.lineTo(cell1.right.x + span.x / 2, cell1.right.y)
-                shaft.lineTo(cell1.right.x + span.x / 2,
-                             cell2.bottomleft.y + span.y / 2)
-                shaft.lineTo(cell2.left.x - span.x / 4,
-                             cell2.bottomleft.y + span.y / 2)
-                shaft.lineTo(cell2.left.x - span.x / 4, cell2.left.y)
+                shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y)
+                shaft.lineTo(cell1.right.x + span.x // 2,
+                             cell2.bottomleft.y + span.y // 2)
+                shaft.lineTo(cell2.left.x - span.x // 4,
+                             cell2.bottomleft.y + span.y // 2)
+                shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y)
             else:
-                shaft.lineTo(cell2.left.x - span.x / 4, cell1.right.y)
-                shaft.lineTo(cell2.left.x - span.x / 4, cell2.left.y)
+                shaft.lineTo(cell2.left.x - span.x // 4, cell1.right.y)
+                shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y)
 
             shaft.lineTo(node2.left)
 
         elif _dir == 'right-down':
             shaft.moveTo(node1.right)
-            shaft.lineTo(cell1.right.x + span.x / 2, cell1.right.y)
+            shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y)
 
             if self.edge.skipped:
-                shaft.lineTo(cell1.right.x + span.x / 2,
-                             cell2.topleft.y - span.y / 2)
-                shaft.lineTo(cell2.left.x - span.x / 4,
-                             cell2.topleft.y - span.y / 2)
-                shaft.lineTo(cell2.left.x - span.x / 4, cell2.left.y)
+                shaft.lineTo(cell1.right.x + span.x // 2,
+                             cell2.topleft.y - span.y // 2)
+                shaft.lineTo(cell2.left.x - span.x // 4,
+                             cell2.topleft.y - span.y // 2)
+                shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y)
             else:
-                shaft.lineTo(cell1.right.x + span.x / 2, cell2.left.y)
+                shaft.lineTo(cell1.right.x + span.x // 2, cell2.left.y)
 
             shaft.lineTo(node2.left)
 
         elif _dir == 'up':
             if self.edge.skipped:
                 shaft.moveTo(node1.right)
-                shaft.lineTo(cell1.right.x + span.x / 4, cell1.right.y)
-                shaft.lineTo(cell1.right.x + span.x / 4,
-                             cell2.bottom.y + span.y / 2)
-                shaft.lineTo(cell2.bottom.x, cell2.bottom.y + span.y / 2)
+                shaft.lineTo(cell1.right.x + span.x // 4, cell1.right.y)
+                shaft.lineTo(cell1.right.x + span.x // 4,
+                             cell2.bottom.y + span.y // 2)
+                shaft.lineTo(cell2.bottom.x, cell2.bottom.y + span.y // 2)
             else:
                 shaft.moveTo(node1.top)
 
@@ -641,36 +646,36 @@ class LandscapeEdgeMetrics(EdgeMetrics):
 
         elif _dir in ('left-up', 'left', 'same'):
             shaft.moveTo(node1.right)
-            shaft.lineTo(cell1.right.x + span.x / 4, cell1.right.y)
-            shaft.lineTo(cell1.right.x + span.x / 4,
-                         cell2.top.y - span.y / 2 + span.y / 8)
+            shaft.lineTo(cell1.right.x + span.x // 4, cell1.right.y)
+            shaft.lineTo(cell1.right.x + span.x // 4,
+                         cell2.top.y - span.y // 2 + span.y // 8)
             shaft.lineTo(cell2.top.x,
-                         cell2.top.y - span.y / 2 + span.y / 8)
+                         cell2.top.y - span.y // 2 + span.y // 8)
             shaft.lineTo(node2.top)
 
         elif _dir == 'left-down':
             if self.edge.skipped:
                 shaft.moveTo(node1.right)
-                shaft.lineTo(cell1.right.x + span.x / 2, cell1.right.y)
-                shaft.lineTo(cell1.right.x + span.x / 2,
-                             cell2.top.y - span.y / 2)
-                shaft.lineTo(cell2.top.x, cell2.top.y - span.y / 2)
+                shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y)
+                shaft.lineTo(cell1.right.x + span.x // 2,
+                             cell2.top.y - span.y // 2)
+                shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2)
             else:
                 shaft.moveTo(node1.bottom)
                 shaft.lineTo(cell1.bottom.x,
-                             cell2.top.y - span.y / 2)
-                shaft.lineTo(cell2.top.x, cell2.top.y - span.y / 2)
+                             cell2.top.y - span.y // 2)
+                shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2)
 
             shaft.lineTo(node2.top)
 
         elif _dir == 'down':
             if self.edge.skipped:
                 shaft.moveTo(node1.right)
-                shaft.lineTo(cell1.right.x + span.x / 2, cell1.right.y)
-                shaft.lineTo(cell1.right.x + span.x / 2,
-                             cell2.top.y - span.y / 2 + span.y / 8)
+                shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y)
+                shaft.lineTo(cell1.right.x + span.x // 2,
+                             cell2.top.y - span.y // 2 + span.y // 8)
                 shaft.lineTo(cell2.top.x,
-                             cell2.top.y - span.y / 2 + span.y / 8)
+                             cell2.top.y - span.y // 2 + span.y // 8)
             else:
                 shaft.moveTo(node1.bottom)
 
@@ -692,40 +697,40 @@ class LandscapeEdgeMetrics(EdgeMetrics):
                 box = Box(node1.bottomright.x + span.x,
                           node1.bottomright.y,
                           node2.bottomleft.x - span.x,
-                          node2.bottomleft.y + span.y / 2)
+                          node2.bottomleft.y + span.y // 2)
             else:
-                box = Box(node1.topright.x, node1.topright.y - span.y / 8,
-                          node2.left.x, node2.left.y - span.y / 8)
+                box = Box(node1.topright.x, node1.topright.y - span.y // 8,
+                          node2.left.x, node2.left.y - span.y // 8)
 
         elif _dir == 'right-up':
-            box = Box(node2.left.x - span.x, node1.top.y - node.y / 2,
+            box = Box(node2.left.x - span.x, node1.top.y - node.y // 2,
                       node2.bottomleft.x, node1.top.y)
 
         elif _dir == 'right-down':
-            box = Box(node1.right.x, node2.topleft.y - span.y / 8,
-                      node1.right.x + span.x, node2.left.y - span.y / 8)
+            box = Box(node1.right.x, node2.topleft.y - span.y // 8,
+                      node1.right.x + span.x, node2.left.y - span.y // 8)
 
         elif _dir in ('up', 'left-up', 'left', 'same'):
             if self.edge.node2.xy.y < self.edge.node1.xy.y:
-                box = Box(node1.topright.x - span.x / 2 + span.x / 4,
-                          node1.topright.y - span.y / 2,
-                          node1.topright.x + span.x / 2 + span.x / 4,
+                box = Box(node1.topright.x - span.x // 2 + span.x // 4,
+                          node1.topright.y - span.y // 2,
+                          node1.topright.x + span.x // 2 + span.x // 4,
                           node1.topright.y)
             else:
-                box = Box(node1.top.x + span.x / 4,
+                box = Box(node1.top.x + span.x // 4,
                           node1.top.y - span.y,
-                          node1.topright.x + span.x / 4,
-                          node1.topright.y - span.y / 2)
+                          node1.topright.x + span.x // 4,
+                          node1.topright.y - span.y // 2)
 
         elif _dir in ('left-down', 'down'):
-            box = Box(node2.top.x + span.x / 4,
+            box = Box(node2.top.x + span.x // 4,
                       node2.top.y - span.y,
-                      node2.topright.x + span.x / 4,
-                      node2.topright.y - span.y / 2)
+                      node2.topright.x + span.x // 4,
+                      node2.topright.y - span.y // 2)
 
         # shrink box
-        box = Box(box[0] + span.x / 8, box[1],
-                  box[2] - span.x / 8, box[3])
+        box = Box(box[0] + span.x // 8, box[1],
+                  box[2] - span.x // 8, box[3])
 
         return box
 
@@ -793,64 +798,64 @@ class PortraitEdgeMetrics(EdgeMetrics):
                 shaft.lineTo(node2.left)
             else:
                 shaft.moveTo(node1.bottom)
-                shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y / 2)
-                shaft.lineTo(cell2.right.x + span.x / 4,
-                             cell1.bottom.y + span.y / 2)
-                shaft.lineTo(cell2.right.x + span.x / 4,
-                             cell2.top.y - span.y / 2 + span.y / 8)
+                shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2)
+                shaft.lineTo(cell2.right.x + span.x // 4,
+                             cell1.bottom.y + span.y // 2)
+                shaft.lineTo(cell2.right.x + span.x // 4,
+                             cell2.top.y - span.y // 2 + span.y // 8)
                 shaft.lineTo(cell2.top.x,
-                             cell2.top.y - span.y / 2 + span.y / 8)
+                             cell2.top.y - span.y // 2 + span.y // 8)
                 shaft.lineTo(node2.top)
 
         elif _dir == 'right-down':
             shaft.moveTo(node1.bottom)
-            shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y / 2)
+            shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2)
 
             if self.edge.skipped:
-                shaft.lineTo(cell2.left.x - span.x / 2,
-                             cell1.bottom.y + span.y / 2)
-                shaft.lineTo(cell2.topleft.x - span.x / 2,
-                             cell2.topleft.y - span.y / 2)
-                shaft.lineTo(cell2.top.x, cell2.top.y - span.y / 2)
+                shaft.lineTo(cell2.left.x - span.x // 2,
+                             cell1.bottom.y + span.y // 2)
+                shaft.lineTo(cell2.topleft.x - span.x // 2,
+                             cell2.topleft.y - span.y // 2)
+                shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2)
             else:
-                shaft.lineTo(cell2.top.x, cell1.bottom.y + span.y / 2)
+                shaft.lineTo(cell2.top.x, cell1.bottom.y + span.y // 2)
 
             shaft.lineTo(node2.top)
 
         elif _dir in ('left-up', 'left', 'same'):
             shaft.moveTo(node1.right)
-            shaft.lineTo(cell1.right.x + span.x / 4, cell1.right.y)
-            shaft.lineTo(cell1.right.x + span.x / 4,
-                         cell2.top.y - span.y / 2 + span.y / 8)
+            shaft.lineTo(cell1.right.x + span.x // 4, cell1.right.y)
+            shaft.lineTo(cell1.right.x + span.x // 4,
+                         cell2.top.y - span.y // 2 + span.y // 8)
             shaft.lineTo(cell2.top.x,
-                         cell2.top.y - span.y / 2 + span.y / 8)
+                         cell2.top.y - span.y // 2 + span.y // 8)
             shaft.lineTo(node2.top)
 
         elif _dir == 'left-down':
             shaft.moveTo(node1.bottom)
 
             if self.edge.skipped:
-                shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y / 2)
-                shaft.lineTo(cell2.right.x + span.x / 2,
-                             cell1.bottom.y + span.y / 2)
-                shaft.lineTo(cell2.right.x + span.x / 2,
-                             cell2.top.y - span.y / 2)
+                shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2)
+                shaft.lineTo(cell2.right.x + span.x // 2,
+                             cell1.bottom.y + span.y // 2)
+                shaft.lineTo(cell2.right.x + span.x // 2,
+                             cell2.top.y - span.y // 2)
             else:
-                shaft.lineTo(cell1.bottom.x, cell2.top.y - span.y / 2)
+                shaft.lineTo(cell1.bottom.x, cell2.top.y - span.y // 2)
 
-            shaft.lineTo(cell2.top.x, cell2.top.y - span.y / 2)
+            shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2)
             shaft.lineTo(node2.top)
 
         elif _dir == 'down':
             shaft.moveTo(node1.bottom)
 
             if self.edge.skipped:
-                shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y / 2)
-                shaft.lineTo(cell1.right.x + span.x / 2,
-                             cell1.bottom.y + span.y / 2)
-                shaft.lineTo(cell2.right.x + span.x / 2,
-                             cell2.top.y - span.y / 2)
-                shaft.lineTo(cell2.top.x, cell2.top.y - span.y / 2)
+                shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2)
+                shaft.lineTo(cell1.right.x + span.x // 2,
+                             cell1.bottom.y + span.y // 2)
+                shaft.lineTo(cell2.right.x + span.x // 2,
+                             cell2.top.y - span.y // 2)
+                shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2)
 
             shaft.lineTo(node2.top)
 
@@ -869,44 +874,44 @@ class PortraitEdgeMetrics(EdgeMetrics):
                 box = Box(node1.bottomright.x + span.x,
                           node1.bottomright.y,
                           node2.bottomleft.x - span.x,
-                          node2.bottomleft.y + span.y / 2)
+                          node2.bottomleft.y + span.y // 2)
             else:
-                box = Box(node1.topright.x, node1.topright.y - span.y / 8,
-                          node2.left.x, node2.left.y - span.y / 8)
+                box = Box(node1.topright.x, node1.topright.y - span.y // 8,
+                          node2.left.x, node2.left.y - span.y // 8)
 
         elif _dir == 'right-up':
             box = Box(node2.left.x - span.x, node2.left.y,
                       node2.bottomleft.x, node2.bottomleft.y)
 
         elif _dir == 'right-down':
-            box = Box(node2.topleft.x, node2.topleft.y - span.y / 2,
+            box = Box(node2.topleft.x, node2.topleft.y - span.y // 2,
                       node2.top.x, node2.top.y)
 
         elif _dir in ('up', 'left-up', 'left', 'same'):
             if self.edge.node2.xy.y < self.edge.node1.xy.y:
-                box = Box(node1.topright.x - span.x / 2 + span.x / 4,
-                          node1.topright.y - span.y / 2,
-                          node1.topright.x + span.x / 2 + span.x / 4,
+                box = Box(node1.topright.x - span.x // 2 + span.x // 4,
+                          node1.topright.y - span.y // 2,
+                          node1.topright.x + span.x // 2 + span.x // 4,
                           node1.topright.y)
             else:
-                box = Box(node1.top.x + span.x / 4,
+                box = Box(node1.top.x + span.x // 4,
                           node1.top.y - span.y,
-                          node1.topright.x + span.x / 4,
-                          node1.topright.y - span.y / 2)
+                          node1.topright.x + span.x // 4,
+                          node1.topright.y - span.y // 2)
 
         elif _dir == 'down':
-            box = Box(node2.top.x + span.x / 4,
-                      node2.top.y - span.y / 2,
-                      node2.topright.x + span.x / 4,
+            box = Box(node2.top.x + span.x // 4,
+                      node2.top.y - span.y // 2,
+                      node2.topright.x + span.x // 4,
                       node2.topright.y)
 
         elif _dir == 'left-down':
             box = Box(node1.bottomleft.x, node1.bottomleft.y,
-                      node1.bottom.x, node1.bottom.y + span.y / 2)
+                      node1.bottom.x, node1.bottom.y + span.y // 2)
 
         # shrink box
-        box = Box(box[0] + span.x / 8, box[1],
-                  box[2] - span.x / 8, box[3])
+        box = Box(box[0] + span.x // 8, box[1],
+                  box[2] - span.x // 8, box[3])
 
         return box
 
@@ -950,10 +955,10 @@ class FlowchartLandscapeEdgeMetrics(LandscapeEdgeMetrics):
             shaft.moveTo(node1.bottom)
 
             if self.edge.skipped:
-                shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y / 2)
-                shaft.lineTo(cell2.left.x - span.x / 4,
-                             cell1.bottom.y + span.y / 2)
-                shaft.lineTo(cell2.left.x - span.x / 4, cell2.left.y)
+                shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2)
+                shaft.lineTo(cell2.left.x - span.x // 4,
+                             cell1.bottom.y + span.y // 2)
+                shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y)
             else:
                 shaft.lineTo(cell1.bottom.x, cell2.left.y)
 
@@ -974,9 +979,9 @@ class FlowchartLandscapeEdgeMetrics(LandscapeEdgeMetrics):
             if self.edge.skipped:
                 box = Box(cell1.bottom.x, cell1.bottom.y,
                           cell1.bottomright.x,
-                          cell1.bottomright.y + span.y / 2)
+                          cell1.bottomright.y + span.y // 2)
             else:
-                box = Box(cell1.bottom.x, cell2.left.y - span.y / 2,
+                box = Box(cell1.bottom.x, cell2.left.y - span.y // 2,
                           cell1.bottom.x, cell2.left.y)
         else:
             box = super(FlowchartLandscapeEdgeMetrics, self).labelbox
@@ -1023,10 +1028,11 @@ class FlowchartPortraitEdgeMetrics(PortraitEdgeMetrics):
             shaft.moveTo(node1.right)
 
             if self.edge.skipped:
-                shaft.lineTo(cell1.right.x + span.x * 3 / 4, cell1.right.y)
-                shaft.lineTo(cell1.right.x + span.x * 3 / 4,
-                             cell2.topleft.y - span.y / 2)
-                shaft.lineTo(cell2.top.x, cell2.top.y - span.y / 2)
+                shaft.lineTo(cell1.right.x + span.x * 3 // 4,
+                             cell1.right.y)
+                shaft.lineTo(cell1.right.x + span.x * 3 // 4,
+                             cell2.topleft.y - span.y // 2)
+                shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2)
             else:
                 shaft.lineTo(cell2.top.x, cell1.right.y)
 
@@ -1044,15 +1050,15 @@ class FlowchartPortraitEdgeMetrics(PortraitEdgeMetrics):
         cell2 = self.cell(self.edge.node2, use_padding=False)
 
         if _dir == 'down':
-            box = Box(cell2.topleft.x, cell2.top.y - span.y / 2,
+            box = Box(cell2.topleft.x, cell2.top.y - span.y // 2,
                       cell2.top.x, cell2.top.y)
         elif _dir == 'right':
             if self.edge.skipped:
                 box = Box(cell1.bottom.x, cell1.bottom.y,
                           cell1.bottomright.x,
-                          cell1.bottomright.y + span.y / 2)
+                          cell1.bottomright.y + span.y // 2)
             else:
-                box = Box(cell1.bottom.x, cell2.left.y - span.y / 2,
+                box = Box(cell1.bottom.x, cell2.left.y - span.y // 2,
                           cell1.bottom.x, cell2.left.y)
         else:
             box = super(FlowchartPortraitEdgeMetrics, self).labelbox
diff --git a/src/blockdiag/noderenderer/__init__.py b/src/blockdiag/noderenderer/__init__.py
index 96fcdfd..9bf590f 100644
--- a/src/blockdiag/noderenderer/__init__.py
+++ b/src/blockdiag/noderenderer/__init__.py
@@ -13,6 +13,7 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+from __future__ import division
 import pkg_resources
 from blockdiag.utils import images, Box, XY
 
@@ -66,10 +67,10 @@ class NodeShape(object):
             if image_size is None:
                 iconsize = (0, 0)
             else:
-                boundedbox = [metrics.node_width / 2, metrics.node_height]
+                boundedbox = [metrics.node_width // 2, metrics.node_height]
                 iconsize = images.calc_image_size(image_size, boundedbox)
 
-            vmargin = (metrics.node_height - iconsize[1]) / 2
+            vmargin = (metrics.node_height - iconsize[1]) // 2
             self.iconbox = Box(m.topleft.x,
                                m.topleft.y + vmargin,
                                m.topleft.x + iconsize[0],
@@ -85,7 +86,7 @@ class NodeShape(object):
             node.background = ""
             for i in range(2, 0, -1):
                 # use original_metrics FORCE
-                r = self.metrics.original_metrics.cellsize / 2 * i
+                r = self.metrics.original_metrics.cellsize // 2 * i
                 metrics = self.metrics.shift(r, r)
 
                 self.__class__(node, metrics).render(drawer, _format,
@@ -101,7 +102,7 @@ class NodeShape(object):
         self.render_number_badge(drawer, **kwargs)
 
     def render_icon(self, drawer, **kwargs):
-        if self.node.icon is not None and kwargs.get('shadow') is False:
+        if self.node.icon is not None and kwargs.get('shadow') is not True:
             drawer.image(self.iconbox, self.node.icon)
 
     def render_shape(self, drawer, _, **kwargs):
@@ -121,7 +122,7 @@ class NodeShape(object):
             badgeFill = kwargs.get('badgeFill')
 
             xy = self.metrics.cell(self.node).topleft
-            r = self.metrics.cellsize * 3 / 2
+            r = self.metrics.cellsize * 3 // 2
 
             box = Box(xy.x - r, xy.y - r, xy.x + r, xy.y + r)
             font = self.metrics.font_for(self.node)
diff --git a/src/blockdiag/noderenderer/actor.py b/src/blockdiag/noderenderer/actor.py
index bd5c520..17be67c 100644
--- a/src/blockdiag/noderenderer/actor.py
+++ b/src/blockdiag/noderenderer/actor.py
@@ -13,6 +13,7 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+from __future__ import division
 from blockdiag.noderenderer import NodeShape
 from blockdiag.noderenderer import install_renderer
 from blockdiag.utils import XY, Box
@@ -24,16 +25,16 @@ class Actor(NodeShape):
 
         shortside = min(self.node.width or metrics.node_height,
                         self.node.height or metrics.node_height)
-        r = self.radius = shortside / 8  # radius of actor's head
+        r = self.radius = shortside // 8  # radius of actor's head
         self.center = metrics.cell(node).center
 
-        self.connectors[0] = XY(self.center.x, self.center.y - r * 9 / 2)
+        self.connectors[0] = XY(self.center.x, self.center.y - r * 9 // 2)
         self.connectors[1] = XY(self.center.x + r * 4, self.center.y)
         self.connectors[2] = XY(self.center.x, self.center.y + r * 4)
         self.connectors[3] = XY(self.center.x - r * 4, self.center.y)
 
     def head_part(self):
-        r = self.radius * 3 / 2
+        r = self.radius * 3 // 2
         pt = self.metrics.cell(self.node).center.shift(y=-self.radius * 3)
         return Box(pt.x - r, pt.y - r, pt.x + r, pt.y + r)
 
@@ -42,12 +43,12 @@ class Actor(NodeShape):
         m = self.metrics.cell(self.node)
 
         bodyC = m.center
-        neckWidth = r * 2 / 3  # neck size
+        neckWidth = r * 2 // 3  # neck size
         arm = r * 4  # arm length
         armWidth = r
-        bodyWidth = r * 2 / 3  # half of body width
+        bodyWidth = r * 2 // 3  # half of body width
         bodyHeight = r
-        legXout = r * 7 / 2  # toe outer position
+        legXout = r * 7 // 2  # toe outer position
         legYout = bodyHeight + r * 3
         legXin = r * 2  # toe inner position
         legYin = bodyHeight + r * 3
diff --git a/src/blockdiag/noderenderer/circle.py b/src/blockdiag/noderenderer/circle.py
index 90afd9b..da404ab 100644
--- a/src/blockdiag/noderenderer/circle.py
+++ b/src/blockdiag/noderenderer/circle.py
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+from __future__ import division
 from blockdiag.noderenderer import NodeShape
 from blockdiag.noderenderer import install_renderer
 from blockdiag.utils import Box, XY
@@ -8,8 +9,8 @@ class Circle(NodeShape):
     def __init__(self, node, metrics=None):
         super(Circle, self).__init__(node, metrics)
 
-        r = min(metrics.node_width, metrics.node_height) / 2 + \
-            metrics.cellsize / 2
+        r = min(metrics.node_width, metrics.node_height) // 2 + \
+            metrics.cellsize // 2
         pt = metrics.cell(node).center
         self.connectors = [XY(pt.x, pt.y - r),  # top
                            XY(pt.x + r, pt.y),  # right
diff --git a/src/blockdiag/noderenderer/cloud.py b/src/blockdiag/noderenderer/cloud.py
index 9e22bd7..9fca4d4 100644
--- a/src/blockdiag/noderenderer/cloud.py
+++ b/src/blockdiag/noderenderer/cloud.py
@@ -13,6 +13,7 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+from __future__ import division
 from blockdiag.noderenderer import NodeShape
 from blockdiag.noderenderer import install_renderer
 from blockdiag.utils import Box
@@ -24,8 +25,8 @@ class Cloud(NodeShape):
         super(Cloud, self).__init__(node, metrics)
 
         pt = metrics.cell(node).topleft
-        rx = (self.node.width or self.metrics.node_width) / 12
-        ry = (self.node.height or self.metrics.node_height) / 5
+        rx = (self.node.width or self.metrics.node_width) // 12
+        ry = (self.node.height or self.metrics.node_height) // 5
         self.textbox = Box(pt.x + rx * 2, pt.y + ry,
                            pt.x + rx * 11, pt.y + ry * 4)
 
@@ -41,8 +42,8 @@ class Cloud(NodeShape):
 
         m = self.metrics.cell(self.node)
         pt = m.topleft
-        rx = (self.node.width or self.metrics.node_width) / 12
-        ry = (self.node.height or self.metrics.node_height) / 5
+        rx = (self.node.width or self.metrics.node_width) // 12
+        ry = (self.node.height or self.metrics.node_height) // 5
 
         ellipses = [Box(pt.x + rx * 2, pt.y + ry,
                         pt.x + rx * 5, pt.y + ry * 3),
@@ -95,8 +96,8 @@ class Cloud(NodeShape):
 
         # create pathdata
         m = self.metrics.cell(self.node)
-        rx = (self.node.width or self.metrics.node_width) / 12
-        ry = (self.node.height or self.metrics.node_height) / 5
+        rx = (self.node.width or self.metrics.node_width) // 12
+        ry = (self.node.height or self.metrics.node_height) // 5
 
         pt = m.topleft
         if kwargs.get('shadow'):
@@ -104,12 +105,12 @@ class Cloud(NodeShape):
 
         path = pathdata(pt.x + rx * 2, pt.y + ry * 2)
         path.ellarc(rx * 2, ry, 0, 0, 1, pt.x + rx * 4, pt.y + ry)
-        path.ellarc(rx * 2, ry * 3 / 4, 0, 0, 1, pt.x + rx * 9, pt.y + ry)
+        path.ellarc(rx * 2, ry * 3 // 4, 0, 0, 1, pt.x + rx * 9, pt.y + ry)
         path.ellarc(rx * 2, ry, 0, 0, 1, pt.x + rx * 11, pt.y + ry * 2)
         path.ellarc(rx * 2, ry, 0, 0, 1, pt.x + rx * 11, pt.y + ry * 4)
-        path.ellarc(rx * 2, ry * 5 / 2, 0, 0, 1, pt.x + rx * 8, pt.y + ry * 4)
-        path.ellarc(rx * 2, ry * 5 / 2, 0, 0, 1, pt.x + rx * 5, pt.y + ry * 4)
-        path.ellarc(rx * 2, ry * 5 / 2, 0, 0, 1, pt.x + rx * 2, pt.y + ry * 4)
+        path.ellarc(rx * 2, ry * 5 // 2, 0, 0, 1, pt.x + rx * 8, pt.y + ry * 4)
+        path.ellarc(rx * 2, ry * 5 // 2, 0, 0, 1, pt.x + rx * 5, pt.y + ry * 4)
+        path.ellarc(rx * 2, ry * 5 // 2, 0, 0, 1, pt.x + rx * 2, pt.y + ry * 4)
         path.ellarc(rx * 2, ry, 0, 0, 1, pt.x + rx * 2, pt.y + ry * 2)
 
         # draw outline
@@ -121,7 +122,7 @@ class Cloud(NodeShape):
                 drawer.path(path, fill=fill, outline=fill)
         elif self.node.background:
             drawer.path(path, fill=self.node.color, outline=self.node.color)
-            drawer.loadImage(self.node.background, self.textbox)
+            drawer.image(self.textbox, self.node.background)
             drawer.path(path, fill="none", outline=self.node.linecolor,
                         style=self.node.style)
         else:
diff --git a/src/blockdiag/noderenderer/diamond.py b/src/blockdiag/noderenderer/diamond.py
index 32c8179..4567c22 100644
--- a/src/blockdiag/noderenderer/diamond.py
+++ b/src/blockdiag/noderenderer/diamond.py
@@ -13,6 +13,7 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+from __future__ import division
 from blockdiag.noderenderer import NodeShape
 from blockdiag.noderenderer import install_renderer
 from blockdiag.utils import Box, XY
@@ -29,10 +30,10 @@ class Diamond(NodeShape):
                            XY(m.bottom.x, m.bottom.y + r),
                            XY(m.left.x - r, m.left.y),
                            XY(m.top.x, m.top.y - r)]
-        self.textbox = Box((self.connectors[0].x + self.connectors[3].x) / 2,
-                           (self.connectors[0].y + self.connectors[3].y) / 2,
-                           (self.connectors[1].x + self.connectors[2].x) / 2,
-                           (self.connectors[1].y + self.connectors[2].y) / 2)
+        self.textbox = Box((self.connectors[0].x + self.connectors[3].x) // 2,
+                           (self.connectors[0].y + self.connectors[3].y) // 2,
+                           (self.connectors[1].x + self.connectors[2].x) // 2,
+                           (self.connectors[1].y + self.connectors[2].y) // 2)
 
     def render_shape(self, drawer, _, **kwargs):
         fill = kwargs.get('fill')
diff --git a/src/blockdiag/noderenderer/flowchart/database.py b/src/blockdiag/noderenderer/flowchart/database.py
index 404b93c..2396483 100644
--- a/src/blockdiag/noderenderer/flowchart/database.py
+++ b/src/blockdiag/noderenderer/flowchart/database.py
@@ -13,6 +13,7 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+from __future__ import division
 from blockdiag.noderenderer import NodeShape
 from blockdiag.noderenderer import install_renderer
 from blockdiag.utils import XY, Box
@@ -25,8 +26,8 @@ class Database(NodeShape):
 
         m = self.metrics.cell(self.node)
         r = self.metrics.cellsize
-        self.textbox = Box(m.topleft.x, m.topleft.y + r * 3 / 2,
-                           m.bottomright.x, m.bottomright.y - r / 2)
+        self.textbox = Box(m.topleft.x, m.topleft.y + r * 3 // 2,
+                           m.bottomright.x, m.bottomright.y - r // 2)
 
     def render_shape(self, drawer, _, **kwargs):
         # draw background
@@ -99,9 +100,9 @@ class Database(NodeShape):
             box = self.shift_shadow(box)
 
         path = pathdata(box[0], box[1] + r)
-        path.ellarc(width / 2, r, 0, 0, 1, box[2], box[1] + r)
+        path.ellarc(width // 2, r, 0, 0, 1, box[2], box[1] + r)
         path.line(box[2], box[3] - r)
-        path.ellarc(width / 2, r, 0, 0, 1, box[0], box[3] - r)
+        path.ellarc(width // 2, r, 0, 0, 1, box[0], box[3] - r)
         path.line(box[0], box[1] + r)
 
         # draw outline
@@ -124,7 +125,7 @@ class Database(NodeShape):
         # draw cap of cylinder
         if not kwargs.get('shadow'):
             path = pathdata(box[2], box[1] + r)
-            path.ellarc(width / 2, r, 0, 0, 1, box[0], box[1] + r)
+            path.ellarc(width // 2, r, 0, 0, 1, box[0], box[1] + r)
             drawer.path(path, fill=self.node.color,
                         outline=self.node.linecolor, style=self.node.style)
 
diff --git a/src/blockdiag/noderenderer/flowchart/input.py b/src/blockdiag/noderenderer/flowchart/input.py
index 8092dd7..6e928c4 100644
--- a/src/blockdiag/noderenderer/flowchart/input.py
+++ b/src/blockdiag/noderenderer/flowchart/input.py
@@ -34,11 +34,11 @@ class Input(NodeShape):
         m = self.metrics.cell(self.node)
         r = self.metrics.cellsize * 3
 
-        shape = [XY(m.topleft.x + r,  m.topleft.y),
+        shape = [XY(m.topleft.x + r, m.topleft.y),
                  XY(m.topright.x, m.topright.y),
                  XY(m.bottomright.x - r, m.bottomright.y),
-                 XY(m.bottomleft.x,  m.bottomleft.y),
-                 XY(m.topleft.x + r,  m.topleft.y)]
+                 XY(m.bottomleft.x, m.bottomleft.y),
+                 XY(m.topleft.x + r, m.topleft.y)]
 
         # draw outline
         if kwargs.get('shadow'):
diff --git a/src/blockdiag/noderenderer/flowchart/loopin.py b/src/blockdiag/noderenderer/flowchart/loopin.py
index 5ab7324..ac25d0c 100644
--- a/src/blockdiag/noderenderer/flowchart/loopin.py
+++ b/src/blockdiag/noderenderer/flowchart/loopin.py
@@ -13,6 +13,7 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+from __future__ import division
 from blockdiag.noderenderer import NodeShape
 from blockdiag.noderenderer import install_renderer
 from blockdiag.utils import Box, XY
@@ -23,7 +24,7 @@ class LoopIn(NodeShape):
         super(LoopIn, self).__init__(node, metrics)
 
         m = self.metrics.cell(self.node)
-        ydiff = self.metrics.node_height / 4
+        ydiff = self.metrics.node_height // 4
 
         self.textbox = Box(m.topleft.x, m.topleft.y + ydiff,
                            m.bottomright.x, m.bottomright.y)
@@ -32,8 +33,8 @@ class LoopIn(NodeShape):
         fill = kwargs.get('fill')
 
         m = self.metrics.cell(self.node)
-        xdiff = self.metrics.node_width / 4
-        ydiff = self.metrics.node_height / 4
+        xdiff = self.metrics.node_width // 4
+        ydiff = self.metrics.node_height // 4
 
         shape = [XY(m.topleft.x + xdiff, m.topleft.y),
                  XY(m.topright.x - xdiff, m.topleft.y),
diff --git a/src/blockdiag/noderenderer/flowchart/loopout.py b/src/blockdiag/noderenderer/flowchart/loopout.py
index e534e36..19b962d 100644
--- a/src/blockdiag/noderenderer/flowchart/loopout.py
+++ b/src/blockdiag/noderenderer/flowchart/loopout.py
@@ -13,6 +13,7 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+from __future__ import division
 from blockdiag.noderenderer import NodeShape
 from blockdiag.noderenderer import install_renderer
 from blockdiag.utils import Box, XY
@@ -23,7 +24,7 @@ class LoopOut(NodeShape):
         super(LoopOut, self).__init__(node, metrics)
 
         m = self.metrics.cell(self.node)
-        ydiff = self.metrics.node_height / 4
+        ydiff = self.metrics.node_height // 4
 
         self.textbox = Box(m.topleft.x, m.topleft.y,
                            m.bottomright.x, m.bottomright.y - ydiff)
@@ -32,8 +33,8 @@ class LoopOut(NodeShape):
         fill = kwargs.get('fill')
 
         m = self.metrics.cell(self.node)
-        xdiff = self.metrics.node_width / 4
-        ydiff = self.metrics.node_height / 4
+        xdiff = self.metrics.node_width // 4
+        ydiff = self.metrics.node_height // 4
 
         shape = [XY(m.topleft.x, m.topleft.y),
                  XY(m.topright.x, m.topright.y),
diff --git a/src/blockdiag/noderenderer/flowchart/terminator.py b/src/blockdiag/noderenderer/flowchart/terminator.py
index c8ba449..4d77baa 100644
--- a/src/blockdiag/noderenderer/flowchart/terminator.py
+++ b/src/blockdiag/noderenderer/flowchart/terminator.py
@@ -106,7 +106,7 @@ class Terminator(NodeShape):
                 drawer.path(path, fill=fill, outline=fill)
         elif self.node.background:
             drawer.path(path, fill=self.node.color, outline=self.node.color)
-            drawer.loadImage(self.node.background, self.textbox)
+            drawer.image(self.textbox, self.node.background)
             drawer.path(path, fill="none",
                         outline=self.node.linecolor, style=self.node.style)
         else:
diff --git a/src/blockdiag/noderenderer/roundedbox.py b/src/blockdiag/noderenderer/roundedbox.py
index 54fe322..90f2351 100644
--- a/src/blockdiag/noderenderer/roundedbox.py
+++ b/src/blockdiag/noderenderer/roundedbox.py
@@ -120,7 +120,7 @@ class RoundedBox(NodeShape):
                 drawer.path(path, fill=fill, outline=fill)
         elif self.node.background:
             drawer.path(path, fill=self.node.color, outline=self.node.color)
-            drawer.loadImage(self.node.background, self.textbox)
+            drawer.image(self.textbox, self.node.background)
             drawer.path(path, fill="none",
                         outline=self.node.linecolor, style=self.node.style)
         else:
diff --git a/src/blockdiag/noderenderer/square.py b/src/blockdiag/noderenderer/square.py
index 6019949..6b874e1 100644
--- a/src/blockdiag/noderenderer/square.py
+++ b/src/blockdiag/noderenderer/square.py
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+from __future__ import division
 from blockdiag.noderenderer import NodeShape
 from blockdiag.noderenderer import install_renderer
 from blockdiag.utils import Box, XY
@@ -8,8 +9,8 @@ class Square(NodeShape):
     def __init__(self, node, metrics=None):
         super(Square, self).__init__(node, metrics)
 
-        r = min(metrics.node_width, metrics.node_height) / 2 + \
-            metrics.cellsize / 2
+        r = min(metrics.node_width, metrics.node_height) // 2 + \
+            metrics.cellsize // 2
         pt = metrics.cell(node).center
         self.connectors = [XY(pt.x, pt.y - r),  # top
                            XY(pt.x + r, pt.y),  # right
diff --git a/src/blockdiag/noderenderer/textbox.py b/src/blockdiag/noderenderer/textbox.py
index 9ff5ccb..3b58cb1 100644
--- a/src/blockdiag/noderenderer/textbox.py
+++ b/src/blockdiag/noderenderer/textbox.py
@@ -13,6 +13,7 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+from __future__ import division
 from blockdiag.noderenderer import NodeShape
 from blockdiag.noderenderer import install_renderer
 from blockdiag.utils import images, Box, XY
@@ -27,8 +28,8 @@ class TextBox(NodeShape):
             size = images.calc_image_size(size, self.textbox.size)
 
             pt = self.textbox.center
-            self.textbox = Box(pt.x - size[0] / 2, pt.y - size[1] / 2,
-                               pt.x + size[0] / 2, pt.y + size[1] / 2)
+            self.textbox = Box(pt.x - size[0] // 2, pt.y - size[1] // 2,
+                               pt.x + size[0] // 2, pt.y + size[1] // 2)
 
             self.connectors[0] = XY(pt.x, self.textbox[1])
             self.connectors[1] = XY(self.textbox[2], pt.y)
diff --git a/src/blockdiag/parser.py b/src/blockdiag/parser.py
index 3adb1e9..33bf44a 100644
--- a/src/blockdiag/parser.py
+++ b/src/blockdiag/parser.py
@@ -35,24 +35,23 @@ At the moment, the parser builds only a parse tree, not an abstract syntax tree
   [1]: http://www.graphviz.org/doc/info/lang.html
 '''
 
-import codecs
+import io
 from re import MULTILINE, DOTALL
+from collections import namedtuple
 from funcparserlib.lexer import make_tokenizer, Token, LexerError
 from funcparserlib.parser import (some, a, maybe, many, finished, skip,
                                   forward_decl)
+from blockdiag.utils.compat import u
 
-from blockdiag.utils.collections import namedtuple
 
 ENCODING = 'utf-8'
 
-Graph = namedtuple('Graph', 'type id stmts')
-SubGraph = namedtuple('SubGraph', 'id stmts')
+Diagram = namedtuple('Diagram', 'type id stmts')
+Group = namedtuple('Group', 'id stmts')
 Node = namedtuple('Node', 'id attrs')
 Attr = namedtuple('Attr', 'name value')
-Edge = namedtuple('Edge', 'nodes attrs')
-DefAttrs = namedtuple('DefAttrs', 'object attrs')
-AttrPlugin = namedtuple('AttrPlugin', 'name attrs')
-AttrClass = namedtuple('AttrClass', 'name attrs')
+Edge = namedtuple('Edge', 'from_nodes edge_type to_nodes attrs')
+Extension = namedtuple('Extension', 'type name attrs')
 Statements = namedtuple('Statements', 'stmts')
 
 
@@ -61,144 +60,184 @@ class ParseException(Exception):
 
 
 def tokenize(string):
-    'str -> Sequence(Token)'
-    specs = [
-        ('Comment', (r'/\*(.|[\r\n])*?\*/', MULTILINE)),
-        ('Comment', (r'(//|#).*',)),
-        ('NL',      (r'[\r\n]+',)),
-        ('Space',   (r'[ \t\r\n]+',)),
-        ('Name',    (ur'[A-Za-z_0-9\u0080-\uffff]'
-                     ur'[A-Za-z_\-.0-9\u0080-\uffff]*',)),
-        ('Op',      (r'[{};,=\[\]]|(<->)|(<-)|(--)|(->)|(>-<)|(-<)|(>-)',)),
-        ('Number',  (r'-?(\.[0-9]+)|([0-9]+(\.[0-9]*)?)',)),
-        ('String',  (r'(?P<quote>"|\').*?(?<!\\)(?P=quote)', DOTALL)),
+    """str -> Sequence(Token)"""
+    # flake8: NOQA
+    specs = [                                                                 # NOQA
+        ('Comment', (r'/\*(.|[\r\n])*?\*/', MULTILINE)),                      # NOQA
+        ('Comment', (r'(//|#).*',)),                                          # NOQA
+        ('NL',      (r'[\r\n]+',)),                                           # NOQA
+        ('Space',   (r'[ \t\r\n]+',)),                                        # NOQA
+        ('Name',    (u('[A-Za-z_0-9\u0080-\uffff]') +                         # NOQA
+                     u('[A-Za-z_\\-.0-9\u0080-\uffff]*'),)),                  # NOQA
+        ('Op',      (r'[{};,=\[\]]|(<->)|(<-)|(--)|(->)|(>-<)|(-<)|(>-)',)),  # NOQA
+        ('Number',  (r'-?(\.[0-9]+)|([0-9]+(\.[0-9]*)?)',)),                  # NOQA
+        ('String',  (r'(?P<quote>"|\').*?(?<!\\)(?P=quote)', DOTALL)),        # NOQA
     ]
     useless = ['Comment', 'NL', 'Space']
     t = make_tokenizer(specs)
     return [x for x in t(string) if x.type not in useless]
 
 
+def create_mapper(fn, default_value=None):
+    if default_value is None:
+        return lambda args: fn(*args)
+    else:
+        return lambda args: fn(*args) if args else default_value
+
+
+def flatten(seq):
+    return sum(seq, [])
+
+
+def oneplus_to_list(first, more):
+    return [first] + more
+
+
 def parse(seq):
-    'Sequence(Token) -> object'
-    unarg = lambda f: lambda args: f(*args)
+    """Sequence(Token) -> object"""
     tokval = lambda x: x.value
-    flatten = lambda list: sum(list, [])
-    node_flatten = lambda l: sum([[l[0]]] + list(l[1:]), [])
-    n = lambda s: a(Token('Name', s)) >> tokval
     op = lambda s: a(Token('Op', s)) >> tokval
     op_ = lambda s: skip(op(s))
-    _id = some(lambda t:
-               t.type in ['Name', 'Number', 'String']).named('id') >> tokval
-    make_nodes = lambda args: Statements([Node(n, args[-1]) for n in args[0]])
-    make_graph_attr = lambda args: DefAttrs(u'graph', [Attr(*args)])
-    make_edge = lambda x, x2, xs, attrs: Edge([x, x2] + xs, attrs)
+    _id = some(lambda t: t.type in ['Name', 'Number', 'String']) >> tokval
+    keyword = lambda s: a(Token('Name', s)) >> tokval
+
+    def make_node_list(node_list, attrs):
+        return Statements([Node(node, attrs) for node in node_list])
+
+    def make_edge(first, edge_type, second, followers, attrs):
+        edges = [Edge(first, edge_type, second, attrs)]
+
+        from_node = second
+        for edge_type, to_node in followers:
+            edges.append(Edge(from_node, edge_type, to_node, attrs))
+            from_node = to_node
+
+        return Statements(edges)
 
     #
     # parts of syntax
     #
-    node_id = _id  # + maybe(port)
     node_list = (
-        node_id +
-        many(op_(',') + node_id)
-        >> node_flatten)
-    a_list = (
         _id +
-        maybe(op_('=') + _id) +
-        skip(maybe(op(',')))
-        >> unarg(Attr))
-    attr_list = (
-        many(op_('[') + many(a_list) + op_(']'))
-        >> flatten)
-    graph_attr = _id + op_('=') + _id >> make_graph_attr
-
-    #  nodes statements::
+        many(op_(',') + _id)
+        >> create_mapper(oneplus_to_list)
+    )
+    option_stmt = (
+        _id +
+        maybe(op_('=') + _id)
+        >> create_mapper(Attr)
+    )
+    option_list = (
+        maybe(op_('[') + option_stmt + many(op_(',') + option_stmt) + op_(']'))
+        >> create_mapper(oneplus_to_list, default_value=[])
+    )
+
+    #  node (node list) statement::
     #     A;
     #     B [attr = value, attr = value];
     #     C, D [attr = value, attr = value];
     #
-    multi_node_stmt = node_list + attr_list >> make_nodes
+    node_stmt = (
+        node_list + option_list
+        >> create_mapper(make_node_list)
+    )
 
-    #  edge statements::
+    #  edge statement::
     #     A -> B;
     #     A <- B;
     #
-    edge_rhs = (op('->') | op('--') | op('<-') | op('<->') |
-                op('>-') | op('-<') | op('>-<')) + node_list
+    edge_relation = (
+        op('->') | op('--') | op('<-') | op('<->') |
+        op('>-') | op('-<') | op('>-<')
+    )
     edge_stmt = (
         node_list +
-        edge_rhs +
-        many(edge_rhs) +
-        attr_list
-        >> unarg(make_edge))
+        edge_relation +
+        node_list +
+        many(edge_relation + node_list) +
+        option_list
+        >> create_mapper(make_edge)
+    )
 
-    #  class statements::
-    #     class red [color = red];
+    #  attributes statement::
+    #     default_shape = box;
+    #     default_fontsize = 16;
     #
-    class_stmt = (
-        skip(n('class')) +
-        node_id +
-        attr_list
-        >> unarg(AttrClass))
+    attribute_stmt = (
+        _id + op_('=') + _id
+        >> create_mapper(Attr)
+    )
 
-    #  plugin statements::
+    #  extension statement (class, plugin)::
+    #     class red [color = red];
     #     plugin attributes [name = Name];
     #
-    plugin_stmt = (
-        skip(n('plugin')) +
-        node_id +
-        attr_list
-        >> unarg(AttrPlugin))
+    extension_stmt = (
+        (keyword('class') | keyword('plugin')) +
+        _id +
+        option_list
+        >> create_mapper(Extension)
+    )
 
-    #  group statements::
+    #  group statement::
     #     group {
     #        A;
     #     }
     #
-    group = forward_decl()
-    stmt = (
-        edge_stmt
-        | class_stmt
-        | plugin_stmt
-        | group
-        | graph_attr
-        | multi_node_stmt
+    group_stmt = forward_decl()
+    group_inline_stmt = (
+        edge_stmt |
+        group_stmt |
+        attribute_stmt |
+        node_stmt
     )
-    stmt_list = many(stmt + skip(maybe(op(';'))))
-    group.define(
-        skip(n('group')) +
+    group_inline_stmt_list = (
+        many(group_inline_stmt + skip(maybe(op(';'))))
+    )
+    group_stmt.define(
+        skip(keyword('group')) +
         maybe(_id) +
         op_('{') +
-        stmt_list +
+        group_inline_stmt_list +
         op_('}')
-        >> unarg(SubGraph))
+        >> create_mapper(Group)
+    )
 
     #
-    # graph
+    # diagram statement::
+    #     blockdiag {
+    #        A;
+    #     }
     #
-    graph = (
-        maybe(n('diagram') | n('blockdiag')) +
+    diagram_inline_stmt = (
+        extension_stmt |
+        group_inline_stmt
+    )
+    diagram_inline_stmt_list = (
+        many(diagram_inline_stmt + skip(maybe(op(';'))))
+    )
+    diagram = (
+        maybe(keyword('diagram') | keyword('blockdiag')) +
         maybe(_id) +
         op_('{') +
-        stmt_list +
+        diagram_inline_stmt_list +
         op_('}')
-        >> unarg(Graph))
-    dotfile = graph + skip(finished)
+        >> create_mapper(Diagram)
+    )
+    dotfile = diagram + skip(finished)
 
     return dotfile.parse(seq)
 
 
 def sort_tree(tree):
     def weight(node):
-        if isinstance(node, (Attr, DefAttrs, AttrPlugin, AttrClass)):
+        if isinstance(node, (Attr, Extension)):
             return 1
         else:
             return 2
 
-    def compare(a, b):
-        return cmp(weight(a), weight(b))
-
     if hasattr(tree, 'stmts'):
-        tree.stmts.sort(compare)
+        tree.stmts.sort(key=lambda x: weight(x))
         for stmt in tree.stmts:
             sort_tree(stmt)
 
@@ -209,13 +248,13 @@ def parse_string(string):
     try:
         tree = parse(tokenize(string))
         return sort_tree(tree)
-    except LexerError, e:
+    except LexerError as e:
         message = "Got unexpected token at line %d column %d" % e.place
         raise ParseException(message)
-    except Exception, e:
+    except Exception as e:
         raise ParseException(str(e))
 
 
 def parse_file(path):
-    code = codecs.open(path, 'r', 'utf-8').read()
+    code = io.open(path, 'r', encoding='utf-8-sig').read()
     return parse_string(code)
diff --git a/src/blockdiag/tests/diagrams/branched.diag b/src/blockdiag/tests/diagrams/branched.diag
index 5a9aae7..e4d0908 100644
--- a/src/blockdiag/tests/diagrams/branched.diag
+++ b/src/blockdiag/tests/diagrams/branched.diag
@@ -1,5 +1,5 @@
-diagram {
-  A -> B -> C;
-  A -> D -> E;
-  Z
-}
+diagram {
+  A -> B -> C;
+  A -> D -> E;
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/circular_ref.diag b/src/blockdiag/tests/diagrams/circular_ref.diag
index a028996..6ab650a 100644
--- a/src/blockdiag/tests/diagrams/circular_ref.diag
+++ b/src/blockdiag/tests/diagrams/circular_ref.diag
@@ -1,5 +1,5 @@
-diagram {
-  A -> B -> C -> B
-       B -> D
-  Z
-}
+diagram {
+  A -> B -> C -> B
+       B -> D
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/circular_ref_to_root.diag b/src/blockdiag/tests/diagrams/circular_ref_to_root.diag
index bb7d0bb..2c3e6e2 100644
--- a/src/blockdiag/tests/diagrams/circular_ref_to_root.diag
+++ b/src/blockdiag/tests/diagrams/circular_ref_to_root.diag
@@ -1,5 +1,5 @@
-diagram {
-  A -> B -> C -> A
-       B -> D
-  Z
-}
+diagram {
+  A -> B -> C -> A
+       B -> D
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/circular_skipped_edge.diag b/src/blockdiag/tests/diagrams/circular_skipped_edge.diag
index 9afb986..511cba3 100644
--- a/src/blockdiag/tests/diagrams/circular_skipped_edge.diag
+++ b/src/blockdiag/tests/diagrams/circular_skipped_edge.diag
@@ -1,5 +1,5 @@
-diagram {
-  A -> B -> C -> A
-  A      -> C
-  Z
-}
+diagram {
+  A -> B -> C -> A
+  A      -> C
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/edge_attribute.diag b/src/blockdiag/tests/diagrams/edge_attribute.diag
index da131c3..0ecc4cf 100644
--- a/src/blockdiag/tests/diagrams/edge_attribute.diag
+++ b/src/blockdiag/tests/diagrams/edge_attribute.diag
@@ -1,5 +1,5 @@
-diagram {
-  A -> B -> C [color = red]
-  D -> E [dir = none]
-  F -> G [thick]
-}
+diagram {
+  A -> B -> C [color = red]
+  D -> E [dir = none]
+  F -> G [thick]
+}
diff --git a/src/blockdiag/tests/diagrams/edge_styles.diag b/src/blockdiag/tests/diagrams/edge_styles.diag
index 074c367..4779bdf 100644
--- a/src/blockdiag/tests/diagrams/edge_styles.diag
+++ b/src/blockdiag/tests/diagrams/edge_styles.diag
@@ -1,9 +1,9 @@
-diagram {
-  A -> B [style = "none"];
-  B -> C [style = "solid"];
-  C -> D [style = "dashed"];
-  D -> E [style = "dotted"];
-  E -> F [hstyle = "generalization"];
-  F -> H [hstyle = "composition"];
-  H -> I [hstyle = "aggregation"];
-}
+diagram {
+  A -> B [style = "none"];
+  B -> C [style = "solid"];
+  C -> D [style = "dashed"];
+  D -> E [style = "dotted"];
+  E -> F [hstyle = "generalization"];
+  F -> H [hstyle = "composition"];
+  H -> I [hstyle = "aggregation"];
+}
diff --git a/src/blockdiag/tests/diagrams/empty_group.diag b/src/blockdiag/tests/diagrams/empty_group.diag
index 35c4f88..a1e36d4 100644
--- a/src/blockdiag/tests/diagrams/empty_group.diag
+++ b/src/blockdiag/tests/diagrams/empty_group.diag
@@ -1,5 +1,5 @@
-diagram {
-  group {
-  }
-  Z
-}
+diagram {
+  group {
+  }
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/empty_nested_group.diag b/src/blockdiag/tests/diagrams/empty_nested_group.diag
index 63fa0f4..0872fe2 100644
--- a/src/blockdiag/tests/diagrams/empty_nested_group.diag
+++ b/src/blockdiag/tests/diagrams/empty_nested_group.diag
@@ -1,7 +1,7 @@
-diagram {
-  group {
-    group {
-    }
-  }
-  Z;
-}
+diagram {
+  group {
+    group {
+    }
+  }
+  Z;
+}
diff --git a/src/blockdiag/tests/diagrams/errors/belongs_to_two_groups.diag b/src/blockdiag/tests/diagrams/errors/belongs_to_two_groups.diag
index d2f9deb..2a8b71a 100644
--- a/src/blockdiag/tests/diagrams/errors/belongs_to_two_groups.diag
+++ b/src/blockdiag/tests/diagrams/errors/belongs_to_two_groups.diag
@@ -1,9 +1,9 @@
-diagram {
-  group {
-    A
-  }
-  group {
-    A
-  }
-  Z
-}
+diagram {
+  group {
+    A
+  }
+  group {
+    A
+  }
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/errors/group_follows_node.diag b/src/blockdiag/tests/diagrams/errors/group_follows_node.diag
index 0ba78f2..2255ab4 100644
--- a/src/blockdiag/tests/diagrams/errors/group_follows_node.diag
+++ b/src/blockdiag/tests/diagrams/errors/group_follows_node.diag
@@ -1,6 +1,6 @@
-diagram {
-  A -> group {
-    B
-  }
-  Z
-}
+diagram {
+  A -> group {
+    B
+  }
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/errors/node_follows_group.diag b/src/blockdiag/tests/diagrams/errors/node_follows_group.diag
index 0ba78f2..2255ab4 100644
--- a/src/blockdiag/tests/diagrams/errors/node_follows_group.diag
+++ b/src/blockdiag/tests/diagrams/errors/node_follows_group.diag
@@ -1,6 +1,6 @@
-diagram {
-  A -> group {
-    B
-  }
-  Z
-}
+diagram {
+  A -> group {
+    B
+  }
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/flowable_node.diag b/src/blockdiag/tests/diagrams/flowable_node.diag
index a44399c..0891a41 100644
--- a/src/blockdiag/tests/diagrams/flowable_node.diag
+++ b/src/blockdiag/tests/diagrams/flowable_node.diag
@@ -1,5 +1,5 @@
-diagram {
-  B -> C
-  A -> B
-  Z
-}
+diagram {
+  B -> C
+  A -> B
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/folded_edge.diag b/src/blockdiag/tests/diagrams/folded_edge.diag
index 58d4f35..4300950 100644
--- a/src/blockdiag/tests/diagrams/folded_edge.diag
+++ b/src/blockdiag/tests/diagrams/folded_edge.diag
@@ -1,6 +1,6 @@
-diagram {
-  A -> B -> C [nofolded]
-       B -> D -> E[folded]
-  D -> F
-  Z
-}
+diagram {
+  A -> B -> C [nofolded]
+       B -> D -> E[folded]
+  D -> F
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/group_id_and_node_id_are_not_conflicted.diag b/src/blockdiag/tests/diagrams/group_id_and_node_id_are_not_conflicted.diag
index fd7789f..eeb7470 100644
--- a/src/blockdiag/tests/diagrams/group_id_and_node_id_are_not_conflicted.diag
+++ b/src/blockdiag/tests/diagrams/group_id_and_node_id_are_not_conflicted.diag
@@ -1,7 +1,7 @@
-diagram {
-  A -> B
-  group B {
-    C -> D
-  }
-  Z
-}
+diagram {
+  A -> B
+  group B {
+    C -> D
+  }
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/group_works_node_decorator.diag b/src/blockdiag/tests/diagrams/group_works_node_decorator.diag
index 1c87a02..c824197 100644
--- a/src/blockdiag/tests/diagrams/group_works_node_decorator.diag
+++ b/src/blockdiag/tests/diagrams/group_works_node_decorator.diag
@@ -1,9 +1,9 @@
-diagram {
-  A -> B -> C
-  A -> B -> D
-  A -> E
-  group {
-    A; B; D; E
-  }
-  Z
-}
+diagram {
+  A -> B -> C
+  A -> B -> D
+  A -> E
+  group {
+    A; B; D; E
+  }
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/large_group_and_node.diag b/src/blockdiag/tests/diagrams/large_group_and_node.diag
index 909b08c..485cd3f 100644
--- a/src/blockdiag/tests/diagrams/large_group_and_node.diag
+++ b/src/blockdiag/tests/diagrams/large_group_and_node.diag
@@ -1,10 +1,10 @@
-diagram {
-  group {
-    A -> B
-    A -> C
-    A -> D
-    A -> E
-  }
-  B -> F
-  Z
-}
+diagram {
+  group {
+    A -> B
+    A -> C
+    A -> D
+    A -> E
+  }
+  B -> F
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/large_group_and_node2.diag b/src/blockdiag/tests/diagrams/large_group_and_node2.diag
index fcf1aaa..8e14ec7 100644
--- a/src/blockdiag/tests/diagrams/large_group_and_node2.diag
+++ b/src/blockdiag/tests/diagrams/large_group_and_node2.diag
@@ -1,7 +1,7 @@
-diagram {
-  group {
-    A -> B -> C
-  }
-  C -> D -> E
-  Z
-}
+diagram {
+  group {
+    A -> B -> C
+  }
+  C -> D -> E
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/large_group_and_two_nodes.diag b/src/blockdiag/tests/diagrams/large_group_and_two_nodes.diag
index f4837a4..b5895c1 100644
--- a/src/blockdiag/tests/diagrams/large_group_and_two_nodes.diag
+++ b/src/blockdiag/tests/diagrams/large_group_and_two_nodes.diag
@@ -1,11 +1,11 @@
-diagram {
-  group {
-    A -> B
-    A -> C
-    A -> D
-    A -> E
-  }
-  B -> F
-  C -> G
-  Z
-}
+diagram {
+  group {
+    A -> B
+    A -> C
+    A -> D
+    A -> E
+  }
+  B -> F
+  C -> G
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/multiple_groups.diag b/src/blockdiag/tests/diagrams/multiple_groups.diag
index 17a1b77..b1b4881 100644
--- a/src/blockdiag/tests/diagrams/multiple_groups.diag
+++ b/src/blockdiag/tests/diagrams/multiple_groups.diag
@@ -1,16 +1,16 @@
-diagram {
-  group {
-    A;  B;  C;  D
-  }
-  group {
-    E;  F;  G
-  }
-  group {
-    H;  I
-  }
-  group {
-    J
-  }
-  A -> E -> H -> J
-  Z
-}
+diagram {
+  group {
+    A;  B;  C;  D
+  }
+  group {
+    E;  F;  G
+  }
+  group {
+    H;  I
+  }
+  group {
+    J
+  }
+  A -> E -> H -> J
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/multiple_nested_groups.diag b/src/blockdiag/tests/diagrams/multiple_nested_groups.diag
index da88c1f..65272b8 100644
--- a/src/blockdiag/tests/diagrams/multiple_nested_groups.diag
+++ b/src/blockdiag/tests/diagrams/multiple_nested_groups.diag
@@ -1,14 +1,14 @@
-diagram {
-  group {
-    A -> B;
-    A -> C;
-
-    group {
-      B
-    }
-    group {
-      C
-    }
-  }
-  Z
-}
+diagram {
+  group {
+    A -> B;
+    A -> C;
+
+    group {
+      B
+    }
+    group {
+      C
+    }
+  }
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/nested_groups.diag b/src/blockdiag/tests/diagrams/nested_groups.diag
index 09b46f0..f60042b 100644
--- a/src/blockdiag/tests/diagrams/nested_groups.diag
+++ b/src/blockdiag/tests/diagrams/nested_groups.diag
@@ -1,9 +1,9 @@
-diagram {
-  group {
-    A;
-    group {
-      B;
-    }
-  }
-  Z;
-}
+diagram {
+  group {
+    A;
+    group {
+      B;
+    }
+  }
+  Z;
+}
diff --git a/src/blockdiag/tests/diagrams/nested_groups_and_edges.diag b/src/blockdiag/tests/diagrams/nested_groups_and_edges.diag
index 3de67a5..34f7762 100644
--- a/src/blockdiag/tests/diagrams/nested_groups_and_edges.diag
+++ b/src/blockdiag/tests/diagrams/nested_groups_and_edges.diag
@@ -1,11 +1,11 @@
-diagram {
-  A -> B -> C;
-
-  group {
-    B;
-    group {
-      C;
-    }
-  }
-  Z;
-}
+diagram {
+  A -> B -> C;
+
+  group {
+    B;
+    group {
+      C;
+    }
+  }
+  Z;
+}
diff --git a/src/blockdiag/tests/diagrams/nested_groups_work_node_decorator.diag b/src/blockdiag/tests/diagrams/nested_groups_work_node_decorator.diag
index f566be0..0677cf9 100644
--- a/src/blockdiag/tests/diagrams/nested_groups_work_node_decorator.diag
+++ b/src/blockdiag/tests/diagrams/nested_groups_work_node_decorator.diag
@@ -1,11 +1,11 @@
-diagram {
-  A; B;
-
-  group {
-    A
-    group {
-      B
-    }
-  }
-  Z
-}
+diagram {
+  A; B;
+
+  group {
+    A
+    group {
+      B
+    }
+  }
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/nested_skipped_circular.diag b/src/blockdiag/tests/diagrams/nested_skipped_circular.diag
index 6bad6ee..d049966 100644
--- a/src/blockdiag/tests/diagrams/nested_skipped_circular.diag
+++ b/src/blockdiag/tests/diagrams/nested_skipped_circular.diag
@@ -1,7 +1,7 @@
-diagram {
-  A -> B                -> F -> G
-       B -> C      -> E -> F
-            C -> D -> E
-  F -> A
-  Z
-}
+diagram {
+  A -> B                -> F -> G
+       B -> C      -> E -> F
+            C -> D -> E
+  F -> A
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/node_attribute.diag b/src/blockdiag/tests/diagrams/node_attribute.diag
index ee9af22..f31d784 100644
--- a/src/blockdiag/tests/diagrams/node_attribute.diag
+++ b/src/blockdiag/tests/diagrams/node_attribute.diag
@@ -1,12 +1,12 @@
-diagram {
-  A [label="B", color="red"];
-  B [label="double quoted"];
-  C [label='single quoted', color = 'red'];
-  D [label="'\"double\" quoted'", color = 'red'];
-  E [label='"\'single\' quoted"', color = 'red', numbered = "1"];
-  F [textcolor = red];
-  G [stacked];
-  H [fontsize = 16];
-  I [linecolor = red];
-  J [label="Hello", label_orientation=vertical];
-}
+diagram {
+  A [label="B", color="red"];
+  B [label="double quoted"];
+  C [label='single quoted', color = 'red'];
+  D [label="'\"double\" quoted'", color = 'red'];
+  E [label='"\'single\' quoted"', color = 'red', numbered = "1"];
+  F [textcolor = red];
+  G [stacked];
+  H [fontsize = 16];
+  I [linecolor = red];
+  J [label="Hello", label_orientation=vertical];
+}
diff --git a/src/blockdiag/tests/diagrams/node_in_group_follows_outer_node.diag b/src/blockdiag/tests/diagrams/node_in_group_follows_outer_node.diag
index 7b364f1..223551b 100644
--- a/src/blockdiag/tests/diagrams/node_in_group_follows_outer_node.diag
+++ b/src/blockdiag/tests/diagrams/node_in_group_follows_outer_node.diag
@@ -1,7 +1,7 @@
-diagram {
-  group {
-    A -> B
-  }
-  B -> C
-  Z
-}
+diagram {
+  group {
+    A -> B
+  }
+  B -> C
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/node_styles.diag b/src/blockdiag/tests/diagrams/node_styles.diag
index e94e61b..e3892ae 100644
--- a/src/blockdiag/tests/diagrams/node_styles.diag
+++ b/src/blockdiag/tests/diagrams/node_styles.diag
@@ -1,5 +1,5 @@
-diagram {
-  A [shape = "roundedbox", style = "dotted"];
-  B [shape = "ellipse", style = "dashed"];
-  C [shape = "flowchart.database", style = "dashed"];
-}
+diagram {
+  A [shape = "roundedbox", style = "dotted"];
+  B [shape = "ellipse", style = "dashed"];
+  C [shape = "flowchart.database", style = "dashed"];
+}
diff --git a/src/blockdiag/tests/diagrams/outer_node_follows_node_in_group.diag b/src/blockdiag/tests/diagrams/outer_node_follows_node_in_group.diag
index 5fa91fd..db32858 100644
--- a/src/blockdiag/tests/diagrams/outer_node_follows_node_in_group.diag
+++ b/src/blockdiag/tests/diagrams/outer_node_follows_node_in_group.diag
@@ -1,7 +1,7 @@
-diagram {
-  group {
-    B -> C
-  }
-  A -> B
-  Z
-}
+diagram {
+  group {
+    B -> C
+  }
+  A -> B
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/reverse_multiple_groups.diag b/src/blockdiag/tests/diagrams/reverse_multiple_groups.diag
index 8792260..12103ee 100644
--- a/src/blockdiag/tests/diagrams/reverse_multiple_groups.diag
+++ b/src/blockdiag/tests/diagrams/reverse_multiple_groups.diag
@@ -1,16 +1,16 @@
-diagram {
-  group {
-    A;  B;  C;  D
-  }
-  group {
-    E;  F;  G
-  }
-  group {
-    H;  I
-  }
-  group {
-    J
-  }
-  J -> H -> E -> A
-  Z
-}
+diagram {
+  group {
+    A;  B;  C;  D
+  }
+  group {
+    E;  F;  G
+  }
+  group {
+    H;  I
+  }
+  group {
+    J
+  }
+  J -> H -> E -> A
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/self_ref.diag b/src/blockdiag/tests/diagrams/self_ref.diag
index d4d953a..e4c1f38 100644
--- a/src/blockdiag/tests/diagrams/self_ref.diag
+++ b/src/blockdiag/tests/diagrams/self_ref.diag
@@ -1,4 +1,4 @@
-diagram {
-  A -> B -> B
-  Z
-}
+diagram {
+  A -> B -> B
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/simple_group.diag b/src/blockdiag/tests/diagrams/simple_group.diag
index 68bf4c2..efd57a5 100644
--- a/src/blockdiag/tests/diagrams/simple_group.diag
+++ b/src/blockdiag/tests/diagrams/simple_group.diag
@@ -1,7 +1,7 @@
-diagram {
-  group {
-    A -> B
-    A -> C
-  }
-  Z
-}
+diagram {
+  group {
+    A -> B
+    A -> C
+  }
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/single_edge.diag b/src/blockdiag/tests/diagrams/single_edge.diag
index f36edf5..b08ad62 100644
--- a/src/blockdiag/tests/diagrams/single_edge.diag
+++ b/src/blockdiag/tests/diagrams/single_edge.diag
@@ -1,3 +1,3 @@
-diagram {
-  A -> B;
-}
+diagram {
+  A -> B;
+}
diff --git a/src/blockdiag/tests/diagrams/single_node.diag b/src/blockdiag/tests/diagrams/single_node.diag
index 203f740..8621ae7 100644
--- a/src/blockdiag/tests/diagrams/single_node.diag
+++ b/src/blockdiag/tests/diagrams/single_node.diag
@@ -1,3 +1,3 @@
-diagram {
-  A;
-}
+diagram {
+  A;
+}
diff --git a/src/blockdiag/tests/diagrams/skipped_circular.diag b/src/blockdiag/tests/diagrams/skipped_circular.diag
index fd7ce2b..cd212bf 100644
--- a/src/blockdiag/tests/diagrams/skipped_circular.diag
+++ b/src/blockdiag/tests/diagrams/skipped_circular.diag
@@ -1,6 +1,6 @@
-diagram {
-  A      -> C
-  A -> B -> C
-  C -> A
-  Z
-}
+diagram {
+  A      -> C
+  A -> B -> C
+  C -> A
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/skipped_edge.diag b/src/blockdiag/tests/diagrams/skipped_edge.diag
index 5a99655..fd156b1 100644
--- a/src/blockdiag/tests/diagrams/skipped_edge.diag
+++ b/src/blockdiag/tests/diagrams/skipped_edge.diag
@@ -1,5 +1,5 @@
-diagram {
-  A -> B -> C
-  A      -> C
-  Z
-}
+diagram {
+  A -> B -> C
+  A      -> C
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/triple_branched.diag b/src/blockdiag/tests/diagrams/triple_branched.diag
index 8155ac9..fe5aa2a 100644
--- a/src/blockdiag/tests/diagrams/triple_branched.diag
+++ b/src/blockdiag/tests/diagrams/triple_branched.diag
@@ -1,6 +1,6 @@
-diagram {
-  A -> D
-  B -> D
-  C -> D
-  Z
-}
+diagram {
+  A -> D
+  B -> D
+  C -> D
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/twin_circular_ref_to_root.diag b/src/blockdiag/tests/diagrams/twin_circular_ref_to_root.diag
index 31495dd..4ec6226 100644
--- a/src/blockdiag/tests/diagrams/twin_circular_ref_to_root.diag
+++ b/src/blockdiag/tests/diagrams/twin_circular_ref_to_root.diag
@@ -1,5 +1,5 @@
-diagram {
-  A -> B -> A
-  A -> C -> A
-  Z
-}
+diagram {
+  A -> B -> A
+  A -> C -> A
+  Z
+}
diff --git a/src/blockdiag/tests/diagrams/two_edges.diag b/src/blockdiag/tests/diagrams/two_edges.diag
index 6683b17..cbc7704 100644
--- a/src/blockdiag/tests/diagrams/two_edges.diag
+++ b/src/blockdiag/tests/diagrams/two_edges.diag
@@ -1,3 +1,3 @@
-diagram {
-  A -> B -> C;
-}
+diagram {
+  A -> B -> C;
+}
diff --git a/src/blockdiag/tests/diagrams/white.gif b/src/blockdiag/tests/diagrams/white.gif
new file mode 100644
index 0000000..c761db5
Binary files /dev/null and b/src/blockdiag/tests/diagrams/white.gif differ
diff --git a/src/blockdiag/tests/test_boot_params.py b/src/blockdiag/tests/test_boot_params.py
index e0c2c2b..bf850f3 100644
--- a/src/blockdiag/tests/test_boot_params.py
+++ b/src/blockdiag/tests/test_boot_params.py
@@ -1,253 +1,186 @@
 # -*- coding: utf-8 -*-
 
-import os
 import sys
+if sys.version_info < (2, 7):
+    import unittest2 as unittest
+else:
+    import unittest
+
+import os
+import io
 import tempfile
-import unittest2
-from blockdiag.tests.utils import argv_wrapper, assertRaises, with_pdf
+from blockdiag.tests.utils import with_pdf
 
 import blockdiag
 from blockdiag.command import BlockdiagOptions
 from blockdiag.utils.bootstrap import detectfont
+from blockdiag.utils.compat import u
 
 
-class TestBootParams(unittest2.TestCase):
+class TestBootParams(unittest.TestCase):
     def setUp(self):
         self.parser = BlockdiagOptions(blockdiag)
 
-    @argv_wrapper
     def test_type_option_svg(self):
-        sys.argv = ['', '-Tsvg', 'input.diag']
-        options = self.parser.parse()
+        options = self.parser.parse(['-Tsvg', 'input.diag'])
         self.assertEqual(options.output, 'input.svg')
 
-        sys.argv = ['', '-TSVG', 'input.diag']
-        options = self.parser.parse()
+        options = self.parser.parse(['-TSVG', 'input.diag'])
         self.assertEqual(options.output, 'input.svg')
 
-        sys.argv = ['', '-TSvg', 'input.diag']
-        options = self.parser.parse()
+        options = self.parser.parse(['-TSvg', 'input.diag'])
         self.assertEqual(options.output, 'input.svg')
 
-        sys.argv = ['', '-TSvg', 'input.test.diag']
-        options = self.parser.parse()
+        options = self.parser.parse(['-TSvg', 'input.test.diag'])
         self.assertEqual(options.output, 'input.test.svg')
 
-    @argv_wrapper
     def test_type_option_png(self):
-        sys.argv = ['', '-Tpng', 'input.diag']
-        options = self.parser.parse()
+        options = self.parser.parse(['-Tpng', 'input.diag'])
         self.assertEqual(options.output, 'input.png')
 
     @with_pdf
-    @argv_wrapper
     def test_type_option_pdf(self):
-        sys.argv = ['', '-Tpdf', 'input.diag']
-        options = self.parser.parse()
+        options = self.parser.parse(['-Tpdf', 'input.diag'])
         self.assertEqual(options.output, 'input.pdf')
 
-    @assertRaises(RuntimeError)
-    @argv_wrapper
     def test_invalid_type_option(self):
-        sys.argv = ['', '-Tsvgz', 'input.diag']
-        self.parser.parse()
+        with self.assertRaises(RuntimeError):
+            self.parser.parse(['-Tsvgz', 'input.diag'])
 
-    @argv_wrapper
     def test_separate_option_svg(self):
-        sys.argv = ['', '-Tsvg', '--separate', 'input.diag']
-        self.parser.parse()
+        self.parser.parse(['-Tsvg', '--separate', 'input.diag'])
 
-    @argv_wrapper
     def test_separate_option_png(self):
-        sys.argv = ['', '-Tpng', '--separate', 'input.diag']
-        self.parser.parse()
+        self.parser.parse(['-Tpng', '--separate', 'input.diag'])
 
     @with_pdf
-    @argv_wrapper
     def test_separate_option_pdf(self):
-        sys.argv = ['', '-Tpdf', '--separate', 'input.diag']
-        self.parser.parse()
-
-    @argv_wrapper
-    def test_svg_ignore_pil_option(self):
-        sys.argv = ['', '-Tsvg', '--ignore-pil', 'input.diag']
-        self.parser.parse()
-
-    @assertRaises(RuntimeError)
-    @argv_wrapper
-    def test_png_ignore_pil_option(self):
-        sys.argv = ['', '-Tpng', '--ignore-pil', 'input.diag']
-        self.parser.parse()
-
-    @assertRaises(RuntimeError)
-    @argv_wrapper
-    def test_pdf_ignore_pil_option(self):
-        sys.argv = ['', '-Tpdf', '--ignore-pil', 'input.diag']
-        self.parser.parse()
-
-    @argv_wrapper
+        self.parser.parse(['-Tpdf', '--separate', 'input.diag'])
+
     def test_svg_nodoctype_option(self):
-        sys.argv = ['', '-Tsvg', '--nodoctype', 'input.diag']
-        self.parser.parse()
+        self.parser.parse(['-Tsvg', '--nodoctype', 'input.diag'])
 
-    @assertRaises(RuntimeError)
-    @argv_wrapper
     def test_png_nodoctype_option(self):
-        sys.argv = ['', '-Tpng', '--nodoctype', 'input.diag']
-        self.parser.parse()
+        with self.assertRaises(RuntimeError):
+            self.parser.parse(['-Tpng', '--nodoctype', 'input.diag'])
 
-    @assertRaises(RuntimeError)
-    @argv_wrapper
     def test_pdf_nodoctype_option(self):
-        sys.argv = ['', '-Tpdf', '--nodoctype', 'input.diag']
-        self.parser.parse()
+        with self.assertRaises(RuntimeError):
+            self.parser.parse(['-Tpdf', '--nodoctype', 'input.diag'])
 
-    @assertRaises(RuntimeError)
-    @argv_wrapper
     def test_svg_notransparency_option(self):
-        sys.argv = ['', '-Tsvg', '--no-transparency', 'input.diag']
-        self.parser.parse()
+        with self.assertRaises(RuntimeError):
+            self.parser.parse(['-Tsvg', '--no-transparency', 'input.diag'])
 
-    @argv_wrapper
     def test_png_notransparency_option(self):
-        sys.argv = ['', '-Tpng', '--no-transparency', 'input.diag']
-        self.parser.parse()
+        self.parser.parse(['-Tpng', '--no-transparency', 'input.diag'])
 
-    @assertRaises(RuntimeError)
-    @argv_wrapper
     def test_pdf_notransparency_option(self):
-        sys.argv = ['', '-Tpdf', '--no-transparency', 'input.diag']
-        self.parser.parse()
+        with self.assertRaises(RuntimeError):
+            self.parser.parse(['-Tpdf', '--no-transparency', 'input.diag'])
 
-    @argv_wrapper
     def test_config_option(self):
         try:
             tmp = tempfile.mkstemp()
-            sys.argv = ['', '-c', tmp[1], 'input.diag']
-            self.parser.parse()
+            self.parser.parse(['-c', tmp[1], 'input.diag'])
         finally:
             os.close(tmp[0])
             os.unlink(tmp[1])
 
-    @argv_wrapper
     def test_config_option_with_bom(self):
         try:
             tmp = tempfile.mkstemp()
-            fp = os.fdopen(tmp[0], 'wt')
-            fp.write("\xEF\xBB\xBF[blockdiag]\n")
+            fp = io.open(tmp[0], 'wt', encoding='utf-8-sig')
+            fp.write(u("[blockdiag]\n"))
             fp.close()
 
-            sys.argv = ['', '-c', tmp[1], 'input.diag']
-            self.parser.parse()
+            self.parser.parse(['-c', tmp[1], 'input.diag'])
         finally:
             os.unlink(tmp[1])
 
-    @assertRaises(RuntimeError)
-    @argv_wrapper
     def test_invalid_config_option(self):
-        sys.argv = ['', '-c', '/unknown_config_file', 'input.diag']
-        self.parser.parse()
+        with self.assertRaises(RuntimeError):
+            self.parser.parse(['-c', '/unknown_config_file', 'input.diag'])
 
-    @assertRaises(RuntimeError)
-    @argv_wrapper
     def test_invalid_dir_config_option(self):
         try:
             tmp = tempfile.mkdtemp()
 
-            sys.argv = ['', '-c', tmp, 'input.diag']
-            self.parser.parse()
+            with self.assertRaises(RuntimeError):
+                self.parser.parse(['-c', tmp, 'input.diag'])
         finally:
             os.rmdir(tmp)
 
-    @argv_wrapper
     def test_config_option_fontpath(self):
         try:
             tmp = tempfile.mkstemp()
-            config = '[blockdiag]\nfontpath = /path/to/font\n'
-            os.fdopen(tmp[0], 'wt').write(config)
+            config = u("[blockdiag]\nfontpath = /path/to/font\n")
+            io.open(tmp[0], 'wt').write(config)
 
-            sys.argv = ['', '-c', tmp[1], 'input.diag']
-            options = self.parser.parse()
+            options = self.parser.parse(['-c', tmp[1], 'input.diag'])
             self.assertEqual(options.font, ['/path/to/font'])
         finally:
             os.unlink(tmp[1])
 
-    @argv_wrapper
     def test_exist_font_config_option(self):
         try:
             tmp = tempfile.mkstemp()
 
-            sys.argv = ['', '-f', tmp[1], 'input.diag']
-            options = self.parser.parse()
+            options = self.parser.parse(['-f', tmp[1], 'input.diag'])
             self.assertEqual(options.font, [tmp[1]])
             fontpath = detectfont(options)
             self.assertEqual(fontpath, tmp[1])
         finally:
             os.unlink(tmp[1])
 
-    @assertRaises(RuntimeError)
-    @argv_wrapper
     def test_not_exist_font_config_option(self):
-        sys.argv = ['', '-f', '/font_is_not_exist', 'input.diag']
-        options = self.parser.parse()
-        detectfont(options)
+        with self.assertRaises(RuntimeError):
+            args = ['-f', '/font_is_not_exist', 'input.diag']
+            options = self.parser.parse(args)
+            detectfont(options)
 
-    @assertRaises(RuntimeError)
-    @argv_wrapper
     def test_not_exist_font_config_option2(self):
-        sys.argv = ['', '-f', '/font_is_not_exist',
+        with self.assertRaises(RuntimeError):
+            args = ['-f', '/font_is_not_exist',
                     '-f', '/font_is_not_exist2', 'input.diag']
-        options = self.parser.parse()
-        detectfont(options)
+            options = self.parser.parse(args)
+            detectfont(options)
 
-    @argv_wrapper
     def test_no_size_option(self):
-        sys.argv = ['', 'input.diag']
-        options = self.parser.parse()
+        options = self.parser.parse(['input.diag'])
         self.assertEqual(None, options.size)
 
-    @argv_wrapper
     def test_size_option(self):
-        sys.argv = ['', '--size', '480x360', 'input.diag']
-        options = self.parser.parse()
+        options = self.parser.parse(['--size', '480x360', 'input.diag'])
         self.assertEqual([480, 360], options.size)
 
-    @assertRaises(RuntimeError)
-    @argv_wrapper
     def test_invalid_size_option1(self):
-        sys.argv = ['', '--size', '480-360', 'input.diag']
-        self.parser.parse()
+        with self.assertRaises(RuntimeError):
+            self.parser.parse(['--size', '480-360', 'input.diag'])
 
-    @assertRaises(RuntimeError)
-    @argv_wrapper
     def test_invalid_size_option2(self):
-        sys.argv = ['', '--size', '480', 'input.diag']
-        self.parser.parse()
+        with self.assertRaises(RuntimeError):
+            self.parser.parse(['--size', '480', 'input.diag'])
 
-    @assertRaises(RuntimeError)
-    @argv_wrapper
     def test_invalid_size_option3(self):
-        sys.argv = ['', '--size', 'foobar', 'input.diag']
-        self.parser.parse()
+        with self.assertRaises(RuntimeError):
+            self.parser.parse(['--size', 'foobar', 'input.diag'])
 
-    @argv_wrapper
     def test_auto_font_detection(self):
-        sys.argv = ['', 'input.diag']
-        options = self.parser.parse()
+        options = self.parser.parse(['input.diag'])
         fontpath = detectfont(options)
         self.assertTrue(fontpath)
 
-    @assertRaises(RuntimeError)
-    @argv_wrapper
     def test_not_exist_fontmap_config(self):
-        sys.argv = ['', '--fontmap', '/fontmap_is_not_exist', 'input.diag']
-        options = self.parser.parse()
-        fontpath = detectfont(options)
-        self.assertTrue(fontpath)
+        with self.assertRaises(RuntimeError):
+            args = ['--fontmap', '/fontmap_is_not_exist', 'input.diag']
+            options = self.parser.parse(args)
+            fontpath = detectfont(options)
+            self.assertTrue(fontpath)
 
-    @assertRaises(RuntimeError)
     def test_unknown_image_driver(self):
         from blockdiag.drawer import DiagramDraw
         from blockdiag.elements import Diagram
 
-        DiagramDraw('unknown', Diagram())
+        with self.assertRaises(RuntimeError):
+            DiagramDraw('unknown', Diagram())
diff --git a/src/blockdiag/tests/test_builder.py b/src/blockdiag/tests/test_builder.py
index cefe4bf..60f22f8 100644
--- a/src/blockdiag/tests/test_builder.py
+++ b/src/blockdiag/tests/test_builder.py
@@ -1,164 +1,157 @@
 # -*- coding: utf-8 -*-
 
-from nose.tools import eq_
-from blockdiag.tests.utils import __build_diagram, __validate_node_attributes
-
-
-def test_diagram_attributes():
-    diagram = __build_diagram('diagram_attributes.diag')
-
-    eq_(160, diagram.node_width)
-    eq_(160, diagram.node_height)
-    eq_(32, diagram.span_width)
-    eq_(32, diagram.span_height)
-    eq_((128, 128, 128), diagram.linecolor)       # gray
-    eq_('diamond', diagram.nodes[0].shape)
-    eq_((255, 0, 0), diagram.nodes[0].color)      # red
-    eq_((0, 128, 0), diagram.nodes[0].textcolor)  # green
-    eq_(16, diagram.nodes[0].fontsize)
-    eq_((0, 0, 255), diagram.nodes[1].color)      # blue
-    eq_((0, 128, 0), diagram.nodes[1].textcolor)  # green
-    eq_(16, diagram.nodes[1].fontsize)
-
-    eq_((128, 128, 128), diagram.edges[0].color)  # gray
-    eq_((0, 128, 0), diagram.edges[0].textcolor)  # green
-    eq_(16, diagram.edges[0].fontsize)
-
-
-def test_diagram_attributes_order_diagram():
-    colors = {'A': (255, 0, 0), 'B': (255, 0, 0)}
-    linecolors = {'A': (255, 0, 0), 'B': (255, 0, 0)}
-    __validate_node_attributes('diagram_attributes_order.diag',
-                               color=colors, linecolor=linecolors)
-
-
-def test_circular_ref_to_root_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0),
-                 'D': (2, 1), 'Z': (0, 2)}
-    __validate_node_attributes('circular_ref_to_root.diag', xy=positions)
-
-
-def test_circular_ref_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0),
-                 'D': (2, 1), 'Z': (0, 2)}
-    __validate_node_attributes('circular_ref.diag', xy=positions)
-
-
-def test_circular_ref2_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (3, 0),
-                 'E': (3, 1), 'F': (4, 0), 'Z': (0, 2)}
-    __validate_node_attributes('circular_ref2.diag', xy=positions)
-
-
-def test_circular_ref_and_parent_node_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (1, 1),
-                 'D': (2, 1), 'Z': (0, 2)}
-    __validate_node_attributes('circular_ref_and_parent_node.diag',
-                               xy=positions)
-
-
-def test_labeled_circular_ref_diagram():
-    positions = {'A': (0, 0), 'B': (2, 0), 'C': (1, 0),
-                 'Z': (0, 1)}
-    __validate_node_attributes('labeled_circular_ref.diag', xy=positions)
-
-
-def test_twin_forked_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (1, 2), 'D': (2, 0),
-                 'E': (3, 0), 'F': (3, 1), 'G': (4, 1), 'Z': (0, 3)}
-    __validate_node_attributes('twin_forked.diag', xy=positions)
-
-
-def test_skipped_edge_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}
-    __validate_node_attributes('skipped_edge.diag', xy=positions)
-
-
-def test_circular_skipped_edge_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}
-    __validate_node_attributes('circular_skipped_edge.diag', xy=positions)
-
-
-def test_triple_branched_diagram():
-    positions = {'A': (0, 0), 'B': (0, 1), 'C': (0, 2),
-                 'D': (1, 0), 'Z': (0, 3)}
-    __validate_node_attributes('triple_branched.diag', xy=positions)
-
-
-def test_twin_circular_ref_to_root_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'Z': (0, 2)}
-    __validate_node_attributes('twin_circular_ref_to_root.diag', xy=positions)
-
-
-def test_twin_circular_ref_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0),
-                 'D': (1, 1), 'Z': (0, 2)}
-    __validate_node_attributes('twin_circular_ref.diag', xy=positions)
-
-
-def test_skipped_circular_diagram():
-    positions = {'A': (0, 0), 'B': (1, 1), 'C': (2, 0),
-                 'Z': (0, 2)}
-    __validate_node_attributes('skipped_circular.diag', xy=positions)
-
-
-def test_skipped_twin_circular_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 1),
-                 'D': (2, 2), 'E': (3, 0), 'Z': (0, 3)}
-    __validate_node_attributes('skipped_twin_circular.diag', xy=positions)
-
-
-def test_nested_skipped_circular_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 1),
-                 'D': (3, 2), 'E': (4, 1), 'F': (5, 0),
-                 'G': (6, 0), 'Z': (0, 3)}
-    __validate_node_attributes('nested_skipped_circular.diag', xy=positions)
-
-
-def test_self_ref_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'Z': (0, 1)}
-    __validate_node_attributes('self_ref.diag', xy=positions)
-
-
-def test_diagram_orientation_diagram():
-    positions = {'A': (0, 0), 'B': (0, 1), 'C': (0, 2),
-                 'D': (1, 2), 'Z': (2, 0)}
-    __validate_node_attributes('diagram_orientation.diag', xy=positions)
-
-
-def test_nested_group_orientation2_diagram():
-    positions = {'A': (0, 0), 'B': (0, 1), 'C': (0, 2), 'D': (1, 2),
-                 'E': (2, 2), 'F': (2, 3), 'Z': (3, 0)}
-    __validate_node_attributes('nested_group_orientation2.diag', xy=positions)
-
-
-def test_slided_children_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (1, 3),
-                 'E': (2, 3), 'F': (3, 2), 'G': (2, 1), 'H': (4, 1)}
-    __validate_node_attributes('slided_children.diag', xy=positions)
-
-
-def test_rhombus_relation_height_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (2, 0),
-                 'E': (3, 0), 'F': (3, 1), 'Z': (0, 2)}
-    __validate_node_attributes('rhombus_relation_height.diag', xy=positions)
-
-
-def test_non_rhombus_relation_height_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (0, 1),
-                 'E': (0, 2), 'F': (1, 2), 'G': (1, 3), 'H': (2, 3),
-                 'I': (2, 4), 'J': (1, 5), 'K': (2, 5), 'Z': (0, 6)}
-    __validate_node_attributes('non_rhombus_relation_height.diag',
-                               xy=positions)
-
-
-def test_define_class_diagram():
-    colors = {'A': (255, 0, 0), 'B': (255, 255, 255), 'C': (255, 255, 255)}
-    styles = {'A': 'dashed', 'B': None, 'C': None}
-
-    edge_colors = {('A', 'B'): (255, 0, 0), ('B', 'C'): (0, 0, 0)}
-    edge_styles = {('A', 'B'): 'dashed', ('B', 'C'): None}
-
-    __validate_node_attributes('define_class.diag',
-                               color=colors, edge_color=edge_colors,
-                               style=styles, edge_style=edge_styles)
+from blockdiag.tests.utils import BuilderTestCase
+
+
+class TestBuilder(BuilderTestCase):
+    def test_diagram_attributes(self):
+        diagram = self.build('diagram_attributes.diag')
+
+        self.assertEqual(160, diagram.node_width)
+        self.assertEqual(160, diagram.node_height)
+        self.assertEqual(32, diagram.span_width)
+        self.assertEqual(32, diagram.span_height)
+        self.assertEqual((128, 128, 128), diagram.linecolor)       # gray
+        self.assertEqual('diamond', diagram.nodes[0].shape)
+        self.assertEqual((255, 0, 0), diagram.nodes[0].color)      # red
+        self.assertEqual((0, 128, 0), diagram.nodes[0].textcolor)  # green
+        self.assertEqual(16, diagram.nodes[0].fontsize)
+        self.assertEqual((0, 0, 255), diagram.nodes[1].color)      # blue
+        self.assertEqual((0, 128, 0), diagram.nodes[1].textcolor)  # green
+        self.assertEqual(16, diagram.nodes[1].fontsize)
+
+        self.assertEqual((128, 128, 128), diagram.edges[0].color)  # gray
+        self.assertEqual((0, 128, 0), diagram.edges[0].textcolor)  # green
+        self.assertEqual(16, diagram.edges[0].fontsize)
+
+    def test_diagram_attributes_order_diagram(self):
+        diagram = self.build('diagram_attributes_order.diag')
+        self.assertNodeColor(diagram, {'A': (255, 0, 0), 'B': (255, 0, 0)})
+        self.assertNodeLineColor(diagram, {'A': (255, 0, 0), 'B': (255, 0, 0)})
+
+    def test_circular_ref_to_root_diagram(self):
+        diagram = self.build('circular_ref_to_root.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'D': (2, 1),
+                                    'Z': (0, 2)})
+
+    def test_circular_ref_diagram(self):
+        diagram = self.build('circular_ref.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'D': (2, 1),
+                                    'Z': (0, 2)})
+
+    def test_circular_ref2_diagram(self):
+        diagram = self.build('circular_ref2.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'D': (3, 0),
+                                    'E': (3, 1), 'F': (4, 0),
+                                    'Z': (0, 2)})
+
+    def test_circular_ref_and_parent_node_diagram(self):
+        diagram = self.build('circular_ref_and_parent_node.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (1, 1), 'D': (2, 1),
+                                    'Z': (0, 2)})
+
+    def test_labeled_circular_ref_diagram(self):
+        diagram = self.build('labeled_circular_ref.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (2, 0),
+                                    'C': (1, 0), 'Z': (0, 1)})
+
+    def test_twin_forked_diagram(self):
+        diagram = self.build('twin_forked.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (1, 2), 'D': (2, 0),
+                                    'E': (3, 0), 'F': (3, 1),
+                                    'G': (4, 1), 'Z': (0, 3)})
+
+    def test_skipped_edge_diagram(self):
+        diagram = self.build('skipped_edge.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'Z': (0, 1)})
+
+    def test_circular_skipped_edge_diagram(self):
+        diagram = self.build('circular_skipped_edge.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'Z': (0, 1)})
+
+    def test_triple_branched_diagram(self):
+        diagram = self.build('triple_branched.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1),
+                                    'C': (0, 2), 'D': (1, 0),
+                                    'Z': (0, 3)})
+
+    def test_twin_circular_ref_to_root_diagram(self):
+        diagram = self.build('twin_circular_ref_to_root.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (1, 1), 'Z': (0, 2)})
+
+    def test_twin_circular_ref_diagram(self):
+        diagram = self.build('twin_circular_ref.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'D': (1, 1),
+                                    'Z': (0, 2)})
+
+    def test_skipped_circular_diagram(self):
+        diagram = self.build('skipped_circular.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 1),
+                                    'C': (2, 0), 'Z': (0, 2)})
+
+    def test_skipped_twin_circular_diagram(self):
+        diagram = self.build('skipped_twin_circular.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 1), 'D': (2, 2),
+                                    'E': (3, 0), 'Z': (0, 3)})
+
+    def test_nested_skipped_circular_diagram(self):
+        diagram = self.build('nested_skipped_circular.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 1), 'D': (3, 2),
+                                    'E': (4, 1), 'F': (5, 0),
+                                    'G': (6, 0), 'Z': (0, 3)})
+
+    def test_self_ref_diagram(self):
+        diagram = self.build('self_ref.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'Z': (0, 1)})
+
+    def test_diagram_orientation_diagram(self):
+        diagram = self.build('diagram_orientation.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1),
+                                    'C': (0, 2), 'D': (1, 2),
+                                    'Z': (2, 0)})
+
+    def test_nested_group_orientation2_diagram(self):
+        diagram = self.build('nested_group_orientation2.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1),
+                                    'C': (0, 2), 'D': (1, 2),
+                                    'E': (2, 2), 'F': (2, 3),
+                                    'Z': (3, 0)})
+
+    def test_slided_children_diagram(self):
+        diagram = self.build('slided_children.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'D': (1, 3),
+                                    'E': (2, 3), 'F': (3, 2),
+                                    'G': (2, 1), 'H': (4, 1)})
+
+    def test_non_rhombus_relation_height_diagram(self):
+        diagram = self.build('non_rhombus_relation_height.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'D': (0, 1),
+                                    'E': (0, 2), 'F': (1, 2),
+                                    'G': (1, 3), 'H': (2, 3),
+                                    'I': (2, 4), 'J': (1, 5),
+                                    'K': (2, 5), 'Z': (0, 6)})
+
+    def test_define_class_diagram(self):
+        diagram = self.build('define_class.diag')
+        self.assertNodeColor(diagram, {'A': (255, 0, 0),
+                                       'B': (255, 255, 255),
+                                       'C': (255, 255, 255)})
+        self.assertNodeStyle(diagram, {'A': 'dashed', 'B': None, 'C': None})
+        self.assertEdgeColor(diagram, {('A', 'B'): (255, 0, 0),
+                                       ('B', 'C'): (0, 0, 0)})
+        self.assertEdgeStyle(diagram, {('A', 'B'): 'dashed',
+                                       ('B', 'C'): None})
diff --git a/src/blockdiag/tests/test_builder_edge.py b/src/blockdiag/tests/test_builder_edge.py
index d1bd5b8..9eacd7f 100644
--- a/src/blockdiag/tests/test_builder_edge.py
+++ b/src/blockdiag/tests/test_builder_edge.py
@@ -1,155 +1,143 @@
 # -*- coding: utf-8 -*-
 
-from blockdiag.tests.utils import (stderr_wrapper, __build_diagram,
-                                   __validate_node_attributes)
-
-
-def test_single_edge_diagram():
-    diagram = __build_diagram('single_edge.diag')
-
-    assert len(diagram.nodes) == 2
-    assert len(diagram.edges) == 1
-
-    positions = {'A': (0, 0), 'B': (1, 0)}
-    labels = {'A': 'A', 'B': 'B'}
-    __validate_node_attributes('single_edge.diag', xy=positions, label=labels)
-
-
-def test_two_edges_diagram():
-    diagram = __build_diagram('two_edges.diag')
-
-    assert len(diagram.nodes) == 3
-    assert len(diagram.edges) == 2
-
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0)}
-    __validate_node_attributes('two_edges.diag', xy=positions)
-
-
-def test_edge_shape():
-    diagram = __build_diagram('edge_shape.diag')
-
-    for edge in diagram.edges:
-        if edge.node1.id == 'A':
-            assert edge.dir == 'none'
-        elif edge.node1.id == 'B':
-            assert edge.dir == 'forward'
-        elif edge.node1.id == 'C':
-            assert edge.dir == 'back'
-        elif edge.node1.id == 'D':
-            assert edge.dir == 'both'
-
-
-def test_edge_attribute():
-    diagram = __build_diagram('edge_attribute.diag')
-
-    for edge in diagram.edges:
-        if edge.node1.id == 'D':
-            assert edge.dir == 'none'
-            assert edge.color == (0, 0, 0)
-            assert edge.thick is None
-        elif edge.node1.id == 'F':
-            assert edge.dir == 'forward'
-            assert edge.color == (0, 0, 0)
-            assert edge.thick == 3
-        else:
-            assert edge.dir == 'forward'
-            assert edge.color == (255, 0, 0)  # red
-            assert edge.thick is None
-
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0),
-                 'D': (0, 1), 'E': (1, 1), 'F': (0, 2), 'G': (1, 2)}
-    __validate_node_attributes('edge_attribute.diag', xy=positions)
-
-
-def test_folded_edge_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (0, 1),
-                 'E': (0, 2), 'F': (1, 1), 'Z': (0, 3)}
-    __validate_node_attributes('folded_edge.diag', xy=positions)
-
-
-def test_skipped_edge_right_diagram():
-    filename = 'skipped_edge_right.diag'
-    skipped = {('A', 'B'): False, ('A', 'C'): True}
-    __validate_node_attributes(filename, edge_skipped=skipped)
-
-
-def test_skipped_edge_rightup_diagram():
-    filename = 'skipped_edge_rightup.diag'
-    skipped = {('A', 'B'): False, ('D', 'C'): True}
-    __validate_node_attributes(filename, edge_skipped=skipped)
-
-
-def test_skipped_edge_rightdown_diagram():
-    filename = 'skipped_edge_rightdown.diag'
-    skipped = {('A', 'B'): False, ('A', 'C'): True}
-    __validate_node_attributes(filename, edge_skipped=skipped)
-
-
-def test_skipped_edge_up_diagram():
-    filename = 'skipped_edge_up.diag'
-    skipped = {('C', 'A'): True}
-    __validate_node_attributes(filename, edge_skipped=skipped)
-
-
-def test_skipped_edge_down_diagram():
-    filename = 'skipped_edge_down.diag'
-    skipped = {('A', 'C'): True}
-    __validate_node_attributes(filename, edge_skipped=skipped)
-
-
-def test_skipped_edge_leftdown_diagram():
-    filename = 'skipped_edge_leftdown.diag'
-    skipped = {('A', 'B'): False, ('C', 'G'): True}
-    __validate_node_attributes(filename, edge_skipped=skipped)
-
-
- at stderr_wrapper
-def test_skipped_edge_flowchart_rightdown_diagram():
-    filename = 'skipped_edge_flowchart_rightdown.diag'
-    skipped = {('A', 'B'): False, ('A', 'D'): True}
-    __validate_node_attributes(filename, edge_skipped=skipped)
-
-
- at stderr_wrapper
-def test_skipped_edge_flowchart_rightdown2_diagram():
-    filename = 'skipped_edge_flowchart_rightdown2.diag'
-    skipped = {('B', 'C'): False, ('A', 'C'): True}
-    __validate_node_attributes(filename, edge_skipped=skipped)
-
-
-def test_skipped_edge_portrait_right_diagram():
-    filename = 'skipped_edge_portrait_right.diag'
-    skipped = {('A', 'C'): True}
-    __validate_node_attributes(filename, edge_skipped=skipped)
-
-
-def test_skipped_edge_portrait_rightdown_diagram():
-    filename = 'skipped_edge_portrait_rightdown.diag'
-    skipped = {('A', 'B'): False, ('A', 'E'): True}
-    __validate_node_attributes(filename, edge_skipped=skipped)
-
-
-def test_skipped_edge_portrait_leftdown_diagram():
-    filename = 'skipped_edge_portrait_leftdown.diag'
-    skipped = {('A', 'B'): False, ('D', 'C'): True}
-    __validate_node_attributes(filename, edge_skipped=skipped)
-
-
-def test_skipped_edge_portrait_down_diagram():
-    filename = 'skipped_edge_portrait_down.diag'
-    skipped = {('A', 'B'): False, ('A', 'C'): True}
-    __validate_node_attributes(filename, edge_skipped=skipped)
-
-
- at stderr_wrapper
-def test_skipped_edge_portrait_flowchart_rightdown_diagram():
-    filename = 'skipped_edge_portrait_flowchart_rightdown.diag'
-    skipped = {('A', 'B'): False, ('A', 'D'): True}
-    __validate_node_attributes(filename, edge_skipped=skipped)
-
-
- at stderr_wrapper
-def test_skipped_edge_portrait_flowchart_rightdown2_diagram():
-    filename = 'skipped_edge_portrait_flowchart_rightdown2.diag'
-    skipped = {('B', 'C'): False, ('A', 'C'): True}
-    __validate_node_attributes(filename, edge_skipped=skipped)
+from blockdiag.tests.utils import BuilderTestCase, capture_stderr
+
+
+class TestBuilderEdge(BuilderTestCase):
+    def test_diagram_attributes(self):
+        diagram = self.build('diagram_attributes.diag')
+        self.assertEqual(2, len(diagram.nodes))
+        self.assertEqual(1, len(diagram.edges))
+
+    def test_single_edge_diagram(self):
+        diagram = self.build('single_edge.diag')
+        self.assertEqual(2, len(diagram.nodes))
+        self.assertEqual(1, len(diagram.edges))
+
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0)})
+        self.assertNodeLabel(diagram, {'A': 'A', 'B': 'B'})
+
+    def test_two_edges_diagram(self):
+        diagram = self.build('two_edges.diag')
+        self.assertEqual(3, len(diagram.nodes))
+        self.assertEqual(2, len(diagram.edges))
+
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0)})
+
+    def test_edge_shape(self):
+        diagram = self.build('edge_shape.diag')
+        self.assertEdgeDir(diagram, {('A', 'B'): 'none',
+                                     ('B', 'C'): 'forward',
+                                     ('C', 'D'): 'back',
+                                     ('D', 'E'): 'both'})
+
+    def test_edge_attribute(self):
+        diagram = self.build('edge_attribute.diag')
+        self.assertEdgeDir(diagram, {('A', 'B'): 'forward',
+                                     ('B', 'C'): 'forward',
+                                     ('C', 'D'): 'forward',
+                                     ('D', 'E'): 'none',
+                                     ('E', 'F'): 'both',
+                                     ('F', 'G'): 'forward'})
+        self.assertEdgeColor(diagram, {('A', 'B'): (255, 0, 0),  # red
+                                       ('B', 'C'): (255, 0, 0),  # red
+                                       ('C', 'D'): (255, 0, 0),  # red
+                                       ('D', 'E'): (0, 0, 0),
+                                       ('E', 'F'): (255, 0, 0),  # red
+                                       ('F', 'G'): (0, 0, 0)})
+        self.assertEdgeThick(diagram, {('A', 'B'): None,
+                                       ('B', 'C'): None,
+                                       ('C', 'D'): None,
+                                       ('D', 'E'): None,
+                                       ('E', 'F'): None,
+                                       ('F', 'G'): 3})
+
+    def test_folded_edge_diagram(self):
+        diagram = self.build('folded_edge.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'D': (0, 1),
+                                    'E': (0, 2), 'F': (1, 1),
+                                    'Z': (0, 3)})
+
+    def test_skipped_edge_right_diagram(self):
+        diagram = self.build('skipped_edge_right.diag')
+        self.assertEdgeSkipped(diagram, {('A', 'B'): False,
+                                         ('A', 'C'): True,
+                                         ('B', 'C'): False})
+
+    def test_skipped_edge_rightdown_diagram(self):
+        diagram = self.build('skipped_edge_rightdown.diag')
+        self.assertEdgeSkipped(diagram, {('A', 'B'): False,
+                                         ('A', 'C'): True,
+                                         ('A', 'D'): False,
+                                         ('B', 'C'): False,
+                                         ('B', 'D'): False})
+
+    def test_skipped_edge_up_diagram(self):
+        diagram = self.build('skipped_edge_up.diag')
+        self.assertEdgeSkipped(diagram, {('C', 'A'): True})
+
+    def test_skipped_edge_down_diagram(self):
+        diagram = self.build('skipped_edge_down.diag')
+        self.assertEdgeSkipped(diagram, {('A', 'C'): True})
+
+    def test_skipped_edge_leftdown_diagram(self):
+        diagram = self.build('skipped_edge_leftdown.diag')
+        self.assertEdgeSkipped(diagram, {('A', 'B'): False,
+                                         ('B', 'C'): False,
+                                         ('B', 'D'): False,
+                                         ('C', 'G'): True,
+                                         ('F', 'G'): False})
+
+    @capture_stderr
+    def test_skipped_edge_flowchart_rightdown_diagram(self):
+        diagram = self.build('skipped_edge_flowchart_rightdown.diag')
+        self.assertEdgeSkipped(diagram, {('A', 'B'): False,
+                                         ('A', 'C'): False,
+                                         ('A', 'D'): True,
+                                         ('C', 'D'): False})
+
+    @capture_stderr
+    def test_skipped_edge_flowchart_rightdown2_diagram(self):
+        diagram = self.build('skipped_edge_flowchart_rightdown2.diag')
+        self.assertEdgeSkipped(diagram, {('B', 'C'): False,
+                                         ('A', 'C'): True})
+
+    def test_skipped_edge_portrait_right_diagram(self):
+        diagram = self.build('skipped_edge_portrait_right.diag')
+        self.assertEdgeSkipped(diagram, {('A', 'C'): True})
+
+    def test_skipped_edge_portrait_rightdown_diagram(self):
+        diagram = self.build('skipped_edge_portrait_rightdown.diag')
+        self.assertEdgeSkipped(diagram, {('A', 'B'): False,
+                                         ('A', 'C'): False,
+                                         ('A', 'E'): True,
+                                         ('B', 'D'): False,
+                                         ('C', 'E'): False})
+
+    def test_skipped_edge_portrait_leftdown_diagram(self):
+        diagram = self.build('skipped_edge_portrait_leftdown.diag')
+        self.assertEdgeSkipped(diagram, {('A', 'B'): False,
+                                         ('B', 'C'): False,
+                                         ('D', 'C'): True,
+                                         ('D', 'E'): False})
+
+    def test_skipped_edge_portrait_down_diagram(self):
+        diagram = self.build('skipped_edge_portrait_down.diag')
+        self.assertEdgeSkipped(diagram, {('A', 'B'): False,
+                                         ('A', 'C'): True,
+                                         ('B', 'C'): False})
+
+    @capture_stderr
+    def test_skipped_edge_portrait_flowchart_rightdown_diagram(self):
+        diagram = self.build('skipped_edge_portrait_flowchart_rightdown.diag')
+        self.assertEdgeSkipped(diagram, {('A', 'B'): False,
+                                         ('A', 'C'): False,
+                                         ('A', 'D'): True,
+                                         ('C', 'D'): False})
+
+    @capture_stderr
+    def test_skipped_edge_portrait_flowchart_rightdown2_diagram(self):
+        diagram = self.build('skipped_edge_portrait_flowchart_rightdown2.diag')
+        self.assertEdgeSkipped(diagram, {('B', 'C'): False,
+                                         ('A', 'C'): True})
diff --git a/src/blockdiag/tests/test_builder_errors.py b/src/blockdiag/tests/test_builder_errors.py
index 0e1e6b5..bfe15ec 100644
--- a/src/blockdiag/tests/test_builder_errors.py
+++ b/src/blockdiag/tests/test_builder_errors.py
@@ -1,101 +1,101 @@
 # -*- coding: utf-8 -*-
 
-from nose.tools import raises
-from blockdiag.tests.utils import __build_diagram
-
+from blockdiag.tests.utils import BuilderTestCase
 from blockdiag.parser import ParseException
 
 
- at raises(AttributeError)
-def test_unknown_diagram_default_shape_diagram():
-    __build_diagram('errors/unknown_diagram_default_shape.diag')
-
-
- at raises(AttributeError)
-def test_unknown_diagram_edge_layout_diagram():
-    __build_diagram('errors/unknown_diagram_edge_layout.diag')
-
-
- at raises(AttributeError)
-def test_unknown_diagram_orientation_diagram():
-    __build_diagram('errors/unknown_diagram_orientation.diag')
-
-
- at raises(AttributeError)
-def test_unknown_node_shape_diagram():
-    __build_diagram('errors/unknown_node_shape.diag')
-
-
- at raises(AttributeError)
-def test_unknown_node_attribute_diagram():
-    __build_diagram('errors/unknown_node_attribute.diag')
-
-
- at raises(AttributeError)
-def test_unknown_node_style_diagram():
-    __build_diagram('errors/unknown_node_style.diag')
-
-
- at raises(AttributeError)
-def test_unknown_node_class_diagram():
-    __build_diagram('errors/unknown_node_class.diag')
-
-
- at raises(AttributeError)
-def test_unknown_edge_dir_diagram():
-    __build_diagram('errors/unknown_edge_dir.diag')
-
-
- at raises(AttributeError)
-def test_unknown_edge_style_diagram():
-    __build_diagram('errors/unknown_edge_style.diag')
-
-
- at raises(AttributeError)
-def test_unknown_edge_hstyle_diagram():
-    __build_diagram('errors/unknown_edge_hstyle.diag')
-
-
- at raises(AttributeError)
-def test_unknown_edge_class_diagram():
-    __build_diagram('errors/unknown_edge_class.diag')
-
-
- at raises(AttributeError)
-def test_unknown_group_shape_diagram():
-    __build_diagram('errors/unknown_group_shape.diag')
-
-
- at raises(AttributeError)
-def test_unknown_group_class_diagram():
-    __build_diagram('errors/unknown_group_class.diag')
-
-
- at raises(AttributeError)
-def test_unknown_group_orientation_diagram():
-    __build_diagram('errors/unknown_group_orientation.diag')
-
-
- at raises(RuntimeError)
-def test_belongs_to_two_groups_diagram():
-    __build_diagram('errors/belongs_to_two_groups.diag')
-
-
- at raises(AttributeError)
-def test_unknown_plugin_diagram():
-    __build_diagram('errors/unknown_plugin.diag')
-
-
- at raises(ParseException)
-def test_node_follows_group_diagram():
-    __build_diagram('errors/node_follows_group.diag')
-
-
- at raises(ParseException)
-def test_group_follows_node_diagram():
-    __build_diagram('errors/group_follows_node.diag')
-
-
- at raises(ParseException)
-def test_lexer_error_diagram():
-    __build_diagram('errors/lexer_error.diag')
+class TestBuilderError(BuilderTestCase):
+    def test_unknown_diagram_default_shape_diagram(self):
+        filename = 'errors/unknown_diagram_default_shape.diag'
+        with self.assertRaises(AttributeError):
+            self.build(filename)
+
+    def test_unknown_diagram_edge_layout_diagram(self):
+        filename = 'errors/unknown_diagram_edge_layout.diag'
+        with self.assertRaises(AttributeError):
+            self.build(filename)
+
+    def test_unknown_diagram_orientation_diagram(self):
+        filename = 'errors/unknown_diagram_orientation.diag'
+        with self.assertRaises(AttributeError):
+            self.build(filename)
+
+    def test_unknown_node_shape_diagram(self):
+        filename = 'errors/unknown_node_shape.diag'
+        with self.assertRaises(AttributeError):
+            self.build(filename)
+
+    def test_unknown_node_attribute_diagram(self):
+        filename = 'errors/unknown_node_attribute.diag'
+        with self.assertRaises(AttributeError):
+            self.build(filename)
+
+    def test_unknown_node_style_diagram(self):
+        filename = 'errors/unknown_node_style.diag'
+        with self.assertRaises(AttributeError):
+            self.build(filename)
+
+    def test_unknown_node_class_diagram(self):
+        filename = 'errors/unknown_node_class.diag'
+        with self.assertRaises(AttributeError):
+            self.build(filename)
+
+    def test_unknown_edge_dir_diagram(self):
+        filename = 'errors/unknown_edge_dir.diag'
+        with self.assertRaises(AttributeError):
+            self.build(filename)
+
+    def test_unknown_edge_style_diagram(self):
+        filename = 'errors/unknown_edge_style.diag'
+        with self.assertRaises(AttributeError):
+            self.build(filename)
+
+    def test_unknown_edge_hstyle_diagram(self):
+        filename = 'errors/unknown_edge_hstyle.diag'
+        with self.assertRaises(AttributeError):
+            self.build(filename)
+
+    def test_unknown_edge_class_diagram(self):
+        filename = 'errors/unknown_edge_class.diag'
+        with self.assertRaises(AttributeError):
+            self.build(filename)
+
+    def test_unknown_group_shape_diagram(self):
+        filename = 'errors/unknown_group_shape.diag'
+        with self.assertRaises(AttributeError):
+            self.build(filename)
+
+    def test_unknown_group_class_diagram(self):
+        filename = 'errors/unknown_group_class.diag'
+        with self.assertRaises(AttributeError):
+            self.build(filename)
+
+    def test_unknown_group_orientation_diagram(self):
+        filename = 'errors/unknown_group_orientation.diag'
+        with self.assertRaises(AttributeError):
+            self.build(filename)
+
+    def test_belongs_to_two_groups_diagram(self):
+        filename = 'errors/belongs_to_two_groups.diag'
+        with self.assertRaises(RuntimeError):
+            self.build(filename)
+
+    def test_unknown_plugin_diagram(self):
+        filename = 'errors/unknown_plugin.diag'
+        with self.assertRaises(AttributeError):
+            self.build(filename)
+
+    def test_node_follows_group_diagram(self):
+        filename = 'errors/node_follows_group.diag'
+        with self.assertRaises(ParseException):
+            self.build(filename)
+
+    def test_group_follows_node_diagram(self):
+        filename = 'errors/group_follows_node.diag'
+        with self.assertRaises(ParseException):
+            self.build(filename)
+
+    def test_lexer_error_diagram(self):
+        filename = 'errors/lexer_error.diag'
+        with self.assertRaises(ParseException):
+            self.build(filename)
diff --git a/src/blockdiag/tests/test_builder_group.py b/src/blockdiag/tests/test_builder_group.py
index efea8b5..8767bf0 100644
--- a/src/blockdiag/tests/test_builder_group.py
+++ b/src/blockdiag/tests/test_builder_group.py
@@ -1,209 +1,214 @@
 # -*- coding: utf-8 -*-
 
-from blockdiag.tests.utils import __build_diagram, __validate_node_attributes
-
-
-def test_nested_groups_diagram():
-    positions = {'A': (0, 0), 'B': (0, 1), 'Z': (0, 2)}
-    __validate_node_attributes('nested_groups.diag', xy=positions)
-
-
-def test_nested_groups_and_edges_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}
-    __validate_node_attributes('nested_groups_and_edges.diag', xy=positions)
-
-
-def test_empty_group_diagram():
-    positions = {'Z': (0, 0)}
-    __validate_node_attributes('empty_group.diag', xy=positions)
-
-
-def test_empty_nested_group_diagram():
-    positions = {'Z': (0, 0)}
-    __validate_node_attributes('empty_nested_group.diag', xy=positions)
-
-
-def test_empty_group_declaration_diagram():
-    positions = {'A': (0, 0), 'Z': (0, 1)}
-    __validate_node_attributes('empty_group_declaration.diag', xy=positions)
-
-
-def test_simple_group_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'Z': (0, 2)}
-    __validate_node_attributes('simple_group.diag', xy=positions)
-
-
-def test_group_declare_as_node_attribute_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0),
-                 'D': (2, 1), 'E': (2, 2), 'Z': (0, 3)}
-    __validate_node_attributes('group_declare_as_node_attribute.diag',
-                               xy=positions)
-
-
-def test_group_attribute():
-    diagram = __build_diagram('group_attribute.diag')
-
-    for node in (x for x in diagram.nodes if not x.drawable):
-        node.color = 'red'
-        node.label = 'group label'
-        node.shape = 'line'
-
-
-def test_merge_groups_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (0, 1),
-                 'D': (1, 1), 'Z': (0, 2)}
-    __validate_node_attributes('merge_groups.diag', xy=positions)
-
-
-def test_node_attribute_and_group_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}
-    labels = {'A': 'foo', 'B': 'bar', 'C': 'baz', 'Z': 'Z'}
-    colors = {'A': (255, 0, 0), 'B': '#888888', 'C': (0, 0, 255),
-              'Z': (255, 255, 255)}
-    __validate_node_attributes('node_attribute_and_group.diag',
-                               xy=positions, label=labels, color=colors)
-
-
-def test_group_sibling_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (1, 2), 'D': (2, 0),
-                 'E': (2, 1), 'F': (2, 2), 'Z': (0, 3)}
-    __validate_node_attributes('group_sibling.diag', xy=positions)
-
-
-def test_group_order_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'Z': (0, 2)}
-    __validate_node_attributes('group_order.diag', xy=positions)
-
-
-def test_group_order2_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (1, 1),
-                 'D': (2, 1), 'E': (1, 2), 'F': (2, 2), 'Z': (0, 3)}
-    __validate_node_attributes('group_order2.diag', xy=positions)
-
-
-def test_group_order3_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0),
-                 'D': (2, 1), 'E': (1, 2), 'Z': (0, 3)}
-    __validate_node_attributes('group_order3.diag', xy=positions)
-
-
-def test_group_children_height_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2),
-                 'E': (2, 0), 'F': (2, 2), 'Z': (0, 3)}
-    __validate_node_attributes('group_children_height.diag', xy=positions)
-
-
-def test_group_children_order_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2),
-                 'E': (2, 0), 'F': (2, 1), 'G': (2, 2), 'Z': (0, 3)}
-    __validate_node_attributes('group_children_order.diag', xy=positions)
-
-
-def test_group_children_order2_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2),
-                 'E': (2, 1), 'F': (2, 0), 'G': (2, 2), 'Z': (0, 3)}
-    __validate_node_attributes('group_children_order2.diag', xy=positions)
-
-
-def test_group_children_order3_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2),
-                 'E': (2, 0), 'F': (2, 1), 'G': (2, 2), 'Q': (0, 3),
-                 'Z': (0, 4)}
-    __validate_node_attributes('group_children_order3.diag', xy=positions)
-
-
-def test_group_children_order4_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2),
-                 'E': (2, 0), 'Z': (0, 3)}
-    __validate_node_attributes('group_children_order4.diag', xy=positions)
-
-
-def test_node_in_group_follows_outer_node_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}
-    __validate_node_attributes('node_in_group_follows_outer_node.diag',
-                               xy=positions)
-
-
-def test_group_id_and_node_id_are_not_conflicted_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (0, 1),
-                 'D': (1, 1), 'Z': (0, 2)}
-    __validate_node_attributes('group_id_and_node_id_are_not_conflicted.diag',
-                               xy=positions)
-
-
-def test_outer_node_follows_node_in_group_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}
-    __validate_node_attributes('outer_node_follows_node_in_group.diag',
-                               xy=positions)
-
-
-def test_large_group_and_node_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2),
-                 'E': (1, 3), 'F': (2, 0), 'Z': (0, 4)}
-    __validate_node_attributes('large_group_and_node.diag', xy=positions)
-
-
-def test_large_group_and_node2_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (3, 0),
-                 'E': (4, 0), 'F': (5, 0), 'Z': (0, 1)}
-    __validate_node_attributes('large_group_and_node2.diag', xy=positions)
-
-
-def test_large_group_and_two_nodes_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2),
-                 'E': (1, 3), 'F': (2, 0), 'G': (2, 1), 'Z': (0, 4)}
-    __validate_node_attributes('large_group_and_two_nodes.diag', xy=positions)
-
-
-def test_group_height_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0),
-                 'D': (2, 1), 'E': (1, 2), 'Z': (0, 3)}
-    __validate_node_attributes('group_height.diag', xy=positions)
-
-
-def test_multiple_groups_diagram():
-    positions = {'A': (0, 0), 'B': (0, 1), 'C': (0, 2), 'D': (0, 3),
-                 'E': (1, 0), 'F': (1, 1), 'G': (1, 2), 'H': (2, 0),
-                 'I': (2, 1), 'J': (3, 0), 'Z': (0, 4)}
-    __validate_node_attributes('multiple_groups.diag', xy=positions)
-
-
-def test_multiple_nested_groups_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'Z': (0, 2)}
-    __validate_node_attributes('multiple_nested_groups.diag', xy=positions)
-
-
-def test_group_works_node_decorator_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (3, 0),
-                 'D': (2, 0), 'E': (1, 1), 'Z': (0, 2)}
-    __validate_node_attributes('group_works_node_decorator.diag', xy=positions)
-
-
-def test_nested_groups_work_node_decorator_diagram():
-    positions = {'A': (0, 0), 'B': (0, 1), 'Z': (0, 2)}
-    __validate_node_attributes('nested_groups_work_node_decorator.diag',
-                               xy=positions)
-
-
-def test_reversed_multiple_groups_diagram():
-    positions = {'A': (3, 0), 'B': (3, 1), 'C': (3, 2), 'D': (3, 3),
-                 'E': (2, 0), 'F': (2, 1), 'G': (2, 2), 'H': (1, 0),
-                 'I': (1, 1), 'J': (0, 0), 'Z': (0, 4)}
-    __validate_node_attributes('reverse_multiple_groups.diag', xy=positions)
-
-
-def test_group_and_skipped_edge_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0),
-                 'D': (3, 0), 'E': (1, 1), 'Z': (0, 2)}
-    __validate_node_attributes('group_and_skipped_edge.diag', xy=positions)
-
-
-def test_group_orientation_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (1, 1),
-                 'D': (2, 1), 'Z': (0, 2)}
-    __validate_node_attributes('group_orientation.diag', xy=positions)
-
-
-def test_nested_group_orientation_diagram():
-    positions = {'A': (0, 0), 'B': (0, 1), 'C': (1, 0), 'Z': (0, 2)}
-    __validate_node_attributes('nested_group_orientation.diag', xy=positions)
+from blockdiag.tests.utils import BuilderTestCase
+
+
+class TestBuilderGroup(BuilderTestCase):
+    def test_nested_groups_diagram(self):
+        diagram = self.build('nested_groups.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1),
+                                    'Z': (0, 2)})
+
+    def test_nested_groups_and_edges_diagram(self):
+        diagram = self.build('nested_groups_and_edges.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1),
+                                    'C': (2, 0), 'Z': (0, 1)})
+
+    def test_empty_group_diagram(self):
+        diagram = self.build('empty_group.diag')
+        self.assertNodeXY(diagram, {'Z': (0, 0)})
+
+    def test_empty_nested_group_diagram(self):
+        diagram = self.build('empty_nested_group.diag')
+        self.assertNodeXY(diagram, {'Z': (0, 0)})
+
+    def test_empty_group_declaration_diagram(self):
+        diagram = self.build('empty_group_declaration.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'Z': (0, 1)})
+
+    def test_simple_group_diagram(self):
+        diagram = self.build('simple_group.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (1, 1), 'Z': (0, 2)})
+
+    def test_group_declare_as_node_attribute_diagram(self):
+        diagram = self.build('group_declare_as_node_attribute.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'D': (2, 1),
+                                    'E': (2, 2), 'Z': (0, 3)})
+
+    def test_group_attribute(self):
+        diagram = self.build('group_attribute.diag')
+        groups = list(diagram.traverse_groups())
+        self.assertEqual(1, len(groups))
+        self.assertEqual((255, 0, 0), groups[0].color)
+        self.assertEqual('line', groups[0].shape)
+
+    def test_merge_groups_diagram(self):
+        diagram = self.build('merge_groups.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (0, 1), 'D': (1, 1),
+                                    'Z': (0, 2)})
+
+    def test_node_attribute_and_group_diagram(self):
+        diagram = self.build('node_attribute_and_group.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'Z': (0, 1)})
+        self.assertNodeLabel(diagram, {'A': 'foo', 'B': 'bar',
+                                       'C': 'baz', 'Z': 'Z'})
+        self.assertNodeColor(diagram, {'A': (255, 0, 0), 'B': '#888888',
+                                       'C': (0, 0, 255), 'Z': (255, 255, 255)})
+
+    def test_group_sibling_diagram(self):
+        diagram = self.build('group_sibling.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (1, 2), 'D': (2, 0),
+                                    'E': (2, 1), 'F': (2, 2),
+                                    'Z': (0, 3)})
+
+    def test_group_order_diagram(self):
+        diagram = self.build('group_order.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (1, 1), 'Z': (0, 2)})
+
+    def test_group_order2_diagram(self):
+        diagram = self.build('group_order2.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (1, 1), 'D': (2, 1),
+                                    'E': (1, 2), 'F': (2, 2),
+                                    'Z': (0, 3)})
+
+    def test_group_order3_diagram(self):
+        diagram = self.build('group_order3.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'D': (2, 1),
+                                    'E': (1, 2), 'Z': (0, 3)})
+
+    def test_group_children_height_diagram(self):
+        diagram = self.build('group_children_height.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (1, 1), 'D': (1, 2),
+                                    'E': (2, 0), 'F': (2, 2),
+                                    'Z': (0, 3)})
+
+    def test_group_children_order_diagram(self):
+        diagram = self.build('group_children_order.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (1, 1), 'D': (1, 2),
+                                    'E': (2, 0), 'F': (2, 1),
+                                    'G': (2, 2), 'Z': (0, 3)})
+
+    def test_group_children_order2_diagram(self):
+        diagram = self.build('group_children_order2.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (1, 1), 'D': (1, 2),
+                                    'E': (2, 1), 'F': (2, 0),
+                                    'G': (2, 2), 'Z': (0, 3)})
+
+    def test_group_children_order3_diagram(self):
+        diagram = self.build('group_children_order3.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (1, 1), 'D': (1, 2),
+                                    'E': (2, 0), 'F': (2, 1),
+                                    'G': (2, 2), 'Q': (0, 3),
+                                    'Z': (0, 4)})
+
+    def test_group_children_order4_diagram(self):
+        diagram = self.build('group_children_order4.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (1, 1), 'D': (1, 2),
+                                    'E': (2, 0), 'Z': (0, 3)})
+
+    def test_node_in_group_follows_outer_node_diagram(self):
+        diagram = self.build('node_in_group_follows_outer_node.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'Z': (0, 1)})
+
+    def test_group_id_and_node_id_are_not_conflicted_diagram(self):
+        diagram = self.build('group_id_and_node_id_are_not_conflicted.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (0, 1), 'D': (1, 1),
+                                    'Z': (0, 2)})
+
+    def test_outer_node_follows_node_in_group_diagram(self):
+        diagram = self.build('outer_node_follows_node_in_group.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'Z': (0, 1)})
+
+    def test_large_group_and_node_diagram(self):
+        diagram = self.build('large_group_and_node.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (1, 1), 'D': (1, 2),
+                                    'E': (1, 3), 'F': (2, 0),
+                                    'Z': (0, 4)})
+
+    def test_large_group_and_node2_diagram(self):
+        diagram = self.build('large_group_and_node2.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'D': (3, 0),
+                                    'E': (4, 0), 'F': (5, 0),
+                                    'Z': (0, 1)})
+
+    def test_large_group_and_two_nodes_diagram(self):
+        diagram = self.build('large_group_and_two_nodes.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (1, 1), 'D': (1, 2),
+                                    'E': (1, 3), 'F': (2, 0),
+                                    'G': (2, 1), 'Z': (0, 4)})
+
+    def test_group_height_diagram(self):
+        diagram = self.build('group_height.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'D': (2, 1),
+                                    'E': (1, 2), 'Z': (0, 3)})
+
+    def test_multiple_groups_diagram(self):
+        diagram = self.build('multiple_groups.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1),
+                                    'C': (0, 2), 'D': (0, 3),
+                                    'E': (1, 0), 'F': (1, 1),
+                                    'G': (1, 2), 'H': (2, 0),
+                                    'I': (2, 1), 'J': (3, 0),
+                                    'Z': (0, 4)})
+
+    def test_multiple_nested_groups_diagram(self):
+        diagram = self.build('multiple_nested_groups.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (1, 1), 'Z': (0, 2)})
+
+    def test_group_works_node_decorator_diagram(self):
+        diagram = self.build('group_works_node_decorator.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (3, 0), 'D': (2, 0),
+                                    'E': (1, 1), 'Z': (0, 2)})
+
+    def test_nested_groups_work_node_decorator_diagram(self):
+        diagram = self.build('nested_groups_work_node_decorator.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1),
+                                    'Z': (0, 2)})
+
+    def test_reversed_multiple_groups_diagram(self):
+        diagram = self.build('reverse_multiple_groups.diag')
+        self.assertNodeXY(diagram, {'A': (3, 0), 'B': (3, 1),
+                                    'C': (3, 2), 'D': (3, 3),
+                                    'E': (2, 0), 'F': (2, 1),
+                                    'G': (2, 2), 'H': (1, 0),
+                                    'I': (1, 1), 'J': (0, 0),
+                                    'Z': (0, 4)})
+
+    def test_group_and_skipped_edge_diagram(self):
+        diagram = self.build('group_and_skipped_edge.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'D': (3, 0),
+                                    'E': (1, 1), 'Z': (0, 2)})
+
+    def test_group_orientation_diagram(self):
+        diagram = self.build('group_orientation.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (1, 1), 'D': (2, 1),
+                                    'Z': (0, 2)})
+
+    def test_nested_group_orientation_diagram(self):
+        diagram = self.build('nested_group_orientation.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1),
+                                    'C': (1, 0), 'Z': (0, 2)})
diff --git a/src/blockdiag/tests/test_builder_node.py b/src/blockdiag/tests/test_builder_node.py
index 67a2814..4291afd 100644
--- a/src/blockdiag/tests/test_builder_node.py
+++ b/src/blockdiag/tests/test_builder_node.py
@@ -1,138 +1,139 @@
 # -*- coding: utf-8 -*-
 
-from blockdiag.tests.utils import __build_diagram, __validate_node_attributes
-
-from blockdiag.utils.collections import defaultdict
-
-
-def test_single_node_diagram():
-    diagram = __build_diagram('single_node.diag')
-
-    assert len(diagram.nodes) == 1
-    assert len(diagram.edges) == 0
-    assert diagram.nodes[0].label == 'A'
-    assert diagram.nodes[0].xy == (0, 0)
-
-
-def test_node_shape_diagram():
-    shapes = {'A': 'box', 'B': 'roundedbox', 'C': 'diamond',
-              'D': 'ellipse', 'E': 'note', 'F': 'cloud',
-              'G': 'mail', 'H': 'beginpoint', 'I': 'endpoint',
-              'J': 'minidiamond', 'K': 'flowchart.condition',
-              'L': 'flowchart.database', 'M': 'flowchart.input',
-              'N': 'flowchart.loopin', 'O': 'flowchart.loopout',
-              'P': 'actor', 'Q': 'flowchart.terminator', 'R': 'textbox',
-              'S': 'dots', 'T': 'none', 'U': 'square', 'V': 'circle',
-              'Z': 'box'}
-    __validate_node_attributes('node_shape.diag', shape=shapes)
-
-
-def test_node_shape_namespace_diagram():
-    shapes = {'A': 'flowchart.condition', 'B': 'condition', 'Z': 'box'}
-    __validate_node_attributes('node_shape_namespace.diag', shape=shapes)
-
-
-def test_node_has_multilined_label_diagram():
-    positions = {'A': (0, 0), 'Z': (0, 1)}
-    labels = {'A': "foo\nbar", 'Z': 'Z'}
-    __validate_node_attributes('node_has_multilined_label.diag',
-                               xy=positions, label=labels)
-
-
-def test_quoted_node_id_diagram():
-    positions = {'A': (0, 0), "'A'": (1, 0), 'B': (2, 0), 'Z': (0, 1)}
-    __validate_node_attributes('quoted_node_id.diag', xy=positions)
-
-
-def test_node_id_includes_dot_diagram():
-    positions = {'A.B': (0, 0), 'C.D': (1, 0), 'Z': (0, 1)}
-    __validate_node_attributes('node_id_includes_dot.diag', xy=positions)
-
-
-def test_multiple_nodes_definition_diagram():
-    positions = {'A': (0, 0), 'B': (0, 1), 'Z': (0, 2)}
-    colors = {'A': (255, 0, 0), 'B': (255, 0, 0), 'Z': (255, 255, 255)}
-    __validate_node_attributes('multiple_nodes_definition.diag', xy=positions,
-                               color=colors)
-
-
-def test_multiple_node_relation_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (1, 1),
-                 'D': (2, 0), 'Z': (0, 2)}
-    __validate_node_attributes('multiple_node_relation.diag', xy=positions)
-
-
-def test_node_attribute():
-    labels = {'A': 'B', 'B': 'double quoted', 'C': 'single quoted',
-              'D': '\'"double" quoted\'', 'E': '"\'single\' quoted"',
-              'F': 'F', 'G': 'G', 'H': 'H', 'I': 'I', 'J': 'Hello'}
-    colors = {'A': (255, 0, 0), 'B': (255, 255, 255), 'C': (255, 0, 0),
-              'D': (255, 0, 0), 'E': (255, 0, 0), 'F': (255, 255, 255),
-              'G': (255, 255, 255), 'H': (255, 255, 255), 'I': (255, 255, 255),
-              'J': (255, 255, 255)}
-    textcolors = defaultdict(lambda: (0, 0, 0))
-    textcolors['F'] = (255, 0, 0)
-    numbered = defaultdict(lambda: None)
-    numbered['E'] = '1'
-    stacked = defaultdict(lambda: False)
-    stacked['G'] = True
-    fontsize = defaultdict(lambda: None)
-    fontsize['H'] = 16
-    linecolors = defaultdict(lambda: (0, 0, 0))
-    linecolors['I'] = (255, 0, 0)
-    orientations = defaultdict(lambda: 'horizontal')
-    orientations['J'] = 'vertical'
-
-    __validate_node_attributes('node_attribute.diag', label=labels,
-                               color=colors, textcolor=textcolors,
-                               numbered=numbered, stacked=stacked,
-                               fontsize=fontsize, linecolor=linecolors,
-                               label_orientation=orientations)
-
-
-def test_node_height_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0),
-                 'D': (2, 1), 'E': (1, 1), 'Z': (0, 2)}
-    __validate_node_attributes('node_height.diag', xy=positions)
-
-
-def test_branched_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0),
-                 'D': (1, 1), 'E': (2, 1), 'Z': (0, 2)}
-    __validate_node_attributes('branched.diag', xy=positions)
-
-
-def test_multiple_parent_node_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (0, 2),
-                 'D': (1, 2), 'E': (0, 1), 'Z': (0, 3)}
-    __validate_node_attributes('multiple_parent_node.diag', xy=positions)
-
-
-def test_twin_multiple_parent_node_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (0, 1),
-                 'D': (1, 1), 'E': (0, 2), 'Z': (0, 3)}
-    __validate_node_attributes('twin_multiple_parent_node.diag', xy=positions)
-
-
-def test_flowable_node_diagram():
-    positions = {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}
-    __validate_node_attributes('flowable_node.diag', xy=positions)
-
-
-def test_plugin_autoclass_diagram():
-    positions = {'A_emphasis': (0, 0), 'B_emphasis': (1, 0), 'C': (1, 1)}
-    styles = {'A_emphasis': 'dashed', 'B_emphasis': 'dashed', 'C': None}
-    colors = {'A_emphasis': (255, 0, 0), 'B_emphasis': (255, 0, 0),
-              'C': (255, 255, 255)}
-
-    __validate_node_attributes('plugin_autoclass.diag', xy=positions,
-                               style=styles, color=colors)
-
-
-def test_plugin_attributes_diagram():
-    attr1 = {'A': "1", 'B': None}
-    attr2 = {'A': "2", 'B': None}
-    attr3 = {'A': "3", 'B': None}
-
-    __validate_node_attributes('plugin_attributes.diag', test_attr1=attr1,
-                               test_attr2=attr2, test_attr3=attr3)
+from collections import defaultdict
+from blockdiag.tests.utils import BuilderTestCase
+
+
+class TestBuilderNode(BuilderTestCase):
+    def test_single_node_diagram(self):
+        diagram = self.build('single_node.diag')
+
+        self.assertEqual(1, len(diagram.nodes))
+        self.assertEqual(0, len(diagram.edges))
+        self.assertEqual('A', diagram.nodes[0].label)
+        self.assertEqual((0, 0), diagram.nodes[0].xy)
+
+    def test_node_shape_diagram(self):
+        expected = {'A': 'box', 'B': 'roundedbox', 'C': 'diamond',
+                    'D': 'ellipse', 'E': 'note', 'F': 'cloud',
+                    'G': 'mail', 'H': 'beginpoint', 'I': 'endpoint',
+                    'J': 'minidiamond', 'K': 'flowchart.condition',
+                    'L': 'flowchart.database', 'M': 'flowchart.input',
+                    'N': 'flowchart.loopin', 'O': 'flowchart.loopout',
+                    'P': 'actor', 'Q': 'flowchart.terminator', 'R': 'textbox',
+                    'S': 'dots', 'T': 'none', 'U': 'square', 'V': 'circle',
+                    'Z': 'box'}
+        diagram = self.build('node_shape.diag')
+        self.assertNodeShape(diagram, expected)
+
+    def test_node_shape_namespace_diagram(self):
+        diagram = self.build('node_shape_namespace.diag')
+        self.assertNodeShape(diagram, {'A': 'flowchart.condition',
+                                       'B': 'condition',
+                                       'Z': 'box'})
+
+    def test_node_has_multilined_label_diagram(self):
+        diagram = self.build('node_has_multilined_label.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'Z': (0, 1)})
+        self.assertNodeLabel(diagram, {'A': "foo\nbar", 'Z': 'Z'})
+
+    def test_quoted_node_id_diagram(self):
+        diagram = self.build('quoted_node_id.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), "'A'": (1, 0),
+                                    'B': (2, 0), 'Z': (0, 1)})
+
+    def test_node_id_includes_dot_diagram(self):
+        diagram = self.build('node_id_includes_dot.diag')
+        self.assertNodeXY(diagram, {'A.B': (0, 0), 'C.D': (1, 0),
+                                    'Z': (0, 1)})
+
+    def test_multiple_nodes_definition_diagram(self):
+        diagram = self.build('multiple_nodes_definition.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1),
+                                    'Z': (0, 2)})
+        self.assertNodeColor(diagram, {'A': (255, 0, 0), 'B': (255, 0, 0),
+                                       'Z': (255, 255, 255)})
+
+    def test_multiple_node_relation_diagram(self):
+        diagram = self.build('multiple_node_relation.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (1, 1), 'D': (2, 0),
+                                    'Z': (0, 2)})
+
+    def test_node_attribute(self):
+        labels = {'A': 'B', 'B': 'double quoted', 'C': 'single quoted',
+                  'D': '\'"double" quoted\'', 'E': '"\'single\' quoted"',
+                  'F': 'F', 'G': 'G', 'H': 'H', 'I': 'I', 'J': 'Hello'}
+        colors = {'A': (255, 0, 0), 'B': (255, 255, 255), 'C': (255, 0, 0),
+                  'D': (255, 0, 0), 'E': (255, 0, 0), 'F': (255, 255, 255),
+                  'G': (255, 255, 255), 'H': (255, 255, 255),
+                  'I': (255, 255, 255), 'J': (255, 255, 255)}
+        textcolors = defaultdict(lambda: (0, 0, 0))
+        textcolors['F'] = (255, 0, 0)
+        linecolors = defaultdict(lambda: (0, 0, 0))
+        linecolors['I'] = (255, 0, 0)
+        numbered = defaultdict(lambda: None)
+        numbered['E'] = '1'
+        stacked = defaultdict(lambda: False)
+        stacked['G'] = True
+        fontsize = defaultdict(lambda: None)
+        fontsize['H'] = 16
+        orientations = defaultdict(lambda: 'horizontal')
+        orientations['J'] = 'vertical'
+
+        diagram = self.build('node_attribute.diag')
+        self.assertNodeLabel(diagram, labels)
+        self.assertNodeColor(diagram, colors)
+        self.assertNodeTextColor(diagram, textcolors)
+        self.assertNodeLineColor(diagram, linecolors)
+        self.assertNodeNumbered(diagram, numbered)
+        self.assertNodeStacked(diagram, stacked)
+        self.assertNodeFontsize(diagram, fontsize)
+        self.assertNodeLabel_Orientation(diagram, orientations)
+
+    def test_node_height_diagram(self):
+        diagram = self.build('node_height.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'D': (2, 1),
+                                    'E': (1, 1), 'Z': (0, 2)})
+
+    def test_branched_diagram(self):
+        diagram = self.build('branched.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'D': (1, 1),
+                                    'E': (2, 1), 'Z': (0, 2)})
+
+    def test_multiple_parent_node_diagram(self):
+        diagram = self.build('multiple_parent_node.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (0, 2), 'D': (1, 2),
+                                    'E': (0, 1), 'Z': (0, 3)})
+
+    def test_twin_multiple_parent_node_diagram(self):
+        diagram = self.build('twin_multiple_parent_node.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (0, 1), 'D': (1, 1),
+                                    'E': (0, 2), 'Z': (0, 3)})
+
+    def test_flowable_node_diagram(self):
+        diagram = self.build('flowable_node.diag')
+        self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0),
+                                    'C': (2, 0), 'Z': (0, 1)})
+
+    def test_plugin_autoclass_diagram(self):
+        diagram = self.build('plugin_autoclass.diag')
+        self.assertNodeXY(diagram, {'A_emphasis': (0, 0),
+                                    'B_emphasis': (1, 0),
+                                    'C': (1, 1)})
+        self.assertNodeStyle(diagram, {'A_emphasis': 'dashed',
+                                       'B_emphasis': 'dashed',
+                                       'C': None})
+        self.assertNodeColor(diagram, {'A_emphasis': (255, 0, 0),
+                                       'B_emphasis': (255, 0, 0),
+                                       'C': (255, 255, 255)})
+
+    def test_plugin_attributes_diagram(self):
+        diagram = self.build('plugin_attributes.diag')
+        self.assertNodeTest_Attr1(diagram, {'A': "1", 'B': None})
+        self.assertNodeTest_Attr2(diagram, {'A': "2", 'B': None})
+        self.assertNodeTest_Attr3(diagram, {'A': "3", 'B': None})
diff --git a/src/blockdiag/tests/test_builder_separate.py b/src/blockdiag/tests/test_builder_separate.py
index ced9322..d25fa95 100644
--- a/src/blockdiag/tests/test_builder_separate.py
+++ b/src/blockdiag/tests/test_builder_separate.py
@@ -1,47 +1,41 @@
 # -*- coding: utf-8 -*-
 
+from __future__ import print_function
 from blockdiag.builder import SeparateDiagramBuilder
 from blockdiag.elements import DiagramNode
-from blockdiag.parser import parse_string
-
-
-def __build_diagram(filename):
-    import os
-    testdir = os.path.dirname(__file__)
-    pathname = "%s/diagrams/%s" % (testdir, filename)
-
-    code = open(pathname).read()
-    tree = parse_string(code)
-    return SeparateDiagramBuilder.build(tree)
-
-
-def test_separate1_diagram():
-    diagram = __build_diagram('separate1.diag')
-
-    assert_pos = {0: {'B': (0, 0), 'C': (1, 0), 'D': (4, 0),
-                      'E': (2, 0), 'F': (3, 0)},
-                  1: {'A': (0, 0), 'B': (1, 0), 'D': (3, 0)},
-                  2: {'A': (0, 0), 'Z': (0, 1)}}
-
-    for i, diagram in enumerate(diagram):
-        for node in diagram.traverse_nodes():
-            if isinstance(node, DiagramNode):
-                print node, assert_pos[i][node.id]
-                assert node.xy == assert_pos[i][node.id]
-
-
-def test_separate2_diagram():
-    diagram = __build_diagram('separate2.diag')
-
-    assert_pos = {0: {'A': (0, 0), 'C': (1, 0), 'D': (2, 0),
-                      'E': (0, 2), 'G': (3, 0), 'H': (3, 1)},
-                  1: {'A': (0, 0), 'B': (1, 0), 'E': (2, 0),
-                      'F': (4, 2), 'G': (4, 0), 'H': (4, 1)},
-                  2: {'A': (0, 0), 'F': (2, 2), 'G': (2, 0),
-                      'H': (2, 1), 'Z': (0, 3)}}
-
-    for i, diagram in enumerate(diagram):
-        for node in diagram.traverse_nodes():
-            if isinstance(node, DiagramNode):
-                print node, assert_pos[i][node.id]
-                assert node.xy == assert_pos[i][node.id]
+from blockdiag.tests.utils import BuilderTestCase
+
+
+class TestBuilderSeparated(BuilderTestCase):
+    def _build(self, tree):
+        return SeparateDiagramBuilder.build(tree)
+
+    def test_separate1_diagram(self):
+        diagram = self.build('separate1.diag')
+
+        assert_pos = {0: {'B': (0, 0), 'C': (1, 0), 'D': (4, 0),
+                          'E': (2, 0), 'F': (3, 0)},
+                      1: {'A': (0, 0), 'B': (1, 0), 'D': (3, 0)},
+                      2: {'A': (0, 0), 'Z': (0, 1)}}
+
+        for i, diagram in enumerate(diagram):
+            for node in diagram.traverse_nodes():
+                if isinstance(node, DiagramNode):
+                    print(node)
+                    self.assertEqual(assert_pos[i][node.id], node.xy)
+
+    def test_separate2_diagram(self):
+        diagram = self.build('separate2.diag')
+
+        assert_pos = {0: {'A': (0, 0), 'C': (1, 0), 'D': (2, 0),
+                          'E': (0, 2), 'G': (3, 0), 'H': (3, 1)},
+                      1: {'A': (0, 0), 'B': (1, 0), 'E': (2, 0),
+                          'F': (4, 2), 'G': (4, 0), 'H': (4, 1)},
+                      2: {'A': (0, 0), 'F': (2, 2), 'G': (2, 0),
+                          'H': (2, 1), 'Z': (0, 3)}}
+
+        for i, diagram in enumerate(diagram):
+            for node in diagram.traverse_nodes():
+                if isinstance(node, DiagramNode):
+                    print(node)
+                    self.assertEqual(assert_pos[i][node.id], node.xy)
diff --git a/src/blockdiag/tests/test_generate_diagram.py b/src/blockdiag/tests/test_generate_diagram.py
index 0e4fba7..615c99f 100644
--- a/src/blockdiag/tests/test_generate_diagram.py
+++ b/src/blockdiag/tests/test_generate_diagram.py
@@ -1,147 +1,110 @@
 # -*- coding: utf-8 -*-
 
 import os
-import sys
 import re
-import tempfile
-from blockdiag.tests.utils import argv_wrapper, stderr_wrapper
-from blockdiag.tests.utils import with_pil, supported_pil, with_pdf
+from nose.tools import nottest
+from blockdiag.tests.utils import capture_stderr, TemporaryDirectory
+from blockdiag.tests.utils import supported_pil, supported_pdf
 
 import blockdiag
 import blockdiag.command
 
 
-def get_fontpath():
-    filename = "VL-PGothic-Regular.ttf"
-    testdir = os.path.dirname(__file__)
-    return "%s/truetype/%s" % (testdir, filename)
-
-
-def extra_case(func):
-    pathname = get_fontpath()
-
-    if os.path.exists(pathname):
-        func.__test__ = True
-    else:
-        func.__test__ = False
-
-    return func
+def get_fontpath(testdir=None):
+    if testdir is None:
+        testdir = os.path.dirname(__file__)
+    return os.path.join(testdir, 'truetype', 'VL-PGothic-Regular.ttf')
 
 
- at argv_wrapper
- at stderr_wrapper
-def __build_diagram(filename, _format, args):
-    testdir = os.path.dirname(__file__)
-    diagpath = "%s/diagrams/%s" % (testdir, filename)
-    fontpath = get_fontpath()
-
-    try:
-        tmpdir = tempfile.mkdtemp()
-        tmpfile = tempfile.mkstemp(dir=tmpdir)
-        os.close(tmpfile[0])
-
-        sys.argv = ['blockdiag.py', '-T', _format, '-o', tmpfile[1], diagpath]
-        if args:
-            if isinstance(args[0], (list, tuple)):
-                sys.argv += args[0]
-            else:
-                sys.argv += args
-        if os.path.exists(fontpath):
-            sys.argv += ['-f', fontpath]
-
-        blockdiag.command.main()
-
-        if re.search('ERROR', sys.stderr.getvalue()):
-            raise RuntimeError(sys.stderr.getvalue())
-    finally:
-        for filename in os.listdir(tmpdir):
-            os.unlink(tmpdir + "/" + filename)
-        os.rmdir(tmpdir)
+def get_diagram_files(testdir):
+    diagramsdir = os.path.join(testdir, 'diagrams')
 
+    skipped = ['errors', 'white.gif']
+    for file in os.listdir(diagramsdir):
+        if file in skipped:
+            pass
+        else:
+            yield os.path.join(diagramsdir, file)
 
-def diagram_files():
-    testdir = os.path.dirname(__file__)
-    pathname = "%s/diagrams/" % testdir
 
-    skipped = ['errors',
-               'white.gif']
+def test_generate():
+    mainfunc = blockdiag.command.main
+    basepath = os.path.dirname(__file__)
+    files = get_diagram_files(basepath)
+    options = []
 
-    return [d for d in os.listdir(pathname) if d not in skipped]
+    for testcase in testcase_generator(basepath, mainfunc, files, options):
+        yield testcase
 
 
-def test_generator_svg():
-    args = []
-    if not supported_pil():
-        args.append('--ignore-pil')
+def test_generate_with_separate():
+    mainfunc = blockdiag.command.main
+    basepath = os.path.dirname(__file__)
+    files = get_diagram_files(basepath)
+    filtered = (f for f in files if re.search('separate', f))
+    options = ['--separate']
 
-    for testcase in generator_core('svg', args):
+    for testcase in testcase_generator(basepath, mainfunc, filtered, options):
         yield testcase
 
 
- at with_pil
- at extra_case
-def test_generator_png():
-    for testcase in generator_core('png'):
-        yield testcase
+ at nottest
+def testcase_generator(basepath, mainfunc, files, options):
+    fontpath = get_fontpath(basepath)
+    if os.path.exists(fontpath):
+        options = options + ['-f', fontpath]
 
+    for source in files:
+        yield generate, mainfunc, 'svg', source, options
 
- at with_pdf
- at extra_case
-def test_generator_pdf():
-    for testcase in generator_core('pdf'):
-        yield testcase
+        if supported_pil() and os.path.exists(fontpath):
+            yield generate, mainfunc, 'png', source, options
+            yield generate, mainfunc, 'png', source, options + ['--antialias']
 
+        if supported_pdf() and os.path.exists(fontpath):
+            yield generate, mainfunc, 'pdf', source, options
 
-def generator_core(_format, *args):
-    for diagram in diagram_files():
-        yield __build_diagram, diagram, _format, args
 
-        if re.search('separate', diagram):
-            _args = list(args) + ['--separate']
-            yield __build_diagram, diagram, _format, _args
+ at capture_stderr
+def generate(mainfunc, filetype, source, options):
+    try:
+        tmpdir = TemporaryDirectory()
+        fd, tmpfile = tmpdir.mkstemp()
+        os.close(fd)
 
-        if _format == 'png':
-            _args = list(args) + ['--antialias']
-            yield __build_diagram, diagram, _format, _args
+        mainfunc(['-T', filetype, '-o', tmpfile, source] + list(options))
+    finally:
+        tmpdir.clean()
 
 
- at extra_case
- at argv_wrapper
 def not_exist_font_config_option_test():
     fontpath = get_fontpath()
-    sys.argv = ['', '-f', '/font_is_not_exist', '-f', fontpath, 'input.diag']
-    options = blockdiag.command.BlockdiagOptions(blockdiag).parse()
-    blockdiag.command.detectfont(options)
+    if os.path.exists(fontpath):
+        args = ['-f', '/font_is_not_exist', '-f', fontpath, 'input.diag']
+        options = blockdiag.command.BlockdiagOptions(blockdiag).parse(args)
+
+        from blockdiag.utils.bootstrap import detectfont
+        detectfont(options)
 
 
- at argv_wrapper
- at stderr_wrapper
+ at capture_stderr
 def svg_includes_source_code_tag_test():
     from xml.etree import ElementTree
 
     testdir = os.path.dirname(__file__)
-    diagpath = "%s/diagrams/single_edge.diag" % testdir
-    fontpath = get_fontpath()
+    diagpath = os.path.join(testdir, 'diagrams', 'single_edge.diag')
 
     try:
-        tmpdir = tempfile.mkdtemp()
-        tmpfile = tempfile.mkstemp(dir=tmpdir)
-        os.close(tmpfile[0])
-
-        sys.argv = ['blockdiag.py', '-T', 'SVG', '-o', tmpfile[1], diagpath]
-        if os.path.exists(fontpath):
-            sys.argv += ['-f', fontpath]
-        if not supported_pil():
-            sys.argv += ['--ignore-pil']
-
-        blockdiag.command.main()
+        tmpdir = TemporaryDirectory()
+        fd, tmpfile = tmpdir.mkstemp()
+        os.close(fd)
 
-        if re.search('ERROR', sys.stderr.getvalue()):
-            raise RuntimeError(sys.stderr.getvalue())
+        args = ['-T', 'SVG', '-o', tmpfile, diagpath]
+        blockdiag.command.main(args)
 
         # compare embeded source code
         source_code = open(diagpath).read()
-        tree = ElementTree.parse(tmpfile[1])
+        tree = ElementTree.parse(tmpfile)
         desc = tree.find('{http://www.w3.org/2000/svg}desc')
 
         # strip spaces
@@ -149,6 +112,4 @@ def svg_includes_source_code_tag_test():
         embeded = re.sub('\s+', ' ', desc.text)
         assert source_code == embeded
     finally:
-        for filename in os.listdir(tmpdir):
-            os.unlink(tmpdir + "/" + filename)
-        os.rmdir(tmpdir)
+        tmpdir.clean()
diff --git a/src/blockdiag/tests/test_imagedraw_textfolder.py b/src/blockdiag/tests/test_imagedraw_textfolder.py
index a7dd075..4d1fa2d 100644
--- a/src/blockdiag/tests/test_imagedraw_textfolder.py
+++ b/src/blockdiag/tests/test_imagedraw_textfolder.py
@@ -1,10 +1,16 @@
 # -*- coding: utf-8 -*-
 
-import unittest2
+import sys
+if sys.version_info < (2, 7):
+    import unittest2 as unittest
+else:
+    import unittest
+
 from blockdiag.imagedraw.textfolder import splitlabel
 from blockdiag.imagedraw.textfolder import splittext
 from blockdiag.imagedraw.textfolder import truncate_text
 from blockdiag.utils import Size
+from blockdiag.utils.compat import u
 
 
 CHAR_WIDTH = 14
@@ -17,43 +23,46 @@ class Metrics(object):
         return Size(CHAR_WIDTH * length, CHAR_HEIGHT)
 
 
-class TestTextFolder(unittest2.TestCase):
+class TestTextFolder(unittest.TestCase):
     def test_splitlabel(self):
         # single line text
         text = "abc"
-        self.assertItemsEqual(['abc'], splitlabel(text))
+        self.assertEqual(['abc'], list(splitlabel(text)))
 
         # text include \n (as char a.k.a. \x5c)
         text = "abc\ndef"
-        self.assertItemsEqual(['abc', 'def'], splitlabel(text))
+        self.assertEqual(['abc', 'def'], list(splitlabel(text)))
 
         # text include \n (as mac yensign a.k.a. \xa5)
         text = "abc\xa5ndef"
-        self.assertItemsEqual(['abc', 'def'], splitlabel(text))
+        self.assertEqual(['abc', 'def'], list(splitlabel(text)))
 
         # text includes \n (as text)
         text = "abc\\ndef"
-        self.assertItemsEqual(['abc', 'def'], splitlabel(text))
+        self.assertEqual(['abc', 'def'], list(splitlabel(text)))
 
         # text includes escaped \n
         text = "abc\\\\ndef"
-        self.assertItemsEqual(['abc\\ndef'], splitlabel(text))
+        self.assertEqual(['abc\\ndef'], list(splitlabel(text)))
 
         # text includes escaped \n (\x5c and mac yensign mixed)
-        text = u"abc\xa5\\ndef"
-        self.assertItemsEqual(['abc\\ndef'], splitlabel(text))
+        if sys.version_info[0] == 2:
+            text = u("abc\xa5\\\\ndef")
+        else:
+            text = u("abc\xa5\\ndef")
+        self.assertEqual(['abc\\ndef'], list(splitlabel(text)))
 
         # text include \n and spaces
         text = " abc \n def "
-        self.assertItemsEqual(['abc', 'def'], splitlabel(text))
+        self.assertEqual(['abc', 'def'], list(splitlabel(text)))
 
         # text starts empty line
         text = " \nabc\ndef"
-        self.assertItemsEqual(['abc', 'def'], splitlabel(text))
+        self.assertEqual(['abc', 'def'], list(splitlabel(text)))
 
         # text starts empty line with \n (as text)
         text = " \\nabc\\ndef"
-        self.assertItemsEqual(['', 'abc', 'def'], splitlabel(text))
+        self.assertEqual(['', 'abc', 'def'], list(splitlabel(text)))
 
     def test_splittext_width(self):
         metrics = Metrics()
diff --git a/src/blockdiag/tests/test_imagedraw_utils.py b/src/blockdiag/tests/test_imagedraw_utils.py
new file mode 100644
index 0000000..c2e26d9
--- /dev/null
+++ b/src/blockdiag/tests/test_imagedraw_utils.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+
+import sys
+if sys.version_info < (2, 7):
+    import unittest2 as unittest
+else:
+    import unittest
+
+from blockdiag.imagedraw.utils import (
+    is_zenkaku, zenkaku_len, hankaku_len,
+    string_width, textsize
+)
+from blockdiag.utils.compat import u
+
+
+class TestUtils(unittest.TestCase):
+    def test_is_zenkaku(self):
+        # A
+        self.assertEqual(False, is_zenkaku(u("A")))
+        # あ
+        self.assertEqual(True, is_zenkaku(u("\u3042")))
+
+    def test_zenkaku_len(self):
+        # abc
+        self.assertEqual(0, zenkaku_len(u("abc")))
+        # あいう
+        self.assertEqual(3, zenkaku_len(u("\u3042\u3044\u3046")))
+        # あいc
+        self.assertEqual(2, zenkaku_len(u("\u3042\u3044c")))
+
+    def test_hankaku_len(self):
+        # abc
+        self.assertEqual(3, hankaku_len(u("abc")))
+        # あいう
+        self.assertEqual(0, hankaku_len(u("\u3042\u3044\u3046")))
+        # あいc
+        self.assertEqual(1, hankaku_len(u("\u3042\u3044c")))
+
+    def test_string_width(self):
+        # abc
+        self.assertEqual(3, string_width(u("abc")))
+        # あいう
+        self.assertEqual(6, string_width(u("\u3042\u3044\u3046")))
+        # あいc
+        self.assertEqual(5, string_width(u("\u3042\u3044c")))
+
+    def test_test_textsize(self):
+        from blockdiag.utils.fontmap import FontInfo
+        font = FontInfo('serif', None, 11)
+
+        # abc
+        self.assertEqual((19, 11), textsize(u("abc"), font))
+        # あいう
+        self.assertEqual((33, 11), textsize(u("\u3042\u3044\u3046"), font))
+        # あいc
+        self.assertEqual((29, 11), textsize(u("\u3042\u3044c"), font))
+
+        # abc
+        font = FontInfo('serif', None, 24)
+        self.assertEqual((40, 24), textsize(u("abc"), font))
+
+        # あいう
+        font = FontInfo('serif', None, 18)
+        self.assertEqual((54, 18), textsize(u("\u3042\u3044\u3046"), font))
diff --git a/src/blockdiag/tests/test_parser.py b/src/blockdiag/tests/test_parser.py
index 8676eb3..cde0d6f 100644
--- a/src/blockdiag/tests/test_parser.py
+++ b/src/blockdiag/tests/test_parser.py
@@ -1,141 +1,148 @@
 # -*- coding: utf-8 -*-
+from __future__ import print_function
+
+import sys
+if sys.version_info < (2, 7):
+    import unittest2 as unittest
+else:
+    import unittest
 
 from blockdiag.parser import parse_string, ParseException
-from blockdiag.parser import Graph, SubGraph, Statements, Node, Edge
-from nose.tools import raises
-
-
-def test_parser_basic():
-    # basic digram
-    code = """
-           diagram test {
-              A -> B -> C, D;
-           }
-           """
-
-    tree = parse_string(code)
-    assert isinstance(tree, Graph)
-
-
-def test_parser_without_diagram_id():
-    code = """
-           diagram {
-              A -> B -> C, D;
-           }
-           """
-    tree = parse_string(code)
-    assert isinstance(tree, Graph)
-
-    code = """
-           {
-              A -> B -> C, D;
-           }
-           """
-    tree = parse_string(code)
-    assert isinstance(tree, Graph)
-
-
-def test_parser_empty_diagram():
-    code = """
-           diagram {
-           }
-           """
-    tree = parse_string(code)
-    assert isinstance(tree, Graph)
-
-    code = """
-           {
-           }
-           """
-    tree = parse_string(code)
-    assert isinstance(tree, Graph)
-
-
-def test_parser_diagram_includes_nodes():
-    code = """
-           diagram {
-             A;
-             B [label = "foobar"];
-             C [color = "red"];
-           }
-           """
-    tree = parse_string(code)
-    assert isinstance(tree, Graph)
-    assert len(tree.stmts) == 3
-    assert isinstance(tree.stmts[0], Statements)
-    assert isinstance(tree.stmts[0].stmts[0], Node)
-    assert isinstance(tree.stmts[1], Statements)
-    assert isinstance(tree.stmts[1].stmts[0], Node)
-    assert isinstance(tree.stmts[2], Statements)
-    assert isinstance(tree.stmts[2].stmts[0], Node)
-
-
-def test_parser_diagram_includes_edges():
-    code = """
-           diagram {
-             A -> B -> C;
-           }
-           """
-    tree = parse_string(code)
-    assert isinstance(tree, Graph)
-    print tree.stmts
-    assert len(tree.stmts) == 1
-    assert isinstance(tree.stmts[0], Edge)
-
-    code = """
-           diagram {
-             A -> B -> C [style = dotted];
-             D -> E, F;
-           }
-           """
-    tree = parse_string(code)
-    assert isinstance(tree, Graph)
-    print tree.stmts
-    assert len(tree.stmts) == 2
-    assert isinstance(tree.stmts[0], Edge)
-    assert isinstance(tree.stmts[1], Edge)
-
-
-def test_parser_diagram_includes_groups():
-    code = """
-           diagram {
-             group {
-               A; B;
-             }
-             group {
-               C -> D;
-             }
-           }
-           """
-    tree = parse_string(code)
-    assert isinstance(tree, Graph)
-    assert len(tree.stmts) == 2
-
-    assert isinstance(tree.stmts[0], SubGraph)
-    assert len(tree.stmts[0].stmts) == 2
-    assert isinstance(tree.stmts[0].stmts[0], Statements)
-    assert isinstance(tree.stmts[0].stmts[0].stmts[0], Node)
-    assert isinstance(tree.stmts[0].stmts[1], Statements)
-    assert isinstance(tree.stmts[0].stmts[1].stmts[0], Node)
-
-    assert isinstance(tree.stmts[1], SubGraph)
-    assert len(tree.stmts[1].stmts) == 1
-    assert isinstance(tree.stmts[1].stmts[0], Edge)
-
-
-def test_parser_diagram_includes_diagram_attributes():
-    code = """
-           diagram {
-             fontsize = 12;
-             node_width = 80;
-           }
-           """
-    tree = parse_string(code)
-    assert isinstance(tree, Graph)
-    assert len(tree.stmts) == 2
-
-
- at raises(ParseException)
-def test_parser_parenthesis_ness():
-    code = ""
-    parse_string(code)
+from blockdiag.parser import Diagram, Group, Statements, Node, Edge
+
+
+class TestParser(unittest.TestCase):
+    def test_basic(self):
+        # basic digram
+        code = """
+               diagram test {
+                  A -> B -> C, D;
+               }
+               """
+
+        tree = parse_string(code)
+        self.assertIsInstance(tree, Diagram)
+
+    def test_without_diagram_id(self):
+        code = """
+               diagram {
+                  A -> B -> C, D;
+               }
+               """
+        tree = parse_string(code)
+        self.assertIsInstance(tree, Diagram)
+
+        code = """
+               {
+                  A -> B -> C, D;
+               }
+               """
+        tree = parse_string(code)
+        self.assertIsInstance(tree, Diagram)
+
+    def test_empty_diagram(self):
+        code = """
+               diagram {
+               }
+               """
+        tree = parse_string(code)
+        self.assertIsInstance(tree, Diagram)
+
+        code = """
+               {
+               }
+               """
+        tree = parse_string(code)
+        self.assertIsInstance(tree, Diagram)
+
+    def test_diagram_includes_nodes(self):
+        code = """
+               diagram {
+                 A;
+                 B [label = "foobar"];
+                 C [color = "red"];
+               }
+               """
+        tree = parse_string(code)
+        self.assertIsInstance(tree, Diagram)
+        self.assertEqual(3, len(tree.stmts))
+        self.assertIsInstance(tree.stmts[0], Statements)
+        self.assertIsInstance(tree.stmts[0].stmts[0], Node)
+        self.assertIsInstance(tree.stmts[1], Statements)
+        self.assertIsInstance(tree.stmts[1].stmts[0], Node)
+        self.assertIsInstance(tree.stmts[2], Statements)
+        self.assertIsInstance(tree.stmts[2].stmts[0], Node)
+
+    def test_diagram_includes_edges(self):
+        code = """
+               diagram {
+                 A -> B -> C;
+               }
+               """
+        tree = parse_string(code)
+        self.assertIsInstance(tree, Diagram)
+        self.assertEqual(1, len(tree.stmts))
+        self.assertIsInstance(tree.stmts[0], Statements)
+        self.assertEqual(2, len(tree.stmts[0].stmts))
+        self.assertIsInstance(tree.stmts[0].stmts[0], Edge)
+        self.assertIsInstance(tree.stmts[0].stmts[1], Edge)
+
+        code = """
+               diagram {
+                 A -> B -> C [style = dotted];
+                 D -> E, F;
+               }
+               """
+        tree = parse_string(code)
+        self.assertIsInstance(tree, Diagram)
+        self.assertEqual(2, len(tree.stmts))
+        self.assertIsInstance(tree.stmts[0], Statements)
+        self.assertEqual(2, len(tree.stmts[0].stmts))
+        self.assertIsInstance(tree.stmts[0].stmts[0], Edge)
+        self.assertIsInstance(tree.stmts[0].stmts[1], Edge)
+        self.assertIsInstance(tree.stmts[1], Statements)
+        self.assertEqual(1, len(tree.stmts[1].stmts))
+        self.assertIsInstance(tree.stmts[1].stmts[0], Edge)
+
+    def test_diagram_includes_groups(self):
+        code = """
+               diagram {
+                 group {
+                   A; B;
+                 }
+                 group {
+                   C -> D;
+                 }
+               }
+               """
+        tree = parse_string(code)
+        self.assertIsInstance(tree, Diagram)
+        self.assertEqual(2, len(tree.stmts))
+
+        self.assertIsInstance(tree.stmts[0], Group)
+        self.assertEqual(2, len(tree.stmts[0].stmts))
+        self.assertIsInstance(tree.stmts[0].stmts[0], Statements)
+        self.assertIsInstance(tree.stmts[0].stmts[0].stmts[0], Node)
+        self.assertIsInstance(tree.stmts[0].stmts[1], Statements)
+        self.assertIsInstance(tree.stmts[0].stmts[1].stmts[0], Node)
+
+        self.assertIsInstance(tree.stmts[1], Group)
+        self.assertEqual(1, len(tree.stmts[1].stmts))
+        self.assertIsInstance(tree.stmts[1].stmts[0], Statements)
+        self.assertIsInstance(tree.stmts[1].stmts[0].stmts[0], Edge)
+
+    def test_diagram_includes_diagram_attributes(self):
+        code = """
+               diagram {
+                 fontsize = 12;
+                 node_width = 80;
+               }
+               """
+        tree = parse_string(code)
+        self.assertIsInstance(tree, Diagram)
+        self.assertEqual(2, len(tree.stmts))
+
+    def test_parenthesis_ness(self):
+        with self.assertRaises(ParseException):
+            code = ""
+            parse_string(code)
diff --git a/src/blockdiag/tests/test_pep8.py b/src/blockdiag/tests/test_pep8.py
index 5d946eb..287ef95 100644
--- a/src/blockdiag/tests/test_pep8.py
+++ b/src/blockdiag/tests/test_pep8.py
@@ -1,52 +1,53 @@
-# -*- coding: utf-8 -*-
-
-import os
-import sys
-import pep8
-
-CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
-BASE_DIR = os.path.dirname(CURRENT_DIR)
-
-
-def test_pep8():
-    arglist = [['statistics', True],
-               ['show-source', True],
-               ['repeat', True],
-               ['paths', [BASE_DIR]]]
-
-    pep8style = pep8.StyleGuide(arglist, parse_argv=False, config_file=True)
-    options = pep8style.options
-    if options.doctest:
-        import doctest
-        fail_d, done_d = doctest.testmod(report=False, verbose=options.verbose)
-        fail_s, done_s = pep8.selftest(options)
-        count_failed = fail_s + fail_d
-        if not options.quiet:
-            count_passed = done_d + done_s - count_failed
-            print("%d passed and %d failed." % (count_passed, count_failed))
-            if count_failed:
-                print("Test failed.")
-            else:
-                print("Test passed.")
-        if count_failed:
-            sys.exit(1)
-    if options.testsuite:
-        pep8.init_tests(pep8style)
-    report = pep8style.check_files()
-    if options.statistics:
-        report.print_statistics()
-    if options.benchmark:
-        report.print_benchmark()
-    if options.testsuite and not options.quiet:
-        report.print_results()
-    if report.total_errors:
-        if options.count:
-            sys.stderr.write(str(report.total_errors) + '\n')
-        #sys.exit(1)
-
-    # reporting errors (additional summary)
-    errors = report.get_count('E')
-    warnings = report.get_count('W')
-    message = 'pep8: %d errors / %d warnings' % (errors, warnings)
-    print message
-    assert report.total_errors == 0, message
+# -*- coding: utf-8 -*-
+
+from __future__ import print_function
+import os
+import sys
+import pep8
+
+CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
+BASE_DIR = os.path.dirname(CURRENT_DIR)
+
+
+def test_pep8():
+    arglist = [['statistics', True],
+               ['show-source', True],
+               ['repeat', True],
+               ['paths', [BASE_DIR]]]
+
+    pep8style = pep8.StyleGuide(arglist, parse_argv=False, config_file=True)
+    options = pep8style.options
+    if options.doctest:
+        import doctest
+        fail_d, done_d = doctest.testmod(report=False, verbose=options.verbose)
+        fail_s, done_s = pep8.selftest(options)
+        count_failed = fail_s + fail_d
+        if not options.quiet:
+            count_passed = done_d + done_s - count_failed
+            print("%d passed and %d failed." % (count_passed, count_failed))
+            if count_failed:
+                print("Test failed.")
+            else:
+                print("Test passed.")
+        if count_failed:
+            sys.exit(1)
+    if options.testsuite:
+        pep8.init_tests(pep8style)
+    report = pep8style.check_files()
+    if options.statistics:
+        report.print_statistics()
+    if options.benchmark:
+        report.print_benchmark()
+    if options.testsuite and not options.quiet:
+        report.print_results()
+    if report.total_errors:
+        if options.count:
+            sys.stderr.write(str(report.total_errors) + '\n')
+        #sys.exit(1)
+
+    # reporting errors (additional summary)
+    errors = report.get_count('E')
+    warnings = report.get_count('W')
+    message = 'pep8: %d errors / %d warnings' % (errors, warnings)
+    print(message)
+    assert report.total_errors == 0, message
diff --git a/src/blockdiag/tests/test_rst_directives.py b/src/blockdiag/tests/test_rst_directives.py
index 956306e..c22884e 100644
--- a/src/blockdiag/tests/test_rst_directives.py
+++ b/src/blockdiag/tests/test_rst_directives.py
@@ -1,9 +1,15 @@
 # -*- coding: utf-8 -*-
 
+import sys
+if sys.version_info < (2, 7):
+    import unittest2 as unittest
+else:
+    import unittest
+
 import os
-import tempfile
-import unittest2
-from blockdiag.tests.utils import stderr_wrapper, assertRaises
+import io
+from blockdiag.utils.compat import u
+from blockdiag.tests.utils import capture_stderr, with_pil, TemporaryDirectory
 
 from docutils import nodes
 from docutils.core import publish_doctree, publish_parts
@@ -11,35 +17,22 @@ from docutils.parsers.rst import directives as docutils
 from blockdiag.utils.rst import directives
 
 
-def setup_directive_base(func):
-    def _(self):
-        klass = directives.BlockdiagDirectiveBase
-        docutils.register_directive('blockdiag', klass)
-        func(self)
-
-    _.__name__ = func.__name__
-    return _
-
-
-def use_tmpdir(func):
-    def _(self):
-        try:
-            tmpdir = tempfile.mkdtemp()
-            func(self, tmpdir)
-        finally:
-            for filename in os.listdir(tmpdir):
-                os.unlink(tmpdir + "/" + filename)
-            os.rmdir(tmpdir)
-
-    _.__name__ = func.__name__
-    return _
-
+class TestRstDirectives(unittest.TestCase):
+    def setUp(self):
+        docutils.register_directive('blockdiag',
+                                    directives.BlockdiagDirectiveBase)
+        self._tmpdir = TemporaryDirectory()
 
-class TestRstDirectives(unittest2.TestCase):
     def tearDown(self):
         if 'blockdiag' in docutils._directives:
             del docutils._directives['blockdiag']
 
+        self._tmpdir.clean()
+
+    @property
+    def tmpdir(self):
+        return self._tmpdir.name
+
     def test_setup(self):
         directives.setup()
         options = directives.directive_options
@@ -53,12 +46,10 @@ class TestRstDirectives(unittest2.TestCase):
         self.assertEqual(False, options['nodoctype'])
         self.assertEqual(False, options['noviewbox'])
         self.assertEqual(False, options['inline_svg'])
-        self.assertEqual(False, options['ignore_pil'])
 
     def test_setup_with_args(self):
         directives.setup(format='SVG', antialias=True, fontpath='/dev/null',
-                         nodoctype=True, noviewbox=True, inline_svg=True,
-                         ignore_pil=True)
+                         nodoctype=True, noviewbox=True, inline_svg=True)
         options = directives.directive_options
 
         self.assertIn('blockdiag', docutils._directives)
@@ -70,17 +61,14 @@ class TestRstDirectives(unittest2.TestCase):
         self.assertEqual(True, options['nodoctype'])
         self.assertEqual(True, options['noviewbox'])
         self.assertEqual(True, options['inline_svg'])
-        self.assertEqual(True, options['ignore_pil'])
 
-    @stderr_wrapper
-    @setup_directive_base
+    @capture_stderr
     def test_base_noargs(self):
         text = ".. blockdiag::"
         doctree = publish_doctree(text)
         self.assertEqual(1, len(doctree))
         self.assertEqual(nodes.system_message, type(doctree[0]))
 
-    @setup_directive_base
     def test_base_with_block(self):
         text = ".. blockdiag::\n\n   { A -> B }"
         doctree = publish_doctree(text)
@@ -90,15 +78,13 @@ class TestRstDirectives(unittest2.TestCase):
         self.assertEqual(None, doctree[0]['alt'])
         self.assertEqual({}, doctree[0]['options'])
 
-    @stderr_wrapper
-    @setup_directive_base
+    @capture_stderr
     def test_base_with_emptyblock(self):
         text = ".. blockdiag::\n\n   \n"
         doctree = publish_doctree(text)
         self.assertEqual(1, len(doctree))
         self.assertEqual(nodes.system_message, type(doctree[0]))
 
-    @setup_directive_base
     def test_base_with_filename(self):
         dirname = os.path.dirname(__file__)
         filename = os.path.join(dirname, 'diagrams/node_attribute.diag')
@@ -107,26 +93,23 @@ class TestRstDirectives(unittest2.TestCase):
 
         self.assertEqual(1, len(doctree))
         self.assertEqual(directives.blockdiag, type(doctree[0]))
-        self.assertEqual(open(filename).read(), doctree[0]['code'])
+        self.assertEqual(io.open(filename).read(), doctree[0]['code'])
         self.assertEqual(None, doctree[0]['alt'])
         self.assertEqual({}, doctree[0]['options'])
 
-    @stderr_wrapper
-    @setup_directive_base
+    @capture_stderr
     def test_base_with_filename_not_exists(self):
         text = ".. blockdiag:: unknown.diag"
         doctree = publish_doctree(text)
         self.assertEqual(nodes.system_message, type(doctree[0]))
 
-    @stderr_wrapper
-    @setup_directive_base
+    @capture_stderr
     def test_base_with_block_and_filename(self):
         text = ".. blockdiag:: unknown.diag\n\n   { A -> B }"
         doctree = publish_doctree(text)
         self.assertEqual(1, len(doctree))
         self.assertEqual(nodes.system_message, type(doctree[0]))
 
-    @setup_directive_base
     def test_base_with_options(self):
         text = ".. blockdiag::\n   :alt: hello world\n   :desctable:\n" + \
                "   :maxwidth: 100\n\n   { A -> B }"
@@ -138,47 +121,42 @@ class TestRstDirectives(unittest2.TestCase):
         self.assertEqual(None, doctree[0]['options']['desctable'])
         self.assertEqual(100, doctree[0]['options']['maxwidth'])
 
-    @use_tmpdir
-    def test_block(self, path):
-        directives.setup(format='SVG', outputdir=path)
+    def test_block(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir)
         text = ".. blockdiag::\n\n   { A -> B }"
         doctree = publish_doctree(text)
         self.assertEqual(1, len(doctree))
         self.assertEqual(nodes.image, type(doctree[0]))
         self.assertFalse('alt' in doctree[0])
-        self.assertEqual(0, doctree[0]['uri'].index(path))
+        self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir))
         self.assertFalse('target' in doctree[0])
 
-    @use_tmpdir
-    def test_block_alt(self, path):
-        directives.setup(format='SVG', outputdir=path)
+    def test_block_alt(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir)
         text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
         doctree = publish_doctree(text)
         self.assertEqual(1, len(doctree))
         self.assertEqual(nodes.image, type(doctree[0]))
         self.assertEqual('hello world', doctree[0]['alt'])
-        self.assertEqual(0, doctree[0]['uri'].index(path))
+        self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir))
         self.assertFalse('target' in doctree[0])
 
-    @use_tmpdir
-    @assertRaises(RuntimeError)
-    def test_block_fontpath1(self, path):
-        directives.setup(format='SVG', fontpath=['dummy.ttf'],
-                         outputdir=path)
-        text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
-        publish_doctree(text)
-
-    @use_tmpdir
-    @assertRaises(RuntimeError)
-    def test_block_fontpath2(self, path):
-        directives.setup(format='SVG', fontpath='dummy.ttf',
-                         outputdir=path)
-        text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
-        publish_doctree(text)
-
-    @use_tmpdir
-    def test_caption(self, path):
-        directives.setup(format='SVG', outputdir=path)
+    def test_block_fontpath1(self):
+        with self.assertRaises(RuntimeError):
+            directives.setup(format='SVG', fontpath=['dummy.ttf'],
+                             outputdir=self.tmpdir)
+            text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
+            publish_doctree(text)
+
+    def test_block_fontpath2(self):
+        with self.assertRaises(RuntimeError):
+            directives.setup(format='SVG', fontpath='dummy.ttf',
+                             outputdir=self.tmpdir)
+            text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
+            publish_doctree(text)
+
+    def test_caption(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir)
         text = ".. blockdiag::\n   :caption: hello world\n\n   { A -> B }"
         doctree = publish_doctree(text)
         self.assertEqual(1, len(doctree))
@@ -190,20 +168,18 @@ class TestRstDirectives(unittest2.TestCase):
         self.assertEqual(nodes.Text, type(doctree[0][1][0]))
         self.assertEqual('hello world', doctree[0][1][0])
 
-    @use_tmpdir
-    def test_block_maxwidth(self, path):
-        directives.setup(format='SVG', outputdir=path)
+    def test_block_maxwidth(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir)
         text = ".. blockdiag::\n   :maxwidth: 100\n\n   { A -> B }"
         doctree = publish_doctree(text)
         self.assertEqual(1, len(doctree))
         self.assertEqual(nodes.image, type(doctree[0]))
         self.assertFalse('alt' in doctree[0])
-        self.assertEqual(0, doctree[0]['uri'].index(path))
-        self.assertFalse(0, doctree[0]['target'].index(path))
+        self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir))
+        self.assertFalse(0, doctree[0]['target'].index(self.tmpdir))
 
-    @use_tmpdir
-    def test_block_nodoctype_false(self, path):
-        directives.setup(format='SVG', outputdir=path, nodoctype=False)
+    def test_block_nodoctype_false(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir, nodoctype=False)
         text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
         doctree = publish_doctree(text)
         self.assertEqual(1, len(doctree))
@@ -212,9 +188,8 @@ class TestRstDirectives(unittest2.TestCase):
         self.assertEqual("<?xml version='1.0' encoding='UTF-8'?>\n"
                          "<!DOCTYPE ", svg[:49])
 
-    @use_tmpdir
-    def test_block_nodoctype_true(self, path):
-        directives.setup(format='SVG', outputdir=path, nodoctype=True)
+    def test_block_nodoctype_true(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir, nodoctype=True)
         text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
         doctree = publish_doctree(text)
         self.assertEqual(1, len(doctree))
@@ -223,9 +198,8 @@ class TestRstDirectives(unittest2.TestCase):
         self.assertNotEqual("<?xml version='1.0' encoding='UTF-8'?>\n"
                             "<!DOCTYPE ", svg[:49])
 
-    @use_tmpdir
-    def test_block_noviewbox_false(self, path):
-        directives.setup(format='SVG', outputdir=path, noviewbox=False)
+    def test_block_noviewbox_false(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir, noviewbox=False)
         text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
         doctree = publish_doctree(text)
         self.assertEqual(1, len(doctree))
@@ -233,9 +207,8 @@ class TestRstDirectives(unittest2.TestCase):
         svg = open(doctree[0]['uri']).read()
         self.assertRegexpMatches(svg, '<svg viewBox="0 0 \d+ \d+" ')
 
-    @use_tmpdir
-    def test_block_noviewbox_true(self, path):
-        directives.setup(format='SVG', outputdir=path, noviewbox=True)
+    def test_block_noviewbox_true(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir, noviewbox=True)
         text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
         doctree = publish_doctree(text)
         self.assertEqual(1, len(doctree))
@@ -243,18 +216,16 @@ class TestRstDirectives(unittest2.TestCase):
         svg = open(doctree[0]['uri']).read()
         self.assertRegexpMatches(svg, '<svg height="\d+" width="\d+" ')
 
-    @use_tmpdir
-    def test_block_inline_svg_false(self, path):
-        directives.setup(format='SVG', outputdir=path, inline_svg=False)
+    def test_block_inline_svg_false(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir, inline_svg=False)
         text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
         doctree = publish_doctree(text)
         self.assertEqual(1, len(doctree))
         self.assertEqual(nodes.image, type(doctree[0]))
-        self.assertEqual(1, len(os.listdir(path)))
+        self.assertEqual(1, len(os.listdir(self.tmpdir)))
 
-    @use_tmpdir
-    def test_block_inline_svg_true(self, path):
-        directives.setup(format='SVG', outputdir=path, inline_svg=True)
+    def test_block_inline_svg_true(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir, inline_svg=True)
         text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
         doctree = publish_doctree(text)
         self.assertEqual(1, len(doctree))
@@ -263,26 +234,23 @@ class TestRstDirectives(unittest2.TestCase):
         self.assertEqual(nodes.Text, type(doctree[0][0]))
         self.assertEqual("<?xml version='1.0' encoding='UTF-8'?>\n"
                          "<!DOCTYPE ", doctree[0][0][:49])
-        self.assertEqual(0, len(os.listdir(path)))
+        self.assertEqual(0, len(os.listdir(self.tmpdir)))
 
-    @use_tmpdir
-    def test_block_inline_svg_true_but_nonsvg_format(self, path):
-        directives.setup(format='PNG', outputdir=path, inline_svg=True)
+    @with_pil
+    def test_block_inline_svg_true_but_nonsvg_format(self):
+        directives.setup(format='PNG', outputdir=self.tmpdir, inline_svg=True)
         text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
         doctree = publish_doctree(text)
         self.assertEqual(1, len(doctree))
         self.assertEqual(nodes.image, type(doctree[0]))
 
-    @use_tmpdir
-    def test_block_inline_svg_true_with_multibytes(self, path):
-        directives.setup(format='SVG', outputdir=path,
-                         inline_svg=True, ignore_pil=True)
-        text = u".. blockdiag::\n   :alt: hello world\n\n   { あ -> い }"
+    def test_block_inline_svg_true_with_multibytes(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir)
+        text = u(".. blockdiag::\n   :alt: hello world\n\n   { あ -> い }")
         publish_parts(source=text)
 
-    @use_tmpdir
-    def test_block_max_width_inline_svg(self, path):
-        directives.setup(format='SVG', outputdir=path,
+    def test_block_max_width_inline_svg(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir,
                          nodoctype=True, noviewbox=True, inline_svg=True)
         text = ".. blockdiag::\n   :maxwidth: 100\n\n   { A -> B }"
         doctree = publish_doctree(text)
@@ -292,33 +260,15 @@ class TestRstDirectives(unittest2.TestCase):
         self.assertRegexpMatches(doctree[0][0],
                                  '<svg height="\d+" width="100" ')
 
-    @use_tmpdir
-    def test_block_ignore_pil_false(self, path):
-        directives.setup(format='SVG', outputdir=path, ignore_pil=False)
-        text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
-        doctree = publish_doctree(text)
-        self.assertEqual(1, len(doctree))
-        self.assertEqual(nodes.image, type(doctree[0]))
-
-    @use_tmpdir
-    def test_block_ignore_pil_true(self, path):
-        directives.setup(format='SVG', outputdir=path, ignore_pil=True)
-        text = ".. blockdiag::\n   :alt: hello world\n\n   { A -> B }"
-        doctree = publish_doctree(text)
-        self.assertEqual(1, len(doctree))
-        self.assertEqual(nodes.image, type(doctree[0]))
-
-    @use_tmpdir
-    def test_desctable_without_description(self, path):
-        directives.setup(format='SVG', outputdir=path)
+    def test_desctable_without_description(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir)
         text = ".. blockdiag::\n   :desctable:\n\n   { A -> B }"
         doctree = publish_doctree(text)
         self.assertEqual(1, len(doctree))
         self.assertEqual(nodes.image, type(doctree[0]))
 
-    @use_tmpdir
-    def test_desctable(self, path):
-        directives.setup(format='SVG', outputdir=path)
+    def test_desctable(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir)
         text = ".. blockdiag::\n   :desctable:\n\n" + \
                "   { A [description = foo]; B [description = bar]; }"
         doctree = publish_doctree(text)
@@ -351,9 +301,8 @@ class TestRstDirectives(unittest2.TestCase):
         self.assertEqual('B', tbody[1][0][0][0])
         self.assertEqual('bar', tbody[1][1][0][0])
 
-    @use_tmpdir
-    def test_desctable_using_node_group(self, path):
-        directives.setup(format='SVG', outputdir=path)
+    def test_desctable_using_node_group(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir)
         text = ".. blockdiag::\n   :desctable:\n\n   { A -> B; group { A } }"
         text = ".. blockdiag::\n   :desctable:\n\n" + \
                "   { A [description = foo]; B [description = bar]; " + \
@@ -411,9 +360,8 @@ class TestRstDirectives(unittest2.TestCase):
         self.assertEqual(1, len(tbody[1][1]))
         self.assertEqual('bar', tbody[1][1][0][0])
 
-    @use_tmpdir
-    def test_desctable_with_rest_markups(self, path):
-        directives.setup(format='SVG', outputdir=path)
+    def test_desctable_with_rest_markups(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir)
         text = ".. blockdiag::\n   :desctable:\n\n" + \
                "   { A [description = \"foo *bar* **baz**\"]; " + \
                "     B [description = \"**foo** *bar* baz\"]; }"
@@ -468,9 +416,8 @@ class TestRstDirectives(unittest2.TestCase):
         self.assertEqual(nodes.Text, type(tbody[1][1][0][3]))
         self.assertEqual(' baz', str(tbody[1][1][0][3]))
 
-    @use_tmpdir
-    def test_desctable_with_numbered(self, path):
-        directives.setup(format='SVG', outputdir=path)
+    def test_desctable_with_numbered(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir)
         text = ".. blockdiag::\n   :desctable:\n\n" + \
                "   { A [numbered = 2]; B [numbered = 1]; }"
         doctree = publish_doctree(text)
@@ -503,9 +450,8 @@ class TestRstDirectives(unittest2.TestCase):
         self.assertEqual('2', tbody[1][0][0][0])
         self.assertEqual('A', tbody[1][1][0][0])
 
-    @use_tmpdir
-    def test_desctable_with_numbered_and_description(self, path):
-        directives.setup(format='SVG', outputdir=path)
+    def test_desctable_with_numbered_and_description(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir)
         text = ".. blockdiag::\n   :desctable:\n\n" + \
                "   { A [description = foo, numbered = 2]; " + \
                "     B [description = bar, numbered = 1]; }"
@@ -545,9 +491,8 @@ class TestRstDirectives(unittest2.TestCase):
         self.assertEqual('A', tbody[1][1][0][0])
         self.assertEqual('foo', tbody[1][2][0][0])
 
-    @use_tmpdir
-    def test_desctable_for_edges(self, path):
-        directives.setup(format='SVG', outputdir=path)
+    def test_desctable_for_edges(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir)
         text = ".. blockdiag::\n   :desctable:\n\n" + \
                "   { A -> B [description = \"foo\"]; " + \
                "     C -> D [description = \"bar\"]; " + \
@@ -587,9 +532,8 @@ class TestRstDirectives(unittest2.TestCase):
         self.assertEqual(nodes.Text, type(tbody[1][1][0][0]))
         self.assertEqual('bar', str(tbody[1][1][0][0]))
 
-    @use_tmpdir
-    def test_desctable_for_nodes_and_edges(self, path):
-        directives.setup(format='SVG', outputdir=path)
+    def test_desctable_for_nodes_and_edges(self):
+        directives.setup(format='SVG', outputdir=self.tmpdir)
         text = ".. blockdiag::\n   :desctable:\n\n" + \
                "   { A -> B [description = \"foo\"]; " + \
                "     C -> D [description = \"bar\"]; " + \
diff --git a/src/blockdiag/tests/test_utils_fontmap.py b/src/blockdiag/tests/test_utils_fontmap.py
index 6bd4b8a..464c40f 100644
--- a/src/blockdiag/tests/test_utils_fontmap.py
+++ b/src/blockdiag/tests/test_utils_fontmap.py
@@ -1,20 +1,28 @@
 # -*- coding: utf-8 -*-
 
-import os
 import sys
-import tempfile
-import unittest2
-from blockdiag.tests.utils import stderr_wrapper, assertRaises
+if sys.version_info < (2, 7):
+    import unittest2 as unittest
+else:
+    import unittest
 
-from cStringIO import StringIO
-from blockdiag.utils.collections import namedtuple
+import os
+import tempfile
+from blockdiag.utils.compat import u
+from blockdiag.tests.utils import capture_stderr
+
+try:
+    from io import StringIO
+except ImportError:
+    from cStringIO import StringIO
+from collections import namedtuple
 from blockdiag.utils.fontmap import FontInfo, FontMap
 
 
 FontElement = namedtuple('FontElement', 'fontfamily fontsize')
 
 
-class TestUtilsFontmap(unittest2.TestCase):
+class TestUtilsFontmap(unittest.TestCase):
     def setUp(self):
         fontpath1 = __file__
         fontpath2 = os.path.join(os.path.dirname(__file__), 'utils.py')
@@ -33,33 +41,33 @@ class TestUtilsFontmap(unittest2.TestCase):
         FontInfo("my-cursive", None, 11)
         FontInfo("-fantasy", None, 11)
 
-    @assertRaises(AttributeError)
     def test_fontinfo_invalid_familyname1(self):
-        FontInfo("unknown", None, 11)
+        with self.assertRaises(AttributeError):
+            FontInfo("unknown", None, 11)
 
-    @assertRaises(AttributeError)
     def test_fontinfo_invalid_familyname2(self):
-        FontInfo("sansserif-", None, 11)
+        with self.assertRaises(AttributeError):
+            FontInfo("sansserif-", None, 11)
 
-    @assertRaises(AttributeError)
     def test_fontinfo_invalid_familyname3(self):
-        FontInfo("monospace-unkown", None, 11)
+        with self.assertRaises(AttributeError):
+            FontInfo("monospace-unkown", None, 11)
 
-    @assertRaises(AttributeError)
     def test_fontinfo_invalid_familyname4(self):
-        FontInfo("cursive-bold-bold", None, 11)
+        with self.assertRaises(AttributeError):
+            FontInfo("cursive-bold-bold", None, 11)
 
-    @assertRaises(AttributeError)
     def test_fontinfo_invalid_familyname5(self):
-        FontInfo("SERIF", None, 11)
+        with self.assertRaises(AttributeError):
+            FontInfo("SERIF", None, 11)
 
-    @assertRaises(TypeError)
     def test_fontinfo_invalid_fontsize1(self):
-        FontInfo("serif", None, None)
+        with self.assertRaises(TypeError):
+            FontInfo("serif", None, None)
 
-    @assertRaises(ValueError)
     def test_fontinfo_invalid_fontsize2(self):
-        FontInfo("serif", None, '')
+        with self.assertRaises(ValueError):
+            FontInfo("serif", None, '')
 
     def test_fontinfo_parse(self):
         font = FontInfo("serif", None, 11)
@@ -120,9 +128,9 @@ class TestUtilsFontmap(unittest2.TestCase):
         font = FontInfo("-serif", None, 11)
         self.assertEqual('serif-normal', font.familyname)
 
-    @stderr_wrapper
+    @capture_stderr
     def test_fontmap_empty_config(self):
-        config = StringIO("")
+        config = StringIO(u(""))
         fmap = FontMap(config)
 
         font1 = fmap.find()
@@ -150,7 +158,7 @@ class TestUtilsFontmap(unittest2.TestCase):
         self.assertEqual(font1.path, font4.path)
         self.assertEqual(font1.size, font4.size)
 
-    @stderr_wrapper
+    @capture_stderr
     def test_fontmap_none_config(self):
         fmap = FontMap()
 
@@ -161,7 +169,7 @@ class TestUtilsFontmap(unittest2.TestCase):
         self.assertEqual(11, font1.size)
 
     def test_fontmap_normal_config(self):
-        _config = "[fontmap]\nsansserif: %s\nsansserif-bold: %s\n" % \
+        _config = u("[fontmap]\nsansserif: %s\nsansserif-bold: %s\n") % \
                   (self.fontpath[0], self.fontpath[1])
         config = StringIO(_config)
         fmap = FontMap(config)
@@ -203,20 +211,25 @@ class TestUtilsFontmap(unittest2.TestCase):
         self.assertEqual(font1.size, font6.size)
 
     def test_fontmap_duplicated_fontentry1(self):
-        _config = "[fontmap]\nsansserif: %s\nsansserif: %s\n" % \
+        _config = u("[fontmap]\nsansserif: %s\nsansserif: %s\n") % \
                   (self.fontpath[0], self.fontpath[1])
         config = StringIO(_config)
-        fmap = FontMap(config)
+        if sys.version_info[0] == 2:
+            fmap = FontMap(config)
 
-        font1 = fmap.find()
-        self.assertEqual('sansserif', font1.generic_family)
-        self.assertEqual(self.fontpath[1], font1.path)
-        self.assertEqual(11, font1.size)
+            font1 = fmap.find()
+            self.assertEqual('sansserif', font1.generic_family)
+            self.assertEqual(self.fontpath[1], font1.path)
+            self.assertEqual(11, font1.size)
+        else:
+            import configparser
+            with self.assertRaises(configparser.DuplicateOptionError):
+                FontMap(config)
 
     def test_fontmap_duplicated_fontentry2(self):
         # this testcase is only for python2.6 or later
         if sys.version_info > (2, 6):
-            _config = "[fontmap]\nsansserif: %s\nsansserif-normal: %s\n" % \
+            _config = u("[fontmap]\nsansserif: %s\nsansserif-normal: %s\n") % \
                       (self.fontpath[0], self.fontpath[1])
             config = StringIO(_config)
             fmap = FontMap(config)
@@ -226,9 +239,9 @@ class TestUtilsFontmap(unittest2.TestCase):
             self.assertEqual(self.fontpath[1], font1.path)
             self.assertEqual(11, font1.size)
 
-    @stderr_wrapper
+    @capture_stderr
     def test_fontmap_with_nodefault_fontentry(self):
-        _config = "[fontmap]\nserif: %s\n" % self.fontpath[0]
+        _config = u("[fontmap]\nserif: %s\n") % self.fontpath[0]
         config = StringIO(_config)
         fmap = FontMap(config)
 
@@ -249,9 +262,9 @@ class TestUtilsFontmap(unittest2.TestCase):
         self.assertEqual(None, font3.path)
         self.assertEqual(20, font3.size)
 
-    @stderr_wrapper
+    @capture_stderr
     def test_fontmap_with_nonexistence_fontpath(self):
-        _config = "[fontmap]\nserif: unknown_file\n"
+        _config = u("[fontmap]\nserif: unknown_file\n")
         config = StringIO(_config)
         fmap = FontMap(config)
 
@@ -261,7 +274,7 @@ class TestUtilsFontmap(unittest2.TestCase):
         self.assertEqual(11, font1.size)
 
     def test_fontmap_switch_defaultfamily(self):
-        _config = "[fontmap]\nserif-bold: %s\n" % self.fontpath[0]
+        _config = u("[fontmap]\nserif-bold: %s\n") % self.fontpath[0]
         config = StringIO(_config)
         fmap = FontMap(config)
 
@@ -289,8 +302,8 @@ class TestUtilsFontmap(unittest2.TestCase):
         self.assertEqual(20, font4.size)
 
     def test_fontmap_using_fontalias(self):
-        _config = ("[fontmap]\nserif-bold: %s\n" +
-                   "[fontalias]\ntest = serif-bold\n") % self.fontpath[0]
+        _config = (u("[fontmap]\nserif-bold: %s\n") +
+                   u("[fontalias]\ntest = serif-bold\n")) % self.fontpath[0]
         config = StringIO(_config)
         fmap = FontMap(config)
 
@@ -303,7 +316,7 @@ class TestUtilsFontmap(unittest2.TestCase):
     def test_fontmap_by_file(self):
         tmp = tempfile.mkstemp()
 
-        _config = "[fontmap]\nsansserif: %s\nsansserif-bold: %s\n" % \
+        _config = u("[fontmap]\nsansserif: %s\nsansserif-bold: %s\n") % \
                   (self.fontpath[0], self.fontpath[1])
 
         fp = os.fdopen(tmp[0], 'wt')
@@ -322,13 +335,13 @@ class TestUtilsFontmap(unittest2.TestCase):
     def test_fontmap_including_bom_by_file(self):
         tmp = tempfile.mkstemp()
 
-        _config = ("\xEF\xBB\xBF[fontmap]\nsansserif: %s\n"
-                   "sansserif-bold: %s\n") % \
+        _config = (u("[fontmap]\nsansserif: %s\n") +
+                   u("sansserif-bold: %s\n")) % \
                   (self.fontpath[0], self.fontpath[1])
 
         try:
-            fp = os.fdopen(tmp[0], 'wt')
-            fp.write(_config)
+            fp = os.fdopen(tmp[0], 'wb')
+            fp.write(_config.encode('utf-8-sig'))
             fp.close()
             fmap = FontMap(tmp[1])
 
diff --git a/src/blockdiag/tests/utils.py b/src/blockdiag/tests/utils.py
index f4cd434..d51ddd8 100644
--- a/src/blockdiag/tests/utils.py
+++ b/src/blockdiag/tests/utils.py
@@ -1,17 +1,30 @@
 # -*- coding: utf-8 -*-
-import re
+from __future__ import print_function
+
 import sys
-from StringIO import StringIO
-from nose.tools import eq_
+if sys.version_info < (2, 7):
+    import unittest2 as unittest
+else:
+    import unittest
+
+import os
+import re
+import functools
+from shutil import rmtree
+from tempfile import mkdtemp, mkstemp
 from blockdiag.builder import ScreenNodeBuilder
-from blockdiag.parser import parse_string
+from blockdiag.parser import parse_file
+
+try:
+    from io import StringIO
+except ImportError:
+    from cStringIO import StringIO
 
 
 def supported_pil():
     try:
-        import _imagingft
+        from PIL import _imagingft
         _imagingft
-
         return True
     except:
         return False
@@ -41,87 +54,73 @@ def with_pdf(fn):
     return fn
 
 
-def argv_wrapper(func, argv=[]):
-    def wrap(*args, **kwargs):
-        try:
-            argv = sys.argv
-            sys.argv = []
-            func(*args, **kwargs)
-        finally:
-            sys.argv = argv
-
-    wrap.__name__ = func.__name__
-    return wrap
-
-
-def stderr_wrapper(func):
+def capture_stderr(func):
     def wrap(*args, **kwargs):
         try:
             stderr = sys.stderr
             sys.stderr = StringIO()
 
-            print args, kwargs
             func(*args, **kwargs)
+
+            if re.search('ERROR', sys.stderr.getvalue()):
+                raise AssertionError('Caught error')
         finally:
             if sys.stderr.getvalue():
-                print "---[ stderr ] ---"
-                print sys.stderr.getvalue()
+                print("---[ stderr ] ---")
+                print(sys.stderr.getvalue())
 
             sys.stderr = stderr
 
-    wrap.__name__ = func.__name__
-    return wrap
+    return functools.wraps(func)(wrap)
+
 
+stderr_wrapper = capture_stderr   # FIXME: deprecated
 
-def assertRaises(exc):
-    def decorator(func):
-        def fn(self, *args, **kwargs):
-            try:
-                func(self, *args, **kwargs)
-            except exc:
-                pass
-            else:
-                msg = '%s does not raise exceptions: %s' % \
-                      (func.__name__, str(exc))
-                self.fail(msg)
 
-        fn.__name__ = func.__name__
-        return fn
+class TemporaryDirectory(object):
+    def __init__(self, suffix='', prefix='tmp', dir=None):
+        self.name = mkdtemp(suffix, prefix, dir)
 
-    return decorator
+    def __del__(self):
+        self.clean()
 
+    def clean(self):
+        if os.path.exists(self.name):
+            rmtree(self.name)
 
-def __build_diagram(filename):
-    import os
-    testdir = os.path.dirname(__file__)
-    pathname = "%s/diagrams/%s" % (testdir, filename)
+    def mkstemp(self, suffix='', prefix='tmp', text=False):
+        return mkstemp(suffix, prefix, self.name, text)
 
-    code = open(pathname).read()
-    tree = parse_string(code)
-    return ScreenNodeBuilder.build(tree)
 
+class BuilderTestCase(unittest.TestCase):
+    def build(self, filename):
+        basedir = os.path.dirname(__file__)
+        pathname = os.path.join(basedir, 'diagrams', filename)
+        return self._build(parse_file(pathname))
 
-def __validate_node_attributes(filename, **kwargs):
-    diagram = __build_diagram(filename)
+    def _build(self, tree):
+        return ScreenNodeBuilder.build(tree)
 
-    for name, values in kwargs.items():
-        if re.match('edge_', name):
-            print "[%s]" % name
-            name = re.sub('edge_', '', name)
-            for (id1, id2), value in values.items():
-                found = False
+    def __getattr__(self, name):
+        if name.startswith('assertNode'):
+            def asserter(diagram, attributes):
+                attr_name = name.replace('assertNode', '').lower()
+                print("[node.%s]" % attr_name)
+                for node in (n for n in diagram.nodes if n.drawable):
+                    print(node)
+                    excepted = attributes[node.id]
+                    self.assertEqual(excepted, getattr(node, attr_name))
+
+            return asserter
+        elif name.startswith('assertEdge'):
+            def asserter(diagram, attributes):
+                attr_name = name.replace('assertEdge', '').lower()
+                print("[edge.%s]" % attr_name)
                 for edge in diagram.edges:
-                    if edge.node1.id == id1 and edge.node2.id == id2:
-                        print edge
-                        eq_(value, getattr(edge, name))
-                        found = True
-
-                if not found:
-                    raise RuntimeError('edge (%s -> %s) is not found' %
-                                       (id1, id2))
+                    print(edge)
+                    expected = attributes[(edge.node1.id, edge.node2.id)]
+                    self.assertEqual(expected, getattr(edge, attr_name))
+
+            return asserter
         else:
-            print "[node.%s]" % name
-            for node in (n for n in diagram.nodes if n.drawable):
-                print node
-                value = getattr(node, name)
-                eq_(values[node.id], value)
+            return getattr(super(BuilderTestCase, self), name)
diff --git a/src/blockdiag/utils/__init__.py b/src/blockdiag/utils/__init__.py
index 2a3eeb6..8b06a0a 100644
--- a/src/blockdiag/utils/__init__.py
+++ b/src/blockdiag/utils/__init__.py
@@ -13,9 +13,10 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+from __future__ import division
 import re
 import math
-from blockdiag.utils.collections import namedtuple
+from collections import namedtuple
 
 
 Size = namedtuple('Size', 'width height')
@@ -28,7 +29,13 @@ class XY(tuple):
         return super(XY, cls).__new__(cls, (x, y))
 
     def __getattr__(self, name):
-        return self[self.mapper[name]]
+        try:
+            return self[self.mapper[name]]
+        except KeyError:
+            raise AttributeError(name)
+
+    def __setattr__(self, name, value):
+        raise TypeError("'XY' object does not support item assignment")
 
     def shift(self, x=0, y=0):
         return self.__class__(self.x + x, self.y + y)
@@ -41,7 +48,10 @@ class Box(list):
         super(Box, self).__init__((x1, y1, x2, y2))
 
     def __getattr__(self, name):
-        return self[self.mapper[name]]
+        try:
+            return self[self.mapper[name]]
+        except KeyError:
+            raise AttributeError(name)
 
     def __repr__(self):
         _format = "<%s (%s, %s) %dx%d at 0x%08x>"
@@ -92,7 +102,7 @@ class Box(list):
 
     @property
     def top(self):
-        return XY(self.x1 + self.width / 2, self.y1)
+        return XY(self.x1 + self.width // 2, self.y1)
 
     @property
     def topright(self):
@@ -104,7 +114,7 @@ class Box(list):
 
     @property
     def bottom(self):
-        return XY(self.x1 + self.width / 2, self.y2)
+        return XY(self.x1 + self.width // 2, self.y2)
 
     @property
     def bottomright(self):
@@ -112,15 +122,18 @@ class Box(list):
 
     @property
     def left(self):
-        return XY(self.x1, self.y1 + self.height / 2)
+        return XY(self.x1, self.y1 + self.height // 2)
 
     @property
     def right(self):
-        return XY(self.x2, self.y1 + self.height / 2)
+        return XY(self.x2, self.y1 + self.height // 2)
 
     @property
     def center(self):
-        return XY(self.x1 + self.width / 2, self.y1 + self.height / 2)
+        return XY(self.x1 + self.width // 2, self.y1 + self.height // 2)
+
+    def to_integer_point(self):
+        return Box(*[int(i) for i in self])
 
 
 def unquote(string):
@@ -144,3 +157,12 @@ def unquote(string):
             return string
     else:
         return string
+
+
+def is_Pillow_available():
+    try:
+        from PIL import _imagingft
+        _imagingft
+        return True
+    except ImportError:
+        return False
diff --git a/src/blockdiag/utils/bootstrap.py b/src/blockdiag/utils/bootstrap.py
index 2ee10a4..535f728 100644
--- a/src/blockdiag/utils/bootstrap.py
+++ b/src/blockdiag/utils/bootstrap.py
@@ -16,7 +16,8 @@
 import os
 import re
 import sys
-from optparse import OptionParser
+import codecs
+from optparse import OptionParser, SUPPRESS_HELP
 from blockdiag import imagedraw
 from blockdiag.utils.config import ConfigParser
 from blockdiag.utils.fontmap import parse_fontpath, FontMap
@@ -26,21 +27,21 @@ class Application(object):
     module = None
     options = None
 
-    def run(self):
+    def run(self, args):
         try:
-            self.parse_options()
+            self.parse_options(args)
             self.create_fontmap()
 
             parsed = self.parse_diagram()
             return self.build_diagram(parsed)
-        except SystemExit, e:
+        except SystemExit as e:
             return e
-        except UnicodeEncodeError, e:
+        except UnicodeEncodeError as e:
             msg = "ERROR: UnicodeEncodeError caught " + \
                   "(check your font settings)\n"
             sys.stderr.write(msg)
             return -1
-        except Exception, e:
+        except Exception as e:
             if self.options and self.options.debug:
                 import traceback
                 traceback.print_exc()
@@ -48,36 +49,19 @@ class Application(object):
                 sys.stderr.write("ERROR: %s\n" % e)
             return -1
 
-    def parse_options(self):
-        self.options = Options(self.module).parse()
+    def parse_options(self, args):
+        self.options = Options(self.module).parse(args)
 
     def create_fontmap(self):
         self.fontmap = create_fontmap(self.options)
 
-        fontpath = self.fontmap.find().path
-        _format = self.options.type.lower()
-        ignore_pil = self.options.ignore_pil
-        if _format in ('png', 'svg') and fontpath and ignore_pil is False:
-            try:
-                try:
-                    from PIL import _imagingft
-                except ImportError:
-                    import _imagingft
-            except:
-                msg = "PIL does not support TrueType fonts, " \
-                      "reinstall PIL (and libfreetype2)"
-                if _format != 'png':
-                    msg += " or use --ignore-pil option"
-
-                raise RuntimeError(msg)
-
     def parse_diagram(self):
-        import codecs
         if self.options.input == '-':
-            stream = codecs.getreader('utf-8')(sys.stdin)
+            stream = codecs.getreader('utf-8-sig')(sys.stdin)
             self.code = stream.read()
         else:
-            self.code = codecs.open(self.options.input, 'r', 'utf-8').read()
+            fp = codecs.open(self.options.input, 'r', 'utf-8-sig')
+            self.code = fp.read()
 
         return self.module.parser.parse_string(self.code)
 
@@ -89,7 +73,6 @@ class Application(object):
         drawer = DiagramDraw(self.options.type, diagram,
                              self.options.output, fontmap=self.fontmap,
                              code=self.code, antialias=self.options.antialias,
-                             ignore_pil=self.options.ignore_pil,
                              nodoctype=self.options.nodoctype,
                              transparency=self.options.transparency)
         drawer.draw()
@@ -107,8 +90,8 @@ class Options(object):
         self.module = module
         self.build_parser()
 
-    def parse(self):
-        self.options, self.args = self.parser.parse_args()
+    def parse(self, args):
+        self.options, self.args = self.parser.parse_args(args)
         self.validate()
         self.read_configfile()
 
@@ -131,8 +114,7 @@ class Options(object):
         p.add_option('--fontmap',
                      help='use FONTMAP file to draw diagram', metavar='FONT')
         p.add_option('--ignore-pil', dest='ignore_pil',
-                     default=False, action='store_true',
-                     help='do not use PIL module forcely (SVG only)')
+                     default=False, action='store_true', help=SUPPRESS_HELP)
         p.add_option('--no-transparency', dest='transparency',
                      default=True, action='store_false',
                      help='do not make transparent background of diagram ' +
@@ -184,9 +166,10 @@ class Options(object):
                 msg = "could not output PDF format; Install reportlab."
                 raise RuntimeError(msg)
 
-        if self.options.ignore_pil and self.options.type != 'SVG':
-            msg = "--ignore-pil option work in SVG images."
-            raise RuntimeError(msg)
+        if self.options.ignore_pil:
+            msg = "WARNING: --ignore-pil option is deprecated " + \
+                  "(detect automatically).\n"
+            sys.stderr.write(msg)
 
         if self.options.nodoctype and self.options.type != 'SVG':
             msg = "--nodoctype option work in SVG images."
@@ -253,6 +236,7 @@ def detectfont(options):
         'AppleGothic.ttf',
         'msgothic.ttf',
         'msgoth04.ttf',
+        'msgothic.ttc',
     ]
 
     fontpath = None
diff --git a/src/blockdiag/utils/collections.py b/src/blockdiag/utils/collections.py
deleted file mode 100644
index 89bfee9..0000000
--- a/src/blockdiag/utils/collections.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-#  Copyright 2011 Takeshi KOMIYA
-#
-#  Licensed under the Apache License, Version 2.0 (the "License");
-#  you may not use this file except in compliance with the License.
-#  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-
-class defaultdict(dict):
-    def __init__(self, default_factory, *args, **kwargs):
-        self.default_factory = default_factory
-
-    def __getitem__(self, key):
-        try:
-            return super(defaultdict, self).__getitem__(key)
-        except:
-            return self.default_factory()
-
-
-def namedtuple(name, fields):
-    'Only space-delimited fields are supported.'
-    def prop(i, name):
-        return (name, property(lambda self: self[i]))
-    methods = dict(prop(i, f) for i, f in enumerate(fields.split(' ')))
-    methods.update({
-        '__new__': lambda cls, *args: tuple.__new__(cls, args),
-        '__repr__': lambda self: '%s(%s)' % (
-            name,
-            ', '.join('%s=%r' % (
-                f, getattr(self, f)) for f in fields.split(' ')))})
-    return type(name, (tuple,), methods)
diff --git a/src/blockdiag/utils/compat.py b/src/blockdiag/utils/compat.py
new file mode 100644
index 0000000..fb7d85a
--- /dev/null
+++ b/src/blockdiag/utils/compat.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+#  Copyright 2011 Takeshi KOMIYA
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import sys
+
+if sys.version_info[0] == 2:
+    string_types = (str, unicode)  # NOQA: pyflakes complains to unicode in py3
+else:
+    string_types = (str,)
+
+
+def u(string):
+    if sys.version_info[0] == 2:
+        return unicode(string, "unicode_escape")  # NOQA: pyflakes complains to unicode in py3
+    else:
+        return string
+
+
+def cmp_to_key(mycmp):
+    """Convert a cmp= function into a key= function"""
+    class K(object):
+        __slots__ = ['obj']
+
+        def __init__(self, obj, *args):
+            self.obj = obj
+
+        def __lt__(self, other):
+            return mycmp(self.obj, other.obj) < 0
+
+        def __gt__(self, other):
+            return mycmp(self.obj, other.obj) > 0
+
+        def __eq__(self, other):
+            return mycmp(self.obj, other.obj) == 0
+
+        def __le__(self, other):
+            return mycmp(self.obj, other.obj) <= 0
+
+        def __ge__(self, other):
+            return mycmp(self.obj, other.obj) >= 0
+
+        def __ne__(self, other):
+            return mycmp(self.obj, other.obj) != 0
+
+        def __hash__(self):
+            raise TypeError('hash not implemented')
+
+    return K
diff --git a/src/blockdiag/utils/config.py b/src/blockdiag/utils/config.py
index d404780..142bd17 100644
--- a/src/blockdiag/utils/config.py
+++ b/src/blockdiag/utils/config.py
@@ -13,9 +13,12 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+import io
 import sys
-import codecs
-from ConfigParser import SafeConfigParser
+try:
+    from configparser import SafeConfigParser
+except ImportError:
+    from ConfigParser import SafeConfigParser
 
 
 class ConfigParser(SafeConfigParser):
@@ -30,10 +33,6 @@ class ConfigParser(SafeConfigParser):
             SafeConfigParser.__init__(self)
 
     def read(self, path):
-        if sys.version_info > (2, 5):
-            fd = codecs.open(path, 'r', 'utf-8-sig')
-        else:
-            fd = codecs.open(path, 'r', 'utf-8')
-
+        fd = io.open(path, 'r', encoding='utf-8-sig')
         self.readfp(fd)
         fd.close()
diff --git a/src/blockdiag/utils/fontmap.py b/src/blockdiag/utils/fontmap.py
index 48133f7..2264283 100644
--- a/src/blockdiag/utils/fontmap.py
+++ b/src/blockdiag/utils/fontmap.py
@@ -17,8 +17,9 @@ import re
 import os
 import sys
 import copy
+from collections import namedtuple
 from blockdiag.utils.config import ConfigParser
-from blockdiag.utils.collections import namedtuple
+from blockdiag.utils.compat import u
 
 
 def parse_fontpath(path):
@@ -151,7 +152,7 @@ class FontMap(object):
             font.size = fontsize
         elif element is not None:
             msg = "Unknown fontfamily: %s" % fontfamily
-            sys.stderr.write("WARNING: %s\n" % msg)
+            sys.stderr.write(u("WARNING: %s\n") % msg)
             elem = namedtuple('Font', 'fontsize')(fontsize)
             font = self.find(elem)
         else:
diff --git a/src/blockdiag/utils/functools.py b/src/blockdiag/utils/functools.py
deleted file mode 100644
index 6d078da..0000000
--- a/src/blockdiag/utils/functools.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-#  Copyright 2011 Takeshi KOMIYA
-#
-#  Licensed under the Apache License, Version 2.0 (the "License");
-#  you may not use this file except in compliance with the License.
-#  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-
-def partial(func, *args, **keywords):
-    def newfunc(*fargs, **fkeywords):
-        newkeywords = keywords.copy()
-        newkeywords.update(fkeywords)
-        return func(*(args + fargs), **newkeywords)
-    newfunc.func = func
-    newfunc.args = args
-    newfunc.keywords = keywords
-    return newfunc
-
-
-def wraps(src):
-    def newfunc(dest):
-        dest.__module__ = src.__module__
-        dest.__name__ = src.__name__
-        dest.__doc__ = src.__doc__
-        d = getattr(src, '__dict__', {})
-        if hasattr(dest, '__dict__'):
-            dest.__dict__.update(d)
-        else:
-            dest.__dict__ = d.copy()
-        return dest
-    return newfunc
diff --git a/src/blockdiag/utils/images.py b/src/blockdiag/utils/images.py
index 9ad96a4..6cc4b92 100644
--- a/src/blockdiag/utils/images.py
+++ b/src/blockdiag/utils/images.py
@@ -13,46 +13,45 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+from __future__ import division
 import re
 from blockdiag.utils import urlutil
+from blockdiag.utils.compat import string_types
 
 try:
     from PIL import Image
 except ImportError:
-    try:
-        import Image
-    except ImportError:
-        class Image:
-            @classmethod
-            def open(cls, filename):
-                return cls(filename)
-
-            def __init__(self, filename):
-                self.filename = filename
-
-            @property
-            def size(self):
-                from blockdiag.utils import jpeg
-                import png
+    class Image:
+        @classmethod
+        def open(cls, filename):
+            return cls(filename)
 
+        def __init__(self, filename):
+            self.filename = filename
+
+        @property
+        def size(self):
+            from blockdiag.utils import jpeg
+            import png
+
+            try:
+                size = jpeg.JpegFile.get_size(self.filename)
+            except:
                 try:
-                    size = jpeg.JpegFile.get_size(self.filename)
+                    if isinstance(self.filename, string_types):
+                        content = open(self.filename, 'r')
+                    else:
+                        self.filename.seek(0)
+                        content = self.filename
+                    image = png.Reader(file=content).read()
+                    size = (image[0], image[1])
                 except:
-                    try:
-                        if isinstance(self.filename, (str, unicode)):
-                            content = open(self.filename, 'r')
-                        else:
-                            self.filename.seek(0)
-                            content = self.filename
-                        image = png.Reader(file=content).read()
-                        size = (image[0], image[1])
-                    except:
-                        size = None
-
-                if hasattr(self.filename, 'seek'):
-                    self.filename.seek(0)
-
-                return size
+                    size = None
+
+            if hasattr(self.filename, 'seek'):
+                self.filename.seek(0)
+
+            return size
 
 _image_size_cache = {}
 
@@ -61,10 +60,13 @@ def get_image_size(filename):
     if filename not in _image_size_cache:
         uri = filename
         if urlutil.isurl(filename):
-            import cStringIO
+            try:
+                from io import StringIO
+            except ImportError:
+                from cStringIO import StringIO
             import urllib
             try:
-                uri = cStringIO.StringIO(urllib.urlopen(filename).read())
+                uri = StringIO(urllib.urlopen(filename).read())
             except:
                 return None
 
@@ -75,10 +77,10 @@ def get_image_size(filename):
 
 def calc_image_size(size, bounded):
     if bounded[0] < size[0] or bounded[1] < size[1]:
-        if (size[0] * 1.0 / bounded[0]) < (size[1] * 1.0 / bounded[1]):
-            size = (size[0] * bounded[1] / size[1], bounded[1])
+        if (size[0] * 1.0 // bounded[0]) < (size[1] * 1.0 // bounded[1]):
+            size = (size[0] * bounded[1] // size[1], bounded[1])
         else:
-            size = (bounded[0], size[1] * bounded[0] / size[0])
+            size = (bounded[0], size[1] * bounded[0] // size[0])
 
     return size
 
diff --git a/src/blockdiag/utils/jpeg.py b/src/blockdiag/utils/jpeg.py
index 1c482cf..85345f4 100644
--- a/src/blockdiag/utils/jpeg.py
+++ b/src/blockdiag/utils/jpeg.py
@@ -14,6 +14,9 @@
 #  limitations under the License.
 
 
+from blockdiag.utils.compat import string_types
+
+
 class StreamReader(object):
     def __init__(self, stream):
         self.stream = stream
@@ -74,7 +77,7 @@ class JpegFile(object):
 
     @classmethod
     def get_size(self, filename):
-        if isinstance(filename, (str, unicode)):
+        if isinstance(filename, string_types):
             image = open(filename, 'rb').read()
         else:
             image = filename.read()
diff --git a/src/blockdiag/utils/myitertools.py b/src/blockdiag/utils/myitertools.py
index f67ca4d..364cc5c 100644
--- a/src/blockdiag/utils/myitertools.py
+++ b/src/blockdiag/utils/myitertools.py
@@ -19,7 +19,7 @@ from itertools import cycle
 def istep(seq, step=2):
     iterable = iter(seq)
     while True:
-        yield [iterable.next() for _ in range(step)]
+        yield [next(iterable) for _ in range(step)]
 
 
 def stepslice(iterable, steps):
@@ -28,19 +28,19 @@ def stepslice(iterable, steps):
 
     while True:
         # skip (1)
-        n = step.next()
+        n = next(step)
         if n == 0:
             pass
         elif n == 1:
-            o = iterable.next()
+            o = next(iterable)
             yield o
             yield o
         else:
-            yield iterable.next()
-            for _ in xrange(n - 2):
-                iterable.next()
-            yield iterable.next()
+            yield next(iterable)
+            for _ in range(n - 2):
+                next(iterable)
+            yield next(iterable)
 
         # skip (2)
-        for _ in xrange(step.next()):
-            iterable.next()
+        for _ in range(next(step)):
+            next(iterable)
diff --git a/src/blockdiag/utils/namedtuple.py b/src/blockdiag/utils/namedtuple.py
deleted file mode 100644
index ec1fe3f..0000000
--- a/src/blockdiag/utils/namedtuple.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-#  Copyright 2011 Takeshi KOMIYA
-#
-#  Licensed under the Apache License, Version 2.0 (the "License");
-#  you may not use this file except in compliance with the License.
-#  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-
-from blockdiag.utils.collections import namedtuple
-namedtuple
diff --git a/src/blockdiag/utils/rst/directives.py b/src/blockdiag/utils/rst/directives.py
index 77d07db..18f116b 100644
--- a/src/blockdiag/utils/rst/directives.py
+++ b/src/blockdiag/utils/rst/directives.py
@@ -14,26 +14,25 @@
 #  limitations under the License.
 
 import os
-import codecs
+import io
+from collections import namedtuple
 from docutils import nodes
 from docutils.parsers import rst
 from docutils.statemachine import ViewList
 from blockdiag import parser
 from blockdiag.builder import ScreenNodeBuilder
 from blockdiag.drawer import DiagramDraw
-from blockdiag.utils.bootstrap import detectfont
-from blockdiag.utils.collections import namedtuple
+from blockdiag.utils.bootstrap import create_fontmap
+from blockdiag.utils.compat import string_types
 from blockdiag.utils.rst.nodes import blockdiag
 
-
 directive_options_default = dict(format='PNG',
                                  antialias=False,
                                  fontpath=None,
                                  outputdir=None,
                                  nodoctype=False,
                                  noviewbox=False,
-                                 inline_svg=False,
-                                 ignore_pil=False)
+                                 inline_svg=False)
 directive_options = {}
 
 
@@ -47,20 +46,6 @@ def relfn2path(env, filename):
     return relfn, os.path.join(env.srcdir, relfn)
 
 
-def cmp_node_number(a, b):
-    try:
-        n1 = int(a[0])
-    except (TypeError, ValueError):
-        n1 = 65535
-
-    try:
-        n2 = int(b[0])
-    except (TypeError, ValueError):
-        n2 = 65535
-
-    return cmp(n1, n2)
-
-
 class BlockdiagDirectiveBase(rst.Directive):
     """ Directive to insert arbitrary dot markup. """
     name = "blockdiag"
@@ -87,7 +72,7 @@ class BlockdiagDirectiveBase(rst.Directive):
 
             try:
                 filename = self.source_filename(self.arguments[0])
-                fp = codecs.open(filename, 'r', 'utf-8')
+                fp = io.open(filename, 'r', encoding='utf-8-sig')
                 try:
                     dotcode = fp.read()
                 finally:
@@ -136,7 +121,7 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
 
         try:
             diagram = self.node2diagram(node)
-        except Exception, e:
+        except Exception as e:
             raise self.warning(e.message)
 
         if 'desctable' in node['options']:
@@ -164,7 +149,7 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
     def node2image(self, node, diagram):
         options = node['options']
         filename = self.image_filename(node)
-        fontpath = self.detectfont()
+        fontmap = self.create_fontmap()
         _format = self.global_options['format'].lower()
 
         if _format == 'svg' and self.global_options['inline_svg'] is True:
@@ -172,7 +157,8 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
 
         kwargs = dict(self.global_options)
         del kwargs['format']
-        drawer = DiagramDraw(_format, diagram, filename, **kwargs)
+        drawer = DiagramDraw(_format, diagram, filename,
+                             fontmap=fontmap, **kwargs)
 
         if filename is None or not os.path.isfile(filename):
             drawer.draw()
@@ -185,7 +171,7 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
                     new_size = (options['maxwidth'], int(size[1] * ratio))
                     content = drawer.save(new_size)
 
-                return nodes.raw('', content.decode('utf-8'), format='html')
+                return nodes.raw('', content, format='html')
 
         size = drawer.pagesize()
         if 'maxwidth' in options and options['maxwidth'] < size[0]:
@@ -207,17 +193,17 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
 
         return image
 
-    def detectfont(self):
-        Options = namedtuple('Options', 'font')
+    def create_fontmap(self):
+        Options = namedtuple('Options', 'font fontmap')
         fontpath = self.global_options['fontpath']
         if isinstance(fontpath, (list, tuple)):
-            options = Options(fontpath)
-        elif isinstance(fontpath, (str, unicode)):
-            options = Options([fontpath])
+            options = Options(fontpath, None)
+        elif isinstance(fontpath, string_types):
+            options = Options([fontpath], None)
         else:
-            options = Options([])
+            options = Options([], None)
 
-        return detectfont(options)
+        return create_fontmap(options)
 
     def image_filename(self, node, prefix='', ext='png'):
         try:
@@ -230,7 +216,7 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
         options = dict(node['options'])
         options.update(font=self.global_options['fontpath'],
                        antialias=self.global_options['antialias'])
-        hashseed = node['code'].encode('utf-8') + str(options)
+        hashseed = (node['code'] + str(options)).encode('utf-8')
         hashed = sha(hashseed).hexdigest()
 
         _format = self.global_options['format']
@@ -260,8 +246,14 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
         widths = [25] + [50] * (len(klass.desctable) - 1)
         headers = [klass.attrname[n] for n in klass.desctable]
 
+        def node_number(node):
+            try:
+                return int(node[0])
+            except (TypeError, ValueError):
+                return 65535
+
         descriptions = [n.to_desctable() for n in nodes if n.drawable]
-        descriptions.sort(cmp_node_number)
+        descriptions.sort(key=node_number)
 
         for i in reversed(range(len(headers))):
             if any(desc[i] for desc in descriptions):
@@ -313,7 +305,7 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
             row = nodes.row()
             for attr in desc:
                 entry = nodes.entry()
-                if not isinstance(attr, (str, unicode)):
+                if not isinstance(attr, string_types):
                     attr = str(attr)
                 self.state.nested_parse(ViewList([attr], source=attr),
                                         0, entry)
diff --git a/src/blockdiag/utils/urlutil.py b/src/blockdiag/utils/urlutil.py
index a1a061e..6d18c45 100644
--- a/src/blockdiag/utils/urlutil.py
+++ b/src/blockdiag/utils/urlutil.py
@@ -1,6 +1,9 @@
 # -*- coding: utf-8 -*-
 
-import urlparse
+try:
+    import urlparse
+except ImportError:
+    import urllib.parse as urlparse
 
 
 def isurl(url):
diff --git a/src/blockdiag/utils/uuid.py b/src/blockdiag/utils/uuid.py
index 26e28b1..feb93ee 100644
--- a/src/blockdiag/utils/uuid.py
+++ b/src/blockdiag/utils/uuid.py
@@ -20,6 +20,8 @@ except ImportError:
     from random import random
     uuid = random
 
+from blockdiag.utils.compat import u
+
 
 def generate():
-    return str(uuid())
+    return u(str(uuid()))
diff --git a/src/blockdiag_sphinxhelper.py b/src/blockdiag_sphinxhelper.py
index 5e9cc99..cb250d3 100644
--- a/src/blockdiag_sphinxhelper.py
+++ b/src/blockdiag_sphinxhelper.py
@@ -13,24 +13,18 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+__all__ = [
+    'core', 'utils'
+]
+
 import blockdiag.parser
 import blockdiag.builder
 import blockdiag.drawer
 core = blockdiag
 
 import blockdiag.utils.bootstrap
-import blockdiag.utils.collections
+import blockdiag.utils.compat
 import blockdiag.utils.fontmap
+import blockdiag.utils.rst.nodes
+import blockdiag.utils.rst.directives
 utils = blockdiag.utils
-
-from blockdiag.utils.rst import nodes
-from blockdiag.utils.rst import directives
-
-# FIXME: obsoleted interface (keep for compatibility)
-from blockdiag import command, parser, builder, drawer
-from blockdiag.utils import collections
-from blockdiag.utils.fontmap import FontMap
-from blockdiag.utils.rst.directives import blockdiag, BlockdiagDirective
-
-(command, parser, builder, drawer, collections,
- FontMap, blockdiag, BlockdiagDirective)
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..ad483b6
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,20 @@
+[tox]
+envlist=py26,py27,py32,py33
+
+[testenv]
+deps=
+    nose
+    pep8
+    flake8
+    docutils
+commands=
+    nosetests
+    flake8 src
+
+[testenv:py26]
+deps=
+    nose
+    pep8
+    flake8
+    docutils
+    unittest2

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/blockdiag.git



More information about the debian-science-commits mailing list