diff --git a/CHANGES b/CHANGES index 81f136edc..0a61b6bbb 100644 --- a/CHANGES +++ b/CHANGES @@ -105,6 +105,8 @@ Features added * #1853: support custom text splitter on html search with ``language='ja'``. * #2320: classifier of glossary terms can be used for index entries grouping key. The classifier also be used for translation. See also :ref:`glossary-directive`. +* Select an image by similarity if multiple images are globbed by ``.. image:: filename.*`` +* #1921: Support figure substitutions by :confval:`language` and :confval:`figure_language_filename` Bugs fixed ---------- diff --git a/doc/config.rst b/doc/config.rst index e7005be69..340af887b 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -417,12 +417,18 @@ documentation on :ref:`intl` for details. The code for the language the docs are written in. Any text automatically generated by Sphinx will be in that language. Also, Sphinx will try to substitute individual paragraphs from your documents with the translation - sets obtained from :confval:`locale_dirs`. In the LaTeX builder, a suitable - language will be selected as an option for the *Babel* package. Default is - ``None``, which means that no translation will be done. + sets obtained from :confval:`locale_dirs`. Sphinx will search + language-specific figures named by `figure_language_filename` and substitute + them for original figures. In the LaTeX builder, a suitable language will + be selected as an option for the *Babel* package. Default is ``None``, + which means that no translation will be done. .. versionadded:: 0.5 + .. versionchanged:: 1.4 + + Support figure substitution + Currently supported languages by Sphinx are: * ``bn`` -- Bengali @@ -541,6 +547,14 @@ documentation on :ref:`intl` for details. .. versionadded:: 1.3 +.. confval:: figure_language_filename + + The filename format for language-specific figures. The default value is + ``{root}.{language}{ext}``. It will be expanded to + ``dirname/filename.en.png`` from ``.. image:: dirname/filename.png``. + + .. versionadded:: 1.4 + .. _html-options: Options for HTML output diff --git a/sphinx/config.py b/sphinx/config.py index 5b99c6419..ba3dc11b4 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -53,6 +53,7 @@ class Config(object): language = (None, 'env', [str]), locale_dirs = ([], 'env'), + figure_language_filename = ('{root}.{language}{ext}', 'env', [str]), master_doc = ('contents', 'env'), source_suffix = (['.rst'], 'env'), diff --git a/sphinx/environment.py b/sphinx/environment.py index 0db34f506..b8b40b8e6 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -16,7 +16,6 @@ import time import types import bisect import codecs -import imghdr import string import unicodedata from os import path @@ -40,7 +39,9 @@ from sphinx.util import url_re, get_matching_docs, docname_join, split_into, \ FilenameUniqDict, split_index_msg from sphinx.util.nodes import clean_astext, make_refnode, WarningStream, is_translatable from sphinx.util.osutil import SEP, getcwd, fs_encoding, ensuredir -from sphinx.util.i18n import find_catalog_files +from sphinx.util.images import guess_mimetype +from sphinx.util.i18n import find_catalog_files, get_image_filename_for_language, \ + search_image_for_language from sphinx.util.console import bold, purple from sphinx.util.matching import compile_matchers from sphinx.util.parallel import ParallelTasks, parallel_available, make_chunks @@ -884,6 +885,21 @@ class BuildEnvironment: def process_images(self, docname, doctree): """Process and rewrite image URIs.""" + def collect_candidates(imgpath, candidates): + globbed = {} + for filename in glob(imgpath): + new_imgpath = relative_path(path.join(self.srcdir, 'dummy'), + filename) + try: + mimetype = guess_mimetype(filename) + if mimetype not in candidates: + globbed.setdefault(mimetype, []).append(new_imgpath) + except (OSError, IOError) as err: + self.warn_node('image file %s not readable: %s' % + (filename, err), node) + for key, files in iteritems(globbed): + candidates[key] = sorted(files, key=len)[0] # select by similarity + for node in doctree.traverse(nodes.image): # Map the mimetype to the corresponding image. The writer may # choose the best image from these candidates. The special key * is @@ -896,30 +912,26 @@ class BuildEnvironment: candidates['?'] = imguri continue rel_imgpath, full_imgpath = self.relfn2path(imguri, docname) + if self.config.language: + # substitute figures (ex. foo.png -> foo.en.png) + i18n_full_imgpath = search_image_for_language(full_imgpath, self) + if i18n_full_imgpath != full_imgpath: + full_imgpath = i18n_full_imgpath + rel_imgpath = relative_path(path.join(self.srcdir, 'dummy'), + i18n_full_imgpath) # set imgpath as default URI node['uri'] = rel_imgpath if rel_imgpath.endswith(os.extsep + '*'): - for filename in glob(full_imgpath): - new_imgpath = relative_path(path.join(self.srcdir, 'dummy'), - filename) - if filename.lower().endswith('.pdf'): - candidates['application/pdf'] = new_imgpath - elif filename.lower().endswith('.svg'): - candidates['image/svg+xml'] = new_imgpath - else: - try: - f = open(filename, 'rb') - try: - imgtype = imghdr.what(f) - finally: - f.close() - except (OSError, IOError) as err: - self.warn_node('image file %s not readable: %s' % - (filename, err), node) - if imgtype: - candidates['image/' + imgtype] = new_imgpath + if self.config.language: + # Search language-specific figures at first + i18n_imguri = get_image_filename_for_language(imguri, self) + _, full_i18n_imgpath = self.relfn2path(i18n_imguri, docname) + collect_candidates(full_i18n_imgpath, candidates) + + collect_candidates(full_imgpath, candidates) else: candidates['*'] = rel_imgpath + # map image paths to unique image names (so that they can be put # into a single directory) for imgpath in itervalues(candidates): diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py index 44a47d5e1..4e06677ca 100644 --- a/sphinx/ext/graphviz.py +++ b/sphinx/ext/graphviz.py @@ -25,6 +25,7 @@ from docutils.statemachine import ViewList import sphinx from sphinx.errors import SphinxError from sphinx.locale import _ +from sphinx.util.i18n import search_image_for_language from sphinx.util.osutil import ensuredir, ENOENT, EPIPE, EINVAL from sphinx.util.compat import Directive @@ -77,7 +78,8 @@ class Graphviz(Directive): 'Graphviz directive cannot have both content and ' 'a filename argument', line=self.lineno)] env = self.state.document.settings.env - rel_filename, filename = env.relfn2path(self.arguments[0]) + argument = search_image_for_language(self.arguments[0], env) + rel_filename, filename = env.relfn2path(argument) env.note_dependency(rel_filename) try: fp = codecs.open(filename, 'r', 'utf-8') diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index 32ebee943..94492ff45 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -22,6 +22,7 @@ import babel.dates from babel.messages.pofile import read_po from babel.messages.mofile import write_mo +from sphinx.errors import SphinxError from sphinx.util.osutil import walk from sphinx.util import SEP @@ -190,3 +191,28 @@ def format_date(format, date=None, language=None): result.append(token) return "".join(result) + + +def get_image_filename_for_language(filename, env): + if not env.config.language: + return filename + + filename_format = env.config.figure_language_filename + root, ext = path.splitext(filename) + try: + return filename_format.format(root=root, ext=ext, + language=env.config.language) + except KeyError as exc: + raise SphinxError('Invalid figure_language_filename: %r' % exc) + + +def search_image_for_language(filename, env): + if not env.config.language: + return filename + + translated = get_image_filename_for_language(filename, env) + dirname = path.dirname(env.docname) + if path.exists(path.join(env.srcdir, dirname, translated)): + return translated + else: + return filename diff --git a/sphinx/util/images.py b/sphinx/util/images.py index 931be3d15..143042a58 100644 --- a/sphinx/util/images.py +++ b/sphinx/util/images.py @@ -9,7 +9,9 @@ :license: BSD, see LICENSE for details. """ +import imghdr import imagesize +from os import path try: from PIL import Image # check for the Python Imaging Library @@ -19,6 +21,11 @@ except ImportError: except ImportError: Image = None +mime_suffixes = { + '.pdf': 'application/pdf', + '.svg': 'image/svg+xml', +} + def get_image_size(filename): try: @@ -37,3 +44,16 @@ def get_image_size(filename): return size except: return None + + +def guess_mimetype(filename): + _, ext = path.splitext(filename) + if ext in mime_suffixes: + return mime_suffixes[ext] + else: + with open(filename, 'rb') as f: + imgtype = imghdr.what(f) + if imgtype: + return 'image/' + imgtype + + return None diff --git a/tests/roots/test-ext-graphviz/graph.dot b/tests/roots/test-ext-graphviz/graph.dot new file mode 100644 index 000000000..ca5724492 --- /dev/null +++ b/tests/roots/test-ext-graphviz/graph.dot @@ -0,0 +1,3 @@ +digraph { + bar -> baz +} diff --git a/tests/roots/test-ext-graphviz/graph.xx.dot b/tests/roots/test-ext-graphviz/graph.xx.dot new file mode 100644 index 000000000..e5add5c76 --- /dev/null +++ b/tests/roots/test-ext-graphviz/graph.xx.dot @@ -0,0 +1,3 @@ +digraph { + BAR -> BAZ +} diff --git a/tests/roots/test-ext-graphviz/index.rst b/tests/roots/test-ext-graphviz/index.rst index c04ca42a0..b778307e3 100644 --- a/tests/roots/test-ext-graphviz/index.rst +++ b/tests/roots/test-ext-graphviz/index.rst @@ -16,3 +16,6 @@ Hello |graph| graphviz world :graphviz_dot: neato bar -> baz + + +.. graphviz:: graph.dot diff --git a/tests/roots/test-image-glob/img.ja.png b/tests/roots/test-image-glob/img.ja.png new file mode 100644 index 000000000..4c8f89929 Binary files /dev/null and b/tests/roots/test-image-glob/img.ja.png differ diff --git a/tests/roots/test-image-glob/img.zh.png b/tests/roots/test-image-glob/img.zh.png new file mode 100644 index 000000000..4c8f89929 Binary files /dev/null and b/tests/roots/test-image-glob/img.zh.png differ diff --git a/tests/roots/test-image-glob/rimg.png.xx b/tests/roots/test-image-glob/rimg.png.xx new file mode 100644 index 000000000..1081dc143 Binary files /dev/null and b/tests/roots/test-image-glob/rimg.png.xx differ diff --git a/tests/roots/test-image-glob/rimg.xx.png b/tests/roots/test-image-glob/rimg.xx.png new file mode 100644 index 000000000..1081dc143 Binary files /dev/null and b/tests/roots/test-image-glob/rimg.xx.png differ diff --git a/tests/roots/test-image-glob/subdir/index.rst b/tests/roots/test-image-glob/subdir/index.rst index f086458d5..4ad2b0247 100644 --- a/tests/roots/test-image-glob/subdir/index.rst +++ b/tests/roots/test-image-glob/subdir/index.rst @@ -1,6 +1,8 @@ test-image-glob/subdir ====================== +.. image:: rimg.png + .. image:: svgimg.* .. figure:: svgimg.* diff --git a/tests/roots/test-image-glob/subdir/rimg.png b/tests/roots/test-image-glob/subdir/rimg.png new file mode 100644 index 000000000..1081dc143 Binary files /dev/null and b/tests/roots/test-image-glob/subdir/rimg.png differ diff --git a/tests/roots/test-image-glob/subdir/rimg.xx.png b/tests/roots/test-image-glob/subdir/rimg.xx.png new file mode 100644 index 000000000..1081dc143 Binary files /dev/null and b/tests/roots/test-image-glob/subdir/rimg.xx.png differ diff --git a/tests/roots/test-image-glob/subdir/svgimg.xx.svg b/tests/roots/test-image-glob/subdir/svgimg.xx.svg new file mode 100644 index 000000000..10e035b6d --- /dev/null +++ b/tests/roots/test-image-glob/subdir/svgimg.xx.svg @@ -0,0 +1,158 @@ + + diff --git a/tests/test_build.py b/tests/test_build.py index ee6534b7d..1c0d55e1b 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -112,7 +112,7 @@ def test_numbered_circular_toctree(app, status, warning): 'contents <- sub <- contents') in warnings -@with_app(buildername='html', testroot='image-glob') +@with_app(buildername='dummy', testroot='image-glob') def test_image_glob(app, status, warning): app.builder.build_all() @@ -145,12 +145,16 @@ def test_image_glob(app, status, warning): doctree = pickle.loads((app.doctreedir / 'subdir/index.doctree').bytes()) assert isinstance(doctree[0][1], nodes.image) - assert doctree[0][1]['candidates'] == {'application/pdf': 'subdir/svgimg.pdf', - 'image/svg+xml': 'subdir/svgimg.svg'} - assert doctree[0][1]['uri'] == 'subdir/svgimg.*' + assert doctree[0][1]['candidates'] == {'*': 'subdir/rimg.png'} + assert doctree[0][1]['uri'] == 'subdir/rimg.png' - assert isinstance(doctree[0][2], nodes.figure) - assert isinstance(doctree[0][2][0], nodes.image) - assert doctree[0][2][0]['candidates'] == {'application/pdf': 'subdir/svgimg.pdf', + assert isinstance(doctree[0][2], nodes.image) + assert doctree[0][2]['candidates'] == {'application/pdf': 'subdir/svgimg.pdf', + 'image/svg+xml': 'subdir/svgimg.svg'} + assert doctree[0][2]['uri'] == 'subdir/svgimg.*' + + assert isinstance(doctree[0][3], nodes.figure) + assert isinstance(doctree[0][3][0], nodes.image) + assert doctree[0][3][0]['candidates'] == {'application/pdf': 'subdir/svgimg.pdf', 'image/svg+xml': 'subdir/svgimg.svg'} - assert doctree[0][2][0]['uri'] == 'subdir/svgimg.*' + assert doctree[0][3][0]['uri'] == 'subdir/svgimg.*' diff --git a/tests/test_ext_graphviz.py b/tests/test_ext_graphviz.py index 95b7f5042..dbab33761 100644 --- a/tests/test_ext_graphviz.py +++ b/tests/test_ext_graphviz.py @@ -10,15 +10,39 @@ """ import re +import subprocess +from functools import wraps from util import with_app, SkipTest +def skip_if_graphviz_not_found(fn): + @wraps(fn) + def decorator(app, *args, **kwargs): + found = False + graphviz_dot = getattr(app.config, 'graphviz_dot', '') + try: + if graphviz_dot: + dot = subprocess.Popen([graphviz_dot, '-V'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) # show version + dot.wait() + found = True + except OSError: # No such file or directory + pass + + if not found: + raise SkipTest('graphviz "dot" is not available') + + return fn(app, *args, **kwargs) + + return decorator + + @with_app('html', testroot='ext-graphviz') +@skip_if_graphviz_not_found def test_graphviz_html(app, status, warning): app.builder.build_all() - if "dot command 'dot' cannot be run" in warning.getvalue(): - raise SkipTest('graphviz "dot" is not available') content = (app.outdir / 'index.html').text() html = ('
'
@@ -28,12 +52,14 @@ def test_graphviz_html(app, status, warning):
html = 'Hello \n graphviz world'
assert re.search(html, content, re.S)
+ html = '
'
+ assert re.search(html, content, re.M)
+
@with_app('latex', testroot='ext-graphviz')
+@skip_if_graphviz_not_found
def test_graphviz_latex(app, status, warning):
app.builder.build_all()
- if "dot command 'dot' cannot be run" in warning.getvalue():
- raise SkipTest('graphviz "dot" is not available')
content = (app.outdir / 'SphinxTests.tex').text()
macro = ('\\\\begin{figure}\[htbp\]\n\\\\centering\n\\\\capstart\n\n'
@@ -43,3 +69,13 @@ def test_graphviz_latex(app, status, warning):
macro = 'Hello \\\\includegraphics{graphviz-\w+.pdf} graphviz world'
assert re.search(macro, content, re.S)
+
+
+@with_app('html', testroot='ext-graphviz', confoverrides={'language': 'xx'})
+@skip_if_graphviz_not_found
+def test_graphviz_i18n(app, status, warning):
+ app.builder.build_all()
+
+ content = (app.outdir / 'index.html').text()
+ html = '
'
+ assert re.search(html, content, re.M)
diff --git a/tests/test_intl.py b/tests/test_intl.py
index b24ec65d2..29d068d65 100644
--- a/tests/test_intl.py
+++ b/tests/test_intl.py
@@ -13,6 +13,8 @@ from __future__ import print_function
import os
import re
+import pickle
+from docutils import nodes
from subprocess import Popen, PIPE
from xml.etree import ElementTree
@@ -20,9 +22,9 @@ from babel.messages import pofile
from nose.tools import assert_equal
from six import string_types
-from util import tempdir, rootdir, path, gen_with_app, SkipTest, \
+from util import tempdir, rootdir, path, gen_with_app, with_app, SkipTest, \
assert_re_search, assert_not_re_search, assert_in, assert_not_in, \
- assert_startswith
+ assert_startswith, assert_node
root = tempdir / 'test-intl'
@@ -794,3 +796,87 @@ def test_references(app, status, warning):
warnings = warning.getvalue().replace(os.sep, '/')
warning_expr = u'refs.txt:\\d+: ERROR: Unknown target name:'
yield assert_count(warning_expr, warnings, 0)
+
+
+@with_app(buildername='dummy', testroot='image-glob', confoverrides={'language': 'xx'})
+def test_image_glob_intl(app, status, warning):
+ app.builder.build_all()
+
+ # index.rst
+ doctree = pickle.loads((app.doctreedir / 'index.doctree').bytes())
+
+ assert_node(doctree[0][1], nodes.image, uri='rimg.xx.png',
+ candidates={'*': 'rimg.xx.png'})
+
+ assert isinstance(doctree[0][2], nodes.figure)
+ assert_node(doctree[0][2][0], nodes.image, uri='rimg.xx.png',
+ candidates={'*': 'rimg.xx.png'})
+
+ assert_node(doctree[0][3], nodes.image, uri='img.*',
+ candidates={'application/pdf': 'img.pdf',
+ 'image/gif': 'img.gif',
+ 'image/png': 'img.png'})
+
+ assert isinstance(doctree[0][4], nodes.figure)
+ assert_node(doctree[0][4][0], nodes.image, uri='img.*',
+ candidates={'application/pdf': 'img.pdf',
+ 'image/gif': 'img.gif',
+ 'image/png': 'img.png'})
+
+ # subdir/index.rst
+ doctree = pickle.loads((app.doctreedir / 'subdir/index.doctree').bytes())
+
+ assert_node(doctree[0][1], nodes.image, uri='subdir/rimg.xx.png',
+ candidates={'*': 'subdir/rimg.xx.png'})
+
+ assert_node(doctree[0][2], nodes.image, uri='subdir/svgimg.*',
+ candidates={'application/pdf': 'subdir/svgimg.pdf',
+ 'image/svg+xml': 'subdir/svgimg.xx.svg'})
+
+ assert isinstance(doctree[0][3], nodes.figure)
+ assert_node(doctree[0][3][0], nodes.image, uri='subdir/svgimg.*',
+ candidates={'application/pdf': 'subdir/svgimg.pdf',
+ 'image/svg+xml': 'subdir/svgimg.xx.svg'})
+
+
+@with_app(buildername='dummy', testroot='image-glob',
+ confoverrides={'language': 'xx',
+ 'figure_language_filename': '{root}{ext}.{language}'})
+def test_image_glob_intl_using_figure_language_filename(app, status, warning):
+ app.builder.build_all()
+
+ # index.rst
+ doctree = pickle.loads((app.doctreedir / 'index.doctree').bytes())
+
+ assert_node(doctree[0][1], nodes.image, uri='rimg.png.xx',
+ candidates={'*': 'rimg.png.xx'})
+
+ assert isinstance(doctree[0][2], nodes.figure)
+ assert_node(doctree[0][2][0], nodes.image, uri='rimg.png.xx',
+ candidates={'*': 'rimg.png.xx'})
+
+ assert_node(doctree[0][3], nodes.image, uri='img.*',
+ candidates={'application/pdf': 'img.pdf',
+ 'image/gif': 'img.gif',
+ 'image/png': 'img.png'})
+
+ assert isinstance(doctree[0][4], nodes.figure)
+ assert_node(doctree[0][4][0], nodes.image, uri='img.*',
+ candidates={'application/pdf': 'img.pdf',
+ 'image/gif': 'img.gif',
+ 'image/png': 'img.png'})
+
+ # subdir/index.rst
+ doctree = pickle.loads((app.doctreedir / 'subdir/index.doctree').bytes())
+
+ assert_node(doctree[0][1], nodes.image, uri='subdir/rimg.png',
+ candidates={'*': 'subdir/rimg.png'})
+
+ assert_node(doctree[0][2], nodes.image, uri='subdir/svgimg.*',
+ candidates={'application/pdf': 'subdir/svgimg.pdf',
+ 'image/svg+xml': 'subdir/svgimg.svg'})
+
+ assert isinstance(doctree[0][3], nodes.figure)
+ assert_node(doctree[0][3][0], nodes.image, uri='subdir/svgimg.*',
+ candidates={'application/pdf': 'subdir/svgimg.pdf',
+ 'image/svg+xml': 'subdir/svgimg.svg'})
diff --git a/tests/test_util_i18n.py b/tests/test_util_i18n.py
index de7cf2ca7..6528e1dc3 100644
--- a/tests/test_util_i18n.py
+++ b/tests/test_util_i18n.py
@@ -16,8 +16,9 @@ from os import path
from babel.messages.mofile import read_mo
from sphinx.util import i18n
+from sphinx.errors import SphinxError
-from util import with_tempdir
+from util import TestApp, with_tempdir, raises
def test_catalog_info_for_file_and_path():
@@ -183,3 +184,46 @@ def test_format_date():
assert i18n.format_date(format, date=date, language='en') == 'February 07, 2016'
assert i18n.format_date(format, date=date, language='ja') == u'2月 07, 2016'
assert i18n.format_date(format, date=date, language='de') == 'Februar 07, 2016'
+
+
+def test_get_filename_for_language():
+ app = TestApp()
+
+ # language is None
+ app.env.config.language = None
+ assert app.env.config.language is None
+ assert i18n.get_image_filename_for_language('foo.png', app.env) == 'foo.png'
+ assert i18n.get_image_filename_for_language('foo.bar.png', app.env) == 'foo.bar.png'
+ assert i18n.get_image_filename_for_language('subdir/foo.png', app.env) == 'subdir/foo.png'
+ assert i18n.get_image_filename_for_language('../foo.png', app.env) == '../foo.png'
+ assert i18n.get_image_filename_for_language('foo', app.env) == 'foo'
+
+ # language is en
+ app.env.config.language = 'en'
+ assert i18n.get_image_filename_for_language('foo.png', app.env) == 'foo.en.png'
+ assert i18n.get_image_filename_for_language('foo.bar.png', app.env) == 'foo.bar.en.png'
+ assert i18n.get_image_filename_for_language('dir/foo.png', app.env) == 'dir/foo.en.png'
+ assert i18n.get_image_filename_for_language('../foo.png', app.env) == '../foo.en.png'
+ assert i18n.get_image_filename_for_language('foo', app.env) == 'foo.en'
+
+ # modify figure_language_filename and language is None
+ app.env.config.language = None
+ app.env.config.figure_language_filename = 'images/{language}/{root}{ext}'
+ assert i18n.get_image_filename_for_language('foo.png', app.env) == 'foo.png'
+ assert i18n.get_image_filename_for_language('foo.bar.png', app.env) == 'foo.bar.png'
+ assert i18n.get_image_filename_for_language('subdir/foo.png', app.env) == 'subdir/foo.png'
+ assert i18n.get_image_filename_for_language('../foo.png', app.env) == '../foo.png'
+ assert i18n.get_image_filename_for_language('foo', app.env) == 'foo'
+
+ # modify figure_language_filename and language is 'en'
+ app.env.config.language = 'en'
+ app.env.config.figure_language_filename = 'images/{language}/{root}{ext}'
+ assert i18n.get_image_filename_for_language('foo.png', app.env) == 'images/en/foo.png'
+ assert i18n.get_image_filename_for_language('foo.bar.png', app.env) == 'images/en/foo.bar.png'
+ assert i18n.get_image_filename_for_language('subdir/foo.png', app.env) == 'images/en/subdir/foo.png'
+ assert i18n.get_image_filename_for_language('../foo.png', app.env) == 'images/en/../foo.png'
+ assert i18n.get_image_filename_for_language('foo', app.env) == 'images/en/foo'
+
+ # invalid figure_language_filename
+ app.env.config.figure_language_filename = '{root}.{invalid}{ext}'
+ raises(SphinxError, i18n.get_image_filename_for_language, 'foo.png', app.env)
diff --git a/tests/util.py b/tests/util.py
index 1e20e73e2..969c4e5c4 100644
--- a/tests/util.py
+++ b/tests/util.py
@@ -94,6 +94,16 @@ def assert_startswith(thing, prefix):
assert False, '%r does not start with %r' % (thing, prefix)
+def assert_node(node, cls=None, **kwargs):
+ if cls:
+ assert isinstance(node, cls), '%r is not subclass of %r' % (node, cls)
+
+ for key, value in kwargs.items():
+ assert key in node, '%r does not have %r attribute' % (node, key)
+ assert node[key] == value, \
+ '%r[%s]: %r does not equals %r' % (node, key, node[key], value)
+
+
try:
from nose.tools import assert_in, assert_not_in
except ImportError: