Merge pull request #2276 from tk0miya/1921_figure_substitutions

Fix #1921: Support figure substitutions by locale
This commit is contained in:
Takeshi KOMIYA 2016-02-22 23:55:53 +09:00
commit 904163f5b6
23 changed files with 466 additions and 40 deletions

View File

@ -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
----------

View File

@ -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

View File

@ -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'),

View File

@ -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):

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,3 @@
digraph {
bar -> baz
}

View File

@ -0,0 +1,3 @@
digraph {
BAR -> BAZ
}

View File

@ -16,3 +16,6 @@ Hello |graph| graphviz world
:graphviz_dot: neato
bar -> baz
.. graphviz:: graph.dot

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

View File

@ -1,6 +1,8 @@
test-image-glob/subdir
======================
.. image:: rimg.png
.. image:: svgimg.*
.. figure:: svgimg.*

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

View File

@ -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>

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -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.*'

View File

@ -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 = ('<div class="figure" .*?>\s*<img .*?/>\s*<p class="caption">'
@ -28,12 +52,14 @@ def test_graphviz_html(app, status, warning):
html = 'Hello <img .*?/>\n graphviz world'
assert re.search(html, content, re.S)
html = '<img src=".*?" alt="digraph {\n bar -&gt; baz\n}" />'
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 = '<img src=".*?" alt="digraph {\n BAR -&gt; BAZ\n}" />'
assert re.search(html, content, re.M)

View File

@ -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'})

View File

@ -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)

View File

@ -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: