summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES1
-rw-r--r--doc/config.rst12
-rw-r--r--sphinx/environment.py44
-rw-r--r--sphinx/util/i18n.py25
-rw-r--r--tests/roots/test-image-glob/rimg.xx.pngbin0 -> 218 bytes
-rw-r--r--tests/roots/test-image-glob/subdir/index.rst2
-rw-r--r--tests/roots/test-image-glob/subdir/rimg.pngbin0 -> 218 bytes
-rw-r--r--tests/roots/test-image-glob/subdir/rimg.xx.pngbin0 -> 218 bytes
-rw-r--r--tests/roots/test-image-glob/subdir/svgimg.xx.svg158
-rw-r--r--tests/test_build.py18
-rw-r--r--tests/test_intl.py47
-rw-r--r--tests/test_util_i18n.py33
-rw-r--r--tests/util.py10
13 files changed, 324 insertions, 26 deletions
diff --git a/CHANGES b/CHANGES
index ee0234561..117a7bc63 100644
--- a/CHANGES
+++ b/CHANGES
@@ -106,6 +106,7 @@ Features added
* #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`
Bugs fixed
----------
diff --git a/doc/config.rst b/doc/config.rst
index 9ac9b17d4..99ca15f02 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
diff --git a/sphinx/environment.py b/sphinx/environment.py
index 42bfef931..b8b40b8e6 100644
--- a/sphinx/environment.py
+++ b/sphinx/environment.py
@@ -39,8 +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,21 +912,23 @@ 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 + '*'):
- globbed = {}
- for filename in glob(full_imgpath):
- new_imgpath = relative_path(path.join(self.srcdir, 'dummy'),
- filename)
- try:
- mimetype = guess_mimetype(filename)
- 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
+ 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
diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py
index 32ebee943..28170c385 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,27 @@ 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
+
+ root, ext = path.splitext(filename)
+ try:
+ return "{root}.{language}{ext}".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/tests/roots/test-image-glob/rimg.xx.png b/tests/roots/test-image-glob/rimg.xx.png
new file mode 100644
index 000000000..1081dc143
--- /dev/null
+++ b/tests/roots/test-image-glob/rimg.xx.png
Binary files 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
--- /dev/null
+++ b/tests/roots/test-image-glob/subdir/rimg.png
Binary files 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
--- /dev/null
+++ b/tests/roots/test-image-glob/subdir/rimg.xx.png
Binary files 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 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ height="60"
+ width="60"
+ _SVGFile__filename="oldscale/apps/warning.svg"
+ version="1.0"
+ y="0"
+ x="0"
+ id="svg1"
+ sodipodi:version="0.32"
+ inkscape:version="0.41"
+ sodipodi:docname="exclamation.svg"
+ sodipodi:docbase="/home/danny/work/icons/primary/scalable/actions">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0000000"
+ inkscape:pageshadow="2"
+ inkscape:zoom="7.5136000"
+ inkscape:cx="42.825186"
+ inkscape:cy="24.316071"
+ inkscape:window-width="1020"
+ inkscape:window-height="691"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:current-layer="svg1" />
+ <defs
+ id="defs3">
+ <linearGradient
+ id="linearGradient1160">
+ <stop
+ style="stop-color: #000000;stop-opacity: 1.0;"
+ id="stop1161"
+ offset="0" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ id="stop1162"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ xlink:href="#linearGradient1160"
+ id="linearGradient1163" />
+ </defs>
+ <metadata
+ id="metadata12">
+ <RDF
+ id="RDF13">
+ <Work
+ about=""
+ id="Work14">
+ <title
+ id="title15">Part of the Flat Icon Collection (Thu Aug 26 14:31:40 2004)</title>
+ <description
+ id="description17" />
+ <subject
+ id="subject18">
+ <Bag
+ id="Bag19">
+ <li
+ id="li20" />
+ </Bag>
+ </subject>
+ <publisher
+ id="publisher21">
+ <Agent
+ about=""
+ id="Agent22">
+ <title
+ id="title23" />
+ </Agent>
+ </publisher>
+ <creator
+ id="creator24">
+ <Agent
+ about=""
+ id="Agent25">
+ <title
+ id="title26">Danny Allen</title>
+ </Agent>
+ </creator>
+ <rights
+ id="rights28">
+ <Agent
+ about=""
+ id="Agent29">
+ <title
+ id="title30">Danny Allen</title>
+ </Agent>
+ </rights>
+ <date
+ id="date32" />
+ <format
+ id="format33">image/svg+xml</format>
+ <type
+ id="type35"
+ resource="http://purl.org/dc/dcmitype/StillImage" />
+ <license
+ id="license36"
+ resource="http://creativecommons.org/licenses/LGPL/2.1/">
+ <date
+ id="date37" />
+ </license>
+ <language
+ id="language38">en</language>
+ </Work>
+ </RDF>
+ <rdf:RDF
+ id="RDF40">
+ <cc:Work
+ rdf:about=""
+ id="Work41">
+ <dc:format
+ id="format42">image/svg+xml</dc:format>
+ <dc:type
+ id="type44"
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="g2099">
+ <path
+ style="color:#000000;fill:none;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.1250000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ d="M 55.311891,51.920745 L 4.6880989,51.920744 L 29.999995,8.0792542 L 55.311891,51.920745 z "
+ id="path1724" />
+ <path
+ style="color:#000000;fill:#ffe940;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#000000;stroke-width:3.1250010;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ d="M 55.311891,51.920745 L 4.6880989,51.920744 L 29.999995,8.0792542 L 55.311891,51.920745 z "
+ id="path1722" />
+ <path
+ style="font-size:12.000000;font-weight:900;fill:none;fill-opacity:1.0000000;stroke:#ffffff;stroke-width:8.1250000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 34.944960,10.779626 L 34.944960,33.186510 C 34.944960,34.752415 34.501979,36.081368 33.616007,37.173380 C 32.750636,38.265402 31.545298,38.811408 29.999995,38.811408 C 28.475302,38.811408 27.269965,38.265402 26.383993,37.173380 C 25.498020,36.060767 25.055030,34.731804 25.055030,33.186510 L 25.055030,10.779626 C 25.055030,9.1931155 25.498020,7.8641562 26.383993,6.7927462 C 27.269965,5.7007332 28.475302,5.1547262 29.999995,5.1547262 C 31.009593,5.1547262 31.885265,5.4019740 32.627010,5.8964706 C 33.389356,6.3909681 33.966274,7.0709005 34.357752,7.9362696 C 34.749221,8.7810349 34.944960,9.7288200 34.944960,10.779626 z "
+ id="path1099" />
+ <path
+ style="font-size:12.000000;font-weight:900;fill:#e71c02;fill-opacity:1.0000000;stroke:none;stroke-width:3.1249981;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1.0000000"
+ d="M 29.999995,3.5986440 C 28.102272,3.5986440 26.318514,4.3848272 25.156245,5.8173940 C 24.028906,7.1806889 23.499995,8.9087770 23.499995,10.786144 L 23.499995,33.192394 C 23.499995,35.036302 24.050685,36.772771 25.156245,38.161144 C 26.318514,39.593721 28.102273,40.379893 29.999995,40.379894 C 31.913354,40.379894 33.697195,39.576736 34.843745,38.129894 C 35.959941,36.754118 36.499995,35.052976 36.499995,33.192394 L 36.499995,10.786144 C 36.499995,9.5413010 36.276626,8.3551469 35.781245,7.2861440 C 35.278844,6.1755772 34.477762,5.2531440 33.468745,4.5986440 C 32.454761,3.9226545 31.264694,3.5986439 29.999995,3.5986440 z "
+ id="path835"
+ sodipodi:nodetypes="cccccccccccc" />
+ <path
+ style="color:#000000;fill:none;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:5.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ d="M 36.506243,49.901522 C 36.506243,53.492972 33.591442,56.407773 29.999991,56.407773 C 26.408541,56.407773 23.493739,53.492972 23.493739,49.901522 C 23.493739,46.310071 26.408541,43.395270 29.999991,43.395270 C 33.591442,43.395270 36.506243,46.310071 36.506243,49.901522 z "
+ id="path1727" />
+ <path
+ style="color:#000000;fill:#e71c02;fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:3.1250000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ d="M 36.506243,49.901522 C 36.506243,53.492972 33.591442,56.407773 29.999991,56.407773 C 26.408541,56.407773 23.493739,53.492972 23.493739,49.901522 C 23.493739,46.310071 26.408541,43.395270 29.999991,43.395270 C 33.591442,43.395270 36.506243,46.310071 36.506243,49.901522 z "
+ id="path1725" />
+ </g>
+</svg>
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',
+ assert doctree[0][1]['candidates'] == {'*': 'subdir/rimg.png'}
+ assert doctree[0][1]['uri'] == 'subdir/rimg.png'
+
+ 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][1]['uri'] == 'subdir/svgimg.*'
+ assert doctree[0][2]['uri'] == 'subdir/svgimg.*'
- 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][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_intl.py b/tests/test_intl.py
index b24ec65d2..61079b20e 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,44 @@ 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'})
diff --git a/tests/test_util_i18n.py b/tests/test_util_i18n.py
index de7cf2ca7..01eb48c38 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,33 @@ 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'
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: