├── tests ├── __init__.py ├── docs │ ├── basic │ │ ├── index.rst │ │ └── conf.py │ └── subdir │ │ ├── index.rst │ │ ├── subdir │ │ └── index.rst │ │ └── conf.py ├── test_basic.py ├── test_errors.py ├── test_latex.py └── test_html.py ├── AUTHORS ├── .codeclimate.yml ├── MANIFEST.in ├── .gitignore ├── setup.cfg ├── sphinxcontrib ├── __init__.py └── blockdiag.py ├── .github └── workflows │ ├── release.yml │ └── test.yml ├── tox.ini ├── README.rst ├── LICENSE └── setup.py /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Takeshi KOMIYA 2 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | languages: 2 | Python: true 3 | exclude_paths: 4 | - "tests/*.py" 5 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS 2 | include LICENSE 3 | include README.rst 4 | include CHANGES.* 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.pyc 3 | 4 | .coverage 5 | .tox/ 6 | .mypy_cache/ 7 | bin/ 8 | include/ 9 | lib/ 10 | -------------------------------------------------------------------------------- /tests/docs/basic/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to test's documentation! 2 | ================================ 3 | 4 | .. blockdiag:: 5 | 6 | { 7 | A -> B; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /tests/docs/subdir/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to test's documentation! 2 | ================================ 3 | 4 | .. blockdiag:: 5 | 6 | { 7 | A -> B; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /tests/docs/subdir/subdir/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to test's documentation! 2 | ================================ 3 | 4 | .. blockdiag:: 5 | 6 | { 7 | A -> B; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | ;tag_build = dev 3 | 4 | [build] 5 | build-base = _build 6 | 7 | [sdist] 8 | formats = gztar 9 | 10 | [wheel] 11 | universal = 1 12 | 13 | [aliases] 14 | release = check -r -s register sdist bdist_wheel upload 15 | 16 | [check] 17 | strict = 1 18 | restructuredtext = 1 19 | 20 | [flake8] 21 | max-line-length=120 22 | ignore=W504 23 | exclude=tests/docs/ 24 | -------------------------------------------------------------------------------- /sphinxcontrib/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | sphinxcontrib 4 | ~~~~~~~~~~~~~ 5 | 6 | This package is a namespace package that contains all extensions 7 | distributed in the ``sphinx-contrib`` distribution. 8 | 9 | :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. 10 | :license: BSD, see LICENSE for details. 11 | """ 12 | 13 | __import__('pkg_resources').declare_namespace(__name__) 14 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release a new package 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set up Python 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: '3.x' 17 | - name: Install dependencies 18 | run: pip install docutils setuptools wheel 19 | - name: Build package 20 | run: python setup.py sdist bdist_wheel 21 | - name: Upload package to PyPI 22 | uses: pypa/gh-action-pypi-publish@master 23 | if: startsWith(github.ref, 'refs/tags/') 24 | with: 25 | user: __token__ 26 | password: ${{ secrets.PYPI_API_TOKEN }} 27 | -------------------------------------------------------------------------------- /tests/test_basic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from sphinx_testing import with_app 4 | 5 | 6 | @with_app(buildername='html', srcdir='tests/docs/basic/') 7 | def test_build_html(app, status, warning): 8 | app.builder.build_all() 9 | 10 | 11 | @with_app(buildername='singlehtml', srcdir='tests/docs/basic/') 12 | def test_build_singlehtml(app, status, warning): 13 | app.builder.build_all() 14 | 15 | 16 | @with_app(buildername='latex', srcdir='tests/docs/basic/') 17 | def test_build_latex(app, status, warning): 18 | app.builder.build_all() 19 | 20 | 21 | @with_app(buildername='epub', srcdir='tests/docs/basic/') 22 | def test_build_epub(app, status, warning): 23 | app.builder.build_all() 24 | 25 | 26 | @with_app(buildername='json', srcdir='tests/docs/basic/') 27 | def test_build_json(app, status, warning): 28 | app.builder.build_all() 29 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | ## configuration for tox 2 | 3 | ## tox automates running certain tasks within virtualenvs. The following 4 | ## tox configuration outlines a basic setup for running unit tests and 5 | ## building sphinx docs in separate virtual environments. Give it a try! 6 | 7 | [tox] 8 | envlist = py{37,38,39},blockdiag_dev 9 | 10 | [testenv] 11 | usedevelop = True 12 | deps= 13 | nose 14 | mock 15 | flake8 16 | reportlab 17 | sphinx-testing >= 0.5.2 18 | # for funcparserlib-1.0.0a0 19 | pip_pre=true 20 | passenv= 21 | TRAVIS* 22 | commands= 23 | nosetests 24 | flake8 setup.py sphinxcontrib/ tests/ 25 | 26 | [testenv:blockdiag_dev] 27 | deps= 28 | {[testenv]deps} 29 | git+https://github.com/blockdiag/blockdiag 30 | 31 | [testenv:coverage] 32 | deps= 33 | {[testenv]deps} 34 | coverage 35 | coveralls 36 | commands= 37 | nosetests --with-coverage --cover-package=sphinxcontrib 38 | coveralls 39 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ubuntu: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | name: [py37, py38, py39, blockdiag_dev] 12 | include: 13 | - name: py37 14 | python: 3.7 15 | toxenv: py37 16 | - name: py38 17 | python: 3.8 18 | toxenv: py38 19 | - name: py39 20 | python: 3.9 21 | toxenv: py39 22 | - name: blockdiag_dev 23 | python: 3.9 24 | toxenv: blockdiag_dev 25 | 26 | steps: 27 | - uses: actions/checkout@v2 28 | - name: Set up Python ${{ matrix.python }} 29 | uses: actions/setup-python@v2 30 | with: 31 | python-version: ${{ matrix.python }} 32 | - name: Check Python version 33 | run: python --version 34 | - name: Install dependencies 35 | run: pip install -U tox 36 | - name: Run Tox 37 | run: tox -e ${{ matrix.toxenv }} -- -vv 38 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | sphinxcontrib-blockdiag 3 | ======================= 4 | 5 | .. image:: https://travis-ci.org/blockdiag/sphinxcontrib-blockdiag.svg?branch=master 6 | :target: https://travis-ci.org/blockdiag/sphinxcontrib-blockdiag 7 | 8 | .. image:: https://coveralls.io/repos/blockdiag/sphinxcontrib-blockdiag/badge.png?branch=master 9 | :target: https://coveralls.io/r/blockdiag/sphinxcontrib-blockdiag?branch=master 10 | 11 | .. image:: https://codeclimate.com/github/blockdiag/sphinxcontrib-blockdiag/badges/gpa.svg 12 | :target: https://codeclimate.com/github/blockdiag/sphinxcontrib-blockdiag 13 | 14 | A sphinx extension for embedding block diagram using blockdiag_. 15 | 16 | This extension enables you to insert block diagrams into your document. 17 | Following code is an example:: 18 | 19 | .. blockdiag:: 20 | 21 | diagram { 22 | A -> B -> C; 23 | B -> D; 24 | } 25 | 26 | .. _blockdiag: http://bitbucket.org/blockdiag/blockdiag/ 27 | 28 | 29 | For more details, see `online documentation`_ at http://blockdiag.com/. 30 | 31 | .. _online documentation: http://blockdiag.com/en/blockdiag/sphinxcontrib.html 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | If not otherwise noted, the extensions in this package are licensed 2 | under the following license. 3 | 4 | Copyright (c) 2009 by the contributors (see AUTHORS file). 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | * Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | 14 | * Redistributions in binary form must reproduce the above copyright 15 | notice, this list of conditions and the following disclaimer in the 16 | documentation and/or other materials provided with the distribution. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from setuptools import setup, find_packages 4 | 5 | requires = ['blockdiag>=1.5.0', 'Sphinx>=2.0'] 6 | 7 | setup( 8 | name='sphinxcontrib-blockdiag', 9 | version='3.0.0', 10 | url='https://github.com/blockdiag/sphinxcontrib-blockdiag', 11 | license='BSD', 12 | author='Takeshi KOMIYA', 13 | author_email='i.tkomiya@gmail.com', 14 | description='Sphinx "blockdiag" extension', 15 | long_description=open("README.rst").read(), 16 | zip_safe=False, 17 | classifiers=[ 18 | 'Development Status :: 5 - Production/Stable', 19 | 'Environment :: Console', 20 | 'Environment :: Web Environment', 21 | 'Framework :: Sphinx :: Extension', 22 | 'Intended Audience :: Developers', 23 | 'License :: OSI Approved :: BSD License', 24 | 'Operating System :: OS Independent', 25 | 'Programming Language :: Python', 26 | 'Programming Language :: Python :: 3', 27 | 'Programming Language :: Python :: 3 :: Only', 28 | 'Programming Language :: Python :: 3.7', 29 | 'Programming Language :: Python :: 3.8', 30 | 'Programming Language :: Python :: 3.9', 31 | 'Topic :: Documentation', 32 | 'Topic :: Documentation :: Sphinx', 33 | 'Topic :: Utilities', 34 | ], 35 | platforms='any', 36 | packages=find_packages(exclude=['tests']), 37 | include_package_data=True, 38 | python_requires=">=3.7", 39 | install_requires=requires, 40 | namespace_packages=['sphinxcontrib'], 41 | ) 42 | -------------------------------------------------------------------------------- /tests/test_errors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from mock import patch 4 | from sphinx_testing import with_app 5 | 6 | import sys 7 | import unittest 8 | 9 | 10 | class TestSphinxcontribBlockdiagErrors(unittest.TestCase): 11 | @with_app(srcdir='tests/docs/basic', write_docstring=True) 12 | def test_parse_error(self, app, status, warning): 13 | """ 14 | .. blockdiag:: 15 | 16 | { A -> B; 17 | """ 18 | app.builder.build_all() 19 | self.assertIn('got unexpected token:', warning.getvalue()) 20 | 21 | @with_app(srcdir='tests/docs/basic', confoverrides=dict(blockdiag_html_image_format='JPG')) 22 | def test_unknown_format_error(self, app, status, warning): 23 | app.builder.build_all() 24 | self.assertIn('unknown format: JPG', warning.getvalue()) 25 | 26 | @with_app(srcdir='tests/docs/basic', confoverrides=dict(blockdiag_html_image_format='PDF')) 27 | def test_reportlab_not_found_error(self, app, status, warning): 28 | try: 29 | # unload reportlab and make loading it impossible 30 | sys.modules.pop('reportlab', None) 31 | path = sys.path 32 | sys.path = [] 33 | 34 | app.builder.build_all() 35 | 36 | self.assertIn('Could not output PDF format. Install reportlab.', 37 | warning.getvalue()) 38 | finally: 39 | sys.path = path 40 | 41 | @with_app(srcdir='tests/docs/basic') 42 | @patch("blockdiag.utils.rst.nodes.blockdiag.processor.drawer.DiagramDraw") 43 | def test_rendering_error(self, app, status, warning, DiagramDraw): 44 | DiagramDraw.side_effect = RuntimeError("UNKNOWN ERROR!") 45 | app.builder.build_all() 46 | self.assertIn('UNKNOWN ERROR!', warning.getvalue()) 47 | 48 | @with_app(srcdir='tests/docs/basic') 49 | @patch("sphinxcontrib.blockdiag.blockdiag.drawer.DiagramDraw.draw") 50 | def test_font_settings_error(self, app, status, warning, draw): 51 | draw.side_effect = UnicodeEncodeError("", "", 0, 0, "") 52 | app.builder.build_all() 53 | self.assertIn('UnicodeEncodeError caught (check your font settings)', 54 | warning.getvalue()) 55 | -------------------------------------------------------------------------------- /tests/test_latex.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import re 5 | from sphinx_testing import with_app 6 | 7 | import unittest 8 | 9 | CR = "\r?\n" 10 | 11 | blockdiag_fontpath = '/usr/share/fonts/truetype/ipafont/ipagp.ttf' 12 | with_png_app = with_app(srcdir='tests/docs/basic', 13 | buildername='latex', 14 | write_docstring=True, 15 | confoverrides={ 16 | 'latex_documents': [('index', 'test.tex', '', 'test', 'manual')], 17 | }) 18 | with_pdf_app = with_app(srcdir='tests/docs/basic', 19 | buildername='latex', 20 | write_docstring=True, 21 | confoverrides={ 22 | 'latex_documents': [('index', 'test.tex', '', 'test', 'manual')], 23 | 'blockdiag_latex_image_format': 'PDF', 24 | 'blockdiag_fontpath': blockdiag_fontpath, 25 | }) 26 | with_oldpdf_app = with_app(srcdir='tests/docs/basic', 27 | buildername='latex', 28 | write_docstring=True, 29 | confoverrides={ 30 | 'latex_documents': [('index', 'test.tex', '', 'test', 'manual')], 31 | 'blockdiag_tex_image_format': 'PDF', 32 | 'blockdiag_fontpath': blockdiag_fontpath, 33 | }) 34 | 35 | 36 | class TestSphinxcontribBlockdiagLatex(unittest.TestCase): 37 | @with_png_app 38 | def test_build_png_image(self, app, status, warning): 39 | """ 40 | .. blockdiag:: 41 | 42 | A -> B; 43 | """ 44 | app.builder.build_all() 45 | source = (app.outdir / 'test.tex').read_text(encoding='utf-8') 46 | self.assertRegexpMatches(source, r'\\sphinxincludegraphics{{blockdiag-.*?}.png}') 47 | 48 | @unittest.skipUnless(os.path.exists(blockdiag_fontpath), "TrueType font not found") 49 | @with_pdf_app 50 | def test_build_pdf_image1(self, app, status, warning): 51 | """ 52 | .. blockdiag:: 53 | 54 | A -> B; 55 | """ 56 | app.builder.build_all() 57 | source = (app.outdir / 'test.tex').read_text(encoding='utf-8') 58 | self.assertRegexpMatches(source, r'\\sphinxincludegraphics{{blockdiag-.*?}.pdf}') 59 | 60 | @unittest.skipUnless(os.path.exists(blockdiag_fontpath), "TrueType font not found") 61 | @with_oldpdf_app 62 | def test_build_pdf_image2(self, app, status, warning): 63 | """ 64 | .. blockdiag:: 65 | 66 | A -> B; 67 | """ 68 | app.builder.build_all() 69 | source = (app.outdir / 'test.tex').read_text(encoding='utf-8') 70 | self.assertRegexpMatches(source, r'\\sphinxincludegraphics{{blockdiag-.*?}.pdf}') 71 | 72 | @with_png_app 73 | def test_width_option(self, app, status, warning): 74 | """ 75 | .. blockdiag:: 76 | :width: 3cm 77 | 78 | A -> B; 79 | """ 80 | app.builder.build_all() 81 | source = (app.outdir / 'test.tex').read_text(encoding='utf-8') 82 | self.assertRegexpMatches(source, r'\\sphinxincludegraphics\[width=3cm\]{{blockdiag-.*?}.png}') 83 | 84 | @with_png_app 85 | def test_height_option(self, app, status, warning): 86 | """ 87 | .. blockdiag:: 88 | :height: 4cm 89 | 90 | A -> B; 91 | """ 92 | app.builder.build_all() 93 | source = (app.outdir / 'test.tex').read_text(encoding='utf-8') 94 | self.assertRegexpMatches(source, r'\\sphinxincludegraphics\[height=4cm\]{{blockdiag-.*?}.png}') 95 | 96 | @with_png_app 97 | def test_scale_option(self, app, status, warning): 98 | """ 99 | .. blockdiag:: 100 | :scale: 50% 101 | 102 | A -> B; 103 | """ 104 | app.builder.build_all() 105 | source = (app.outdir / 'test.tex').read_text(encoding='utf-8') 106 | self.assertRegexpMatches(source, r'\\sphinxincludegraphics\[scale=0.5\]{{blockdiag-.*?}.png}') 107 | 108 | @with_png_app 109 | def test_align_option_left(self, app, status, warning): 110 | """ 111 | .. blockdiag:: 112 | :align: left 113 | 114 | A -> B; 115 | """ 116 | app.builder.build_all() 117 | source = (app.outdir / 'test.tex').read_text(encoding='utf-8') 118 | self.assertRegexpMatches(source, 119 | (r'{\\sphinxincludegraphics{{blockdiag-.*?}.png}' 120 | r'\\hspace\*{\\fill}}')) 121 | 122 | @with_png_app 123 | def test_align_option_center(self, app, status, warning): 124 | """ 125 | .. blockdiag:: 126 | :align: center 127 | 128 | A -> B; 129 | """ 130 | app.builder.build_all() 131 | source = (app.outdir / 'test.tex').read_text(encoding='utf-8') 132 | self.assertRegexpMatches(source, 133 | (r'{\\hspace\*{\\fill}' 134 | r'\\sphinxincludegraphics{{blockdiag-.*?}.png}' 135 | r'\\hspace\*{\\fill}}')) 136 | 137 | @with_png_app 138 | def test_align_option_right(self, app, status, warning): 139 | """ 140 | .. blockdiag:: 141 | :align: right 142 | 143 | A -> B; 144 | """ 145 | app.builder.build_all() 146 | source = (app.outdir / 'test.tex').read_text(encoding='utf-8') 147 | self.assertRegexpMatches(source, 148 | (r'{\\hspace\*{\\fill}' 149 | r'\\sphinxincludegraphics{{blockdiag-.*?}.png}}')) 150 | 151 | @with_png_app 152 | def test_caption_option(self, app, status, warning): 153 | """ 154 | .. blockdiag:: 155 | :caption: hello world 156 | 157 | A -> B; 158 | """ 159 | app.builder.build_all() 160 | source = (app.outdir / 'test.tex').read_text(encoding='utf-8') 161 | 162 | figure = re.compile((r'\\begin{figure}\[htbp\]' + CR + 163 | r'\\centering' + CR + 164 | r'\\capstart' + CR + CR + 165 | r'\\noindent\\sphinxincludegraphics{{blockdiag-.*?}.png}' + CR + 166 | r'\\caption{hello world}\\label{\\detokenize{index:id1}}\\end{figure}'), 167 | re.DOTALL) 168 | self.assertRegexpMatches(source, figure) 169 | 170 | @with_png_app 171 | def test_caption_option_and_align_option(self, app, status, warning): 172 | """ 173 | .. blockdiag:: 174 | :align: left 175 | :caption: hello world 176 | 177 | A -> B; 178 | """ 179 | app.builder.build_all() 180 | source = (app.outdir / 'test.tex').read_text(encoding='utf-8') 181 | 182 | figure = re.compile((r'\\begin{wrapfigure}{l}{0pt}' + CR + 183 | r'\\centering' + CR + 184 | r'\\noindent\\sphinxincludegraphics{{blockdiag-.*?}.png}' + CR + 185 | r'\\caption{hello world}\\label{\\detokenize{index:id1}}\\end{wrapfigure}'), 186 | re.DOTALL) 187 | self.assertRegexpMatches(source, figure) 188 | 189 | @with_png_app 190 | def test_href(self, app, status, warning): 191 | """ 192 | .. blockdiag:: 193 | 194 | A -> B; 195 | A [href = ':ref:`target`']; 196 | """ 197 | app.builder.build_all() 198 | source = (app.outdir / 'test.tex').read_text(encoding='utf-8') 199 | self.assertRegexpMatches(source, r'\\sphinxincludegraphics{{blockdiag-.*?}.png}') 200 | -------------------------------------------------------------------------------- /tests/docs/basic/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # test documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Dec 8 15:14:18 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | #sys.path.insert(0, os.path.abspath('.')) 22 | 23 | # -- General configuration ------------------------------------------------ 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | #needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be 29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 | # ones. 31 | extensions = ['sphinxcontrib.blockdiag'] 32 | 33 | # Add any paths that contain templates here, relative to this directory. 34 | templates_path = ['_templates'] 35 | 36 | # The suffix of source filenames. 37 | source_suffix = '.rst' 38 | 39 | # The encoding of source files. 40 | #source_encoding = 'utf-8-sig' 41 | 42 | # The master toctree document. 43 | master_doc = 'index' 44 | 45 | # General information about the project. 46 | project = u'test' 47 | copyright = u'2013, test' 48 | 49 | # The version info for the project you're documenting, acts as replacement for 50 | # |version| and |release|, also used in various other places throughout the 51 | # built documents. 52 | # 53 | # The short X.Y version. 54 | version = '1.0' 55 | # The full version, including alpha/beta/rc tags. 56 | release = '1.0' 57 | 58 | # The language for content autogenerated by Sphinx. Refer to documentation 59 | # for a list of supported languages. 60 | #language = None 61 | 62 | # There are two options for replacing |today|: either, you set today to some 63 | # non-false value, then it is used: 64 | #today = '' 65 | # Else, today_fmt is used as the format for a strftime call. 66 | #today_fmt = '%B %d, %Y' 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | exclude_patterns = ['_build'] 71 | 72 | # The reST default role (used for this markup: `text`) to use for all 73 | # documents. 74 | #default_role = None 75 | 76 | # If true, '()' will be appended to :func: etc. cross-reference text. 77 | #add_function_parentheses = True 78 | 79 | # If true, the current module name will be prepended to all description 80 | # unit titles (such as .. function::). 81 | #add_module_names = True 82 | 83 | # If true, sectionauthor and moduleauthor directives will be shown in the 84 | # output. They are ignored by default. 85 | #show_authors = False 86 | 87 | # The name of the Pygments (syntax highlighting) style to use. 88 | pygments_style = 'sphinx' 89 | 90 | # A list of ignored prefixes for module index sorting. 91 | #modindex_common_prefix = [] 92 | 93 | # If true, keep warnings as "system message" paragraphs in the built documents. 94 | #keep_warnings = False 95 | 96 | 97 | # -- Options for HTML output ---------------------------------------------- 98 | 99 | # The theme to use for HTML and HTML Help pages. See the documentation for 100 | # a list of builtin themes. 101 | html_theme = 'default' 102 | 103 | # Theme options are theme-specific and customize the look and feel of a theme 104 | # further. For a list of options available for each theme, see the 105 | # documentation. 106 | #html_theme_options = {} 107 | 108 | # Add any paths that contain custom themes here, relative to this directory. 109 | #html_theme_path = [] 110 | 111 | # The name for this set of Sphinx documents. If None, it defaults to 112 | # " v documentation". 113 | #html_title = None 114 | 115 | # A shorter title for the navigation bar. Default is the same as html_title. 116 | #html_short_title = None 117 | 118 | # The name of an image file (relative to this directory) to place at the top 119 | # of the sidebar. 120 | #html_logo = None 121 | 122 | # The name of an image file (within the static path) to use as favicon of the 123 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 124 | # pixels large. 125 | #html_favicon = None 126 | 127 | # Add any paths that contain custom static files (such as style sheets) here, 128 | # relative to this directory. They are copied after the builtin static files, 129 | # so a file named "default.css" will overwrite the builtin "default.css". 130 | html_static_path = ['_static'] 131 | 132 | # Add any extra paths that contain custom files (such as robots.txt or 133 | # .htaccess) here, relative to this directory. These files are copied 134 | # directly to the root of the documentation. 135 | #html_extra_path = [] 136 | 137 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 138 | # using the given strftime format. 139 | #html_last_updated_fmt = '%b %d, %Y' 140 | 141 | # If true, SmartyPants will be used to convert quotes and dashes to 142 | # typographically correct entities. 143 | #html_use_smartypants = True 144 | 145 | # Custom sidebar templates, maps document names to template names. 146 | #html_sidebars = {} 147 | 148 | # Additional templates that should be rendered to pages, maps page names to 149 | # template names. 150 | #html_additional_pages = {} 151 | 152 | # If false, no module index is generated. 153 | #html_domain_indices = True 154 | 155 | # If false, no index is generated. 156 | #html_use_index = True 157 | 158 | # If true, the index is split into individual pages for each letter. 159 | #html_split_index = False 160 | 161 | # If true, links to the reST sources are added to the pages. 162 | #html_show_sourcelink = True 163 | 164 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 165 | #html_show_sphinx = True 166 | 167 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 168 | #html_show_copyright = True 169 | 170 | # If true, an OpenSearch description file will be output, and all pages will 171 | # contain a tag referring to it. The value of this option must be the 172 | # base URL from which the finished HTML is served. 173 | #html_use_opensearch = '' 174 | 175 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 176 | #html_file_suffix = None 177 | 178 | # Output file base name for HTML help builder. 179 | htmlhelp_basename = 'testdoc' 180 | 181 | 182 | # -- Options for LaTeX output --------------------------------------------- 183 | 184 | latex_elements = { 185 | # The paper size ('letterpaper' or 'a4paper'). 186 | #'papersize': 'letterpaper', 187 | 188 | # The font size ('10pt', '11pt' or '12pt'). 189 | #'pointsize': '10pt', 190 | 191 | # Additional stuff for the LaTeX preamble. 192 | #'preamble': '', 193 | } 194 | 195 | # Grouping the document tree into LaTeX files. List of tuples 196 | # (source start file, target name, title, 197 | # author, documentclass [howto, manual, or own class]). 198 | latex_documents = [ 199 | ('index', 'test.tex', u'test Documentation', 200 | u'test', 'manual'), 201 | ] 202 | 203 | # The name of an image file (relative to this directory) to place at the top of 204 | # the title page. 205 | #latex_logo = None 206 | 207 | # For "manual" documents, if this is true, then toplevel headings are parts, 208 | # not chapters. 209 | #latex_use_parts = False 210 | 211 | # If true, show page references after internal links. 212 | #latex_show_pagerefs = False 213 | 214 | # If true, show URL addresses after external links. 215 | #latex_show_urls = False 216 | 217 | # Documents to append as an appendix to all manuals. 218 | #latex_appendices = [] 219 | 220 | # If false, no module index is generated. 221 | #latex_domain_indices = True 222 | 223 | 224 | # -- Options for manual page output --------------------------------------- 225 | 226 | # One entry per manual page. List of tuples 227 | # (source start file, name, description, authors, manual section). 228 | man_pages = [ 229 | ('index', 'test', u'test Documentation', 230 | [u'test'], 1) 231 | ] 232 | 233 | # If true, show URL addresses after external links. 234 | #man_show_urls = False 235 | 236 | 237 | # -- Options for Texinfo output ------------------------------------------- 238 | 239 | # Grouping the document tree into Texinfo files. List of tuples 240 | # (source start file, target name, title, author, 241 | # dir menu entry, description, category) 242 | texinfo_documents = [ 243 | ('index', 'test', u'test Documentation', 244 | u'test', 'test', 'One line description of project.', 245 | 'Miscellaneous'), 246 | ] 247 | 248 | # Documents to append as an appendix to all manuals. 249 | #texinfo_appendices = [] 250 | 251 | # If false, no module index is generated. 252 | #texinfo_domain_indices = True 253 | 254 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 255 | #texinfo_show_urls = 'footnote' 256 | 257 | # If true, do not generate a @detailmenu in the "Top" node's menu. 258 | #texinfo_no_detailmenu = False 259 | -------------------------------------------------------------------------------- /tests/docs/subdir/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # test documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Dec 8 15:14:18 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | #sys.path.insert(0, os.path.abspath('.')) 22 | 23 | # -- General configuration ------------------------------------------------ 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | #needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be 29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 | # ones. 31 | extensions = ['sphinxcontrib.blockdiag'] 32 | 33 | # Add any paths that contain templates here, relative to this directory. 34 | templates_path = ['_templates'] 35 | 36 | # The suffix of source filenames. 37 | source_suffix = '.rst' 38 | 39 | # The encoding of source files. 40 | #source_encoding = 'utf-8-sig' 41 | 42 | # The master toctree document. 43 | master_doc = 'index' 44 | 45 | # General information about the project. 46 | project = u'test' 47 | copyright = u'2013, test' 48 | 49 | # The version info for the project you're documenting, acts as replacement for 50 | # |version| and |release|, also used in various other places throughout the 51 | # built documents. 52 | # 53 | # The short X.Y version. 54 | version = '1.0' 55 | # The full version, including alpha/beta/rc tags. 56 | release = '1.0' 57 | 58 | # The language for content autogenerated by Sphinx. Refer to documentation 59 | # for a list of supported languages. 60 | #language = None 61 | 62 | # There are two options for replacing |today|: either, you set today to some 63 | # non-false value, then it is used: 64 | #today = '' 65 | # Else, today_fmt is used as the format for a strftime call. 66 | #today_fmt = '%B %d, %Y' 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | exclude_patterns = ['_build'] 71 | 72 | # The reST default role (used for this markup: `text`) to use for all 73 | # documents. 74 | #default_role = None 75 | 76 | # If true, '()' will be appended to :func: etc. cross-reference text. 77 | #add_function_parentheses = True 78 | 79 | # If true, the current module name will be prepended to all description 80 | # unit titles (such as .. function::). 81 | #add_module_names = True 82 | 83 | # If true, sectionauthor and moduleauthor directives will be shown in the 84 | # output. They are ignored by default. 85 | #show_authors = False 86 | 87 | # The name of the Pygments (syntax highlighting) style to use. 88 | pygments_style = 'sphinx' 89 | 90 | # A list of ignored prefixes for module index sorting. 91 | #modindex_common_prefix = [] 92 | 93 | # If true, keep warnings as "system message" paragraphs in the built documents. 94 | #keep_warnings = False 95 | 96 | 97 | # -- Options for HTML output ---------------------------------------------- 98 | 99 | # The theme to use for HTML and HTML Help pages. See the documentation for 100 | # a list of builtin themes. 101 | html_theme = 'default' 102 | 103 | # Theme options are theme-specific and customize the look and feel of a theme 104 | # further. For a list of options available for each theme, see the 105 | # documentation. 106 | #html_theme_options = {} 107 | 108 | # Add any paths that contain custom themes here, relative to this directory. 109 | #html_theme_path = [] 110 | 111 | # The name for this set of Sphinx documents. If None, it defaults to 112 | # " v documentation". 113 | #html_title = None 114 | 115 | # A shorter title for the navigation bar. Default is the same as html_title. 116 | #html_short_title = None 117 | 118 | # The name of an image file (relative to this directory) to place at the top 119 | # of the sidebar. 120 | #html_logo = None 121 | 122 | # The name of an image file (within the static path) to use as favicon of the 123 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 124 | # pixels large. 125 | #html_favicon = None 126 | 127 | # Add any paths that contain custom static files (such as style sheets) here, 128 | # relative to this directory. They are copied after the builtin static files, 129 | # so a file named "default.css" will overwrite the builtin "default.css". 130 | html_static_path = ['_static'] 131 | 132 | # Add any extra paths that contain custom files (such as robots.txt or 133 | # .htaccess) here, relative to this directory. These files are copied 134 | # directly to the root of the documentation. 135 | #html_extra_path = [] 136 | 137 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 138 | # using the given strftime format. 139 | #html_last_updated_fmt = '%b %d, %Y' 140 | 141 | # If true, SmartyPants will be used to convert quotes and dashes to 142 | # typographically correct entities. 143 | #html_use_smartypants = True 144 | 145 | # Custom sidebar templates, maps document names to template names. 146 | #html_sidebars = {} 147 | 148 | # Additional templates that should be rendered to pages, maps page names to 149 | # template names. 150 | #html_additional_pages = {} 151 | 152 | # If false, no module index is generated. 153 | #html_domain_indices = True 154 | 155 | # If false, no index is generated. 156 | #html_use_index = True 157 | 158 | # If true, the index is split into individual pages for each letter. 159 | #html_split_index = False 160 | 161 | # If true, links to the reST sources are added to the pages. 162 | #html_show_sourcelink = True 163 | 164 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 165 | #html_show_sphinx = True 166 | 167 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 168 | #html_show_copyright = True 169 | 170 | # If true, an OpenSearch description file will be output, and all pages will 171 | # contain a tag referring to it. The value of this option must be the 172 | # base URL from which the finished HTML is served. 173 | #html_use_opensearch = '' 174 | 175 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 176 | #html_file_suffix = None 177 | 178 | # Output file base name for HTML help builder. 179 | htmlhelp_basename = 'testdoc' 180 | 181 | 182 | # -- Options for LaTeX output --------------------------------------------- 183 | 184 | latex_elements = { 185 | # The paper size ('letterpaper' or 'a4paper'). 186 | #'papersize': 'letterpaper', 187 | 188 | # The font size ('10pt', '11pt' or '12pt'). 189 | #'pointsize': '10pt', 190 | 191 | # Additional stuff for the LaTeX preamble. 192 | #'preamble': '', 193 | } 194 | 195 | # Grouping the document tree into LaTeX files. List of tuples 196 | # (source start file, target name, title, 197 | # author, documentclass [howto, manual, or own class]). 198 | latex_documents = [ 199 | ('index', 'test.tex', u'test Documentation', 200 | u'test', 'manual'), 201 | ] 202 | 203 | # The name of an image file (relative to this directory) to place at the top of 204 | # the title page. 205 | #latex_logo = None 206 | 207 | # For "manual" documents, if this is true, then toplevel headings are parts, 208 | # not chapters. 209 | #latex_use_parts = False 210 | 211 | # If true, show page references after internal links. 212 | #latex_show_pagerefs = False 213 | 214 | # If true, show URL addresses after external links. 215 | #latex_show_urls = False 216 | 217 | # Documents to append as an appendix to all manuals. 218 | #latex_appendices = [] 219 | 220 | # If false, no module index is generated. 221 | #latex_domain_indices = True 222 | 223 | 224 | # -- Options for manual page output --------------------------------------- 225 | 226 | # One entry per manual page. List of tuples 227 | # (source start file, name, description, authors, manual section). 228 | man_pages = [ 229 | ('index', 'test', u'test Documentation', 230 | [u'test'], 1) 231 | ] 232 | 233 | # If true, show URL addresses after external links. 234 | #man_show_urls = False 235 | 236 | 237 | # -- Options for Texinfo output ------------------------------------------- 238 | 239 | # Grouping the document tree into Texinfo files. List of tuples 240 | # (source start file, target name, title, author, 241 | # dir menu entry, description, category) 242 | texinfo_documents = [ 243 | ('index', 'test', u'test Documentation', 244 | u'test', 'test', 'One line description of project.', 245 | 'Miscellaneous'), 246 | ] 247 | 248 | # Documents to append as an appendix to all manuals. 249 | #texinfo_appendices = [] 250 | 251 | # If false, no module index is generated. 252 | #texinfo_domain_indices = True 253 | 254 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 255 | #texinfo_show_urls = 'footnote' 256 | 257 | # If true, do not generate a @detailmenu in the "Top" node's menu. 258 | #texinfo_no_detailmenu = False 259 | -------------------------------------------------------------------------------- /sphinxcontrib/blockdiag.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | blockdiag.sphinx_ext 4 | ~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Allow blockdiag-formatted diagrams to be included in Sphinx-generated 7 | documents inline. 8 | 9 | :copyright: Copyright 2010 by Takeshi Komiya. 10 | :license: BSDL. 11 | """ 12 | 13 | import os 14 | import re 15 | import posixpath 16 | import traceback 17 | import pkg_resources 18 | from collections import namedtuple 19 | from docutils import nodes 20 | from sphinx import addnodes 21 | from sphinx.util import logging 22 | from sphinx.util.osutil import ensuredir 23 | 24 | import blockdiag.utils.rst.nodes 25 | import blockdiag.utils.rst.directives 26 | from blockdiag.utils.bootstrap import detectfont, Application 27 | from blockdiag.utils.fontmap import FontMap 28 | from blockdiag.utils.rst.directives import with_blockdiag 29 | 30 | # fontconfig; it will be initialized on `builder-inited` event. 31 | fontmap = None 32 | 33 | logger = logging.getLogger(__name__) 34 | 35 | 36 | class blockdiag_node(blockdiag.utils.rst.nodes.blockdiag): 37 | def to_drawer(self, image_format, builder, **kwargs): 38 | if 'filename' in kwargs: 39 | filename = kwargs.pop('filename') 40 | else: 41 | filename = self.get_abspath(image_format, builder) 42 | 43 | antialias = builder.config.blockdiag_antialias 44 | transparency = builder.config.blockdiag_transparency 45 | image = super(blockdiag_node, self).to_drawer(image_format, filename, fontmap, 46 | antialias=antialias, transparency=transparency, 47 | **kwargs) 48 | for node in image.diagram.traverse_nodes(): 49 | node.href = resolve_reference(builder, node.href) 50 | 51 | return image 52 | 53 | def get_relpath(self, image_format, builder): 54 | options = dict(antialias=builder.config.blockdiag_antialias, 55 | fontpath=builder.config.blockdiag_fontpath, 56 | fontmap=builder.config.blockdiag_fontmap, 57 | format=image_format, 58 | transparency=builder.config.blockdiag_transparency) 59 | return posixpath.join(builder.imgpath, self.get_path(**options)) 60 | 61 | def get_abspath(self, image_format, builder): 62 | options = dict(antialias=builder.config.blockdiag_antialias, 63 | fontpath=builder.config.blockdiag_fontpath, 64 | fontmap=builder.config.blockdiag_fontmap, 65 | format=image_format, 66 | transparency=builder.config.blockdiag_transparency) 67 | 68 | path = os.path.join(builder.outdir, builder.imagedir, self.get_path(**options)) 69 | ensuredir(os.path.dirname(path)) 70 | 71 | return path 72 | 73 | 74 | class Blockdiag(blockdiag.utils.rst.directives.BlockdiagDirective): 75 | node_class = blockdiag_node 76 | 77 | def node2image(self, node, diagram): 78 | return node 79 | 80 | 81 | def resolve_reference(builder, href): 82 | if href is None: 83 | return None 84 | 85 | pattern = re.compile("^:ref:`(.+?)`", re.UNICODE) 86 | matched = pattern.search(href) 87 | if matched is None: 88 | return href 89 | elif not hasattr(builder, 'current_docname'): # ex. latex builder 90 | return matched.group(1) 91 | else: 92 | refid = matched.group(1) 93 | domain = builder.env.domains['std'] 94 | node = addnodes.pending_xref(refexplicit=False) 95 | xref = domain.resolve_xref(builder.env, builder.current_docname, builder, 96 | 'ref', refid, node, node) 97 | if xref: 98 | if 'refid' in xref: 99 | return "#" + xref['refid'] 100 | else: 101 | return xref['refuri'] 102 | else: 103 | logger.warning('undefined label: %s', refid) 104 | return None 105 | 106 | 107 | def html_render_svg(self, node): 108 | image = node.to_drawer('SVG', self.builder, filename=None, nodoctype=True) 109 | image.draw() 110 | 111 | # align 112 | align = node['options'].get('align', 'default') 113 | self.body.append('
' % align) 114 | self.context.append('
\n') 115 | 116 | # reftarget 117 | for node_id in node['ids']: 118 | self.body.append('' % node_id) 119 | 120 | # resize image 121 | size = image.pagesize().resize(**node['options']) 122 | self.body.append(image.save(size)) 123 | self.context.append('') 124 | 125 | 126 | def html_render_clickablemap(self, image, width_ratio, height_ratio): 127 | href_nodes = [node for node in image.nodes if node.href] 128 | if not href_nodes: 129 | return 130 | 131 | self.body.append('' % id(image)) 132 | for node in href_nodes: 133 | x1, y1, x2, y2 = image.metrics.cell(node) 134 | 135 | x1 *= width_ratio 136 | x2 *= width_ratio 137 | y1 *= height_ratio 138 | y2 *= height_ratio 139 | areatag = '' % (x1, y1, x2, y2, node.href) 140 | self.body.append(areatag) 141 | 142 | self.body.append('') 143 | 144 | 145 | def html_render_png(self, node): 146 | image = node.to_drawer('PNG', self.builder) 147 | if not os.path.isfile(image.filename): 148 | image.draw() 149 | image.save() 150 | 151 | # align 152 | align = node['options'].get('align', 'default') 153 | self.body.append('
' % align) 154 | self.context.append('
\n') 155 | 156 | # link to original image 157 | relpath = node.get_relpath('PNG', self.builder) 158 | relpath = relpath.replace(os.path.sep, '/') 159 | if 'width' in node['options'] or 'height' in node['options'] or 'scale' in node['options']: 160 | self.body.append('' % relpath) 161 | self.context.append('') 162 | else: 163 | self.context.append('') 164 | 165 | # tag 166 | original_size = image.pagesize() 167 | resized = original_size.resize(**node['options']) 168 | img_attr = dict(src=relpath, 169 | width=resized.width, 170 | height=resized.height) 171 | 172 | if any(node.href for node in image.nodes): 173 | img_attr['usemap'] = "#map_%d" % id(image) 174 | 175 | width_ratio = float(resized.width) / original_size.width 176 | height_ratio = float(resized.height) / original_size.height 177 | html_render_clickablemap(self, image, width_ratio, height_ratio) 178 | 179 | if 'alt' in node['options']: 180 | img_attr['alt'] = node['options']['alt'] 181 | 182 | self.body.append(self.starttag(node, 'img', '', empty=True, **img_attr)) 183 | 184 | 185 | @with_blockdiag 186 | def html_visit_blockdiag(self, node): 187 | try: 188 | image_format = get_image_format_for(self.builder) 189 | if image_format.upper() == 'SVG': 190 | html_render_svg(self, node) 191 | else: 192 | html_render_png(self, node) 193 | except UnicodeEncodeError: 194 | if self.builder.config.blockdiag_debug: 195 | traceback.print_exc() 196 | 197 | msg = ("blockdiag error: UnicodeEncodeError caught " 198 | "(check your font settings)") 199 | logger.warning(msg) 200 | raise nodes.SkipNode 201 | except Exception as exc: 202 | if self.builder.config.blockdiag_debug: 203 | traceback.print_exc() 204 | 205 | logger.warning('dot code %r: %s', node['code'], exc) 206 | raise nodes.SkipNode 207 | 208 | 209 | def html_depart_blockdiag(self, node): 210 | self.body.append(self.context.pop()) 211 | self.body.append(self.context.pop()) 212 | 213 | 214 | def get_image_format_for(builder): 215 | if builder.format in ('html', 'slides'): 216 | image_format = builder.config.blockdiag_html_image_format.upper() 217 | elif builder.format == 'latex': 218 | if builder.config.blockdiag_tex_image_format: 219 | image_format = builder.config.blockdiag_tex_image_format.upper() 220 | else: 221 | image_format = builder.config.blockdiag_latex_image_format.upper() 222 | else: 223 | image_format = 'PNG' 224 | 225 | if image_format.upper() not in ('PNG', 'PDF', 'SVG'): 226 | raise ValueError('unknown format: %s' % image_format) 227 | 228 | if image_format.upper() == 'PDF': 229 | try: 230 | import reportlab # NOQA: importing test 231 | except ImportError: 232 | raise ImportError('Could not output PDF format. Install reportlab.') 233 | 234 | return image_format 235 | 236 | 237 | def on_builder_inited(self): 238 | # show deprecated message 239 | if self.builder.config.blockdiag_tex_image_format: 240 | logger.warning('blockdiag_tex_image_format is deprecated. Use blockdiag_latex_image_format.') 241 | 242 | # initialize fontmap 243 | global fontmap 244 | 245 | try: 246 | fontmappath = self.builder.config.blockdiag_fontmap 247 | fontmap = FontMap(fontmappath) 248 | except Exception: 249 | fontmap = FontMap(None) 250 | 251 | try: 252 | fontpath = self.builder.config.blockdiag_fontpath 253 | if isinstance(fontpath, str): 254 | fontpath = [fontpath] 255 | 256 | if fontpath: 257 | config = namedtuple('Config', 'font')(fontpath) 258 | fontpath = detectfont(config) 259 | fontmap.set_default_font(fontpath) 260 | except Exception: 261 | pass 262 | 263 | 264 | def on_doctree_resolved(self, doctree, docname): 265 | if self.builder.format in ('html', 'slides'): 266 | return 267 | 268 | try: 269 | image_format = get_image_format_for(self.builder) 270 | except Exception as exc: 271 | if self.builder.config.blockdiag_debug: 272 | traceback.print_exc() 273 | 274 | logger.warning('blockdiag error: %s', exc) 275 | for node in doctree.traverse(blockdiag_node): 276 | node.parent.remove(node) 277 | 278 | return 279 | 280 | for node in doctree.traverse(blockdiag_node): 281 | try: 282 | with Application(): 283 | relfn = node.get_relpath(image_format, self.builder) 284 | image = node.to_drawer(image_format, self.builder) 285 | if not os.path.isfile(image.filename): 286 | image.draw() 287 | image.save() 288 | 289 | image = nodes.image(uri=relfn, candidates={'*': relfn}, **node['options']) 290 | node.parent.replace(node, image) 291 | except Exception as exc: 292 | if self.builder.config.blockdiag_debug: 293 | traceback.print_exc() 294 | 295 | logger.warning('dot code %r: %s', node['code'], exc) 296 | node.parent.remove(node) 297 | 298 | 299 | def setup(app): 300 | app.add_node(blockdiag_node, 301 | html=(html_visit_blockdiag, html_depart_blockdiag)) 302 | app.add_directive('blockdiag', Blockdiag) 303 | app.add_config_value('blockdiag_fontpath', None, 'html') 304 | app.add_config_value('blockdiag_fontmap', None, 'html') 305 | app.add_config_value('blockdiag_antialias', False, 'html') 306 | app.add_config_value('blockdiag_transparency', True, 'html') 307 | app.add_config_value('blockdiag_debug', False, 'html') 308 | app.add_config_value('blockdiag_html_image_format', 'PNG', 'html') 309 | app.add_config_value('blockdiag_tex_image_format', None, 'html') # backward compatibility for 1.3.1 310 | app.add_config_value('blockdiag_latex_image_format', 'PNG', 'html') 311 | app.connect("builder-inited", on_builder_inited) 312 | app.connect("doctree-resolved", on_doctree_resolved) 313 | 314 | return { 315 | 'version': pkg_resources.require('blockdiag')[0].version, 316 | 'parallel_read_safe': True, 317 | 'parallel_write_safe': True, 318 | } 319 | -------------------------------------------------------------------------------- /tests/test_html.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from sphinx_testing import with_app 4 | 5 | import unittest 6 | 7 | 8 | with_png_app = with_app(srcdir='tests/docs/basic', 9 | buildername='html', 10 | write_docstring=True) 11 | with_svg_app = with_app(srcdir='tests/docs/basic', 12 | buildername='html', 13 | write_docstring=True, 14 | confoverrides={ 15 | 'blockdiag_html_image_format': 'SVG' 16 | }) 17 | 18 | 19 | class TestSphinxcontribBlockdiagHTML(unittest.TestCase): 20 | @with_png_app 21 | def test_build_png_image(self, app, status, warning): 22 | """ 23 | .. blockdiag:: 24 | 25 | A -> B; 26 | """ 27 | app.builder.build_all() 28 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 29 | self.assertRegexpMatches(source, '
') 30 | 31 | @with_app(srcdir='tests/docs/subdir', buildername='html', write_docstring=True) 32 | def test_build_png_image_in_subdir(self, app, status, warning): 33 | """ 34 | .. blockdiag:: 35 | 36 | A -> B; 37 | """ 38 | app.builder.build_all() 39 | source = (app.outdir / 'subdir' / 'index.html').read_text(encoding='utf-8') 40 | self.assertRegexpMatches(source, r'
') 41 | 42 | @with_png_app 43 | def test_width_option_on_png(self, app, status, warning): 44 | """ 45 | .. blockdiag:: 46 | :width: 224 47 | 48 | A -> B; 49 | """ 50 | app.builder.build_all() 51 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 52 | self.assertRegexpMatches(source, ('
' 53 | '' 54 | '
')) 55 | 56 | @with_png_app 57 | def test_height_option_on_png(self, app, status, warning): 58 | """ 59 | .. blockdiag:: 60 | :height: 240 61 | 62 | A -> B; 63 | """ 64 | app.builder.build_all() 65 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 66 | self.assertRegexpMatches(source, ('
' 67 | '' 68 | '
')) 69 | 70 | @with_png_app 71 | def test_width_option_and_height_option_on_png(self, app, status, warning): 72 | """ 73 | .. blockdiag:: 74 | :width: 100 75 | :height: 200 76 | 77 | A -> B; 78 | """ 79 | app.builder.build_all() 80 | source = (app.outdir / 'index.html').read_text() 81 | self.assertRegexpMatches(source, ('
' 82 | '' 83 | '
')) 84 | 85 | @with_png_app 86 | def test_scale_option_on_png(self, app, status, warning): 87 | """ 88 | .. blockdiag:: 89 | :scale: 25% 90 | 91 | A -> B; 92 | """ 93 | app.builder.build_all() 94 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 95 | self.assertRegexpMatches(source, ('
' 96 | '' 97 | '
')) 98 | 99 | @with_png_app 100 | def test_width_option_and_scale_option_on_png(self, app, status, warning): 101 | """ 102 | .. blockdiag:: 103 | :width: 28 104 | :scale: 25% 105 | 106 | A -> B; 107 | """ 108 | app.builder.build_all() 109 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 110 | self.assertRegexpMatches(source, ('
' 111 | '' 112 | '
')) 113 | 114 | @with_png_app 115 | def test_align_option_on_png(self, app, status, warning): 116 | """ 117 | .. blockdiag:: 118 | :align: center 119 | 120 | A -> B; 121 | """ 122 | app.builder.build_all() 123 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 124 | self.assertRegexpMatches(source, '
') 125 | 126 | @with_png_app 127 | def test_align_option_and_width_option_on_png(self, app, status, warning): 128 | """ 129 | .. blockdiag:: 130 | :align: center 131 | :width: 224 132 | 133 | A -> B; 134 | """ 135 | app.builder.build_all() 136 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 137 | self.assertRegexpMatches(source, ('
' 138 | '' 139 | '
')) 140 | 141 | @with_png_app 142 | def test_name_option_on_png(self, app, status, warning): 143 | """ 144 | .. blockdiag:: 145 | :name: target 146 | 147 | A -> B; 148 | """ 149 | app.builder.build_all() 150 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 151 | self.assertRegexpMatches(source, '
') 152 | 153 | @with_png_app 154 | def test_name_option_and_width_option_on_png(self, app, status, warning): 155 | """ 156 | .. blockdiag:: 157 | :name: target 158 | :width: 224 159 | 160 | A -> B; 161 | """ 162 | app.builder.build_all() 163 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 164 | self.assertRegexpMatches(source, ('
' 165 | '' 166 | '
')) 167 | 168 | @with_png_app 169 | def test_href_and_scale_option_on_png(self, app, status, warning): 170 | """ 171 | .. blockdiag:: 172 | :scale: 50% 173 | 174 | A -> B; 175 | A [href = 'http://blockdiag.com/']; 176 | """ 177 | app.builder.build_all() 178 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 179 | self.assertRegexpMatches(source, ('')) 185 | 186 | @with_png_app 187 | def test_reftarget_in_href_on_png1(self, app, status, warning): 188 | """ 189 | .. _target: 190 | 191 | heading2 192 | --------- 193 | 194 | .. blockdiag:: 195 | 196 | A -> B; 197 | A [href = ':ref:`target`']; 198 | """ 199 | app.builder.build_all() 200 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 201 | self.assertRegexpMatches(source, ('
' 202 | '' 203 | '
')) 204 | 205 | @with_png_app 206 | def test_reftarget_in_href_on_png2(self, app, status, warning): 207 | """ 208 | .. _hello world: 209 | 210 | heading2 211 | --------- 212 | 213 | .. blockdiag:: 214 | 215 | A -> B; 216 | A [href = ':ref:`hello world`']; 217 | """ 218 | app.builder.build_all() 219 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 220 | self.assertRegexpMatches(source, ('
' 221 | '' 222 | '
')) 223 | 224 | @with_png_app 225 | def test_missing_reftarget_in_href_on_png(self, app, status, warning): 226 | """ 227 | .. blockdiag:: 228 | 229 | A -> B; 230 | A [href = ':ref:`unknown_target`']; 231 | """ 232 | app.builder.build_all() 233 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 234 | self.assertRegexpMatches(source, ('
')) 235 | self.assertIn('undefined label: unknown_target', warning.getvalue()) 236 | 237 | @with_app(srcdir='tests/docs/basic', copy_srcdir_to_tmpdir=True, 238 | write_docstring=True, confoverrides={'blockdiag_html_image_format': 'SVG'}) 239 | def test_build_svg_image(self, app, status, warning): 240 | """ 241 | .. blockdiag:: 242 | 243 | A -> B; 244 | """ 245 | app.builder.build_all() 246 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 247 | self.assertRegexpMatches(source, '
') 248 | 249 | @with_svg_app 250 | def test_width_option_on_svg(self, app, status, warning): 251 | """ 252 | .. blockdiag:: 253 | :width: 224 254 | 255 | A -> B; 256 | """ 257 | app.builder.build_all() 258 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 259 | self.assertRegexpMatches(source, ('
' 260 | '')) 261 | 262 | @with_svg_app 263 | def test_height_option_on_svg(self, app, status, warning): 264 | """ 265 | .. blockdiag:: 266 | :height: 240 267 | 268 | A -> B; 269 | """ 270 | app.builder.build_all() 271 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 272 | self.assertRegexpMatches(source, ('
' 273 | '')) 274 | 275 | @with_svg_app 276 | def test_width_option_and_height_option_on_svg(self, app, status, warning): 277 | """ 278 | .. blockdiag:: 279 | :width: 100 280 | :height: 200 281 | 282 | A -> B; 283 | """ 284 | app.builder.build_all() 285 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 286 | self.assertRegexpMatches(source, ('
' 287 | '')) 288 | 289 | @with_svg_app 290 | def test_scale_option_on_svg(self, app, status, warning): 291 | """ 292 | .. blockdiag:: 293 | :scale: 25% 294 | 295 | A -> B; 296 | """ 297 | app.builder.build_all() 298 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 299 | self.assertRegexpMatches(source, ('
' 300 | '')) 301 | 302 | @with_svg_app 303 | def test_width_option_and_scale_option_on_svg(self, app, status, warning): 304 | """ 305 | .. blockdiag:: 306 | :width: 28 307 | :scale: 25% 308 | 309 | A -> B; 310 | """ 311 | app.builder.build_all() 312 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 313 | self.assertRegexpMatches(source, ('
' 314 | '')) 315 | 316 | @with_svg_app 317 | def test_align_option_on_svg(self, app, status, warning): 318 | """ 319 | .. blockdiag:: 320 | :align: center 321 | 322 | A -> B; 323 | """ 324 | app.builder.build_all() 325 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 326 | self.assertRegexpMatches(source, '
') 327 | 328 | @with_svg_app 329 | def test_name_option_on_svg(self, app, status, warning): 330 | """ 331 | .. blockdiag:: 332 | :name: target 333 | 334 | A -> B; 335 | """ 336 | app.builder.build_all() 337 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 338 | self.assertRegexpMatches(source, '
') 339 | 340 | @with_svg_app 341 | def test_reftarget_in_href_on_svg1(self, app, status, warning): 342 | """ 343 | .. _target: 344 | 345 | heading2 346 | --------- 347 | 348 | .. blockdiag:: 349 | 350 | A -> B; 351 | A [href = ':ref:`target`']; 352 | """ 353 | app.builder.build_all() 354 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 355 | self.assertRegexpMatches(source, '\\n\\s*\\n\\s*') 356 | 357 | @with_svg_app 358 | def test_reftarget_in_href_on_svg2(self, app, status, warning): 359 | """ 360 | .. _hello world: 361 | 362 | heading2 363 | --------- 364 | 365 | .. blockdiag:: 366 | 367 | A -> B; 368 | A [href = ':ref:`hello world`']; 369 | """ 370 | app.builder.build_all() 371 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 372 | self.assertRegexpMatches(source, '\\n\\s*\\n\\s*') 373 | 374 | @with_svg_app 375 | def test_missing_reftarget_in_href_on_svg(self, app, status, warning): 376 | """ 377 | .. blockdiag:: 378 | 379 | A -> B; 380 | A [href = ':ref:`unknown_target`']; 381 | """ 382 | app.builder.build_all() 383 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 384 | self.assertNotRegex(source, '\\n\\s*\\n\\s*') 385 | self.assertIn('undefined label: unknown_target', warning.getvalue()) 386 | 387 | @with_svg_app 388 | def test_autoclass_should_not_effect_to_other_diagram(self, app, status, warning): 389 | """ 390 | This testcase checks that autoclass plugin is unloaded correctly (and it does not effect to other diagram). 391 | 392 | .. blockdiag:: 393 | 394 | plugin autoclass; 395 | class foo [color = red]; 396 | A_foo; 397 | 398 | .. blockdiag:: 399 | 400 | class foo [color = red]; 401 | A_foo; 402 | """ 403 | app.builder.build_all() 404 | source = (app.outdir / 'index.html').read_text(encoding='utf-8') 405 | self.assertRegexpMatches(source, ']+>A_foo') # 2nd diagram has a node labeled 'A_foo'. 406 | --------------------------------------------------------------------------------