[blockdiag] 16/29: Import Upstream version 1.4.3
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 b42615b7b7ef6c80f36acbb430c81e05ca2857aa
Author: Andreas Tille <tille at debian.org>
Date: Tue Jan 10 11:08:04 2017 +0100
Import Upstream version 1.4.3
---
MANIFEST.in | 5 +-
PKG-INFO | 98 +++-
README.rst | 94 +++-
buildout.cfg | 10 +-
setup.cfg | 10 +
setup.py | 16 +-
src/blockdiag.egg-info/PKG-INFO | 98 +++-
src/blockdiag.egg-info/SOURCES.txt | 12 +-
src/blockdiag.egg-info/requires.txt | 13 +-
src/blockdiag/__init__.py | 2 +-
src/blockdiag/builder.py | 2 +-
src/blockdiag/drawer.py | 3 +-
src/blockdiag/elements.py | 71 +--
src/blockdiag/imagedraw/__init__.py | 10 +-
src/blockdiag/imagedraw/base.py | 2 -
src/blockdiag/imagedraw/pdf.py | 38 +-
src/blockdiag/imagedraw/png.py | 82 ++-
src/blockdiag/imagedraw/simplesvg.py | 5 +-
src/blockdiag/imagedraw/svg.py | 31 +-
src/blockdiag/imagedraw/utils/__init__.py | 19 +-
.../none.py => imagedraw/utils/pillow.py} | 27 +-
src/blockdiag/metrics.py | 9 +-
src/blockdiag/noderenderer/__init__.py | 120 +----
src/blockdiag/noderenderer/actor.py | 30 +-
.../noderenderer/{__init__.py => base.py} | 35 +-
src/blockdiag/noderenderer/beginpoint.py | 2 +-
src/blockdiag/noderenderer/box.py | 2 +-
src/blockdiag/noderenderer/circle.py | 19 +-
src/blockdiag/noderenderer/cloud.py | 2 +-
src/blockdiag/noderenderer/diamond.py | 2 +-
src/blockdiag/noderenderer/dots.py | 2 +-
src/blockdiag/noderenderer/ellipse.py | 2 +-
src/blockdiag/noderenderer/endpoint.py | 2 +-
src/blockdiag/noderenderer/flowchart/database.py | 2 +-
src/blockdiag/noderenderer/flowchart/input.py | 2 +-
src/blockdiag/noderenderer/flowchart/loopin.py | 2 +-
src/blockdiag/noderenderer/flowchart/loopout.py | 2 +-
src/blockdiag/noderenderer/flowchart/terminator.py | 2 +-
src/blockdiag/noderenderer/mail.py | 2 +-
src/blockdiag/noderenderer/minidiamond.py | 2 +-
src/blockdiag/noderenderer/none.py | 2 +-
src/blockdiag/noderenderer/note.py | 2 +-
src/blockdiag/noderenderer/roundedbox.py | 2 +-
src/blockdiag/noderenderer/square.py | 16 +-
src/blockdiag/noderenderer/textbox.py | 2 +-
src/blockdiag/parser.py | 10 +-
src/blockdiag/plugins/__init__.py | 29 +-
src/blockdiag/plugins/autoclass.py | 4 +-
src/blockdiag/tests/diagrams/README | 12 +
.../tests/diagrams/background_url_image.diag | 4 +-
.../diagrams/debian-logo-256color-palettealpha.png | Bin 0 -> 474 bytes
.../tests/diagrams/diagram_attributes.diag | 1 +
.../diagrams/errors/unknown_diagram_type.diag | 3 +
src/blockdiag/tests/diagrams/node_attribute.diag | 1 +
src/blockdiag/tests/diagrams/node_icon.diag | 2 +-
src/blockdiag/tests/rst/__init__.py | 0
src/blockdiag/tests/rst/test_base_directives.py | 129 +++++
.../test_blockdiag_directives.py} | 550 +++++++++++++++------
src/blockdiag/tests/test_boot_params.py | 7 +-
src/blockdiag/tests/test_builder.py | 1 +
src/blockdiag/tests/test_builder_errors.py | 5 +
src/blockdiag/tests/test_builder_node.py | 10 +-
src/blockdiag/tests/test_generate_diagram.py | 17 +-
src/blockdiag/tests/test_pep8.py | 2 +-
src/blockdiag/tests/test_utils.py | 40 ++
src/blockdiag/tests/test_utils_fontmap.py | 18 +-
src/blockdiag/tests/utils.py | 8 +-
src/blockdiag/utils/__init__.py | 38 +-
src/blockdiag/utils/bootstrap.py | 19 +-
src/blockdiag/utils/compat.py | 2 +
src/blockdiag/utils/fontmap.py | 11 +-
src/blockdiag/utils/images.py | 131 +++--
src/blockdiag/utils/jpeg.py | 92 ----
src/blockdiag/utils/{rst/nodes.py => logging.py} | 15 +-
src/blockdiag/utils/rst/directives.py | 203 +++++---
src/blockdiag/utils/rst/nodes.py | 36 +-
src/blockdiag/utils/urlutil.py | 13 +
tox.ini | 10 +-
78 files changed, 1489 insertions(+), 847 deletions(-)
diff --git a/MANIFEST.in b/MANIFEST.in
index 02701f8..a08e362 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -6,4 +6,7 @@ include LICENSE
include blockdiag.1
include tox.ini
recursive-include examples blockdiagrc *.diag *.png *.svg
-recursive-include src *.py *.diag *.gif
+recursive-include src README *.py *.diag *.gif *.png
+
+exclude .drone.io.sh
+exclude examples/update.sh
diff --git a/PKG-INFO b/PKG-INFO
index be630f3..7358389 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
-Metadata-Version: 1.0
+Metadata-Version: 1.1
Name: blockdiag
-Version: 1.3.2
+Version: 1.4.3
Summary: blockdiag generates block-diagram image from text
Home-page: http://blockdiag.com/
Author: Takeshi Komiya
@@ -9,35 +9,42 @@ License: Apache License 2.0
Download-URL: http://pypi.python.org/pypi/blockdiag
Description: `blockdiag` generate block-diagram image file from spec-text file.
+ .. image:: https://drone.io/bitbucket.org/blockdiag/blockdiag/status.png
+ :target: https://drone.io/bitbucket.org/blockdiag/blockdiag
+ :alt: drone.io CI build status
+
+ .. image:: https://pypip.in/v/blockdiag/badge.png
+ :target: https://pypi.python.org/pypi/blockdiag/
+ :alt: Latest PyPI version
+
+ .. image:: https://pypip.in/d/blockdiag/badge.png
+ :target: https://pypi.python.org/pypi/blockdiag/
+ :alt: Number of PyPI downloads
+
+
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
+ 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::
+ Use easy_install or pip::
- $ easy_install blockdiag
+ $ sudo easy_install blockdiag
- If you want to export as PDF format, give pdf arguments::
+ Or
- $ easy_install "blockdiag[pdf]"
+ $ sudo pip install blockdiag
- by buildout
- ------------
- Make environment::
+ If you want to export as PDF format, give pdf arguments::
+
+ $ sudo easy_install "blockdiag[pdf]"
- $ hg clone http://bitbucket.org/tk0miya/blockdiag
- $ cd blockdiag
- $ python bootstrap.py
- $ bin/buildout
Copy and modify ini file. example::
@@ -51,7 +58,7 @@ Description: `blockdiag` generate block-diagram image file from spec-text file.
========================
Few examples are available.
You can get more examples at
- `blockdiag.com <http://blockdiag.com/blockdiag/build/html/index.html>`_ .
+ `blockdiag.com`_ .
simple.diag
------------
@@ -108,9 +115,11 @@ Description: `blockdiag` generate block-diagram image file from spec-text file.
Requirements
============
- * Python 2.6, 2.7, 3.2, 3.3
- * Pillow 2.2.1
- * funcparserlib 0.3.6
+ * Python 2.6, 2.7, 3.2, 3.3, 3.4
+ * Pillow 2.2.1 or later
+ * funcparserlib 0.3.6 or later
+ * reportlab (optional)
+ * wand and imagemagick (optional)
* setuptools
@@ -122,6 +131,53 @@ Description: `blockdiag` generate block-diagram image file from spec-text file.
History
=======
+ 1.4.3 (2014-07-30)
+ ------------------
+ * Show warnings on loading imagedrawers in debug mode
+ * ImageDraw#image() accepts Image objects
+ * Fix bugs
+
+ - PNG: could not load png imagedrawer if could not access PIL.PILLOW_VERSION
+
+
+ 1.4.2 (2014-07-12)
+ ------------------
+ * SVG: Adjust text alignment precisely
+ * Add plugin events: node.changing and cleanup
+ * ImageDraw#image() accepts image from IO objects
+ * Fix bugs
+
+ - PDF: Fix failure text rotating
+ - PDF: Fix failure pasting PNG images (256 palette/transparency)
+ - PNG: Fix background of node was transparent on pasting transparent images
+
+ 1.4.1 (2014-07-02)
+ ------------------
+ * Change interface of docutils node (for sphinxcontrib module)
+ * Fix bugs
+
+ 1.4.0 (2014-06-23)
+ ------------------
+ * Support embedding SVG/EPS images as background
+ * Use wand to paste background images that is not supported by Pillow (if installed)
+ * Add options to blockdiag directive (docutils extension)
+
+ - \:width:
+ - \:height:
+ - \:scale:
+ - \:align:
+ - \:name:
+ - \:class:
+ - \:figwidth:
+ - \:figclass:
+
+ * actor shape supports label rendering
+
+ 1.3.3 (2014-04-26)
+ ------------------
+ * Add diagram attribute: default_node_style
+ * Fix bugs
+
1.3.2 (2013-11-19)
------------------
* Fix bugs
@@ -283,7 +339,7 @@ Description: `blockdiag` generate block-diagram image file from spec-text file.
0.9.0 (2011-09-25)
------------------
* Add icon attribute to node
- * Make transparency to background of PNG images
+ * Make transparency to background of PNG images
* Fix bugs
0.8.9 (2011-08-09)
diff --git a/README.rst b/README.rst
index fa950b3..c21bc23 100644
--- a/README.rst
+++ b/README.rst
@@ -1,34 +1,41 @@
`blockdiag` generate block-diagram image file from spec-text file.
+.. image:: https://drone.io/bitbucket.org/blockdiag/blockdiag/status.png
+ :target: https://drone.io/bitbucket.org/blockdiag/blockdiag
+ :alt: drone.io CI build status
+
+.. image:: https://pypip.in/v/blockdiag/badge.png
+ :target: https://pypi.python.org/pypi/blockdiag/
+ :alt: Latest PyPI version
+
+.. image:: https://pypip.in/d/blockdiag/badge.png
+ :target: https://pypi.python.org/pypi/blockdiag/
+ :alt: Number of PyPI downloads
+
+
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
+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::
+Use easy_install or pip::
- $ easy_install blockdiag
+ $ sudo easy_install blockdiag
-If you want to export as PDF format, give pdf arguments::
+ Or
- $ easy_install "blockdiag[pdf]"
+ $ sudo pip install blockdiag
-by buildout
-------------
-Make environment::
+If you want to export as PDF format, give pdf arguments::
+
+ $ sudo easy_install "blockdiag[pdf]"
- $ hg clone http://bitbucket.org/tk0miya/blockdiag
- $ cd blockdiag
- $ python bootstrap.py
- $ bin/buildout
Copy and modify ini file. example::
@@ -42,7 +49,7 @@ spec-text setting sample
========================
Few examples are available.
You can get more examples at
-`blockdiag.com <http://blockdiag.com/blockdiag/build/html/index.html>`_ .
+`blockdiag.com`_ .
simple.diag
------------
@@ -99,9 +106,11 @@ Execute blockdiag command::
Requirements
============
-* Python 2.6, 2.7, 3.2, 3.3
-* Pillow 2.2.1
-* funcparserlib 0.3.6
+* Python 2.6, 2.7, 3.2, 3.3, 3.4
+* Pillow 2.2.1 or later
+* funcparserlib 0.3.6 or later
+* reportlab (optional)
+* wand and imagemagick (optional)
* setuptools
@@ -113,6 +122,53 @@ Apache License 2.0
History
=======
+1.4.3 (2014-07-30)
+------------------
+* Show warnings on loading imagedrawers in debug mode
+* ImageDraw#image() accepts Image objects
+* Fix bugs
+
+ - PNG: could not load png imagedrawer if could not access PIL.PILLOW_VERSION
+
+
+1.4.2 (2014-07-12)
+------------------
+* SVG: Adjust text alignment precisely
+* Add plugin events: node.changing and cleanup
+* ImageDraw#image() accepts image from IO objects
+* Fix bugs
+
+ - PDF: Fix failure text rotating
+ - PDF: Fix failure pasting PNG images (256 palette/transparency)
+ - PNG: Fix background of node was transparent on pasting transparent images
+
+1.4.1 (2014-07-02)
+------------------
+* Change interface of docutils node (for sphinxcontrib module)
+* Fix bugs
+
+1.4.0 (2014-06-23)
+------------------
+* Support embedding SVG/EPS images as background
+* Use wand to paste background images that is not supported by Pillow (if installed)
+* Add options to blockdiag directive (docutils extension)
+
+ - \:width:
+ - \:height:
+ - \:scale:
+ - \:align:
+ - \:name:
+ - \:class:
+ - \:figwidth:
+ - \:figclass:
+
+* actor shape supports label rendering
+
+1.3.3 (2014-04-26)
+------------------
+* Add diagram attribute: default_node_style
+* Fix bugs
+
1.3.2 (2013-11-19)
------------------
* Fix bugs
@@ -274,7 +330,7 @@ History
0.9.0 (2011-09-25)
------------------
* Add icon attribute to node
-* Make transparency to background of PNG images
+* Make transparency to background of PNG images
* Fix bugs
0.8.9 (2011-08-09)
diff --git a/buildout.cfg b/buildout.cfg
index 69df527..e9627ef 100644
--- a/buildout.cfg
+++ b/buildout.cfg
@@ -1,5 +1,5 @@
[buildout]
-parts = blockdiag test tox static_analysis
+parts = blockdiag test static_analysis
develop = .
@@ -12,16 +12,10 @@ interpreter = py
recipe = pbp.recipe.noserunner
eggs =
blockdiag[rst]
- blockdiag[test]
+ blockdiag[testing]
coverage
unittest-xml-reporting
-[tox]
-recipe = zc.recipe.egg
-eggs =
- tox
- detox
-
[static_analysis]
recipe = zc.recipe.egg
eggs =
diff --git a/setup.cfg b/setup.cfg
index 18b023c..bf5ba50 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -9,6 +9,16 @@ build-base = _build
[sdist]
formats = gztar
+[wheel]
+universal = 1
+
+[aliases]
+release = check -r -s register sdist bdist_wheel upload
+
+[check]
+strict = 1
+restructuredtext = 1
+
[flake8]
ignore = _
diff --git a/setup.py b/setup.py
index 0a0f1a7..359b825 100644
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
-import os
import sys
from setuptools import setup, find_packages
@@ -24,13 +23,18 @@ requires = ['setuptools',
'funcparserlib',
'webcolors',
'Pillow']
-test_requires = ['Nose',
- 'pep8>=1.3']
+pdf_requires = ['reportlab']
+test_requires = ['nose',
+ 'mock',
+ 'pep8>=1.3',
+ 'reportlab',
+ 'docutils']
# only for Python2.6
if sys.version_info > (2, 6) and sys.version_info < (2, 7):
requires.append('OrderedDict')
+ pdf_requires[0] = 'reportlab < 3.0'
test_requires.append('unittest2')
@@ -53,10 +57,8 @@ setup(
include_package_data=True,
install_requires=requires,
extras_require=dict(
- test=test_requires,
- pdf=[
- 'reportlab',
- ],
+ testing=test_requires,
+ pdf=pdf_requires,
rst=[
'docutils',
],
diff --git a/src/blockdiag.egg-info/PKG-INFO b/src/blockdiag.egg-info/PKG-INFO
index be630f3..7358389 100644
--- a/src/blockdiag.egg-info/PKG-INFO
+++ b/src/blockdiag.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
-Metadata-Version: 1.0
+Metadata-Version: 1.1
Name: blockdiag
-Version: 1.3.2
+Version: 1.4.3
Summary: blockdiag generates block-diagram image from text
Home-page: http://blockdiag.com/
Author: Takeshi Komiya
@@ -9,35 +9,42 @@ License: Apache License 2.0
Download-URL: http://pypi.python.org/pypi/blockdiag
Description: `blockdiag` generate block-diagram image file from spec-text file.
+ .. image:: https://drone.io/bitbucket.org/blockdiag/blockdiag/status.png
+ :target: https://drone.io/bitbucket.org/blockdiag/blockdiag
+ :alt: drone.io CI build status
+
+ .. image:: https://pypip.in/v/blockdiag/badge.png
+ :target: https://pypi.python.org/pypi/blockdiag/
+ :alt: Latest PyPI version
+
+ .. image:: https://pypip.in/d/blockdiag/badge.png
+ :target: https://pypi.python.org/pypi/blockdiag/
+ :alt: Number of PyPI downloads
+
+
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
+ 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::
+ Use easy_install or pip::
- $ easy_install blockdiag
+ $ sudo easy_install blockdiag
- If you want to export as PDF format, give pdf arguments::
+ Or
- $ easy_install "blockdiag[pdf]"
+ $ sudo pip install blockdiag
- by buildout
- ------------
- Make environment::
+ If you want to export as PDF format, give pdf arguments::
+
+ $ sudo easy_install "blockdiag[pdf]"
- $ hg clone http://bitbucket.org/tk0miya/blockdiag
- $ cd blockdiag
- $ python bootstrap.py
- $ bin/buildout
Copy and modify ini file. example::
@@ -51,7 +58,7 @@ Description: `blockdiag` generate block-diagram image file from spec-text file.
========================
Few examples are available.
You can get more examples at
- `blockdiag.com <http://blockdiag.com/blockdiag/build/html/index.html>`_ .
+ `blockdiag.com`_ .
simple.diag
------------
@@ -108,9 +115,11 @@ Description: `blockdiag` generate block-diagram image file from spec-text file.
Requirements
============
- * Python 2.6, 2.7, 3.2, 3.3
- * Pillow 2.2.1
- * funcparserlib 0.3.6
+ * Python 2.6, 2.7, 3.2, 3.3, 3.4
+ * Pillow 2.2.1 or later
+ * funcparserlib 0.3.6 or later
+ * reportlab (optional)
+ * wand and imagemagick (optional)
* setuptools
@@ -122,6 +131,53 @@ Description: `blockdiag` generate block-diagram image file from spec-text file.
History
=======
+ 1.4.3 (2014-07-30)
+ ------------------
+ * Show warnings on loading imagedrawers in debug mode
+ * ImageDraw#image() accepts Image objects
+ * Fix bugs
+
+ - PNG: could not load png imagedrawer if could not access PIL.PILLOW_VERSION
+
+
+ 1.4.2 (2014-07-12)
+ ------------------
+ * SVG: Adjust text alignment precisely
+ * Add plugin events: node.changing and cleanup
+ * ImageDraw#image() accepts image from IO objects
+ * Fix bugs
+
+ - PDF: Fix failure text rotating
+ - PDF: Fix failure pasting PNG images (256 palette/transparency)
+ - PNG: Fix background of node was transparent on pasting transparent images
+
+ 1.4.1 (2014-07-02)
+ ------------------
+ * Change interface of docutils node (for sphinxcontrib module)
+ * Fix bugs
+
+ 1.4.0 (2014-06-23)
+ ------------------
+ * Support embedding SVG/EPS images as background
+ * Use wand to paste background images that is not supported by Pillow (if installed)
+ * Add options to blockdiag directive (docutils extension)
+
+ - \:width:
+ - \:height:
+ - \:scale:
+ - \:align:
+ - \:name:
+ - \:class:
+ - \:figwidth:
+ - \:figclass:
+
+ * actor shape supports label rendering
+
+ 1.3.3 (2014-04-26)
+ ------------------
+ * Add diagram attribute: default_node_style
+ * Fix bugs
+
1.3.2 (2013-11-19)
------------------
* Fix bugs
@@ -283,7 +339,7 @@ Description: `blockdiag` generate block-diagram image file from spec-text file.
0.9.0 (2011-09-25)
------------------
* Add icon attribute to node
- * Make transparency to background of PNG images
+ * Make transparency to background of PNG images
* Fix bugs
0.8.9 (2011-08-09)
diff --git a/src/blockdiag.egg-info/SOURCES.txt b/src/blockdiag.egg-info/SOURCES.txt
index e78203b..ad4ca91 100644
--- a/src/blockdiag.egg-info/SOURCES.txt
+++ b/src/blockdiag.egg-info/SOURCES.txt
@@ -48,8 +48,10 @@ src/blockdiag/imagedraw/filters/__init__.py
src/blockdiag/imagedraw/filters/linejump.py
src/blockdiag/imagedraw/utils/__init__.py
src/blockdiag/imagedraw/utils/ellipse.py
+src/blockdiag/imagedraw/utils/pillow.py
src/blockdiag/noderenderer/__init__.py
src/blockdiag/noderenderer/actor.py
+src/blockdiag/noderenderer/base.py
src/blockdiag/noderenderer/beginpoint.py
src/blockdiag/noderenderer/box.py
src/blockdiag/noderenderer/circle.py
@@ -87,9 +89,10 @@ 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
+src/blockdiag/tests/test_utils.py
src/blockdiag/tests/test_utils_fontmap.py
src/blockdiag/tests/utils.py
+src/blockdiag/tests/diagrams/README
src/blockdiag/tests/diagrams/auto_jumping_edge.diag
src/blockdiag/tests/diagrams/background_url_image.diag
src/blockdiag/tests/diagrams/beginpoint_color.diag
@@ -99,6 +102,7 @@ src/blockdiag/tests/diagrams/circular_ref2.diag
src/blockdiag/tests/diagrams/circular_ref_and_parent_node.diag
src/blockdiag/tests/diagrams/circular_ref_to_root.diag
src/blockdiag/tests/diagrams/circular_skipped_edge.diag
+src/blockdiag/tests/diagrams/debian-logo-256color-palettealpha.png
src/blockdiag/tests/diagrams/define_class.diag
src/blockdiag/tests/diagrams/diagram_attributes.diag
src/blockdiag/tests/diagrams/diagram_attributes_order.diag
@@ -213,6 +217,7 @@ src/blockdiag/tests/diagrams/errors/node_follows_group.diag
src/blockdiag/tests/diagrams/errors/unknown_diagram_default_shape.diag
src/blockdiag/tests/diagrams/errors/unknown_diagram_edge_layout.diag
src/blockdiag/tests/diagrams/errors/unknown_diagram_orientation.diag
+src/blockdiag/tests/diagrams/errors/unknown_diagram_type.diag
src/blockdiag/tests/diagrams/errors/unknown_edge_class.diag
src/blockdiag/tests/diagrams/errors/unknown_edge_dir.diag
src/blockdiag/tests/diagrams/errors/unknown_edge_hstyle.diag
@@ -225,13 +230,16 @@ src/blockdiag/tests/diagrams/errors/unknown_node_class.diag
src/blockdiag/tests/diagrams/errors/unknown_node_shape.diag
src/blockdiag/tests/diagrams/errors/unknown_node_style.diag
src/blockdiag/tests/diagrams/errors/unknown_plugin.diag
+src/blockdiag/tests/rst/__init__.py
+src/blockdiag/tests/rst/test_base_directives.py
+src/blockdiag/tests/rst/test_blockdiag_directives.py
src/blockdiag/utils/__init__.py
src/blockdiag/utils/bootstrap.py
src/blockdiag/utils/compat.py
src/blockdiag/utils/config.py
src/blockdiag/utils/fontmap.py
src/blockdiag/utils/images.py
-src/blockdiag/utils/jpeg.py
+src/blockdiag/utils/logging.py
src/blockdiag/utils/myitertools.py
src/blockdiag/utils/urlutil.py
src/blockdiag/utils/uuid.py
diff --git a/src/blockdiag.egg-info/requires.txt b/src/blockdiag.egg-info/requires.txt
index b054c00..ff04388 100644
--- a/src/blockdiag.egg-info/requires.txt
+++ b/src/blockdiag.egg-info/requires.txt
@@ -3,12 +3,15 @@ funcparserlib
webcolors
Pillow
+[pdf]
+reportlab
+
[rst]
docutils
-[pdf]
+[testing]
+nose
+mock
+pep8>=1.3
reportlab
-
-[test]
-Nose
-pep8>=1.3
\ No newline at end of file
+docutils
diff --git a/src/blockdiag/__init__.py b/src/blockdiag/__init__.py
index 09ae5dc..2296c6d 100644
--- a/src/blockdiag/__init__.py
+++ b/src/blockdiag/__init__.py
@@ -13,4 +13,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-__version__ = '1.3.2'
+__version__ = '1.4.3'
diff --git a/src/blockdiag/builder.py b/src/blockdiag/builder.py
index 7ab63cf..e0f9527 100644
--- a/src/blockdiag/builder.py
+++ b/src/blockdiag/builder.py
@@ -239,7 +239,7 @@ class DiagramLayoutManager:
parents = []
for node in circular:
for parent in self.get_parent_nodes(node):
- if not parent in circular:
+ if parent not in circular:
parents.append(parent)
for parent in sorted(parents, key=lambda x: x.order):
diff --git a/src/blockdiag/drawer.py b/src/blockdiag/drawer.py
index cae5d15..2d708ed 100644
--- a/src/blockdiag/drawer.py
+++ b/src/blockdiag/drawer.py
@@ -42,7 +42,8 @@ class DiagramDraw(object):
**kwargs)
self.metrics = self.create_metrics(kwargs.get('basediagram', diagram),
- drawer=self.drawer, **kwargs)
+ drawer=self.drawer,
+ fontmap=kwargs.get('fontmap'))
if self.scale_ratio == 2:
self.metrics = AutoScaler(self.metrics,
scale_ratio=self.scale_ratio)
diff --git a/src/blockdiag/elements.py b/src/blockdiag/elements.py
index bc67124..ff1d8c7 100644
--- a/src/blockdiag/elements.py
+++ b/src/blockdiag/elements.py
@@ -15,10 +15,10 @@
import os
import re
-import sys
import copy
from blockdiag.utils import images, unquote, urlutil, uuid, XY
from blockdiag.utils.compat import u
+from blockdiag.utils.logging import warning
from blockdiag import noderenderer, plugins
@@ -35,6 +35,10 @@ class Base(object):
cls.basecolor = images.color_to_rgb(color)
@classmethod
+ def set_default_style(cls, style):
+ cls.style = style
+
+ @classmethod
def set_default_text_color(cls, color):
cls.textcolor = images.color_to_rgb(color)
@@ -52,6 +56,7 @@ class Base(object):
cls.textcolor = (0, 0, 0)
cls.fontfamily = None
cls.fontsize = None
+ cls.style = None
def duplicate(self):
return copy.copy(self)
@@ -87,7 +92,7 @@ class Base(object):
self.style = value.lower()
else:
class_name = self.__class__.__name__
- msg = "WARNING: unknown %s style: %s\n" % (class_name, value)
+ msg = "unknown %s style: %s" % (class_name, value)
raise AttributeError(msg)
@@ -182,8 +187,9 @@ class DiagramNode(Element):
plugins.fire_node_event(self, 'created')
def set_attribute(self, attr):
- super(DiagramNode, self).set_attribute(attr)
- plugins.fire_node_event(self, 'attr_changed', attr)
+ if plugins.fire_node_event(self, 'attr_changing', attr):
+ super(DiagramNode, self).set_attribute(attr)
+ plugins.fire_node_event(self, 'attr_changed', attr)
def set_linecolor(self, color):
self.linecolor = images.color_to_rgb(color)
@@ -192,22 +198,20 @@ class DiagramNode(Element):
if noderenderer.get(value):
self.shape = value
else:
- msg = "WARNING: unknown node shape: %s\n" % value
+ msg = "unknown node shape: %s" % value
raise AttributeError(msg)
def set_icon(self, value):
if urlutil.isurl(value) or os.path.isfile(value):
self.icon = value
else:
- msg = "WARNING: icon image not found: %s\n" % value
- sys.stderr.write(msg)
+ warning("icon image not found: %s", value)
def set_background(self, value):
if urlutil.isurl(value) or os.path.isfile(value):
self.background = value
else:
- msg = "WARNING: background image not found: %s\n" % value
- sys.stderr.write(msg)
+ warning("background image not found: %s", value)
def set_stacked(self, _):
self.stacked = True
@@ -217,7 +221,7 @@ class DiagramNode(Element):
if value in ('horizontal', 'vertical'):
self.label_orientation = value
else:
- msg = "WARNING: unknown label orientation: %s\n" % value
+ msg = "unknown label orientation: %s" % value
raise AttributeError(msg)
def to_desctable(self):
@@ -333,7 +337,7 @@ class NodeGroup(Element):
if value in ('landscape', 'portrait'):
self.orientation = value
else:
- msg = "WARNING: unknown diagram orientation: %s\n" % value
+ msg = "unknown diagram orientation: %s" % value
raise AttributeError(msg)
def set_shape(self, value):
@@ -341,7 +345,7 @@ class NodeGroup(Element):
if value in ('box', 'line'):
self.shape = value
else:
- msg = "WARNING: unknown group shape: %s\n" % value
+ msg = "unknown group shape: %s" % value
raise AttributeError(msg)
@@ -466,7 +470,7 @@ class DiagramEdge(Base):
elif value == '--':
self.dir = 'none'
else:
- msg = "WARNING: unknown edge dir: %s\n" % value
+ msg = "unknown edge dir: %s" % value
raise AttributeError(msg)
def set_color(self, color):
@@ -489,7 +493,7 @@ class DiagramEdge(Base):
self.dir = 'both'
self.hstyle = value
else:
- msg = "WARNING: unknown edge hstyle: %s\n" % value
+ msg = "unknown edge hstyle: %s" % value
raise AttributeError(msg)
def set_folded(self, _):
@@ -564,13 +568,9 @@ class Diagram(NodeGroup):
self.edge_layout = None
def set_plugin(self, name, attrs):
- try:
- kwargs = dict([str(unquote(attr.name)), unquote(attr.value)]
- for attr in attrs)
- plugins.load([name], diagram=self, **kwargs)
- except:
- msg = "WARNING: fail to load plugin: %s\n" % name
- raise AttributeError(msg)
+ kwargs = dict([str(unquote(attr.name)), unquote(attr.value)]
+ for attr in attrs)
+ plugins.load([name], diagram=self, **kwargs)
def set_plugins(self, value):
modules = [name.strip() for name in value.split(',')]
@@ -580,7 +580,7 @@ class Diagram(NodeGroup):
if noderenderer.get(value):
DiagramNode.set_default_shape(value)
else:
- msg = "WARNING: unknown node shape: %s\n" % value
+ msg = "unknown node shape: %s" % value
raise AttributeError(msg)
def set_default_label_orientation(self, value):
@@ -588,13 +588,11 @@ class Diagram(NodeGroup):
if value in ('horizontal', 'vertical'):
DiagramNode.label_orientation = value
else:
- msg = "WARNING: unknown label orientation: %s\n" % value
+ msg = "unknown label orientation: %s" % value
raise AttributeError(msg)
def set_default_text_color(self, color):
- msg = "WARNING: default_text_color is obsoleted; " + \
- "use default_textcolor\n"
- sys.stderr.write(msg)
+ warning("default_text_color is obsoleted; use default_textcolor")
self.set_default_textcolor(color)
def set_default_textcolor(self, color):
@@ -607,10 +605,15 @@ class Diagram(NodeGroup):
color = images.color_to_rgb(color)
self._DiagramNode.set_default_color(color)
+ def set_default_node_style(self, value):
+ if re.search('^(?:none|solid|dotted|dashed|\d+(,\d+)*)$', value, re.I):
+ self._DiagramNode.set_default_style(value)
+ else:
+ msg = "unknown node style: %s" % value
+ raise AttributeError(msg)
+
def set_default_line_color(self, color):
- msg = "WARNING: default_line_color is obsoleted; " + \
- "use default_linecolor\n"
- sys.stderr.write(msg)
+ warning("default_line_color is obsoleted; use default_linecolor")
self.set_default_linecolor(color)
def set_default_linecolor(self, color):
@@ -640,21 +643,19 @@ class Diagram(NodeGroup):
if value in ('solid', 'blur', 'none'):
self.shadow_style = value
else:
- msg = "WARNING: unknown shadow style: %s\n" % value
+ msg = "unknown shadow style: %s" % value
raise AttributeError(msg)
def set_edge_layout(self, value):
value = value.lower()
if value in ('normal', 'flowchart'):
- msg = u("WARNING: edge_layout is very experimental feature!\n")
- sys.stderr.write(msg)
+ warning("edge_layout is very experimental feature!")
self.edge_layout = value
else:
- msg = "WARNING: unknown edge layout: %s\n" % value
+ msg = "unknown edge layout: %s" % value
raise AttributeError(msg)
def set_fontsize(self, value):
- msg = "WARNING: fontsize is obsoleted; use default_fontsize\n"
- sys.stderr.write(msg)
+ warning("fontsize is obsoleted; use default_fontsize")
self.set_default_fontsize(int(value))
diff --git a/src/blockdiag/imagedraw/__init__.py b/src/blockdiag/imagedraw/__init__.py
index 7534957..f91d459 100644
--- a/src/blockdiag/imagedraw/__init__.py
+++ b/src/blockdiag/imagedraw/__init__.py
@@ -14,18 +14,20 @@
# limitations under the License.
import pkg_resources
+from blockdiag.utils.logging import warning
drawers = {}
-def init_imagedrawers():
+def init_imagedrawers(debug=False):
for drawer in pkg_resources.iter_entry_points('blockdiag_imagedrawers'):
try:
module = drawer.load()
if hasattr(module, 'setup'):
module.setup(module)
- except:
- pass
+ except Exception as exc:
+ if debug:
+ warning('Failed to load %s: %r' % (drawer.module_name, exc))
def install_imagedrawer(ext, drawer):
@@ -34,7 +36,7 @@ def install_imagedrawer(ext, drawer):
def create(_format, filename, **kwargs):
if len(drawers) == 0:
- init_imagedrawers()
+ init_imagedrawers(debug=kwargs.get('debug'))
_format = _format.lower()
if _format in drawers:
diff --git a/src/blockdiag/imagedraw/base.py b/src/blockdiag/imagedraw/base.py
index f53b940..9dcec92 100644
--- a/src/blockdiag/imagedraw/base.py
+++ b/src/blockdiag/imagedraw/base.py
@@ -24,8 +24,6 @@ class ImageDraw(object):
supported_path = False
baseline_text_rendering = False
- _method_cache = {}
-
def set_canvas_size(self, size):
pass
diff --git a/src/blockdiag/imagedraw/pdf.py b/src/blockdiag/imagedraw/pdf.py
index a474072..6fe3637 100644
--- a/src/blockdiag/imagedraw/pdf.py
+++ b/src/blockdiag/imagedraw/pdf.py
@@ -14,14 +14,14 @@
# limitations under the License.
import re
-import sys
import math
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
+from reportlab.lib.utils import ImageReader
from blockdiag.imagedraw import base
-from blockdiag.imagedraw.utils import cached
-from blockdiag.utils import urlutil, Box, Size
+from blockdiag.imagedraw.utils import memoize
+from blockdiag.utils import images, Box, Size
from blockdiag.utils.fontmap import parse_fontpath
from blockdiag.utils.compat import string_types
@@ -128,7 +128,7 @@ class PDFImageDraw(base.ImageDraw):
if 'thick' in kwargs:
self.canvas.setLineWidth(1)
- @cached
+ @memoize
def textlinesize(self, string, font):
self.set_font(font)
width = self.canvas.stringWidth(string, font.path, font.size)
@@ -148,13 +148,13 @@ class PDFImageDraw(base.ImageDraw):
if angle == 90:
box = Box(-box.y2, box.x1, -box.y1, box.x1 + box.width)
- box = box.shift(x=self.size.y, y=self.size.y)
+ box = box.shift(x=self.size.height, y=self.size.height)
elif angle == 180:
box = Box(-box.x2, -box.y2, -box.x1, -box.y2 + box.height)
- box = box.shift(y=self.size.y * 2)
+ box = box.shift(y=self.size.height * 2)
elif angle == 270:
box = Box(box.y1, -box.x2, box.y2, -box.x1)
- box = box.shift(x=-self.size.y, y=self.size.y)
+ box = box.shift(x=-self.size.height, y=self.size.height)
self.set_font(font)
lines = self.textfolder(box, string, font, **kwargs)
@@ -213,18 +213,18 @@ class PDFImageDraw(base.ImageDraw):
self.canvas.drawPath(pd, **params)
def image(self, box, url):
- if urlutil.isurl(url):
- from reportlab.lib.utils import ImageReader
- try:
- url = ImageReader(url)
- except:
- msg = "WARNING: Could not retrieve: %s\n" % url
- sys.stderr.write(msg)
- return
-
- y = self.size[1] - box[3]
- self.canvas.drawImage(url, box.x1, y, box.width, box.height,
- mask='auto', preserveAspectRatio=True)
+ try:
+ image = images.open(url, mode='pillow')
+ if image.mode not in ('RGBA', 'L', 'RGB', 'CYMYK'):
+ # convert to format that reportlab can recognize
+ image = image.convert('RGBA')
+
+ y = self.size[1] - box[3]
+ data = ImageReader(image)
+ self.canvas.drawImage(data, box.x1, y, box.width, box.height,
+ mask='auto', preserveAspectRatio=True)
+ except IOError:
+ pass
def save(self, filename, size, _format):
# Ignore size and format parameter; compatibility for ImageDrawEx.
diff --git a/src/blockdiag/imagedraw/png.py b/src/blockdiag/imagedraw/png.py
index e7529a6..1026919 100644
--- a/src/blockdiag/imagedraw/png.py
+++ b/src/blockdiag/imagedraw/png.py
@@ -15,7 +15,6 @@
from __future__ import division
import re
-import sys
import math
from itertools import tee
try:
@@ -25,13 +24,18 @@ except ImportError:
from functools import partial, wraps
from PIL import Image, ImageDraw, ImageFont, ImageFilter
from blockdiag.imagedraw import base
-from blockdiag.imagedraw.utils import cached
+from blockdiag.imagedraw.utils import memoize
from blockdiag.imagedraw.utils.ellipse import dots as ellipse_dots
-from blockdiag.utils import urlutil, Box, Size, XY
+from blockdiag.utils import images, Box, Size, XY
from blockdiag.utils.fontmap import parse_fontpath, FontMap
from blockdiag.utils.myitertools import istep, stepslice
+# apply monkey patch to pillow
+from blockdiag.imagedraw.utils import pillow
+pillow.apply_patch()
+
+
def point_pairs(xylist):
iterable = iter(xylist)
for pt in iterable:
@@ -271,7 +275,7 @@ class ImageDrawExBase(base.ImageDraw):
textfolder = super(ImageDrawExBase, self).textfolder
return partial(textfolder, scale=self.scale_ratio)
- @cached
+ @memoize
def textlinesize(self, string, font):
ttfont = ttfont_for(font)
if ttfont is None:
@@ -281,8 +285,7 @@ class ImageDrawExBase(base.ImageDraw):
size = Size(int(size[0] * font_ratio),
int(size[1] * font_ratio))
else:
- size = self.draw.textsize(string, font=ttfont)
- size = Size(*size)
+ size = Size(*ttfont.getsize(string))
return size
@@ -305,7 +308,7 @@ class ImageDrawExBase(base.ImageDraw):
text_image = image.resize(basesize, Image.ANTIALIAS)
self.paste(text_image, xy, text_image)
else:
- size = self.draw.textsize(string, font=ttfont)
+ size = ttfont.getsize(string)
# Generate mask to support BDF(bitmap font)
mask = Image.new('1', size)
@@ -352,38 +355,36 @@ class ImageDrawExBase(base.ImageDraw):
self.textarea(box, string, _font, **kwargs)
def image(self, box, url):
- if urlutil.isurl(url):
- try:
- from io import StringIO
- except ImportError:
- from cStringIO import StringIO
- import urllib
- try:
- url = StringIO(urllib.urlopen(url).read())
- except:
- msg = "WARNING: Could not retrieve: %s\n" % url
- sys.stderr.write(msg)
- return
- image = Image.open(url)
-
- # resize image.
- w = min([box.width, image.size[0] * self.scale_ratio])
- h = min([box.height, image.size[1] * self.scale_ratio])
- image.thumbnail((w, h), Image.ANTIALIAS)
-
- # centering image.
- w, h = image.size
- if box.width > w:
- x = box[0] + (box.width - w) // 2
- else:
- x = box[0]
+ try:
+ image = images.open(url, mode='pillow')
+
+ # resize image.
+ w = min([box.width, image.size[0] * self.scale_ratio])
+ h = min([box.height, image.size[1] * self.scale_ratio])
+ image.thumbnail((w, h), Image.ANTIALIAS)
+
+ # centering image.
+ w, h = image.size
+ if box.width > w:
+ x = box[0] + (box.width - w) // 2
+ else:
+ x = box[0]
- if box.height > h:
- y = box[1] + (box.height - h) // 2
- else:
- y = box[1]
+ if box.height > h:
+ y = box[1] + (box.height - h) // 2
+ else:
+ y = box[1]
+
+ if image.mode == 'P':
+ # convert P to RGBA to masking transparent pixels
+ image = image.convert('RGBA')
- self.paste(image, (x, y))
+ if image.mode == 'RGBA':
+ self.paste(image, (x, y), mask=image)
+ else:
+ self.paste(image, (x, y))
+ except IOError:
+ pass
def save(self, filename, size, _format):
if filename:
@@ -400,11 +401,8 @@ class ImageDrawExBase(base.ImageDraw):
self._image.save(self.filename, _format)
image = None
else:
- try:
- from io import StringIO
- except ImportError:
- from cStringIO import StringIO
- tmp = StringIO()
+ from io import BytesIO
+ tmp = BytesIO()
self._image.save(tmp, _format)
image = tmp.getvalue()
diff --git a/src/blockdiag/imagedraw/simplesvg.py b/src/blockdiag/imagedraw/simplesvg.py
index fd5fe0b..61c1268 100644
--- a/src/blockdiag/imagedraw/simplesvg.py
+++ b/src/blockdiag/imagedraw/simplesvg.py
@@ -15,10 +15,7 @@
import re
from blockdiag.utils.compat import u, string_types
-try:
- from io import StringIO
-except ImportError:
- from cStringIO import StringIO
+from io import StringIO
def _escape(s):
diff --git a/src/blockdiag/imagedraw/svg.py b/src/blockdiag/imagedraw/svg.py
index c2a159f..5d8af22 100644
--- a/src/blockdiag/imagedraw/svg.py
+++ b/src/blockdiag/imagedraw/svg.py
@@ -13,16 +13,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import os
import re
-import base64
+from base64 import b64encode
from blockdiag.imagedraw import base as _base
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 import memoize
from blockdiag.imagedraw.utils.ellipse import endpoints as ellipse_endpoints
-from blockdiag.utils import urlutil, Box, XY, is_Pillow_available
+from blockdiag.utils import images, Box, XY, is_Pillow_available
feGaussianBlur = svgclass('feGaussianBlur')
@@ -103,7 +104,7 @@ class SVGImageDrawElement(_base.ImageDraw):
stroke_width=thick, **drawing_params(kwargs))
self.svg.addElement(r)
- @cached
+ @memoize
def textlinesize(self, string, font, **kwargs):
if is_Pillow_available():
if not hasattr(self, '_pil_drawer'):
@@ -118,9 +119,12 @@ class SVGImageDrawElement(_base.ImageDraw):
def text(self, point, string, font, **kwargs):
fill = kwargs.get('fill')
+ size = self.textlinesize(string, font)
+ point = point.shift(size.width / 2)
t = text(point.x, point.y, string, fill=rgb(fill),
font_family=font.generic_family, font_size=font.size,
- font_weight=font.weight, font_style=font.style)
+ font_weight=font.weight, font_style=font.style,
+ text_anchor='middle', textLength=size.width)
self.svg.addElement(t)
def textarea(self, box, string, font, **kwargs):
@@ -227,9 +231,20 @@ class SVGImageDrawElement(_base.ImageDraw):
self.svg.addElement(pg)
def image(self, box, url):
- if not urlutil.isurl(url):
- string = open(url, 'rb').read()
- url = "data:;base64," + str(base64.b64encode(string))
+ if hasattr(url, 'read'):
+ url = "data:;base64," + str(b64encode(url.read()))
+ else:
+ ext = os.path.splitext(url)[1].lower()
+ if ext not in ('.jpg', '.png', '.gif'):
+ stream = None
+ try:
+ stream = images.open(url, mode='png')
+ url = "data:;base64," + str(b64encode(stream.read()))
+ except IOError:
+ pass
+ finally:
+ if stream:
+ stream.close()
im = image(url, box.x1, box.y1, box.width, box.height)
self.svg.addElement(im)
diff --git a/src/blockdiag/imagedraw/utils/__init__.py b/src/blockdiag/imagedraw/utils/__init__.py
index 364c6d3..a8f717c 100644
--- a/src/blockdiag/imagedraw/utils/__init__.py
+++ b/src/blockdiag/imagedraw/utils/__init__.py
@@ -15,6 +15,7 @@
import math
import unicodedata
+from functools import wraps
from blockdiag.utils import Size
from blockdiag.utils.compat import u
@@ -54,17 +55,15 @@ def textsize(string, font):
return Size(int(math.ceil(width)), font.size)
-def cached(fn):
- def func(self, *args, **kwargs):
- name = fn.__name__
- key = args + tuple(kwargs.values())
+def memoize(fn):
+ fn.cache = {}
- if name not in self._method_cache:
- self._method_cache[name] = {}
+ @wraps(fn)
+ def func(*args, **kwargs):
+ key = str(args) + str(kwargs)
+ if key not in fn.cache:
+ fn.cache[key] = fn(*args, **kwargs)
- if key not in self._method_cache[name]:
- self._method_cache[name][key] = fn(self, *args, **kwargs)
-
- return self._method_cache[name][key]
+ return fn.cache[key]
return func
diff --git a/src/blockdiag/noderenderer/none.py b/src/blockdiag/imagedraw/utils/pillow.py
similarity index 50%
copy from src/blockdiag/noderenderer/none.py
copy to src/blockdiag/imagedraw/utils/pillow.py
index 81ef91d..6e6f839 100644
--- a/src/blockdiag/noderenderer/none.py
+++ b/src/blockdiag/imagedraw/utils/pillow.py
@@ -13,23 +13,26 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from blockdiag.noderenderer import NodeShape
-from blockdiag.noderenderer import install_renderer
+def patch_FreeTypeFont_getsize():
+ try:
+ from PIL import PILLOW_VERSION
+ from PIL.ImageFont import FreeTypeFont
-class NoneShape(NodeShape):
- def __init__(self, node, metrics=None):
- super(NoneShape, self).__init__(node, metrics)
+ # Avoid offset problem in Pillow (>= 2.2.0, < 2.6.0)
+ if "2.2.0" <= PILLOW_VERSION < "2.6.0":
+ original_getsize = FreeTypeFont.getsize
- p = metrics.cell(node).center
- self.connectors = [p, p, p, p]
+ def getsize(self, string):
+ size = original_getsize(self, string)
+ offset = self.getoffset(string)
- def render_label(self, drawer, **kwargs):
- pass
+ return (size[0] + offset[0], size[1] + offset[1])
- def render_shape(self, drawer, _, **kwargs):
+ FreeTypeFont.getsize = getsize
+ except ImportError:
pass
-def setup(self):
- install_renderer('none', NoneShape)
+def apply_patch():
+ patch_FreeTypeFont_getsize()
diff --git a/src/blockdiag/metrics.py b/src/blockdiag/metrics.py
index dcb671d..c7da902 100644
--- a/src/blockdiag/metrics.py
+++ b/src/blockdiag/metrics.py
@@ -140,8 +140,8 @@ class DiagramMetrics(object):
span_width = cellsize * 8
span_height = cellsize * 5
- def __init__(self, diagram, **kwargs):
- self.drawer = kwargs.get('drawer')
+ def __init__(self, diagram, drawer=None, fontmap=None):
+ self.drawer = drawer
if diagram.node_width is not None:
self.node_width = diagram.node_width
@@ -155,7 +155,6 @@ class DiagramMetrics(object):
if diagram.span_height is not None:
self.span_height = diagram.span_height
- fontmap = kwargs.get('fontmap')
if fontmap is not None:
self.fontmap = fontmap
else:
@@ -350,8 +349,8 @@ class SpreadSheetMetrics(SubMetrics):
x, y = self._node_bottomright(dummy, use_padding=False)
x_span = self.span_width[width]
y_span = self.span_height[height]
- return XY(x + margin.x + padding[1] + x_span,
- y + margin.y + padding[2] + y_span)
+ return Size(x + margin.x + padding[1] + x_span,
+ y + margin.y + padding[2] + y_span)
class NodeMetrics(SubMetrics):
diff --git a/src/blockdiag/noderenderer/__init__.py b/src/blockdiag/noderenderer/__init__.py
index 9bf590f..7495ec4 100644
--- a/src/blockdiag/noderenderer/__init__.py
+++ b/src/blockdiag/noderenderer/__init__.py
@@ -15,7 +15,7 @@
from __future__ import division
import pkg_resources
-from blockdiag.utils import images, Box, XY
+from blockdiag.noderenderer.base import NodeShape # NOQA: backward compatibility
renderers = {}
searchpath = []
@@ -48,121 +48,3 @@ def get(shape):
return renderers[name]
return renderers.get(shape)
-
-
-class NodeShape(object):
- def __init__(self, node, metrics=None):
- self.node = node
- self.metrics = metrics
-
- m = self.metrics.cell(self.node)
- self.textalign = 'center'
- self.connectors = [m.top, m.right, m.bottom, m.left]
-
- if node.icon is None:
- self.iconbox = None
- self.textbox = m.box
- else:
- image_size = images.get_image_size(node.icon)
- if image_size is None:
- iconsize = (0, 0)
- else:
- boundedbox = [metrics.node_width // 2, metrics.node_height]
- iconsize = images.calc_image_size(image_size, boundedbox)
-
- vmargin = (metrics.node_height - iconsize[1]) // 2
- self.iconbox = Box(m.topleft.x,
- m.topleft.y + vmargin,
- m.topleft.x + iconsize[0],
- m.topleft.y + vmargin + iconsize[1])
-
- self.textbox = Box(self.iconbox[2], m.top.y,
- m.bottomright.x, m.bottomright.y)
-
- def render(self, drawer, _format, **kwargs):
- if self.node.stacked and not kwargs.get('stacked'):
- node = self.node.duplicate()
- node.label = ""
- node.background = ""
- for i in range(2, 0, -1):
- # use original_metrics FORCE
- r = self.metrics.original_metrics.cellsize // 2 * i
- metrics = self.metrics.shift(r, r)
-
- self.__class__(node, metrics).render(drawer, _format,
- stacked=True, **kwargs)
-
- if hasattr(self, 'render_vector_shape') and _format == 'SVG':
- self.render_vector_shape(drawer, _format, **kwargs)
- else:
- self.render_shape(drawer, _format, **kwargs)
-
- self.render_icon(drawer, **kwargs)
- self.render_label(drawer, **kwargs)
- self.render_number_badge(drawer, **kwargs)
-
- def render_icon(self, drawer, **kwargs):
- 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):
- pass
-
- def render_label(self, drawer, **kwargs):
- if not kwargs.get('shadow'):
- font = self.metrics.font_for(self.node)
- drawer.textarea(self.textbox, self.node.label, font,
- rotate=self.node.rotate,
- fill=self.node.textcolor, halign=self.textalign,
- line_spacing=self.metrics.line_spacing,
- orientation=self.node.label_orientation)
-
- def render_number_badge(self, drawer, **kwargs):
- if self.node.numbered is not None and kwargs.get('shadow') is None:
- badgeFill = kwargs.get('badgeFill')
-
- xy = self.metrics.cell(self.node).topleft
- 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)
- drawer.ellipse(box, outline=self.node.linecolor, fill=badgeFill)
- drawer.textarea(box, self.node.numbered, font,
- rotate=self.node.rotate,
- fill=self.node.textcolor)
-
- @property
- def top(self):
- return self.connectors[0]
-
- @property
- def left(self):
- return self.connectors[3]
-
- @property
- def right(self):
- point = self.connectors[1]
- if self.node.stacked:
- point = XY(point.x + self.metrics.cellsize, point.y)
- return point
-
- @property
- def bottom(self):
- point = self.connectors[2]
- if self.node.stacked:
- point = XY(point.x, point.y + self.metrics.cellsize)
- return point
-
- def shift_shadow(self, value):
- xdiff = self.metrics.shadow_offset.x
- ydiff = self.metrics.shadow_offset.y
-
- if isinstance(value, XY):
- ret = XY(value.x + xdiff, value.y + ydiff)
- elif isinstance(value, Box):
- ret = Box(value.x1 + xdiff, value.y1 + ydiff,
- value.x2 + xdiff, value.y2 + ydiff)
- elif isinstance(value, (list, tuple)):
- ret = [self.shift_shadow(x) for x in value]
-
- return ret
diff --git a/src/blockdiag/noderenderer/actor.py b/src/blockdiag/noderenderer/actor.py
index 17be67c..9db3850 100644
--- a/src/blockdiag/noderenderer/actor.py
+++ b/src/blockdiag/noderenderer/actor.py
@@ -14,25 +14,38 @@
# 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
+from blockdiag.noderenderer.base import NodeShape
+from blockdiag.utils import XY, Box, Size
class Actor(NodeShape):
def __init__(self, node, metrics=None):
super(Actor, self).__init__(node, metrics)
- shortside = min(self.node.width or metrics.node_height,
- self.node.height or metrics.node_height)
+ m = metrics.cell(node)
+ if node.label:
+ font = metrics.font_for(self.node)
+ textsize = metrics.textsize(node.label, font)
+ shortside = min(m.width, m.height - textsize.height)
+ else:
+ textsize = Size(0, 0)
+ shortside = min(m.width, m.height)
+
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[1] = XY(self.center.x + r * 4, self.center.y)
- self.connectors[2] = XY(self.center.x, self.center.y + r * 4)
+ self.connectors[2] = XY(self.center.x,
+ self.center.y + r * 4 + textsize.height)
self.connectors[3] = XY(self.center.x - r * 4, self.center.y)
+ self.textbox = Box(m.left.x,
+ self.center.y + r * 4,
+ m.right.x,
+ self.connectors[2].y)
+
def head_part(self):
r = self.radius * 3 // 2
pt = self.metrics.cell(self.node).center.shift(y=-self.radius * 3)
@@ -76,10 +89,6 @@ class Actor(NodeShape):
def render_shape(self, drawer, _, **kwargs):
fill = kwargs.get('fill')
- # FIXME: Actor does not support
- # - background image
- # - textarea
-
# draw body part
body = self.body_part()
if kwargs.get('shadow'):
@@ -105,9 +114,6 @@ class Actor(NodeShape):
drawer.ellipse(head, fill=self.node.color,
outline=self.node.linecolor, style=self.node.style)
- def render_label(self, drawer, **kwargs):
- pass
-
def setup(self):
install_renderer('actor', Actor)
diff --git a/src/blockdiag/noderenderer/__init__.py b/src/blockdiag/noderenderer/base.py
similarity index 87%
copy from src/blockdiag/noderenderer/__init__.py
copy to src/blockdiag/noderenderer/base.py
index 9bf590f..33bf6c2 100644
--- a/src/blockdiag/noderenderer/__init__.py
+++ b/src/blockdiag/noderenderer/base.py
@@ -14,44 +14,11 @@
# limitations under the License.
from __future__ import division
-import pkg_resources
from blockdiag.utils import images, Box, XY
-renderers = {}
-searchpath = []
-
-
-def init_renderers():
- for plugin in pkg_resources.iter_entry_points('blockdiag_noderenderer'):
- module = plugin.load()
- if hasattr(module, 'setup'):
- module.setup(module)
-
-
-def install_renderer(name, renderer):
- renderers[name] = renderer
-
-
-def set_default_namespace(path):
- searchpath[:] = []
- for path in path.split(','):
- searchpath.append(path)
-
-
-def get(shape):
- if not renderers:
- init_renderers()
-
- for path in searchpath:
- name = "%s.%s" % (path, shape)
- if name in renderers:
- return renderers[name]
-
- return renderers.get(shape)
-
class NodeShape(object):
- def __init__(self, node, metrics=None):
+ def __init__(self, node, metrics):
self.node = node
self.metrics = metrics
diff --git a/src/blockdiag/noderenderer/beginpoint.py b/src/blockdiag/noderenderer/beginpoint.py
index 3229918..dec5e2e 100644
--- a/src/blockdiag/noderenderer/beginpoint.py
+++ b/src/blockdiag/noderenderer/beginpoint.py
@@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
from blockdiag.utils import XY, Box
diff --git a/src/blockdiag/noderenderer/box.py b/src/blockdiag/noderenderer/box.py
index 4f2abb3..ab81814 100644
--- a/src/blockdiag/noderenderer/box.py
+++ b/src/blockdiag/noderenderer/box.py
@@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
class Box(NodeShape):
diff --git a/src/blockdiag/noderenderer/circle.py b/src/blockdiag/noderenderer/circle.py
index da404ab..a4e1577 100644
--- a/src/blockdiag/noderenderer/circle.py
+++ b/src/blockdiag/noderenderer/circle.py
@@ -1,7 +1,21 @@
# -*- 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 __future__ import division
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
from blockdiag.utils import Box, XY
@@ -9,7 +23,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 + \
+ cell = metrics.cell(node)
+ r = min(cell.box.width, cell.box.height) // 2 + \
metrics.cellsize // 2
pt = metrics.cell(node).center
self.connectors = [XY(pt.x, pt.y - r), # top
diff --git a/src/blockdiag/noderenderer/cloud.py b/src/blockdiag/noderenderer/cloud.py
index 9fca4d4..a218fbe 100644
--- a/src/blockdiag/noderenderer/cloud.py
+++ b/src/blockdiag/noderenderer/cloud.py
@@ -14,8 +14,8 @@
# limitations under the License.
from __future__ import division
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
from blockdiag.utils import Box
from blockdiag.imagedraw.simplesvg import pathdata
diff --git a/src/blockdiag/noderenderer/diamond.py b/src/blockdiag/noderenderer/diamond.py
index 4567c22..3fdb6b2 100644
--- a/src/blockdiag/noderenderer/diamond.py
+++ b/src/blockdiag/noderenderer/diamond.py
@@ -14,8 +14,8 @@
# limitations under the License.
from __future__ import division
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
from blockdiag.utils import Box, XY
diff --git a/src/blockdiag/noderenderer/dots.py b/src/blockdiag/noderenderer/dots.py
index d5bf313..e2a145f 100644
--- a/src/blockdiag/noderenderer/dots.py
+++ b/src/blockdiag/noderenderer/dots.py
@@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
from blockdiag.utils import Box, XY
diff --git a/src/blockdiag/noderenderer/ellipse.py b/src/blockdiag/noderenderer/ellipse.py
index caecd7b..e0f09c8 100644
--- a/src/blockdiag/noderenderer/ellipse.py
+++ b/src/blockdiag/noderenderer/ellipse.py
@@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
from blockdiag.utils import Box
diff --git a/src/blockdiag/noderenderer/endpoint.py b/src/blockdiag/noderenderer/endpoint.py
index ac1eca3..73657b5 100644
--- a/src/blockdiag/noderenderer/endpoint.py
+++ b/src/blockdiag/noderenderer/endpoint.py
@@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
from blockdiag.utils import XY, Box
diff --git a/src/blockdiag/noderenderer/flowchart/database.py b/src/blockdiag/noderenderer/flowchart/database.py
index 2396483..3af4bac 100644
--- a/src/blockdiag/noderenderer/flowchart/database.py
+++ b/src/blockdiag/noderenderer/flowchart/database.py
@@ -14,8 +14,8 @@
# limitations under the License.
from __future__ import division
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
from blockdiag.utils import XY, Box
from blockdiag.imagedraw.simplesvg import pathdata
diff --git a/src/blockdiag/noderenderer/flowchart/input.py b/src/blockdiag/noderenderer/flowchart/input.py
index 6e928c4..5359fc1 100644
--- a/src/blockdiag/noderenderer/flowchart/input.py
+++ b/src/blockdiag/noderenderer/flowchart/input.py
@@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
from blockdiag.utils import Box, XY
diff --git a/src/blockdiag/noderenderer/flowchart/loopin.py b/src/blockdiag/noderenderer/flowchart/loopin.py
index ac25d0c..b36b8b6 100644
--- a/src/blockdiag/noderenderer/flowchart/loopin.py
+++ b/src/blockdiag/noderenderer/flowchart/loopin.py
@@ -14,8 +14,8 @@
# limitations under the License.
from __future__ import division
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
from blockdiag.utils import Box, XY
diff --git a/src/blockdiag/noderenderer/flowchart/loopout.py b/src/blockdiag/noderenderer/flowchart/loopout.py
index 19b962d..3be9451 100644
--- a/src/blockdiag/noderenderer/flowchart/loopout.py
+++ b/src/blockdiag/noderenderer/flowchart/loopout.py
@@ -14,8 +14,8 @@
# limitations under the License.
from __future__ import division
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
from blockdiag.utils import Box, XY
diff --git a/src/blockdiag/noderenderer/flowchart/terminator.py b/src/blockdiag/noderenderer/flowchart/terminator.py
index 4d77baa..7d9d1f7 100644
--- a/src/blockdiag/noderenderer/flowchart/terminator.py
+++ b/src/blockdiag/noderenderer/flowchart/terminator.py
@@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
from blockdiag.utils import XY, Box
from blockdiag.imagedraw.simplesvg import pathdata
diff --git a/src/blockdiag/noderenderer/mail.py b/src/blockdiag/noderenderer/mail.py
index b7935c3..91c7c2a 100644
--- a/src/blockdiag/noderenderer/mail.py
+++ b/src/blockdiag/noderenderer/mail.py
@@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
from blockdiag.utils import Box, XY
diff --git a/src/blockdiag/noderenderer/minidiamond.py b/src/blockdiag/noderenderer/minidiamond.py
index 39014d1..5ad1d07 100644
--- a/src/blockdiag/noderenderer/minidiamond.py
+++ b/src/blockdiag/noderenderer/minidiamond.py
@@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
from blockdiag.utils import Box, XY
diff --git a/src/blockdiag/noderenderer/none.py b/src/blockdiag/noderenderer/none.py
index 81ef91d..77ce79c 100644
--- a/src/blockdiag/noderenderer/none.py
+++ b/src/blockdiag/noderenderer/none.py
@@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
class NoneShape(NodeShape):
diff --git a/src/blockdiag/noderenderer/note.py b/src/blockdiag/noderenderer/note.py
index 5cfc15a..9f13351 100644
--- a/src/blockdiag/noderenderer/note.py
+++ b/src/blockdiag/noderenderer/note.py
@@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
from blockdiag.utils import XY
diff --git a/src/blockdiag/noderenderer/roundedbox.py b/src/blockdiag/noderenderer/roundedbox.py
index 90f2351..db074d2 100644
--- a/src/blockdiag/noderenderer/roundedbox.py
+++ b/src/blockdiag/noderenderer/roundedbox.py
@@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
from blockdiag.utils import XY, Box
from blockdiag.imagedraw.simplesvg import pathdata
diff --git a/src/blockdiag/noderenderer/square.py b/src/blockdiag/noderenderer/square.py
index 6b874e1..cc44dd7 100644
--- a/src/blockdiag/noderenderer/square.py
+++ b/src/blockdiag/noderenderer/square.py
@@ -1,7 +1,21 @@
# -*- 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 __future__ import division
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
from blockdiag.utils import Box, XY
diff --git a/src/blockdiag/noderenderer/textbox.py b/src/blockdiag/noderenderer/textbox.py
index 3b58cb1..76bf4a7 100644
--- a/src/blockdiag/noderenderer/textbox.py
+++ b/src/blockdiag/noderenderer/textbox.py
@@ -14,8 +14,8 @@
# limitations under the License.
from __future__ import division
-from blockdiag.noderenderer import NodeShape
from blockdiag.noderenderer import install_renderer
+from blockdiag.noderenderer.base import NodeShape
from blockdiag.utils import images, Box, XY
diff --git a/src/blockdiag/parser.py b/src/blockdiag/parser.py
index 33bf44a..7f0a14f 100644
--- a/src/blockdiag/parser.py
+++ b/src/blockdiag/parser.py
@@ -46,7 +46,7 @@ from blockdiag.utils.compat import u
ENCODING = 'utf-8'
-Diagram = namedtuple('Diagram', 'type id stmts')
+Diagram = namedtuple('Diagram', 'id stmts')
Group = namedtuple('Group', 'id stmts')
Node = namedtuple('Node', 'id attrs')
Attr = namedtuple('Attr', 'name value')
@@ -209,6 +209,11 @@ def parse(seq):
# A;
# }
#
+ diagram_id = (
+ (keyword('diagram') | keyword('blockdiag')) +
+ maybe(_id)
+ >> list
+ )
diagram_inline_stmt = (
extension_stmt |
group_inline_stmt
@@ -217,8 +222,7 @@ def parse(seq):
many(diagram_inline_stmt + skip(maybe(op(';'))))
)
diagram = (
- maybe(keyword('diagram') | keyword('blockdiag')) +
- maybe(_id) +
+ maybe(diagram_id) +
op_('{') +
diagram_inline_stmt_list +
op_('}')
diff --git a/src/blockdiag/plugins/__init__.py b/src/blockdiag/plugins/__init__.py
index 8dffd4f..6c0da69 100644
--- a/src/blockdiag/plugins/__init__.py
+++ b/src/blockdiag/plugins/__init__.py
@@ -16,6 +16,7 @@
from pkg_resources import iter_entry_points
node_handlers = []
+general_handlers = {}
def load(plugins, diagram, **kwargs):
@@ -26,27 +27,43 @@ def load(plugins, diagram, **kwargs):
module.setup(module, diagram, **kwargs)
break
else:
- msg = "WARNING: unknown plugin: %s\n" % name
+ msg = "unknown plugin: %s" % name
raise AttributeError(msg)
+def install_general_handler(name, handler):
+ if name not in general_handlers:
+ general_handlers[name] = []
+
+ general_handlers[name].append(handler)
+
+
+def fire_general_event(name, *args):
+ handlers = general_handlers.get(name, [])
+ return all(handler(*args) for handler in handlers)
+
+
def install_node_handler(handler):
if handler not in node_handlers:
node_handlers.append(handler)
def fire_node_event(node, name, *args):
- method = "on_" + name
- for handler in node_handlers:
- getattr(handler, method)(node, *args)
+ return all(handler.fire(name, node, *args) for handler in node_handlers)
class NodeHandler(object):
def __init__(self, diagram, **kwargs):
self.diagram = diagram
+ def fire(self, name, *args):
+ return getattr(self, "on_" + name)(*args)
+
def on_created(self, node):
- pass
+ return True
+
+ def on_attr_changing(self, node, attr):
+ return True
def on_attr_changed(self, node, attr):
- pass
+ return True
diff --git a/src/blockdiag/plugins/autoclass.py b/src/blockdiag/plugins/autoclass.py
index 2e74c1d..676e8ce 100644
--- a/src/blockdiag/plugins/autoclass.py
+++ b/src/blockdiag/plugins/autoclass.py
@@ -20,7 +20,7 @@ from blockdiag import plugins
class AutoClass(plugins.NodeHandler):
def on_created(self, node):
if node.id is None:
- return
+ return True
for name, klass in self.diagram.classes.items():
pattern = "_%s$" % re.escape(name)
@@ -29,6 +29,8 @@ class AutoClass(plugins.NodeHandler):
node.label = re.sub(pattern, '', node.id)
node.set_attributes(klass.attrs)
+ return True
+
def setup(self, diagram, **kwargs):
plugins.install_node_handler(AutoClass(diagram, **kwargs))
diff --git a/src/blockdiag/tests/diagrams/README b/src/blockdiag/tests/diagrams/README
new file mode 100644
index 0000000..e0329be
--- /dev/null
+++ b/src/blockdiag/tests/diagrams/README
@@ -0,0 +1,12 @@
+Copyrights of images
+=====================
+
+debian-logo-256color-palettealpha.png
+--------------------------------------
+
+The Debian Open Use Logo(s) are Copyright (c) 1999 Software in the Public Interest, Inc., and are released under the terms of the GNU Lesser General Public License, version 3 or any later version, or, at your option, of the Creative Commons Attribution-ShareAlike 3.0 Unported License.
+
+
+it is modified format::
+
+ $ convert debian-logo-original.png -type PaletteAlpha -colors 256 -transparent #000000 debian-logo-256color-palettealpha.png
diff --git a/src/blockdiag/tests/diagrams/background_url_image.diag b/src/blockdiag/tests/diagrams/background_url_image.diag
index 6d53242..18183a9 100644
--- a/src/blockdiag/tests/diagrams/background_url_image.diag
+++ b/src/blockdiag/tests/diagrams/background_url_image.diag
@@ -1,5 +1,7 @@
{
A [background = "http://python.org/images/python-logo.gif"];
- B [background = "https://si2.twimg.com/profile_images/1287595787/tk0miya.jpg"];
+ B [background = "http://blockdiag.com/favicon.ico"];
+ C [background = "http://upload.wikimedia.org/wikipedia/commons/9/9b/Scalable_Vector_Graphics_Circle2.svg"];
+ D [background = "http://people.sc.fsu.edu/~jburkardt/data/eps/circle.eps"];
Z;
}
diff --git a/src/blockdiag/tests/diagrams/debian-logo-256color-palettealpha.png b/src/blockdiag/tests/diagrams/debian-logo-256color-palettealpha.png
new file mode 100644
index 0000000..6557d74
Binary files /dev/null and b/src/blockdiag/tests/diagrams/debian-logo-256color-palettealpha.png differ
diff --git a/src/blockdiag/tests/diagrams/diagram_attributes.diag b/src/blockdiag/tests/diagrams/diagram_attributes.diag
index 2dae457..997c742 100644
--- a/src/blockdiag/tests/diagrams/diagram_attributes.diag
+++ b/src/blockdiag/tests/diagrams/diagram_attributes.diag
@@ -5,6 +5,7 @@ blockdiag {
span_height = 32;
default_fontsize = 16;
default_shape = diamond
+ default_node_style = dotted
default_node_color = red
default_group_color = blue
default_linecolor = gray
diff --git a/src/blockdiag/tests/diagrams/errors/unknown_diagram_type.diag b/src/blockdiag/tests/diagrams/errors/unknown_diagram_type.diag
new file mode 100644
index 0000000..0d3974f
--- /dev/null
+++ b/src/blockdiag/tests/diagrams/errors/unknown_diagram_type.diag
@@ -0,0 +1,3 @@
+unknown {
+ A
+}
diff --git a/src/blockdiag/tests/diagrams/node_attribute.diag b/src/blockdiag/tests/diagrams/node_attribute.diag
index f31d784..70881b5 100644
--- a/src/blockdiag/tests/diagrams/node_attribute.diag
+++ b/src/blockdiag/tests/diagrams/node_attribute.diag
@@ -9,4 +9,5 @@ diagram {
H [fontsize = 16];
I [linecolor = red];
J [label="Hello", label_orientation=vertical];
+ K [background = "src/blockdiag/tests/diagrams/debian-logo-256color-palettealpha.png"];
}
diff --git a/src/blockdiag/tests/diagrams/node_icon.diag b/src/blockdiag/tests/diagrams/node_icon.diag
index fc87f99..5a81785 100644
--- a/src/blockdiag/tests/diagrams/node_icon.diag
+++ b/src/blockdiag/tests/diagrams/node_icon.diag
@@ -2,5 +2,5 @@
A -> B;
A [label = "aaaaaaaaaaaaaaaaa", icon = "/usr/share/pixmaps/debian-logo.png"];
- B [label = "aaaaaaaaaaaaaaaaa", icon = "https://si2.twimg.com/profile_images/1287595787/tk0miya.jpg"];
+ B [label = "aaaaaaaaaaaaaaaaa", icon = "http://blockdiag.com/favicon.ico"];
}
diff --git a/src/blockdiag/tests/rst/__init__.py b/src/blockdiag/tests/rst/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/blockdiag/tests/rst/test_base_directives.py b/src/blockdiag/tests/rst/test_base_directives.py
new file mode 100644
index 0000000..3baedf8
--- /dev/null
+++ b/src/blockdiag/tests/rst/test_base_directives.py
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+
+import sys
+if sys.version_info < (2, 7):
+ import unittest2 as unittest
+else:
+ import unittest
+
+import os
+import io
+from blockdiag.tests.utils import capture_stderr, TemporaryDirectory
+
+from docutils import nodes
+from docutils.core import publish_doctree
+from docutils.parsers.rst import directives as docutils
+from blockdiag.utils.rst import directives
+from blockdiag.utils.rst.nodes import blockdiag as blockdiag_node
+
+
+class TestRstDirectives(unittest.TestCase):
+ def setUp(self):
+ docutils.register_directive('blockdiag',
+ directives.BlockdiagDirectiveBase)
+ self._tmpdir = TemporaryDirectory()
+
+ def tearDown(self):
+ if 'blockdiag' in docutils._directives:
+ del docutils._directives['blockdiag']
+
+ self._tmpdir.clean()
+
+ @capture_stderr
+ def test_without_args(self):
+ text = ".. blockdiag::"
+ doctree = publish_doctree(text)
+ self.assertEqual(1, len(doctree))
+ self.assertEqual(nodes.system_message, type(doctree[0]))
+
+ def test_block(self):
+ text = (".. blockdiag::\n"
+ "\n"
+ " { A -> B }")
+ doctree = publish_doctree(text)
+ self.assertEqual(1, len(doctree))
+ self.assertEqual(blockdiag_node, type(doctree[0]))
+ self.assertEqual('{ A -> B }', doctree[0]['code'])
+ self.assertEqual(None, doctree[0]['alt'])
+ self.assertEqual({}, doctree[0]['options'])
+
+ @capture_stderr
+ def test_emptyblock(self):
+ text = ".. blockdiag::\n\n \n"
+ text = (".. blockdiag::\n"
+ "\n"
+ " ")
+ doctree = publish_doctree(text)
+ self.assertEqual(1, len(doctree))
+ self.assertEqual(nodes.system_message, type(doctree[0]))
+
+ def test_filename(self):
+ dirname = os.path.dirname(__file__)
+ filename = os.path.join(dirname, '../diagrams/node_attribute.diag')
+ text = ".. blockdiag:: %s" % filename
+ doctree = publish_doctree(text)
+
+ self.assertEqual(1, len(doctree))
+ self.assertEqual(blockdiag_node, type(doctree[0]))
+ self.assertEqual(io.open(filename, encoding='utf-8-sig').read(),
+ doctree[0]['code'])
+ self.assertEqual(None, doctree[0]['alt'])
+ self.assertEqual({}, doctree[0]['options'])
+
+ @capture_stderr
+ def test_filename_not_exists(self):
+ text = ".. blockdiag:: unknown.diag"
+ doctree = publish_doctree(text)
+ self.assertEqual(nodes.system_message, type(doctree[0]))
+
+ @capture_stderr
+ def test_both_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]))
+
+ def test_full_options(self):
+ text = (".. blockdiag::\n"
+ " :alt: hello world\n"
+ " :align: center\n"
+ " :desctable:\n"
+ " :width: 200\n"
+ " :height: 100\n"
+ " :scale: 50%\n"
+ " :name: foo\n"
+ " :class: bar\n"
+ " :figwidth: 400\n"
+ " :figclass: baz\n"
+ "\n"
+ " A -> B")
+ doctree = publish_doctree(text)
+ self.assertEqual(1, len(doctree))
+ self.assertEqual(blockdiag_node, type(doctree[0]))
+ self.assertEqual('A -> B', doctree[0]['code'])
+ self.assertEqual('hello world', doctree[0]['alt'])
+ self.assertEqual('center', doctree[0]['options']['align'])
+ self.assertEqual(None, doctree[0]['options']['desctable'])
+ self.assertEqual('200', doctree[0]['options']['width'])
+ self.assertEqual('100', doctree[0]['options']['height'])
+ self.assertEqual(50, doctree[0]['options']['scale'])
+ self.assertEqual('hello world', doctree[0]['options']['alt'])
+ self.assertEqual('foo', doctree[0]['options']['name'])
+ self.assertEqual(['bar'], doctree[0]['options']['classes'])
+ self.assertEqual('400px', doctree[0]['options']['figwidth'])
+ self.assertEqual(['baz'], doctree[0]['options']['figclass'])
+
+ @capture_stderr
+ def test_maxwidth_option(self):
+ text = (".. blockdiag::\n"
+ " :maxwidth: 200\n"
+ "\n"
+ " A -> B")
+ doctree = publish_doctree(text)
+ self.assertEqual(2, len(doctree))
+ self.assertEqual(blockdiag_node, type(doctree[0]))
+ self.assertEqual('A -> B', doctree[0]['code'])
+ self.assertEqual('200', doctree[0]['options']['width'])
+ self.assertEqual(nodes.system_message, type(doctree[1]))
diff --git a/src/blockdiag/tests/test_rst_directives.py b/src/blockdiag/tests/rst/test_blockdiag_directives.py
similarity index 57%
rename from src/blockdiag/tests/test_rst_directives.py
rename to src/blockdiag/tests/rst/test_blockdiag_directives.py
index c22884e..4348eaf 100644
--- a/src/blockdiag/tests/test_rst_directives.py
+++ b/src/blockdiag/tests/rst/test_blockdiag_directives.py
@@ -7,20 +7,17 @@ else:
import unittest
import os
-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
+from docutils.core import publish_doctree
from docutils.parsers.rst import directives as docutils
from blockdiag.utils.rst import directives
class TestRstDirectives(unittest.TestCase):
def setUp(self):
- docutils.register_directive('blockdiag',
- directives.BlockdiagDirectiveBase)
self._tmpdir = TemporaryDirectory()
def tearDown(self):
@@ -62,102 +59,274 @@ class TestRstDirectives(unittest.TestCase):
self.assertEqual(True, options['noviewbox'])
self.assertEqual(True, options['inline_svg'])
- @capture_stderr
- def test_base_noargs(self):
- text = ".. blockdiag::"
+ def test_setup_fontpath1(self):
+ with self.assertRaises(RuntimeError):
+ directives.setup(format='SVG', fontpath=['dummy.ttf'],
+ outputdir=self.tmpdir)
+ text = (".. blockdiag::\n"
+ "\n"
+ " A -> B")
+ publish_doctree(text)
+
+ def test_setup_fontpath2(self):
+ with self.assertRaises(RuntimeError):
+ directives.setup(format='SVG', fontpath='dummy.ttf',
+ outputdir=self.tmpdir)
+ text = (".. blockdiag::\n"
+ "\n"
+ " A -> B")
+ publish_doctree(text)
+
+ def test_setup_nodoctype_is_true(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir, nodoctype=True)
+ text = (".. blockdiag::\n"
+ "\n"
+ " A -> B")
doctree = publish_doctree(text)
self.assertEqual(1, len(doctree))
- self.assertEqual(nodes.system_message, type(doctree[0]))
+ self.assertEqual(nodes.image, type(doctree[-1]))
+ svg = open(doctree[0]['uri']).read()
+ self.assertEqual("<svg viewBox=\"0 0 ", svg[:18])
- def test_base_with_block(self):
- text = ".. blockdiag::\n\n { A -> B }"
+ def test_setup_nodoctype_is_false(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir, nodoctype=False)
+ text = (".. blockdiag::\n"
+ "\n"
+ " A -> B")
doctree = publish_doctree(text)
self.assertEqual(1, len(doctree))
- self.assertEqual(directives.blockdiag, type(doctree[0]))
- self.assertEqual('{ A -> B }', doctree[0]['code'])
- self.assertEqual(None, doctree[0]['alt'])
- self.assertEqual({}, doctree[0]['options'])
+ self.assertEqual(nodes.image, type(doctree[0]))
+ svg = open(doctree[0]['uri']).read()
+ self.assertEqual("<?xml version='1.0' encoding='UTF-8'?>\n"
+ "<!DOCTYPE ", svg[:49])
- @capture_stderr
- def test_base_with_emptyblock(self):
- text = ".. blockdiag::\n\n \n"
+ def test_setup_noviewbox_is_true(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir, noviewbox=True)
+ text = (".. blockdiag::\n"
+ "\n"
+ " A -> B")
doctree = publish_doctree(text)
self.assertEqual(1, len(doctree))
- self.assertEqual(nodes.system_message, type(doctree[0]))
+ self.assertEqual(nodes.image, type(doctree[0]))
+ svg = open(doctree[0]['uri']).read()
+ self.assertRegexpMatches(svg, '<svg height="\d+" width="\d+" ')
- def test_base_with_filename(self):
- dirname = os.path.dirname(__file__)
- filename = os.path.join(dirname, 'diagrams/node_attribute.diag')
- text = ".. blockdiag:: %s" % filename
+ def test_setup_noviewbox_is_false(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir, noviewbox=False)
+ text = (".. blockdiag::\n"
+ "\n"
+ " A -> B")
doctree = publish_doctree(text)
+ self.assertEqual(1, len(doctree))
+ self.assertEqual(nodes.image, type(doctree[0]))
+ svg = open(doctree[0]['uri']).read()
+ self.assertRegexpMatches(svg, '<svg viewBox="0 0 \d+ \d+" ')
+ def test_setup_inline_svg_is_true(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir, inline_svg=True)
+ text = (".. blockdiag::\n"
+ "\n"
+ " A -> B")
+ doctree = publish_doctree(text)
self.assertEqual(1, len(doctree))
- self.assertEqual(directives.blockdiag, type(doctree[0]))
- self.assertEqual(io.open(filename).read(), doctree[0]['code'])
- self.assertEqual(None, doctree[0]['alt'])
- self.assertEqual({}, doctree[0]['options'])
+ self.assertEqual(nodes.raw, type(doctree[0]))
+ self.assertEqual('html', doctree[0]['format'])
+ 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(self.tmpdir)))
- @capture_stderr
- def test_base_with_filename_not_exists(self):
- text = ".. blockdiag:: unknown.diag"
+ def test_setup_inline_svg_is_false(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir, inline_svg=False)
+ text = (".. blockdiag::\n"
+ "\n"
+ " A -> B")
doctree = publish_doctree(text)
- self.assertEqual(nodes.system_message, type(doctree[0]))
+ self.assertEqual(1, len(doctree))
+ self.assertEqual(nodes.image, type(doctree[0]))
+ self.assertEqual(1, len(os.listdir(self.tmpdir)))
- @capture_stderr
- def test_base_with_block_and_filename(self):
- text = ".. blockdiag:: unknown.diag\n\n { A -> B }"
+ @with_pil
+ def test_setup_inline_svg_is_true_but_format_isnt_svg(self):
+ directives.setup(format='PNG', outputdir=self.tmpdir, inline_svg=True)
+ text = (".. blockdiag::\n"
+ "\n"
+ " A -> B")
doctree = publish_doctree(text)
self.assertEqual(1, len(doctree))
- self.assertEqual(nodes.system_message, type(doctree[0]))
+ self.assertEqual(nodes.image, type(doctree[0]))
- def test_base_with_options(self):
- text = ".. blockdiag::\n :alt: hello world\n :desctable:\n" + \
- " :maxwidth: 100\n\n { A -> B }"
+ def test_setup_inline_svg_is_true_with_multibytes(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir)
+ text = u(".. blockdiag::\n"
+ "\n"
+ " あ -> い")
doctree = publish_doctree(text)
self.assertEqual(1, len(doctree))
- self.assertEqual(directives.blockdiag, type(doctree[0]))
- self.assertEqual('{ A -> B }', doctree[0]['code'])
- self.assertEqual('hello world', doctree[0]['alt'])
- self.assertEqual(None, doctree[0]['options']['desctable'])
- self.assertEqual(100, doctree[0]['options']['maxwidth'])
+ self.assertEqual(nodes.image, type(doctree[0]))
+
+ def test_setup_inline_svg_is_true_and_width_option1(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir,
+ nodoctype=True, noviewbox=True, inline_svg=True)
+ text = (".. blockdiag::\n"
+ " :width: 100\n"
+ "\n"
+ " A -> B")
+ doctree = publish_doctree(text)
+ self.assertEqual(1, len(doctree))
+ self.assertEqual(nodes.raw, type(doctree[0]))
+ self.assertEqual(nodes.Text, type(doctree[0][0]))
+ self.assertRegexpMatches(doctree[0][0],
+ '<svg height="\d+" width="100" ')
+
+ def test_setup_inline_svg_is_true_and_width_option2(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir,
+ nodoctype=True, noviewbox=True, inline_svg=True)
+ text = (".. blockdiag::\n"
+ " :width: 10000\n"
+ "\n"
+ " A -> B")
+ doctree = publish_doctree(text)
+ self.assertEqual(1, len(doctree))
+ self.assertEqual(nodes.raw, type(doctree[0]))
+ self.assertEqual(nodes.Text, type(doctree[0][0]))
+ self.assertRegexpMatches(doctree[0][0],
+ '<svg height="\d+" width="10000" ')
+
+ def test_setup_inline_svg_is_true_and_height_option1(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir,
+ nodoctype=True, noviewbox=True, inline_svg=True)
+ text = (".. blockdiag::\n"
+ " :height: 100\n"
+ "\n"
+ " A -> B")
+ doctree = publish_doctree(text)
+ self.assertEqual(1, len(doctree))
+ self.assertEqual(nodes.raw, type(doctree[0]))
+ self.assertEqual(nodes.Text, type(doctree[0][0]))
+ self.assertRegexpMatches(doctree[0][0],
+ '<svg height="100" width="\d+" ')
- def test_block(self):
+ def test_setup_inline_svg_is_true_and_height_option2(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir,
+ nodoctype=True, noviewbox=True, inline_svg=True)
+ text = (".. blockdiag::\n"
+ " :height: 10000\n"
+ "\n"
+ " A -> B")
+ doctree = publish_doctree(text)
+ self.assertEqual(1, len(doctree))
+ self.assertEqual(nodes.raw, type(doctree[0]))
+ self.assertEqual(nodes.Text, type(doctree[0][0]))
+ self.assertRegexpMatches(doctree[0][0],
+ '<svg height="10000" width="\d+" ')
+
+ def test_setup_inline_svg_is_true_and_width_and_height_option(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir,
+ nodoctype=True, noviewbox=True, inline_svg=True)
+ text = (".. blockdiag::\n"
+ " :width: 200\n"
+ " :height: 100\n"
+ "\n"
+ " A -> B")
+ doctree = publish_doctree(text)
+ self.assertEqual(1, len(doctree))
+ self.assertEqual(nodes.raw, type(doctree[0]))
+ self.assertEqual(nodes.Text, type(doctree[0][0]))
+ self.assertRegexpMatches(doctree[0][0],
+ '<svg height="100" width="200" ')
+
+ def test_call_with_brace(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.assertEqual(0, doctree[0]['uri'].index(self.tmpdir))
+
+ def test_call_without_braces(self):
directives.setup(format='SVG', outputdir=self.tmpdir)
- text = ".. blockdiag::\n\n { A -> B }"
+ 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(self.tmpdir))
- self.assertFalse('target' in doctree[0])
- def test_block_alt(self):
+ def test_alt_option(self):
directives.setup(format='SVG', outputdir=self.tmpdir)
- text = ".. blockdiag::\n :alt: hello world\n\n { A -> B }"
+ 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(self.tmpdir))
- self.assertFalse('target' in doctree[0])
- 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_align_option1(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir)
+ text = (".. blockdiag::\n"
+ " :align: left\n"
+ "\n"
+ " A -> B")
+ doctree = publish_doctree(text)
+ self.assertEqual(1, len(doctree))
+ self.assertEqual(nodes.image, type(doctree[0]))
+ self.assertEqual('left', doctree[0]['align'])
+ self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir))
- 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_align_option2(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir)
+ text = (".. blockdiag::\n"
+ " :align: center\n"
+ "\n"
+ " A -> B")
+ doctree = publish_doctree(text)
+ self.assertEqual(1, len(doctree))
+ self.assertEqual(nodes.image, type(doctree[0]))
+ self.assertEqual('center', doctree[0]['align'])
+ self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir))
- def test_caption(self):
+ def test_align_option3(self):
directives.setup(format='SVG', outputdir=self.tmpdir)
- text = ".. blockdiag::\n :caption: hello world\n\n { A -> B }"
+ text = (".. blockdiag::\n"
+ " :align: right\n"
+ "\n"
+ " A -> B")
+ doctree = publish_doctree(text)
+ self.assertEqual(1, len(doctree))
+ self.assertEqual(nodes.image, type(doctree[0]))
+ self.assertEqual('right', doctree[0]['align'])
+ self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir))
+
+ @capture_stderr
+ def test_align_option4(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir)
+ text = (".. blockdiag::\n"
+ " :align: unknown\n"
+ "\n"
+ " A -> B")
+ doctree = publish_doctree(text)
+ self.assertEqual(1, len(doctree))
+ self.assertEqual(nodes.system_message, type(doctree[0]))
+
+ # clear stderr outputs (ignore ERROR)
+ from io import StringIO
+ sys.stderr = StringIO()
+
+ def test_caption_option(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))
self.assertEqual(nodes.figure, type(doctree[0]))
@@ -168,109 +337,143 @@ class TestRstDirectives(unittest.TestCase):
self.assertEqual(nodes.Text, type(doctree[0][1][0]))
self.assertEqual('hello world', doctree[0][1][0])
- def test_block_maxwidth(self):
+ def test_caption_option_and_align_option(self):
directives.setup(format='SVG', outputdir=self.tmpdir)
- text = ".. blockdiag::\n :maxwidth: 100\n\n { A -> B }"
+ text = (".. blockdiag::\n"
+ " :align: left\n"
+ " :caption: hello world\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(self.tmpdir))
- self.assertFalse(0, doctree[0]['target'].index(self.tmpdir))
+ self.assertEqual(nodes.figure, type(doctree[0]))
+ self.assertEqual('left', doctree[0]['align'])
+ self.assertEqual(2, len(doctree[0]))
+ self.assertEqual(nodes.image, type(doctree[0][0]))
+ self.assertNotIn('align', doctree[0][0])
+ self.assertEqual(nodes.caption, type(doctree[0][1]))
+ self.assertEqual(1, len(doctree[0][1]))
+ self.assertEqual(nodes.Text, type(doctree[0][1][0]))
+ self.assertEqual('hello world', doctree[0][1][0])
- 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 }"
+ @capture_stderr
+ def test_maxwidth_option(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(2, len(doctree))
self.assertEqual(nodes.image, type(doctree[0]))
- svg = open(doctree[0]['uri']).read()
- self.assertEqual("<?xml version='1.0' encoding='UTF-8'?>\n"
- "<!DOCTYPE ", svg[:49])
+ self.assertEqual('100', doctree[0]['width'])
+ self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir))
+ self.assertEqual(nodes.system_message, type(doctree[1]))
- 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 }"
+ def test_width_option(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir)
+ text = (".. blockdiag::\n"
+ " :width: 100\n"
+ "\n"
+ " A -> B")
doctree = publish_doctree(text)
self.assertEqual(1, len(doctree))
- self.assertEqual(nodes.image, type(doctree[-1]))
- svg = open(doctree[0]['uri']).read()
- self.assertNotEqual("<?xml version='1.0' encoding='UTF-8'?>\n"
- "<!DOCTYPE ", svg[:49])
+ self.assertEqual(nodes.image, type(doctree[0]))
+ self.assertEqual('100', doctree[0]['width'])
+ self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir))
- 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 }"
+ def test_height_option(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir)
+ text = (".. blockdiag::\n"
+ " :height: 100\n"
+ "\n"
+ " A -> B")
doctree = publish_doctree(text)
self.assertEqual(1, len(doctree))
self.assertEqual(nodes.image, type(doctree[0]))
- svg = open(doctree[0]['uri']).read()
- self.assertRegexpMatches(svg, '<svg viewBox="0 0 \d+ \d+" ')
+ self.assertEqual('100', doctree[0]['height'])
+ self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir))
- 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 }"
+ def test_scale_option(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir)
+ text = (".. blockdiag::\n"
+ " :scale: 50%\n"
+ "\n"
+ " A -> B")
doctree = publish_doctree(text)
self.assertEqual(1, len(doctree))
self.assertEqual(nodes.image, type(doctree[0]))
- svg = open(doctree[0]['uri']).read()
- self.assertRegexpMatches(svg, '<svg height="\d+" width="\d+" ')
+ self.assertEqual(50, doctree[0]['scale'])
+ self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir))
- 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 }"
+ def test_name_option(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir)
+ text = (".. blockdiag::\n"
+ " :name: foo%\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(self.tmpdir)))
+ self.assertEqual(['foo%'], doctree[0]['names'])
+ self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir))
- 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 }"
+ def test_class_option(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir)
+ text = (".. blockdiag::\n"
+ " :class: bar%\n"
+ "\n"
+ " A -> B")
doctree = publish_doctree(text)
self.assertEqual(1, len(doctree))
- self.assertEqual(nodes.raw, type(doctree[0]))
- self.assertEqual('html', doctree[0]['format'])
- 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(self.tmpdir)))
+ self.assertEqual(nodes.image, type(doctree[0]))
+ self.assertEqual(['bar'], doctree[0]['classes'])
+ self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir))
- @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 }"
+ def test_figwidth_option1(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir)
+ text = (".. blockdiag::\n"
+ " :caption: hello world\n"
+ " :figwidth: 100\n"
+ "\n"
+ " A -> B")
doctree = publish_doctree(text)
self.assertEqual(1, len(doctree))
- self.assertEqual(nodes.image, type(doctree[0]))
+ self.assertEqual(nodes.figure, type(doctree[0]))
+ self.assertEqual('100px', doctree[0]['width'])
- def test_block_inline_svg_true_with_multibytes(self):
+ def test_figwidth_option2(self):
directives.setup(format='SVG', outputdir=self.tmpdir)
- text = u(".. blockdiag::\n :alt: hello world\n\n { あ -> い }")
- publish_parts(source=text)
-
- 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 }"
+ text = (".. blockdiag::\n"
+ " :caption: hello world\n"
+ " :figwidth: image\n"
+ "\n"
+ " A -> B")
doctree = publish_doctree(text)
self.assertEqual(1, len(doctree))
- self.assertEqual(nodes.raw, type(doctree[0]))
- self.assertEqual(nodes.Text, type(doctree[0][0]))
- self.assertRegexpMatches(doctree[0][0],
- '<svg height="\d+" width="100" ')
+ self.assertEqual(nodes.figure, type(doctree[0]))
+ self.assertEqual('448px', doctree[0]['width'])
- def test_desctable_without_description(self):
+ def test_figclass_option(self):
directives.setup(format='SVG', outputdir=self.tmpdir)
- text = ".. blockdiag::\n :desctable:\n\n { A -> B }"
+ text = (".. blockdiag::\n"
+ " :caption: hello world\n"
+ " :figclass: baz\n"
+ "\n"
+ " A -> B")
doctree = publish_doctree(text)
self.assertEqual(1, len(doctree))
- self.assertEqual(nodes.image, type(doctree[0]))
+ self.assertEqual(nodes.figure, type(doctree[0]))
+ self.assertEqual(['baz'], doctree[0]['classes'])
- def test_desctable(self):
+ def test_desctable_option(self):
directives.setup(format='SVG', outputdir=self.tmpdir)
- text = ".. blockdiag::\n :desctable:\n\n" + \
- " { A [description = foo]; B [description = bar]; }"
+ text = (".. blockdiag::\n"
+ " :desctable:\n"
+ "\n"
+ " A [description = foo]"
+ " B [description = bar]"
+ " group { A }")
doctree = publish_doctree(text)
self.assertEqual(2, len(doctree))
self.assertEqual(nodes.image, type(doctree[0]))
@@ -301,12 +504,24 @@ class TestRstDirectives(unittest.TestCase):
self.assertEqual('B', tbody[1][0][0][0])
self.assertEqual('bar', tbody[1][1][0][0])
- def test_desctable_using_node_group(self):
+ def test_desctable_option_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]))
+
+ def test_desctable_option_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]; " + \
- " group { A } }"
+ text = (".. blockdiag::\n"
+ " :desctable:\n"
+ "\n"
+ " A [description = foo]"
+ " B [description = bar]"
+ " group { A }")
doctree = publish_doctree(text)
self.assertEqual(2, len(doctree))
self.assertEqual(nodes.image, type(doctree[0]))
@@ -360,11 +575,13 @@ class TestRstDirectives(unittest.TestCase):
self.assertEqual(1, len(tbody[1][1]))
self.assertEqual('bar', tbody[1][1][0][0])
- def test_desctable_with_rest_markups(self):
+ def test_desctable_option_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\"]; }"
+ text = (".. blockdiag::\n"
+ " :desctable:\n"
+ "\n"
+ " A [description = \"foo *bar* **baz**\"]"
+ " B [description = \"**foo** *bar* baz\"]")
doctree = publish_doctree(text)
self.assertEqual(2, len(doctree))
self.assertEqual(nodes.image, type(doctree[0]))
@@ -416,10 +633,13 @@ class TestRstDirectives(unittest.TestCase):
self.assertEqual(nodes.Text, type(tbody[1][1][0][3]))
self.assertEqual(' baz', str(tbody[1][1][0][3]))
- def test_desctable_with_numbered(self):
+ def test_desctable_option_with_numbered(self):
directives.setup(format='SVG', outputdir=self.tmpdir)
- text = ".. blockdiag::\n :desctable:\n\n" + \
- " { A [numbered = 2]; B [numbered = 1]; }"
+ text = (".. blockdiag::\n"
+ " :desctable:\n"
+ "\n"
+ " A [numbered = 2]"
+ " B [numbered = 1]")
doctree = publish_doctree(text)
self.assertEqual(2, len(doctree))
self.assertEqual(nodes.image, type(doctree[0]))
@@ -450,11 +670,13 @@ class TestRstDirectives(unittest.TestCase):
self.assertEqual('2', tbody[1][0][0][0])
self.assertEqual('A', tbody[1][1][0][0])
- def test_desctable_with_numbered_and_description(self):
+ def test_desctable_option_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]; }"
+ text = (".. blockdiag::\n"
+ " :desctable:\n"
+ "\n"
+ " A [description = foo, numbered = 2]"
+ " B [description = bar, numbered = 1]")
doctree = publish_doctree(text)
self.assertEqual(2, len(doctree))
self.assertEqual(nodes.image, type(doctree[0]))
@@ -491,13 +713,15 @@ class TestRstDirectives(unittest.TestCase):
self.assertEqual('A', tbody[1][1][0][0])
self.assertEqual('foo', tbody[1][2][0][0])
- def test_desctable_for_edges(self):
+ def test_desctable_option_for_edges(self):
directives.setup(format='SVG', outputdir=self.tmpdir)
- text = ".. blockdiag::\n :desctable:\n\n" + \
- " { A -> B [description = \"foo\"]; " + \
- " C -> D [description = \"bar\"]; " + \
- " C [label = \"label_C\"]; " + \
- " D [label = \"label_D\"]; }"
+ text = (".. blockdiag::\n"
+ " :desctable:\n"
+ "\n"
+ " A -> B [description = \"foo\"]"
+ " C -> D [description = \"bar\"]"
+ " C [label = \"label_C\"]"
+ " D [label = \"label_D\"]")
doctree = publish_doctree(text)
self.assertEqual(2, len(doctree))
self.assertEqual(nodes.image, type(doctree[0]))
@@ -532,13 +756,15 @@ class TestRstDirectives(unittest.TestCase):
self.assertEqual(nodes.Text, type(tbody[1][1][0][0]))
self.assertEqual('bar', str(tbody[1][1][0][0]))
- def test_desctable_for_nodes_and_edges(self):
+ def test_desctable_option_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\"]; " + \
- " C [label = \"label_C\", description = foo]; " + \
- " D [label = \"label_D\"]; }"
+ text = (".. blockdiag::\n"
+ " :desctable:\n"
+ "\n"
+ " A -> B [description = \"foo\"]"
+ " C -> D [description = \"bar\"]"
+ " C [label = \"label_C\", description = foo]"
+ " D [label = \"label_D\"]")
doctree = publish_doctree(text)
self.assertEqual(3, len(doctree))
self.assertEqual(nodes.image, type(doctree[0]))
@@ -573,3 +799,13 @@ class TestRstDirectives(unittest.TestCase):
self.assertEqual(1, len(tbody[1][1][0]))
self.assertEqual(nodes.Text, type(tbody[1][1][0][0]))
self.assertEqual('bar', str(tbody[1][1][0][0]))
+
+ @capture_stderr
+ def test_broken_diagram(self):
+ directives.setup(format='SVG', outputdir=self.tmpdir)
+ text = (".. blockdiag::\n"
+ "\n"
+ " A ->")
+ doctree = publish_doctree(text)
+ self.assertEqual(1, len(doctree))
+ self.assertEqual(nodes.system_message, type(doctree[0]))
diff --git a/src/blockdiag/tests/test_boot_params.py b/src/blockdiag/tests/test_boot_params.py
index bf850f3..846ee09 100644
--- a/src/blockdiag/tests/test_boot_params.py
+++ b/src/blockdiag/tests/test_boot_params.py
@@ -115,7 +115,7 @@ class TestBootParams(unittest.TestCase):
try:
tmp = tempfile.mkstemp()
config = u("[blockdiag]\nfontpath = /path/to/font\n")
- io.open(tmp[0], 'wt').write(config)
+ io.open(tmp[0], 'wt', encoding='utf-8-sig').write(config)
options = self.parser.parse(['-c', tmp[1], 'input.diag'])
self.assertEqual(options.font, ['/path/to/font'])
@@ -166,11 +166,6 @@ class TestBootParams(unittest.TestCase):
with self.assertRaises(RuntimeError):
self.parser.parse(['--size', 'foobar', 'input.diag'])
- def test_auto_font_detection(self):
- options = self.parser.parse(['input.diag'])
- fontpath = detectfont(options)
- self.assertTrue(fontpath)
-
def test_not_exist_fontmap_config(self):
with self.assertRaises(RuntimeError):
args = ['--fontmap', '/fontmap_is_not_exist', 'input.diag']
diff --git a/src/blockdiag/tests/test_builder.py b/src/blockdiag/tests/test_builder.py
index 60f22f8..e1d1f3a 100644
--- a/src/blockdiag/tests/test_builder.py
+++ b/src/blockdiag/tests/test_builder.py
@@ -13,6 +13,7 @@ class TestBuilder(BuilderTestCase):
self.assertEqual(32, diagram.span_height)
self.assertEqual((128, 128, 128), diagram.linecolor) # gray
self.assertEqual('diamond', diagram.nodes[0].shape)
+ self.assertEqual('dotted', diagram.nodes[0].style)
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)
diff --git a/src/blockdiag/tests/test_builder_errors.py b/src/blockdiag/tests/test_builder_errors.py
index bfe15ec..21d7f5e 100644
--- a/src/blockdiag/tests/test_builder_errors.py
+++ b/src/blockdiag/tests/test_builder_errors.py
@@ -95,6 +95,11 @@ class TestBuilderError(BuilderTestCase):
with self.assertRaises(ParseException):
self.build(filename)
+ def test_unknown_diagram_type(self):
+ filename = 'errors/unknown_diagram_type.diag'
+ with self.assertRaises(ParseException):
+ self.build(filename)
+
def test_lexer_error_diagram(self):
filename = 'errors/lexer_error.diag'
with self.assertRaises(ParseException):
diff --git a/src/blockdiag/tests/test_builder_node.py b/src/blockdiag/tests/test_builder_node.py
index 4291afd..bf3b939 100644
--- a/src/blockdiag/tests/test_builder_node.py
+++ b/src/blockdiag/tests/test_builder_node.py
@@ -63,11 +63,13 @@ class TestBuilderNode(BuilderTestCase):
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'}
+ 'F': 'F', 'G': 'G', 'H': 'H', 'I': 'I',
+ 'J': 'Hello', 'K': 'K'}
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)}
+ 'I': (255, 255, 255), 'J': (255, 255, 255),
+ 'K': (255, 255, 255)}
textcolors = defaultdict(lambda: (0, 0, 0))
textcolors['F'] = (255, 0, 0)
linecolors = defaultdict(lambda: (0, 0, 0))
@@ -80,6 +82,9 @@ class TestBuilderNode(BuilderTestCase):
fontsize['H'] = 16
orientations = defaultdict(lambda: 'horizontal')
orientations['J'] = 'vertical'
+ backgrounds = defaultdict(lambda: None)
+ backgrounds['K'] = ('src/blockdiag/tests/diagrams/'
+ 'debian-logo-256color-palettealpha.png')
diagram = self.build('node_attribute.diag')
self.assertNodeLabel(diagram, labels)
@@ -90,6 +95,7 @@ class TestBuilderNode(BuilderTestCase):
self.assertNodeStacked(diagram, stacked)
self.assertNodeFontsize(diagram, fontsize)
self.assertNodeLabel_Orientation(diagram, orientations)
+ self.assertNodeBackground(diagram, backgrounds)
def test_node_height_diagram(self):
diagram = self.build('node_height.diag')
diff --git a/src/blockdiag/tests/test_generate_diagram.py b/src/blockdiag/tests/test_generate_diagram.py
index 615c99f..e890685 100644
--- a/src/blockdiag/tests/test_generate_diagram.py
+++ b/src/blockdiag/tests/test_generate_diagram.py
@@ -6,6 +6,12 @@ from nose.tools import nottest
from blockdiag.tests.utils import capture_stderr, TemporaryDirectory
from blockdiag.tests.utils import supported_pil, supported_pdf
+import sys
+if sys.version_info < (2, 7):
+ import unittest2 as unittest
+else:
+ import unittest
+
import blockdiag
import blockdiag.command
@@ -19,7 +25,8 @@ def get_fontpath(testdir=None):
def get_diagram_files(testdir):
diagramsdir = os.path.join(testdir, 'diagrams')
- skipped = ['errors', 'white.gif']
+ skipped = ['README', 'debian-logo-256color-palettealpha.png',
+ 'errors', 'white.gif']
for file in os.listdir(diagramsdir):
if file in skipped:
pass
@@ -60,9 +67,14 @@ def testcase_generator(basepath, mainfunc, files, options):
if supported_pil() and os.path.exists(fontpath):
yield generate, mainfunc, 'png', source, options
yield generate, mainfunc, 'png', source, options + ['--antialias']
+ else:
+ yield unittest.skip("Pillow is not available")(generate)
+ yield unittest.skip("Pillow is not available")(generate)
if supported_pdf() and os.path.exists(fontpath):
yield generate, mainfunc, 'pdf', source, options
+ else:
+ yield unittest.skip("reportlab is not available")(generate)
@capture_stderr
@@ -72,7 +84,8 @@ def generate(mainfunc, filetype, source, options):
fd, tmpfile = tmpdir.mkstemp()
os.close(fd)
- mainfunc(['-T', filetype, '-o', tmpfile, source] + list(options))
+ mainfunc(['--debug', '-T', filetype, '-o', tmpfile, source] +
+ list(options))
finally:
tmpdir.clean()
diff --git a/src/blockdiag/tests/test_pep8.py b/src/blockdiag/tests/test_pep8.py
index 287ef95..3d74cbf 100644
--- a/src/blockdiag/tests/test_pep8.py
+++ b/src/blockdiag/tests/test_pep8.py
@@ -43,7 +43,7 @@ def test_pep8():
if report.total_errors:
if options.count:
sys.stderr.write(str(report.total_errors) + '\n')
- #sys.exit(1)
+ # sys.exit(1)
# reporting errors (additional summary)
errors = report.get_count('E')
diff --git a/src/blockdiag/tests/test_utils.py b/src/blockdiag/tests/test_utils.py
new file mode 100644
index 0000000..97ed133
--- /dev/null
+++ b/src/blockdiag/tests/test_utils.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+import sys
+if sys.version_info < (2, 7):
+ import unittest2 as unittest
+else:
+ import unittest
+
+from blockdiag.utils import Size
+
+
+class TestUtils(unittest.TestCase):
+ def test_size_resize(self):
+ size = Size(10, 20)
+
+ resized = size.resize(width=50, height=50)
+ self.assertEqual((50, 50), resized)
+
+ resized = size.resize(width=50)
+ self.assertEqual((50, 100), resized)
+
+ resized = size.resize(height=50)
+ self.assertEqual((25, 50), resized)
+
+ resized = size.resize(scale=50)
+ self.assertEqual((5, 10), resized)
+
+ resized = size.resize(width=50, scale=50)
+ self.assertEqual((25, 50), resized)
+
+ resized = size.resize(height=50, scale=50)
+ self.assertEqual((12.5, 25), resized)
+
+ resized = size.resize(width=50, height=50, scale=50)
+ self.assertEqual((25, 25), resized)
+
+ def test_size_to_integer_point(self):
+ size = Size(1.5, 2.5)
+
+ self.assertEqual((1, 2), size.to_integer_point())
diff --git a/src/blockdiag/tests/test_utils_fontmap.py b/src/blockdiag/tests/test_utils_fontmap.py
index 464c40f..5c79da3 100644
--- a/src/blockdiag/tests/test_utils_fontmap.py
+++ b/src/blockdiag/tests/test_utils_fontmap.py
@@ -11,10 +11,7 @@ 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 io import StringIO
from collections import namedtuple
from blockdiag.utils.fontmap import FontInfo, FontMap
@@ -239,6 +236,19 @@ class TestUtilsFontmap(unittest.TestCase):
self.assertEqual(self.fontpath[1], font1.path)
self.assertEqual(11, font1.size)
+ def test_fontmap_with_capital_character(self):
+ _config = u("[fontmap]\nCapitalCase-sansserif: %s\n") % \
+ self.fontpath[0]
+ config = StringIO(_config)
+ fmap = FontMap(config)
+
+ element = FontElement('CapitalCase-sansserif', 11)
+ font1 = fmap.find(element)
+ self.assertEqual('sansserif', font1.generic_family)
+ self.assertEqual('capitalcase-sansserif-normal', font1.familyname)
+ self.assertEqual(self.fontpath[0], font1.path)
+ self.assertEqual(11, font1.size)
+
@capture_stderr
def test_fontmap_with_nodefault_fontentry(self):
_config = u("[fontmap]\nserif: %s\n") % self.fontpath[0]
diff --git a/src/blockdiag/tests/utils.py b/src/blockdiag/tests/utils.py
index d51ddd8..87d4aef 100644
--- a/src/blockdiag/tests/utils.py
+++ b/src/blockdiag/tests/utils.py
@@ -16,9 +16,11 @@ from blockdiag.builder import ScreenNodeBuilder
from blockdiag.parser import parse_file
try:
- from io import StringIO
-except ImportError:
+ # sys.stderr in py2.x allows mixture of str and unicode
from cStringIO import StringIO
+except ImportError:
+ # sys.stderr in py3.x allows only str objects (disallow bytes objs)
+ from io import StringIO
def supported_pil():
@@ -62,7 +64,7 @@ def capture_stderr(func):
func(*args, **kwargs)
- if re.search('ERROR', sys.stderr.getvalue()):
+ if re.search('(ERROR|Traceback)', sys.stderr.getvalue()):
raise AssertionError('Caught error')
finally:
if sys.stderr.getvalue():
diff --git a/src/blockdiag/utils/__init__.py b/src/blockdiag/utils/__init__.py
index 8b06a0a..231aa72 100644
--- a/src/blockdiag/utils/__init__.py
+++ b/src/blockdiag/utils/__init__.py
@@ -16,10 +16,6 @@
from __future__ import division
import re
import math
-from collections import namedtuple
-
-
-Size = namedtuple('Size', 'width height')
class XY(tuple):
@@ -41,6 +37,40 @@ class XY(tuple):
return self.__class__(self.x + x, self.y + y)
+class Size(tuple):
+ def __new__(cls, width, height):
+ return super(Size, cls).__new__(cls, (width, height))
+
+ @property
+ def width(self):
+ return self[0]
+
+ @property
+ def height(self):
+ return self[1]
+
+ def resize(self, **kwargs):
+ if 'width' in kwargs and 'height' in kwargs:
+ size = Size(float(kwargs['width']), float(kwargs['height']))
+ elif 'width' in kwargs:
+ width = float(kwargs['width'])
+ size = Size(width, self.height * width / self.width)
+ elif 'height' in kwargs:
+ height = float(kwargs['height'])
+ size = Size(self.width * height / self.height, height)
+ else:
+ size = self
+
+ if 'scale' in kwargs:
+ scale = float(kwargs['scale']) / 100
+ size = Size(size.width * scale, size.height * scale)
+
+ return size
+
+ def to_integer_point(self):
+ return Size(int(self.width), int(self.height))
+
+
class Box(list):
mapper = dict(x1=0, y1=1, x2=2, y2=3, x=0, y=1)
diff --git a/src/blockdiag/utils/bootstrap.py b/src/blockdiag/utils/bootstrap.py
index 535f728..1d18719 100644
--- a/src/blockdiag/utils/bootstrap.py
+++ b/src/blockdiag/utils/bootstrap.py
@@ -17,10 +17,13 @@ import os
import re
import sys
import codecs
+import traceback
from optparse import OptionParser, SUPPRESS_HELP
from blockdiag import imagedraw
+from blockdiag import plugins
from blockdiag.utils.config import ConfigParser
from blockdiag.utils.fontmap import parse_fontpath, FontMap
+from blockdiag.utils.logging import warning, error
class Application(object):
@@ -37,17 +40,16 @@ class Application(object):
except SystemExit as e:
return e
except UnicodeEncodeError as e:
- msg = "ERROR: UnicodeEncodeError caught " + \
- "(check your font settings)\n"
- sys.stderr.write(msg)
+ error("UnicodeEncodeError caught (check your font settings)")
return -1
except Exception as e:
if self.options and self.options.debug:
- import traceback
traceback.print_exc()
else:
- sys.stderr.write("ERROR: %s\n" % e)
+ error("%s" % e)
return -1
+ finally:
+ plugins.fire_general_event('cleanup')
def parse_options(self, args):
self.options = Options(self.module).parse(args)
@@ -145,7 +147,7 @@ class Options(object):
self.options.type = self.options.type.upper()
try:
- imagedraw.create(self.options.type, None)
+ imagedraw.create(self.options.type, None, debug=self.options.debug)
except:
msg = "unknown format: %s" % self.options.type
raise RuntimeError(msg)
@@ -167,9 +169,8 @@ class Options(object):
raise RuntimeError(msg)
if self.options.ignore_pil:
- msg = "WARNING: --ignore-pil option is deprecated " + \
- "(detect automatically).\n"
- sys.stderr.write(msg)
+ warning("--ignore-pil option is deprecated "
+ "(detect automatically).")
if self.options.nodoctype and self.options.type != 'SVG':
msg = "--nodoctype option work in SVG images."
diff --git a/src/blockdiag/utils/compat.py b/src/blockdiag/utils/compat.py
index fb7d85a..973cb16 100644
--- a/src/blockdiag/utils/compat.py
+++ b/src/blockdiag/utils/compat.py
@@ -17,8 +17,10 @@ import sys
if sys.version_info[0] == 2:
string_types = (str, unicode) # NOQA: pyflakes complains to unicode in py3
+ from urllib import urlopen # NOQA: exporting for common interface
else:
string_types = (str,)
+ from urllib.request import urlopen # NOQA: exporting for common interface
def u(string):
diff --git a/src/blockdiag/utils/fontmap.py b/src/blockdiag/utils/fontmap.py
index 2264283..05c0236 100644
--- a/src/blockdiag/utils/fontmap.py
+++ b/src/blockdiag/utils/fontmap.py
@@ -15,11 +15,10 @@
import re
import os
-import sys
import copy
from collections import namedtuple
from blockdiag.utils.config import ConfigParser
-from blockdiag.utils.compat import u
+from blockdiag.utils.logging import warning
def parse_fontpath(path):
@@ -134,11 +133,10 @@ class FontMap(object):
font = FontInfo(fontfamily, path, self.fontsize)
self.fonts[font.familyname] = font
else:
- msg = 'fontfile `%s` is not found: %s' % (fontfamily, path)
- sys.stderr.write("WARNING: %s\n" % msg)
+ warning('fontfile `%s` is not found: %s', fontfamily, path)
def _regulate_familyname(self, name):
- return FontInfo(name, None, self.BASE_FONTSIZE).familyname
+ return FontInfo(name, None, self.BASE_FONTSIZE).familyname.lower()
def find(self, element=None):
fontfamily = getattr(element, 'fontfamily', None) or \
@@ -151,8 +149,7 @@ class FontMap(object):
font = self.fonts[name].duplicate()
font.size = fontsize
elif element is not None:
- msg = "Unknown fontfamily: %s" % fontfamily
- sys.stderr.write(u("WARNING: %s\n") % msg)
+ warning("Unknown fontfamily: %s", fontfamily)
elem = namedtuple('Font', 'fontsize')(fontsize)
font = self.find(elem)
else:
diff --git a/src/blockdiag/utils/images.py b/src/blockdiag/utils/images.py
index 6cc4b92..fcc7b44 100644
--- a/src/blockdiag/utils/images.py
+++ b/src/blockdiag/utils/images.py
@@ -14,65 +14,32 @@
# limitations under the License.
from __future__ import division
+import io
import re
+from PIL import Image
+from tempfile import NamedTemporaryFile
from blockdiag.utils import urlutil
-from blockdiag.utils.compat import string_types
-
-try:
- from PIL 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
-
- try:
- size = jpeg.JpegFile.get_size(self.filename)
- except:
- try:
- 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:
- size = None
-
- if hasattr(self.filename, 'seek'):
- self.filename.seek(0)
-
- return size
-
-_image_size_cache = {}
+from blockdiag.utils.compat import u
+from blockdiag.utils.logging import warning
+urlopen_cache = {}
-def get_image_size(filename):
- if filename not in _image_size_cache:
- uri = filename
- if urlutil.isurl(filename):
- try:
- from io import StringIO
- except ImportError:
- from cStringIO import StringIO
- import urllib
- try:
- uri = StringIO(urllib.urlopen(filename).read())
- except:
- return None
- _image_size_cache[filename] = Image.open(uri).size
+def urlopen(url, *args, **kwargs):
+ """ auto caching urlopen() (using tempfile) """
+ from blockdiag.utils.compat import urlopen as orig_urlopen
+
+ if url not in urlopen_cache:
+ tmpfile = NamedTemporaryFile()
+ tmpfile.write(orig_urlopen(url, *args, **kwargs).read())
+ tmpfile.flush()
+ urlopen_cache[url] = tmpfile
+
+ return io.open(urlopen_cache[url].name, 'rb')
- return _image_size_cache[filename]
+
+def get_image_size(filename):
+ return open(filename).size
def calc_image_size(size, bounded):
@@ -95,3 +62,61 @@ def color_to_rgb(color):
rgb = webcolors.name_to_rgb(color)
return rgb
+
+
+def wand_open(url, stream):
+ try:
+ import wand.image
+ except:
+ warning("unknown image type: %s", url)
+ raise IOError
+
+ try:
+ png_image = io.BytesIO()
+ with wand.image.Image(file=stream) as img:
+ img.format = 'PNG'
+ img.save(file=png_image)
+ png_image.seek(0)
+ return png_image
+ except Exception as exc:
+ warning("Fail to convert %s to PNG: %r", url, exc)
+ raise IOError
+
+
+def pillow_open(url, stream):
+ try:
+ if isinstance(url, Image.Image):
+ return url
+ else:
+ return Image.open(stream)
+ except IOError:
+ stream.seek(0)
+ png_stream = wand_open(url, stream)
+
+ return Image.open(png_stream)
+
+
+def open(url, mode='Pillow'):
+ if hasattr(url, 'read') or isinstance(url, Image.Image):
+ stream = url
+ elif not urlutil.isurl(url):
+ stream = io.open(url, 'rb')
+ else:
+ try:
+ # wrap BytesIO for rewind stream
+ stream = io.BytesIO(urlopen(url).read())
+ except:
+ warning(u("Could not retrieve: %s"), url)
+ raise IOError
+
+ image = pillow_open(url, stream)
+ if mode.lower() == 'pillow':
+ # stream will be closed by GC
+ return image
+ else: # mode == 'png'
+ png_image = io.BytesIO()
+ image.save(png_image, 'PNG')
+ stream.close()
+
+ png_image.seek(0)
+ return png_image
diff --git a/src/blockdiag/utils/jpeg.py b/src/blockdiag/utils/jpeg.py
deleted file mode 100644
index 85345f4..0000000
--- a/src/blockdiag/utils/jpeg.py
+++ /dev/null
@@ -1,92 +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.compat import string_types
-
-
-class StreamReader(object):
- def __init__(self, stream):
- self.stream = stream
- self.pos = 0
-
- def read_byte(self):
- byte = self.stream[self.pos]
- self.pos += 1
- return ord(byte)
-
- def read_word(self):
- byte1, byte2 = self.stream[self.pos:self.pos + 2]
- self.pos += 2
- return (ord(byte1) << 8) + ord(byte2)
-
- def read_bytes(self, n):
- _bytes = self.stream[self.pos:self.pos + n]
- self.pos += n
- return _bytes
-
-
-class JpegHeaderReader(StreamReader):
- M_SOI = 0xd8
- M_SOS = 0xda
-
- def read_marker(self):
- if self.read_byte() != 255:
- raise ValueError("error reading marker")
- return self.read_byte()
-
- def skip_marker(self):
- """Skip over an unknown or uninteresting variable-length marker"""
- length = self.read_word()
- self.read_bytes(length - 2)
-
- def __iter__(self):
- while True:
- if self.read_byte() != 255:
- raise ValueError("error reading marker")
-
- marker = self.read_byte()
- if marker == self.M_SOI:
- length = 0
- data = ''
- else:
- length = self.read_word()
- data = self.read_bytes(length - 2)
-
- yield (marker, data)
-
- if marker == self.M_SOS:
- raise StopIteration()
-
-
-class JpegFile(object):
- M_SOF0 = 0xc0
- M_SOF1 = 0xc1
-
- @classmethod
- def get_size(self, filename):
- if isinstance(filename, string_types):
- image = open(filename, 'rb').read()
- else:
- image = filename.read()
-
- headers = JpegHeaderReader(image)
- for header in headers:
- if header[0] in (self.M_SOF0, self.M_SOF1):
- data = header[1]
-
- height = (ord(data[1]) << 8) + ord(data[2])
- width = (ord(data[3]) << 8) + ord(data[4])
- return (width, height)
diff --git a/src/blockdiag/utils/rst/nodes.py b/src/blockdiag/utils/logging.py
similarity index 67%
copy from src/blockdiag/utils/rst/nodes.py
copy to src/blockdiag/utils/logging.py
index 14494b9..4aa6b89 100644
--- a/src/blockdiag/utils/rst/nodes.py
+++ b/src/blockdiag/utils/logging.py
@@ -13,8 +13,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from docutils import nodes
+import sys
+from blockdiag.utils.compat import u
-class blockdiag(nodes.General, nodes.Element):
- pass
+def warning(msg, *args):
+ sys.stderr.write(u("WARNING: "))
+ sys.stderr.write(msg % args)
+ sys.stderr.write(u("\n"))
+
+
+def error(msg, *args):
+ sys.stderr.write(u("ERROR: "))
+ sys.stderr.write(msg % args)
+ sys.stderr.write(u("\n"))
diff --git a/src/blockdiag/utils/rst/directives.py b/src/blockdiag/utils/rst/directives.py
index 18f116b..44cbd36 100644
--- a/src/blockdiag/utils/rst/directives.py
+++ b/src/blockdiag/utils/rst/directives.py
@@ -15,16 +15,16 @@
import os
import io
+from hashlib import sha1
from collections import namedtuple
from docutils import nodes
from docutils.parsers import rst
+from docutils.parsers.rst.roles import set_classes
from docutils.statemachine import ViewList
-from blockdiag import parser
-from blockdiag.builder import ScreenNodeBuilder
-from blockdiag.drawer import DiagramDraw
+
from blockdiag.utils.bootstrap import create_fontmap
from blockdiag.utils.compat import string_types
-from blockdiag.utils.rst.nodes import blockdiag
+from blockdiag.utils.rst.nodes import blockdiag as blockdiag_node
directive_options_default = dict(format='PNG',
antialias=False,
@@ -46,10 +46,22 @@ def relfn2path(env, filename):
return relfn, os.path.join(env.srcdir, relfn)
+def align(argument):
+ align_values = ('left', 'center', 'right')
+ return rst.directives.choice(argument, align_values)
+
+
+def figwidth_value(argument):
+ if argument.lower() == 'image':
+ return 'image'
+ else:
+ return rst.directives.length_or_percentage_or_unitless(argument, 'px')
+
+
class BlockdiagDirectiveBase(rst.Directive):
""" Directive to insert arbitrary dot markup. """
name = "blockdiag"
- node_class = blockdiag
+ node_class = blockdiag_node
has_content = True
required_arguments = 0
@@ -57,9 +69,17 @@ class BlockdiagDirectiveBase(rst.Directive):
final_argument_whitespace = False
option_spec = {
'alt': rst.directives.unchanged,
+ 'height': rst.directives.length_or_unitless,
+ 'width': rst.directives.length_or_percentage_or_unitless,
+ 'scale': rst.directives.percentage,
+ 'align': align,
'caption': rst.directives.unchanged,
'desctable': rst.directives.flag,
- 'maxwidth': rst.directives.nonnegative_int,
+ 'maxwidth': rst.directives.nonnegative_int, # deprecated
+ 'name': rst.directives.unchanged,
+ 'class': rst.directives.class_option,
+ 'figwidth': figwidth_value,
+ 'figclass': rst.directives.class_option,
}
def run(self):
@@ -88,19 +108,27 @@ class BlockdiagDirectiveBase(rst.Directive):
return [self.state_machine.reporter.warning(msg,
line=self.lineno)]
+ set_classes(self.options)
node = self.node_class()
+ results = [node]
+
node['code'] = dotcode
+ node['caption'] = self.options.pop('caption', None)
+ node['options'] = self.options
+
+ # for sphinxcontrib.* module (backward compatibility)
node['alt'] = self.options.get('alt')
- if 'caption' in self.options:
- node['caption'] = self.options.get('caption')
- node['options'] = {}
- if 'maxwidth' in self.options:
- node['options']['maxwidth'] = self.options['maxwidth']
- if 'desctable' in self.options:
- node['options']['desctable'] = self.options['desctable']
+ # replace maxwidth option to width (backward compatibility)
+ if 'maxwidth' in node['options']:
+ node['options']['width'] = str(node['options']['maxwidth'])
+
+ msg = ':maxwidth: option is deprecated. Use :width: option.'
+ warning = self.state_machine.reporter.warning(msg,
+ line=self.lineno)
+ results.append(warning)
- return [node]
+ return results
def source_filename(self, filename):
if hasattr(self.state.document.settings, 'env'):
@@ -112,7 +140,16 @@ class BlockdiagDirectiveBase(rst.Directive):
class BlockdiagDirective(BlockdiagDirectiveBase):
+ processor = None # backward compatibility for 1.4.0
+
def run(self):
+ figwidth = self.options.pop('figwidth', None)
+ figclasses = self.options.pop('figclass', None)
+ if self.options.get('caption'):
+ align = self.options.pop('align', None)
+ else:
+ align = None
+
results = super(BlockdiagDirective, self).run()
node = results[0]
@@ -122,18 +159,29 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
try:
diagram = self.node2diagram(node)
except Exception as e:
- raise self.warning(e.message)
+ raise self.warning(str(e))
if 'desctable' in node['options']:
- del node['options']['desctable']
results += self.description_tables(diagram)
results[0] = self.node2image(node, diagram)
+ self.add_name(results[0])
- if 'caption' in node:
+ if node.get('caption'):
fig = nodes.figure()
fig += results[0]
fig += nodes.caption(text=node['caption'])
+
+ if figwidth == 'image':
+ width = self.get_actual_width(node, diagram)
+ fig['width'] = str(width) + 'px'
+ elif figwidth is not None:
+ fig['width'] = figwidth
+ if figclasses:
+ fig['classes'] += figclasses
+ if align:
+ fig['align'] = align
+
results[0] = fig
return results
@@ -143,55 +191,66 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
return directive_options
def node2diagram(self, node):
- tree = parser.parse_string(node['code'])
- return ScreenNodeBuilder.build(tree)
+ if hasattr(node, 'to_diagram'):
+ return node.to_diagram()
+ else:
+ try:
+ tree = self.processor.parser.parse_string(node['code'])
+ except:
+ code = '%s { %s }' % (self.name, node['code'])
+ tree = self.processor.parser.parse_string(code)
+ node['code'] = code # replace if succeeded
- def node2image(self, node, diagram):
- options = node['options']
- filename = self.image_filename(node)
+ return self.processor.builder.ScreenNodeBuilder.build(tree)
+
+ def get_actual_width(self, node, diagram):
fontmap = self.create_fontmap()
- _format = self.global_options['format'].lower()
+ if hasattr(node, 'to_drawer'):
+ drawer = node.to_drawer('SVG', None, fontmap,
+ **self.global_options)
+ else:
+ drawer = self.processor.drawer.DiagramDraw('SVG', diagram,
+ None, fontmap=fontmap)
+
+ return drawer.pagesize()[0]
+ def node2image(self, node, diagram):
+ _format = self.global_options['format'].lower()
if _format == 'svg' and self.global_options['inline_svg'] is True:
- filename = None
+ return self.node2image_inline_svg(node, diagram)
- kwargs = dict(self.global_options)
- del kwargs['format']
- drawer = DiagramDraw(_format, diagram, filename,
- fontmap=fontmap, **kwargs)
+ filename = self.image_filename(node)
+ fontmap = self.create_fontmap()
+ if hasattr(node, 'to_drawer'):
+ drawer = node.to_drawer(_format, filename, fontmap,
+ **self.global_options)
+ else:
+ drawer = self.processor.drawer.DiagramDraw(_format, diagram,
+ filename,
+ fontmap=fontmap,
+ **self.global_options)
- if filename is None or not os.path.isfile(filename):
+ if not os.path.isfile(filename):
drawer.draw()
- content = drawer.save()
-
- if _format == 'svg' and self.global_options['inline_svg'] is True:
- size = drawer.pagesize()
- if 'maxwidth' in options and options['maxwidth'] < size[0]:
- ratio = float(options['maxwidth']) / size[0]
- new_size = (options['maxwidth'], int(size[1] * ratio))
- content = drawer.save(new_size)
-
- return nodes.raw('', content, format='html')
+ drawer.save()
- size = drawer.pagesize()
- if 'maxwidth' in options and options['maxwidth'] < size[0]:
- ratio = float(options['maxwidth']) / size[0]
- thumb_size = (options['maxwidth'], int(size[1] * ratio))
+ return nodes.image(uri=filename, **node['options'])
- thumb_filename = self.image_filename(node, prefix='_thumb')
- if not os.path.isfile(thumb_filename):
- drawer.filename = thumb_filename
- drawer.draw()
- drawer.save(thumb_size)
-
- image = nodes.image(uri=thumb_filename, target=filename)
+ def node2image_inline_svg(self, node, diagram):
+ fontmap = self.create_fontmap()
+ if hasattr(node, 'to_drawer'):
+ drawer = node.to_drawer('SVG', None, fontmap,
+ **self.global_options)
else:
- image = nodes.image(uri=filename)
+ drawer = self.processor.drawer.DiagramDraw('svg', diagram,
+ None, fontmap=fontmap,
+ **self.global_options)
+ drawer.draw()
- if node['alt']:
- image['alt'] = node['alt']
+ size = drawer.pagesize().resize(**node['options']).to_integer_point()
+ content = drawer.save(size)
- return image
+ return nodes.raw('', content, format='html')
def create_fontmap(self):
Options = namedtuple('Options', 'font fontmap')
@@ -206,26 +265,22 @@ class BlockdiagDirective(BlockdiagDirectiveBase):
return create_fontmap(options)
def image_filename(self, node, prefix='', ext='png'):
- try:
- from hashlib import sha1
- sha = sha1
- except ImportError:
- from sha import sha
- sha = sha
-
- options = dict(node['options'])
- options.update(font=self.global_options['fontpath'],
- antialias=self.global_options['antialias'])
- hashseed = (node['code'] + str(options)).encode('utf-8')
- hashed = sha(hashseed).hexdigest()
-
- _format = self.global_options['format']
- outputdir = self.global_options['outputdir']
- filename = "%s%s-%s.%s" % (self.name, prefix, hashed, _format.lower())
- if outputdir:
- filename = os.path.join(outputdir, filename)
-
- return filename
+ if hasattr(node, 'get_path'):
+ return node.get_path(**self.global_options)
+ else:
+ options = dict(node['options'])
+ options.update(font=self.global_options['fontpath'],
+ antialias=self.global_options['antialias'])
+ hashseed = (node['code'] + str(options)).encode('utf-8')
+ hashed = sha1(hashseed).hexdigest()
+
+ _format = self.global_options['format'].lower()
+ outputdir = self.global_options['outputdir']
+ filename = "%s%s-%s.%s" % (self.name, prefix, hashed, _format)
+ if outputdir:
+ filename = os.path.join(outputdir, filename)
+
+ return filename
def description_tables(self, diagram):
tables = []
diff --git a/src/blockdiag/utils/rst/nodes.py b/src/blockdiag/utils/rst/nodes.py
index 14494b9..71c80c0 100644
--- a/src/blockdiag/utils/rst/nodes.py
+++ b/src/blockdiag/utils/rst/nodes.py
@@ -13,8 +13,42 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import os
+from hashlib import sha1
from docutils import nodes
+import blockdiag.parser
+import blockdiag.builder
+import blockdiag.drawer
class blockdiag(nodes.General, nodes.Element):
- pass
+ name = 'blockdiag'
+ processor = blockdiag
+
+ def to_diagram(self):
+ try:
+ tree = self.processor.parser.parse_string(self['code'])
+ except:
+ code = '%s { %s }' % (self.name, self['code'])
+ tree = self.processor.parser.parse_string(code)
+ self['code'] = code # replace if succeeded
+
+ return self.processor.builder.ScreenNodeBuilder.build(tree)
+
+ def to_drawer(self, image_format, filename, fontmap, **kwargs):
+ diagram = self.to_diagram()
+ return self.processor.drawer.DiagramDraw(image_format, diagram,
+ filename, fontmap=fontmap,
+ **kwargs)
+
+ def get_path(self, **options):
+ options.update(self['options'])
+ hashseed = (self['code'] + str(options)).encode('utf-8')
+ hashed = sha1(hashseed).hexdigest()
+
+ filename = "%s-%s.%s" % (self.name, hashed, options['format'].lower())
+ outputdir = options.get('outputdir')
+ if outputdir:
+ filename = os.path.join(outputdir, filename)
+
+ return filename
diff --git a/src/blockdiag/utils/urlutil.py b/src/blockdiag/utils/urlutil.py
index 6d18c45..70b1699 100644
--- a/src/blockdiag/utils/urlutil.py
+++ b/src/blockdiag/utils/urlutil.py
@@ -1,4 +1,17 @@
# -*- 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.
try:
import urlparse
diff --git a/tox.ini b/tox.ini
index ad483b6..3b0b529 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,12 +1,14 @@
[tox]
-envlist=py26,py27,py32,py33
+envlist=py26,py27,py32,py33,py34
[testenv]
deps=
nose
- pep8
+ mock
flake8
docutils
+ reportlab
+ wand
commands=
nosetests
flake8 src
@@ -14,7 +16,9 @@ commands=
[testenv:py26]
deps=
nose
- pep8
+ mock
flake8
docutils
unittest2
+ reportlab
+ wand
--
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