Merge pull request #2276 from tk0miya/1921_figure_substitutions
Fix #1921: Support figure substitutions by locale
2
CHANGES
@ -105,6 +105,8 @@ Features added
|
|||||||
* #1853: support custom text splitter on html search with ``language='ja'``.
|
* #1853: support custom text splitter on html search with ``language='ja'``.
|
||||||
* #2320: classifier of glossary terms can be used for index entries grouping key.
|
* #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`.
|
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
|
Bugs fixed
|
||||||
----------
|
----------
|
||||||
|
@ -417,12 +417,18 @@ documentation on :ref:`intl` for details.
|
|||||||
The code for the language the docs are written in. Any text automatically
|
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
|
generated by Sphinx will be in that language. Also, Sphinx will try to
|
||||||
substitute individual paragraphs from your documents with the translation
|
substitute individual paragraphs from your documents with the translation
|
||||||
sets obtained from :confval:`locale_dirs`. In the LaTeX builder, a suitable
|
sets obtained from :confval:`locale_dirs`. Sphinx will search
|
||||||
language will be selected as an option for the *Babel* package. Default is
|
language-specific figures named by `figure_language_filename` and substitute
|
||||||
``None``, which means that no translation will be done.
|
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
|
.. versionadded:: 0.5
|
||||||
|
|
||||||
|
.. versionchanged:: 1.4
|
||||||
|
|
||||||
|
Support figure substitution
|
||||||
|
|
||||||
Currently supported languages by Sphinx are:
|
Currently supported languages by Sphinx are:
|
||||||
|
|
||||||
* ``bn`` -- Bengali
|
* ``bn`` -- Bengali
|
||||||
@ -541,6 +547,14 @@ documentation on :ref:`intl` for details.
|
|||||||
|
|
||||||
.. versionadded:: 1.3
|
.. 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:
|
.. _html-options:
|
||||||
|
|
||||||
Options for HTML output
|
Options for HTML output
|
||||||
|
@ -53,6 +53,7 @@ class Config(object):
|
|||||||
|
|
||||||
language = (None, 'env', [str]),
|
language = (None, 'env', [str]),
|
||||||
locale_dirs = ([], 'env'),
|
locale_dirs = ([], 'env'),
|
||||||
|
figure_language_filename = ('{root}.{language}{ext}', 'env', [str]),
|
||||||
|
|
||||||
master_doc = ('contents', 'env'),
|
master_doc = ('contents', 'env'),
|
||||||
source_suffix = (['.rst'], 'env'),
|
source_suffix = (['.rst'], 'env'),
|
||||||
|
@ -16,7 +16,6 @@ import time
|
|||||||
import types
|
import types
|
||||||
import bisect
|
import bisect
|
||||||
import codecs
|
import codecs
|
||||||
import imghdr
|
|
||||||
import string
|
import string
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from os import path
|
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
|
FilenameUniqDict, split_index_msg
|
||||||
from sphinx.util.nodes import clean_astext, make_refnode, WarningStream, is_translatable
|
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.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.console import bold, purple
|
||||||
from sphinx.util.matching import compile_matchers
|
from sphinx.util.matching import compile_matchers
|
||||||
from sphinx.util.parallel import ParallelTasks, parallel_available, make_chunks
|
from sphinx.util.parallel import ParallelTasks, parallel_available, make_chunks
|
||||||
@ -884,6 +885,21 @@ class BuildEnvironment:
|
|||||||
|
|
||||||
def process_images(self, docname, doctree):
|
def process_images(self, docname, doctree):
|
||||||
"""Process and rewrite image URIs."""
|
"""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):
|
for node in doctree.traverse(nodes.image):
|
||||||
# Map the mimetype to the corresponding image. The writer may
|
# Map the mimetype to the corresponding image. The writer may
|
||||||
# choose the best image from these candidates. The special key * is
|
# choose the best image from these candidates. The special key * is
|
||||||
@ -896,30 +912,26 @@ class BuildEnvironment:
|
|||||||
candidates['?'] = imguri
|
candidates['?'] = imguri
|
||||||
continue
|
continue
|
||||||
rel_imgpath, full_imgpath = self.relfn2path(imguri, docname)
|
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
|
# set imgpath as default URI
|
||||||
node['uri'] = rel_imgpath
|
node['uri'] = rel_imgpath
|
||||||
if rel_imgpath.endswith(os.extsep + '*'):
|
if rel_imgpath.endswith(os.extsep + '*'):
|
||||||
for filename in glob(full_imgpath):
|
if self.config.language:
|
||||||
new_imgpath = relative_path(path.join(self.srcdir, 'dummy'),
|
# Search language-specific figures at first
|
||||||
filename)
|
i18n_imguri = get_image_filename_for_language(imguri, self)
|
||||||
if filename.lower().endswith('.pdf'):
|
_, full_i18n_imgpath = self.relfn2path(i18n_imguri, docname)
|
||||||
candidates['application/pdf'] = new_imgpath
|
collect_candidates(full_i18n_imgpath, candidates)
|
||||||
elif filename.lower().endswith('.svg'):
|
|
||||||
candidates['image/svg+xml'] = new_imgpath
|
collect_candidates(full_imgpath, candidates)
|
||||||
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
|
|
||||||
else:
|
else:
|
||||||
candidates['*'] = rel_imgpath
|
candidates['*'] = rel_imgpath
|
||||||
|
|
||||||
# map image paths to unique image names (so that they can be put
|
# map image paths to unique image names (so that they can be put
|
||||||
# into a single directory)
|
# into a single directory)
|
||||||
for imgpath in itervalues(candidates):
|
for imgpath in itervalues(candidates):
|
||||||
|
@ -25,6 +25,7 @@ from docutils.statemachine import ViewList
|
|||||||
import sphinx
|
import sphinx
|
||||||
from sphinx.errors import SphinxError
|
from sphinx.errors import SphinxError
|
||||||
from sphinx.locale import _
|
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.osutil import ensuredir, ENOENT, EPIPE, EINVAL
|
||||||
from sphinx.util.compat import Directive
|
from sphinx.util.compat import Directive
|
||||||
|
|
||||||
@ -77,7 +78,8 @@ class Graphviz(Directive):
|
|||||||
'Graphviz directive cannot have both content and '
|
'Graphviz directive cannot have both content and '
|
||||||
'a filename argument', line=self.lineno)]
|
'a filename argument', line=self.lineno)]
|
||||||
env = self.state.document.settings.env
|
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)
|
env.note_dependency(rel_filename)
|
||||||
try:
|
try:
|
||||||
fp = codecs.open(filename, 'r', 'utf-8')
|
fp = codecs.open(filename, 'r', 'utf-8')
|
||||||
|
@ -22,6 +22,7 @@ import babel.dates
|
|||||||
from babel.messages.pofile import read_po
|
from babel.messages.pofile import read_po
|
||||||
from babel.messages.mofile import write_mo
|
from babel.messages.mofile import write_mo
|
||||||
|
|
||||||
|
from sphinx.errors import SphinxError
|
||||||
from sphinx.util.osutil import walk
|
from sphinx.util.osutil import walk
|
||||||
from sphinx.util import SEP
|
from sphinx.util import SEP
|
||||||
|
|
||||||
@ -190,3 +191,28 @@ def format_date(format, date=None, language=None):
|
|||||||
result.append(token)
|
result.append(token)
|
||||||
|
|
||||||
return "".join(result)
|
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
|
||||||
|
@ -9,7 +9,9 @@
|
|||||||
:license: BSD, see LICENSE for details.
|
:license: BSD, see LICENSE for details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import imghdr
|
||||||
import imagesize
|
import imagesize
|
||||||
|
from os import path
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PIL import Image # check for the Python Imaging Library
|
from PIL import Image # check for the Python Imaging Library
|
||||||
@ -19,6 +21,11 @@ except ImportError:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
Image = None
|
Image = None
|
||||||
|
|
||||||
|
mime_suffixes = {
|
||||||
|
'.pdf': 'application/pdf',
|
||||||
|
'.svg': 'image/svg+xml',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_image_size(filename):
|
def get_image_size(filename):
|
||||||
try:
|
try:
|
||||||
@ -37,3 +44,16 @@ def get_image_size(filename):
|
|||||||
return size
|
return size
|
||||||
except:
|
except:
|
||||||
return None
|
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
|
||||||
|
3
tests/roots/test-ext-graphviz/graph.dot
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
digraph {
|
||||||
|
bar -> baz
|
||||||
|
}
|
3
tests/roots/test-ext-graphviz/graph.xx.dot
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
digraph {
|
||||||
|
BAR -> BAZ
|
||||||
|
}
|
@ -16,3 +16,6 @@ Hello |graph| graphviz world
|
|||||||
:graphviz_dot: neato
|
:graphviz_dot: neato
|
||||||
|
|
||||||
bar -> baz
|
bar -> baz
|
||||||
|
|
||||||
|
|
||||||
|
.. graphviz:: graph.dot
|
||||||
|
BIN
tests/roots/test-image-glob/img.ja.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
tests/roots/test-image-glob/img.zh.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
tests/roots/test-image-glob/rimg.png.xx
Normal file
After Width: | Height: | Size: 218 B |
BIN
tests/roots/test-image-glob/rimg.xx.png
Normal file
After Width: | Height: | Size: 218 B |
@ -1,6 +1,8 @@
|
|||||||
test-image-glob/subdir
|
test-image-glob/subdir
|
||||||
======================
|
======================
|
||||||
|
|
||||||
|
.. image:: rimg.png
|
||||||
|
|
||||||
.. image:: svgimg.*
|
.. image:: svgimg.*
|
||||||
|
|
||||||
.. figure:: svgimg.*
|
.. figure:: svgimg.*
|
||||||
|
BIN
tests/roots/test-image-glob/subdir/rimg.png
Normal file
After Width: | Height: | Size: 218 B |
BIN
tests/roots/test-image-glob/subdir/rimg.xx.png
Normal file
After Width: | Height: | Size: 218 B |
158
tests/roots/test-image-glob/subdir/svgimg.xx.svg
Normal 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 |
@ -112,7 +112,7 @@ def test_numbered_circular_toctree(app, status, warning):
|
|||||||
'contents <- sub <- contents') in warnings
|
'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):
|
def test_image_glob(app, status, warning):
|
||||||
app.builder.build_all()
|
app.builder.build_all()
|
||||||
|
|
||||||
@ -145,12 +145,16 @@ def test_image_glob(app, status, warning):
|
|||||||
doctree = pickle.loads((app.doctreedir / 'subdir/index.doctree').bytes())
|
doctree = pickle.loads((app.doctreedir / 'subdir/index.doctree').bytes())
|
||||||
|
|
||||||
assert isinstance(doctree[0][1], nodes.image)
|
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'}
|
||||||
'image/svg+xml': 'subdir/svgimg.svg'}
|
assert doctree[0][1]['uri'] == 'subdir/rimg.png'
|
||||||
assert doctree[0][1]['uri'] == 'subdir/svgimg.*'
|
|
||||||
|
|
||||||
assert isinstance(doctree[0][2], nodes.figure)
|
assert isinstance(doctree[0][2], nodes.image)
|
||||||
assert isinstance(doctree[0][2][0], nodes.image)
|
assert doctree[0][2]['candidates'] == {'application/pdf': 'subdir/svgimg.pdf',
|
||||||
assert doctree[0][2][0]['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'}
|
'image/svg+xml': 'subdir/svgimg.svg'}
|
||||||
assert doctree[0][2][0]['uri'] == 'subdir/svgimg.*'
|
assert doctree[0][3][0]['uri'] == 'subdir/svgimg.*'
|
||||||
|
@ -10,15 +10,39 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
from util import with_app, SkipTest
|
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')
|
@with_app('html', testroot='ext-graphviz')
|
||||||
|
@skip_if_graphviz_not_found
|
||||||
def test_graphviz_html(app, status, warning):
|
def test_graphviz_html(app, status, warning):
|
||||||
app.builder.build_all()
|
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()
|
content = (app.outdir / 'index.html').text()
|
||||||
html = ('<div class="figure" .*?>\s*<img .*?/>\s*<p class="caption">'
|
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'
|
html = 'Hello <img .*?/>\n graphviz world'
|
||||||
assert re.search(html, content, re.S)
|
assert re.search(html, content, re.S)
|
||||||
|
|
||||||
|
html = '<img src=".*?" alt="digraph {\n bar -> baz\n}" />'
|
||||||
|
assert re.search(html, content, re.M)
|
||||||
|
|
||||||
|
|
||||||
@with_app('latex', testroot='ext-graphviz')
|
@with_app('latex', testroot='ext-graphviz')
|
||||||
|
@skip_if_graphviz_not_found
|
||||||
def test_graphviz_latex(app, status, warning):
|
def test_graphviz_latex(app, status, warning):
|
||||||
app.builder.build_all()
|
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()
|
content = (app.outdir / 'SphinxTests.tex').text()
|
||||||
macro = ('\\\\begin{figure}\[htbp\]\n\\\\centering\n\\\\capstart\n\n'
|
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'
|
macro = 'Hello \\\\includegraphics{graphviz-\w+.pdf} graphviz world'
|
||||||
assert re.search(macro, content, re.S)
|
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 -> BAZ\n}" />'
|
||||||
|
assert re.search(html, content, re.M)
|
||||||
|
@ -13,6 +13,8 @@ from __future__ import print_function
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import pickle
|
||||||
|
from docutils import nodes
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
@ -20,9 +22,9 @@ from babel.messages import pofile
|
|||||||
from nose.tools import assert_equal
|
from nose.tools import assert_equal
|
||||||
from six import string_types
|
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_re_search, assert_not_re_search, assert_in, assert_not_in, \
|
||||||
assert_startswith
|
assert_startswith, assert_node
|
||||||
|
|
||||||
|
|
||||||
root = tempdir / 'test-intl'
|
root = tempdir / 'test-intl'
|
||||||
@ -794,3 +796,87 @@ def test_references(app, status, warning):
|
|||||||
warnings = warning.getvalue().replace(os.sep, '/')
|
warnings = warning.getvalue().replace(os.sep, '/')
|
||||||
warning_expr = u'refs.txt:\\d+: ERROR: Unknown target name:'
|
warning_expr = u'refs.txt:\\d+: ERROR: Unknown target name:'
|
||||||
yield assert_count(warning_expr, warnings, 0)
|
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'})
|
||||||
|
@ -16,8 +16,9 @@ from os import path
|
|||||||
|
|
||||||
from babel.messages.mofile import read_mo
|
from babel.messages.mofile import read_mo
|
||||||
from sphinx.util import i18n
|
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():
|
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='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='ja') == u'2月 07, 2016'
|
||||||
assert i18n.format_date(format, date=date, language='de') == 'Februar 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)
|
||||||
|
@ -94,6 +94,16 @@ def assert_startswith(thing, prefix):
|
|||||||
assert False, '%r does not start with %r' % (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:
|
try:
|
||||||
from nose.tools import assert_in, assert_not_in
|
from nose.tools import assert_in, assert_not_in
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|