Merge branch 'master' into refactor_literalinclude

This commit is contained in:
Takeshi KOMIYA
2017-02-17 23:37:35 +09:00
48 changed files with 708 additions and 508 deletions

View File

@@ -1,10 +1,11 @@
language: python
sudo: false
dist: trusty
cache:
directories:
- $HOME/.cache/pip
python:
- "pypy"
- "pypy-5.4.1"
- "2.7"
- "3.4"
- "3.5"
@@ -26,7 +27,7 @@ matrix:
env: DOCUTILS=0.12
- python: nightly
env: DOCUTILS=0.12
- python: pypy
- python: "pypy-5.4.1"
env: DOCUTILS=0.12
addons:
apt:

View File

@@ -13,6 +13,7 @@ Incompatible changes
* LaTeX positioned long tables horizontally centered, and short ones
flushed left (no text flow around table.) The position now defaults to center in
both cases, and it will obey Docutils 0.13 ``:align:`` option (refs #3415, #3377)
* option directive also allows all punctuations for the option name (refs: #3366)
Features removed
----------------
@@ -108,6 +109,7 @@ Bugs fixed
* #3394: When ``'pointsize'`` is not ``10pt``, Japanese ``'manual'`` document
gets wrong PDF page dimensions
* #3399: quickstart: conf.py was not overwritten by template
* #3366: option directive does not allow punctuations
Testing
--------

View File

@@ -12,58 +12,43 @@ interesting examples.
Documentation using the alabaster theme
---------------------------------------
* CodePy: https://documen.tician.de/codepy/
* MeshPy: https://documen.tician.de/meshpy/
* PyCuda: https://documen.tician.de/pycuda/
* PyLangAcq: http://pylangacq.org/
Documentation using the classic theme
-------------------------------------
* APSW: http://apidoc.apsw.googlecode.com/hg/index.html
* ASE: https://wiki.fysik.dtu.dk/ase/
* APSW: https://rogerbinns.github.io/apsw/
* Calibre: http://manual.calibre-ebook.com/
* CodePy: https://documen.tician.de/codepy/
* Cython: http://docs.cython.org/
* Cormoran: http://cormoran.nhopkg.org/docs/
* Director: http://pythonhosted.org/director/
* Dirigible: http://www.projectdirigible.com/
* F2py: http://f2py.sourceforge.net/docs/
* GeoDjango: https://docs.djangoproject.com/en/dev/ref/contrib/gis/
* Genomedata:
http://noble.gs.washington.edu/proj/genomedata/doc/1.2.2/genomedata.html
* gevent: http://www.gevent.org/
* Google Wave API:
http://wave-robot-python-client.googlecode.com/svn/trunk/pydocs/index.html
* GSL Shell: http://www.nongnu.org/gsl-shell/
* Heapkeeper: http://heapkeeper.org/
* Hands-on Python Tutorial:
http://anh.cs.luc.edu/python/hands-on/3.1/handsonHtml/
* Hedge: https://documen.tician.de/hedge/
* Leo: http://leoeditor.com/
* Lino: http://www.lino-framework.org/
* MeshPy: https://documen.tician.de/meshpy/
* mpmath: http://mpmath.googlecode.com/svn/trunk/doc/build/index.html
* mpmath: http://mpmath.org/doc/current/
* OpenEXR: http://excamera.com/articles/26/doc/index.html
* OpenGDA: http://www.opengda.org/gdadoc/html/
* openWNS: http://docs.openwns.org/
* Paste: http://pythonpaste.org/script/
* Paver: http://paver.github.io/paver/
* Pioneers and Prominent Men of Utah: http://pioneers.rstebbing.com/
* PyCantonese: http://pycantonese.org/
* Pyccuracy: https://github.com/heynemann/pyccuracy/wiki/
* PyCuda: https://documen.tician.de/pycuda/
* Pyevolve: http://pyevolve.sourceforge.net/
* Pylo: https://documen.tician.de/pylo/
* PyMQI: http://pythonhosted.org/pymqi/
* PyPubSub: http://pubsub.sourceforge.net/
* pySPACE: http://pyspace.github.io/pyspace/
* Python: https://docs.python.org/3/
* python-apt: http://apt.alioth.debian.org/python-apt-doc/
* PyUblas: https://documen.tician.de/pyublas/
* Quex: http://quex.sourceforge.net/doc/html/main.html
* Ring programming language: http://ring-lang.sourceforge.net/doc/index.html
* Scapy: http://www.secdev.org/projects/scapy/doc/
* Seaborn: https://stanford.edu/~mwaskom/software/seaborn/
* Segway: http://noble.gs.washington.edu/proj/segway/doc/1.1.0/segway.html
* SimPy: http://simpy.readthedocs.org/en/latest/
* SymPy: http://docs.sympy.org/
* WTForms: http://wtforms.simplecodes.com/docs/
* z3c: http://www.ibiblio.org/paulcarduner/z3ctutorial/
@@ -129,6 +114,7 @@ Documentation using the sphinxdoc theme
Documentation using another builtin theme
-----------------------------------------
* ASE: https://wiki.fysik.dtu.dk/ase/ (sphinx_rtd_theme)
* C/C++ Development with Eclipse: http://eclipsebook.in/ (agogo)
* ESWP3 (http://eswp3.org) (sphinx_rtd_theme)
* Jinja: http://jinja.pocoo.org/ (scrolls)
@@ -137,13 +123,17 @@ Documentation using another builtin theme
* Linguistica: http://linguistica-uchicago.github.io/lxa5/ (sphinx_rtd_theme)
* MoinMoin: https://moin-20.readthedocs.io/en/latest/ (sphinx_rtd_theme)
* MPipe: http://vmlaker.github.io/mpipe/ (sphinx13)
* Paver: http://paver.readthedocs.io/en/latest/
* pip: https://pip.pypa.io/en/latest/ (sphinx_rtd_theme)
* Pyramid web framework:
http://docs.pylonsproject.org/projects/pyramid/en/latest/ (pyramid)
* Programmieren mit PyGTK und Glade (German):
http://www.florian-diesch.de/doc/python-und-glade/online/ (agogo)
* PyPubSub: http://pypubsub.readthedocs.io/ (bizstyle)
* Pyramid web framework:
http://docs.pylonsproject.org/projects/pyramid/en/latest/ (pyramid)
* Quex: http://quex.sourceforge.net/doc/html/main.html
* Satchmo: http://docs.satchmoproject.com/en/latest/ (sphinx_rtd_theme)
* Setuptools: http://pythonhosted.org/setuptools/ (nature)
* SimPy: http://simpy.readthedocs.org/en/latest/
* Spring Python: http://docs.spring.io/spring-python/1.2.x/sphinx/html/ (nature)
* sqlparse: http://python-sqlparse.googlecode.com/svn/docs/api/index.html
(agogo)
@@ -169,6 +159,7 @@ Documentation using a custom theme/integrated in a site
* Flask-OpenID: http://pythonhosted.org/Flask-OpenID/
* Gameduino: http://excamera.com/sphinx/gameduino/
* GeoServer: http://docs.geoserver.org/
* gevent: http://www.gevent.org/
* GHC - Glasgow Haskell Compiler: http://downloads.haskell.org/~ghc/master/users-guide/
* Glashammer: http://glashammer.org/
* Istihza (Turkish Python documentation project): http://belgeler.istihza.com/py2/
@@ -194,6 +185,7 @@ Documentation using a custom theme/integrated in a site
* QGIS: http://qgis.org/en/docs/index.html
* qooxdoo: http://manual.qooxdoo.org/current/
* Roundup: http://www.roundup-tracker.org/
* Seaborn: https://stanford.edu/~mwaskom/software/seaborn/
* Selenium: http://docs.seleniumhq.org/docs/
* Self: http://www.selflanguage.org/
* Substance D: http://docs.pylonsproject.org/projects/substanced/en/latest/

View File

@@ -38,7 +38,7 @@ DONT_CHECK = -i .ropeproject \
all: clean-pyc clean-backupfiles style-check type-check test
style-check:
@$(PYTHON) utils/check_sources.py $(DONT_CHECK) .
@PYTHONWARNINGS=all $(PYTHON) utils/check_sources.py $(DONT_CHECK) .
type-check:
mypy sphinx/

View File

@@ -184,9 +184,17 @@ Includes
string option, only lines that precede the first lines containing that string
are included.
With lines selected this way it is still possible to use ``lines``, the
numbers being now interpreted relative to the already selected lines.
When lines have been selected in any of the ways described above, the
line numbers in ``emphasize-lines`` refer to the selection, with the
line count starting at ``1``.
When specifying particular parts of a file to display, it can be useful to
display exactly which lines are being presented.
This can be done using the ``lineno-match`` option.
display the original line numbers. This can be done using the
``lineno-match`` option, which is however allowed only when the selection
consists of contiguous lines.
You can prepend and/or append a line to the included code, using the
``prepend`` and ``append`` option, respectively. This is useful e.g. for

View File

@@ -226,7 +226,7 @@ as long as the text::
Normally, there are no heading levels assigned to certain characters as the
structure is determined from the succession of headings. However, this
convention is used in `Python's Style Guide for documentating
convention is used in `Python's Style Guide for documenting
<https://docs.python.org/devguide/documenting.html#style-guide>`_ which you may
follow:

View File

@@ -12,10 +12,10 @@
import os
import re
import codecs
import zipfile
from os import path
from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile
from datetime import datetime
from collections import namedtuple
try:
from PIL import Image
@@ -28,10 +28,12 @@ except ImportError:
from docutils import nodes
from sphinx import addnodes
from sphinx import package_dir
from sphinx.builders.html import StandaloneHTMLBuilder
from sphinx.util import logging
from sphinx.util import status_iterator
from sphinx.util.osutil import ensuredir, copyfile, make_filename, EEXIST
from sphinx.util.osutil import ensuredir, copyfile, make_filename
from sphinx.util.fileutil import copy_asset_file
from sphinx.util.smartypants import sphinx_smarty_pants as ssp
if False:
@@ -43,101 +45,14 @@ if False:
logger = logging.getLogger(__name__)
# (Fragment) templates from which the metainfo files content.opf, toc.ncx,
# mimetype, and META-INF/container.xml are created.
# (Fragment) templates from which the metainfo files content.opf and
# toc.ncx are created.
# This template section also defines strings that are embedded in the html
# output but that may be customized by (re-)setting module attributes,
# e.g. from conf.py.
MIMETYPE_TEMPLATE = 'application/epub+zip' # no EOL!
CONTAINER_TEMPLATE = u'''\
<?xml version="1.0" encoding="UTF-8"?>
<container version="1.0"
xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
<rootfiles>
<rootfile full-path="content.opf"
media-type="application/oebps-package+xml"/>
</rootfiles>
</container>
'''
TOC_TEMPLATE = u'''\
<?xml version="1.0"?>
<ncx version="2005-1" xmlns="http://www.daisy.org/z3986/2005/ncx/">
<head>
<meta name="dtb:uid" content="%(uid)s"/>
<meta name="dtb:depth" content="%(level)d"/>
<meta name="dtb:totalPageCount" content="0"/>
<meta name="dtb:maxPageNumber" content="0"/>
</head>
<docTitle>
<text>%(title)s</text>
</docTitle>
<navMap>
%(navpoints)s
</navMap>
</ncx>
'''
NAVPOINT_TEMPLATE = u'''\
%(indent)s <navPoint id="%(navpoint)s" playOrder="%(playorder)d">
%(indent)s <navLabel>
%(indent)s <text>%(text)s</text>
%(indent)s </navLabel>
%(indent)s <content src="%(refuri)s" />
%(indent)s </navPoint>'''
NAVPOINT_INDENT = ' '
NODE_NAVPOINT_TEMPLATE = 'navPoint%d'
CONTENT_TEMPLATE = u'''\
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="2.0"
unique-identifier="%(uid)s">
<metadata xmlns:opf="http://www.idpf.org/2007/opf"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:language>%(lang)s</dc:language>
<dc:title>%(title)s</dc:title>
<dc:creator opf:role="aut">%(author)s</dc:creator>
<dc:publisher>%(publisher)s</dc:publisher>
<dc:rights>%(copyright)s</dc:rights>
<dc:identifier id="%(uid)s" opf:scheme="%(scheme)s">%(id)s</dc:identifier>
<dc:date>%(date)s</dc:date>
</metadata>
<manifest>
<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml" />
%(files)s
</manifest>
<spine toc="ncx">
%(spine)s
</spine>
<guide>
%(guide)s
</guide>
</package>
'''
COVER_TEMPLATE = u'''\
<meta name="cover" content="%(cover)s"/>
'''
COVERPAGE_NAME = u'epub-cover.xhtml'
FILE_TEMPLATE = u'''\
<item id="%(id)s"
href="%(href)s"
media-type="%(media_type)s" />'''
SPINE_TEMPLATE = u'''\
<itemref idref="%(idref)s" />'''
NO_LINEAR_SPINE_TEMPLATE = u'''\
<itemref idref="%(idref)s" linear="no" />'''
GUIDE_TEMPLATE = u'''\
<reference type="%(type)s" title="%(title)s" href="%(uri)s" />'''
TOCTREE_TEMPLATE = u'toctree-l%d'
DOCTYPE = u'''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
@@ -178,6 +93,12 @@ VECTOR_GRAPHICS_EXTENSIONS = ('.svg',)
REFURI_RE = re.compile("([^#:]*#)(.*)")
ManifestItem = namedtuple('ManifestItem', ['href', 'id', 'media_type'])
Spine = namedtuple('Spine', ['idref', 'linear'])
Guide = namedtuple('Guide', ['type', 'title', 'uri'])
NavPoint = namedtuple('NavPoint', ['navpoint', 'playorder', 'text', 'refuri', 'children'])
# The epub publisher
class EpubBuilder(StandaloneHTMLBuilder):
@@ -190,6 +111,8 @@ class EpubBuilder(StandaloneHTMLBuilder):
"""
name = 'epub2'
template_dir = path.join(package_dir, 'templates', 'epub2')
# don't copy the reST source
copysource = False
supported_image_types = ['image/svg+xml', 'image/png', 'image/gif',
@@ -208,19 +131,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
# don't generate search index or include search page
search = False
mimetype_template = MIMETYPE_TEMPLATE
container_template = CONTAINER_TEMPLATE
toc_template = TOC_TEMPLATE
navpoint_template = NAVPOINT_TEMPLATE
navpoint_indent = NAVPOINT_INDENT
node_navpoint_template = NODE_NAVPOINT_TEMPLATE
content_template = CONTENT_TEMPLATE
cover_template = COVER_TEMPLATE
coverpage_name = COVERPAGE_NAME
file_template = FILE_TEMPLATE
spine_template = SPINE_TEMPLATE
no_linear_spine_template = NO_LINEAR_SPINE_TEMPLATE
guide_template = GUIDE_TEMPLATE
toctree_template = TOCTREE_TEMPLATE
doctype = DOCTYPE
link_target_template = LINK_TARGET_TEMPLATE
@@ -237,6 +148,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
self.link_suffix = '.xhtml'
self.playorder = 0
self.tocid = 0
self.id_cache = {} # type: Dict[unicode, unicode]
self.use_index = self.get_builder_config('use_index', 'epub')
def get_theme_config(self):
@@ -244,14 +156,14 @@ class EpubBuilder(StandaloneHTMLBuilder):
return self.config.epub_theme, self.config.epub_theme_options
# generic support functions
def make_id(self, name, id_cache={}):
# type: (unicode, Dict[unicode, unicode]) -> unicode
def make_id(self, name):
# type: (unicode) -> unicode
# id_cache is intentionally mutable
"""Return a unique id for name."""
id = id_cache.get(name)
id = self.id_cache.get(name)
if not id:
id = 'epub-%d' % self.env.new_serialno('epub')
id_cache[name] = id
self.id_cache[name] = id
return id
def esc(self, name):
@@ -552,24 +464,19 @@ class EpubBuilder(StandaloneHTMLBuilder):
# type: (unicode, unicode) -> None
"""Write the metainfo file mimetype."""
logger.info('writing %s file...', outname)
with codecs.open(path.join(outdir, outname), 'w', 'utf-8') as f: # type: ignore
f.write(self.mimetype_template)
copy_asset_file(path.join(self.template_dir, 'mimetype'),
path.join(outdir, outname))
def build_container(self, outdir, outname):
# type: (unicode, unicode) -> None
"""Write the metainfo file META-INF/cointainer.xml."""
"""Write the metainfo file META-INF/container.xml."""
logger.info('writing %s file...', outname)
fn = path.join(outdir, outname)
try:
os.mkdir(path.dirname(fn))
except OSError as err:
if err.errno != EEXIST:
raise
with codecs.open(path.join(outdir, outname), 'w', 'utf-8') as f: # type: ignore
f.write(self.container_template) # type: ignore
filename = path.join(outdir, outname)
ensuredir(path.dirname(filename))
copy_asset_file(path.join(self.template_dir, 'container.xml'), filename)
def content_metadata(self, files, spine, guide):
# type: (List[unicode], List[unicode], List[unicode]) -> Dict[unicode, Any]
def content_metadata(self):
# type: () -> Dict[unicode, Any]
"""Create a dictionary with all metadata for the content.opf
file properly escaped.
"""
@@ -583,9 +490,9 @@ class EpubBuilder(StandaloneHTMLBuilder):
metadata['scheme'] = self.esc(self.config.epub_scheme)
metadata['id'] = self.esc(self.config.epub_identifier)
metadata['date'] = self.esc(datetime.utcnow().strftime("%Y-%m-%d"))
metadata['files'] = files
metadata['spine'] = spine
metadata['guide'] = guide
metadata['manifest_items'] = []
metadata['spines'] = []
metadata['guides'] = []
return metadata
def build_content(self, outdir, outname):
@@ -594,12 +501,12 @@ class EpubBuilder(StandaloneHTMLBuilder):
a file list and the spine (the reading order).
"""
logger.info('writing %s file...', outname)
metadata = self.content_metadata()
# files
if not outdir.endswith(os.sep):
outdir += os.sep
olen = len(outdir)
projectfiles = [] # type: List[unicode]
self.files = [] # type: List[unicode]
self.ignored_files = ['.buildinfo', 'mimetype', 'content.opf',
'toc.ncx', 'META-INF/container.xml',
@@ -609,7 +516,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
if not self.use_index:
self.ignored_files.append('genindex' + self.out_suffix)
for root, dirs, files in os.walk(outdir):
for fn in files:
for fn in sorted(files):
filename = path.join(root, fn)[olen:]
if filename in self.ignored_files:
continue
@@ -622,70 +529,57 @@ class EpubBuilder(StandaloneHTMLBuilder):
type='epub', subtype='unknown_project_files')
continue
filename = filename.replace(os.sep, '/')
projectfiles.append(self.file_template % {
'href': self.esc(filename),
'id': self.esc(self.make_id(filename)),
'media_type': self.esc(self.media_types[ext])
})
item = ManifestItem(self.esc(filename),
self.esc(self.make_id(filename)),
self.esc(self.media_types[ext]))
metadata['manifest_items'].append(item)
self.files.append(filename)
# spine
spine = []
spinefiles = set()
for item in self.refnodes:
if '#' in item['refuri']:
for refnode in self.refnodes:
if '#' in refnode['refuri']:
continue
if item['refuri'] in self.ignored_files:
if refnode['refuri'] in self.ignored_files:
continue
spine.append(self.spine_template % {
'idref': self.esc(self.make_id(item['refuri']))
})
spinefiles.add(item['refuri'])
spine = Spine(self.esc(self.make_id(refnode['refuri'])), True)
metadata['spines'].append(spine)
spinefiles.add(refnode['refuri'])
for info in self.domain_indices:
spine.append(self.spine_template % {
'idref': self.esc(self.make_id(info[0] + self.out_suffix))
})
spine = Spine(self.esc(self.make_id(info[0] + self.out_suffix)), True)
metadata['spines'].append(spine)
spinefiles.add(info[0] + self.out_suffix)
if self.use_index:
spine.append(self.spine_template % {
'idref': self.esc(self.make_id('genindex' + self.out_suffix))
})
spine = Spine(self.esc(self.make_id('genindex' + self.out_suffix)), True)
metadata['spines'].append(spine)
spinefiles.add('genindex' + self.out_suffix)
# add auto generated files
for name in self.files:
if name not in spinefiles and name.endswith(self.out_suffix):
spine.append(self.no_linear_spine_template % {
'idref': self.esc(self.make_id(name))
})
spine = Spine(self.esc(self.make_id(name)), False)
metadata['spines'].append(spine)
# add the optional cover
content_tmpl = self.content_template
html_tmpl = None
if self.config.epub_cover:
image, html_tmpl = self.config.epub_cover
image = image.replace(os.sep, '/')
mpos = content_tmpl.rfind('</metadata>')
cpos = content_tmpl.rfind('\n', 0, mpos) + 1
content_tmpl = content_tmpl[:cpos] + \
COVER_TEMPLATE % {'cover': self.esc(self.make_id(image))} + \
content_tmpl[cpos:]
metadata['cover'] = self.esc(self.make_id(image))
if html_tmpl:
spine.insert(0, self.spine_template % {
'idref': self.esc(self.make_id(self.coverpage_name))})
spine = Spine(self.esc(self.make_id(self.coverpage_name)), True)
metadata['spines'].insert(0, spine)
if self.coverpage_name not in self.files:
ext = path.splitext(self.coverpage_name)[-1]
self.files.append(self.coverpage_name)
projectfiles.append(self.file_template % {
'href': self.esc(self.coverpage_name),
'id': self.esc(self.make_id(self.coverpage_name)),
'media_type': self.esc(self.media_types[ext])
})
item = ManifestItem(self.esc(filename),
self.esc(self.make_id(filename)),
self.esc(self.media_types[ext]))
metadata['manifest_items'].append(item)
ctx = {'image': self.esc(image), 'title': self.config.project}
self.handle_page(
path.splitext(self.coverpage_name)[0], ctx, html_tmpl)
spinefiles.add(self.coverpage_name)
guide = []
auto_add_cover = True
auto_add_toc = True
if self.config.epub_guide:
@@ -697,64 +591,43 @@ class EpubBuilder(StandaloneHTMLBuilder):
auto_add_cover = False
if type == 'toc':
auto_add_toc = False
guide.append(self.guide_template % {
'type': self.esc(type),
'title': self.esc(title),
'uri': self.esc(uri)
})
metadata['guides'].append(Guide(self.esc(type),
self.esc(title),
self.esc(uri)))
if auto_add_cover and html_tmpl:
guide.append(self.guide_template % {
'type': 'cover',
'title': self.guide_titles['cover'],
'uri': self.esc(self.coverpage_name)
})
metadata['guides'].append(Guide('cover',
self.guide_titles['cover'],
self.esc(self.coverpage_name)))
if auto_add_toc and self.refnodes:
guide.append(self.guide_template % {
'type': 'toc',
'title': self.guide_titles['toc'],
'uri': self.esc(self.refnodes[0]['refuri'])
})
projectfiles = '\n'.join(projectfiles) # type: ignore
spine = '\n'.join(spine) # type: ignore
guide = '\n'.join(guide) # type: ignore
metadata['guides'].append(Guide('toc',
self.guide_titles['toc'],
self.esc(self.refnodes[0]['refuri'])))
# write the project file
with codecs.open(path.join(outdir, outname), 'w', 'utf-8') as f: # type: ignore
f.write(content_tmpl % # type: ignore
self.content_metadata(projectfiles, spine, guide))
copy_asset_file(path.join(self.template_dir, 'content.opf_t'),
path.join(outdir, outname),
metadata)
def new_navpoint(self, node, level, incr=True):
# type: (nodes.Node, int, bool) -> unicode
# type: (nodes.Node, int, bool) -> NavPoint
"""Create a new entry in the toc from the node at given level."""
# XXX Modifies the node
if incr:
self.playorder += 1
self.tocid += 1
node['indent'] = self.navpoint_indent * level
node['navpoint'] = self.esc(self.node_navpoint_template % self.tocid)
node['playorder'] = self.playorder
return self.navpoint_template % node
def insert_subnav(self, node, subnav):
# type: (nodes.Node, unicode) -> unicode
"""Insert nested navpoints for given node.
The node and subnav are already rendered to text.
"""
nlist = node.rsplit('\n', 1)
nlist.insert(-1, subnav)
return '\n'.join(nlist)
return NavPoint(self.esc('navPoint%d' % self.tocid), self.playorder,
node['text'], node['refuri'], [])
def build_navpoints(self, nodes):
# type: (nodes.Node) -> unicode
# type: (nodes.Node) -> List[NavPoint]
"""Create the toc navigation structure.
Subelements of a node are nested inside the navpoint. For nested nodes
the parent node is reinserted in the subnav.
"""
navstack = []
navlist = []
level = 1
navstack = [] # type: List[NavPoint]
navstack.append(NavPoint('dummy', '', '', '', []))
level = 0
lastnode = None
for node in nodes:
if not node['text']:
@@ -765,32 +638,33 @@ class EpubBuilder(StandaloneHTMLBuilder):
if node['level'] > self.config.epub_tocdepth:
continue
if node['level'] == level:
navlist.append(self.new_navpoint(node, level))
navpoint = self.new_navpoint(node, level)
navstack.pop()
navstack[-1].children.append(navpoint)
navstack.append(navpoint)
elif node['level'] == level + 1:
navstack.append(navlist)
navlist = []
level += 1
if lastnode and self.config.epub_tocdup:
# Insert starting point in subtoc with same playOrder
navlist.append(self.new_navpoint(lastnode, level, False))
navlist.append(self.new_navpoint(node, level))
navstack[-1].children.append(self.new_navpoint(lastnode, level, False))
navpoint = self.new_navpoint(node, level)
navstack[-1].children.append(navpoint)
navstack.append(navpoint)
elif node['level'] < level:
while node['level'] < len(navstack):
navstack.pop()
level = node['level']
navpoint = self.new_navpoint(node, level)
navstack[-1].children.append(navpoint)
navstack.append(navpoint)
else:
while node['level'] < level:
subnav = '\n'.join(navlist)
navlist = navstack.pop()
navlist[-1] = self.insert_subnav(navlist[-1], subnav)
level -= 1
navlist.append(self.new_navpoint(node, level))
raise
lastnode = node
while level != 1:
subnav = '\n'.join(navlist)
navlist = navstack.pop()
navlist[-1] = self.insert_subnav(navlist[-1], subnav)
level -= 1
return '\n'.join(navlist)
return navstack[0].children
def toc_metadata(self, level, navpoints):
# type: (int, List[unicode]) -> Dict[unicode, Any]
# type: (int, List[NavPoint]) -> Dict[unicode, Any]
"""Create a dictionary with all metadata for the toc.ncx file
properly escaped.
"""
@@ -818,8 +692,9 @@ class EpubBuilder(StandaloneHTMLBuilder):
navpoints = self.build_navpoints(refnodes)
level = max(item['level'] for item in self.refnodes)
level = min(level, self.config.epub_tocdepth)
with codecs.open(path.join(outdir, outname), 'w', 'utf-8') as f: # type: ignore
f.write(self.toc_template % self.toc_metadata(level, navpoints)) # type: ignore
copy_asset_file(path.join(self.template_dir, 'toc.ncx_t'),
path.join(outdir, outname),
self.toc_metadata(level, navpoints))
def build_epub(self, outdir, outname):
# type: (unicode, unicode) -> None
@@ -829,16 +704,13 @@ class EpubBuilder(StandaloneHTMLBuilder):
entry.
"""
logger.info('writing %s file...', outname)
projectfiles = ['META-INF/container.xml', 'content.opf', 'toc.ncx'] # type: List[unicode] # NOQA
projectfiles.extend(self.files)
epub = zipfile.ZipFile(path.join(outdir, outname), 'w', # type: ignore
zipfile.ZIP_DEFLATED)
epub.write(path.join(outdir, 'mimetype'), 'mimetype', # type: ignore
zipfile.ZIP_STORED)
for file in projectfiles:
fp = path.join(outdir, file)
epub.write(fp, file, zipfile.ZIP_DEFLATED) # type: ignore
epub.close()
epub_filename = path.join(outdir, outname)
with ZipFile(epub_filename, 'w', ZIP_DEFLATED) as epub: # type: ignore
epub.write(path.join(outdir, 'mimetype'), 'mimetype', ZIP_STORED) # type: ignore
for filename in [u'META-INF/container.xml', u'content.opf', u'toc.ncx']:
epub.write(path.join(outdir, filename), filename, ZIP_DEFLATED) # type: ignore
for filename in self.files:
epub.write(path.join(outdir, filename), filename, ZIP_DEFLATED) # type: ignore
def setup(app):

View File

@@ -10,13 +10,15 @@
:license: BSD, see LICENSE for details.
"""
import codecs
from os import path
from datetime import datetime
from collections import namedtuple
from sphinx.config import string_classes
from sphinx import package_dir
from sphinx.config import string_classes, ENUM
from sphinx.builders.epub import EpubBuilder
from sphinx.util import logging
from sphinx.util.fileutil import copy_asset_file
if False:
# For type annotation
@@ -27,79 +29,24 @@ if False:
logger = logging.getLogger(__name__)
# (Fragment) templates from which the metainfo files content.opf, toc.ncx,
# mimetype, and META-INF/container.xml are created.
# This template section also defines strings that are embedded in the html
# output but that may be customized by (re-)setting module attributes,
# e.g. from conf.py.
NavPoint = namedtuple('NavPoint', ['text', 'refuri', 'children'])
NAVIGATION_DOC_TEMPLATE = u'''\
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"\
xmlns:epub="http://www.idpf.org/2007/ops" lang="%(lang)s" xml:lang="%(lang)s">
<head>
<title>%(toc_locale)s</title>
</head>
<body>
<nav epub:type="toc">
<h1>%(toc_locale)s</h1>
<ol>
%(navlist)s
</ol>
</nav>
</body>
</html>
'''
NAVLIST_TEMPLATE = u'''%(indent)s <li><a href="%(refuri)s">%(text)s</a></li>'''
NAVLIST_TEMPLATE_HAS_CHILD = u'''%(indent)s <li><a href="%(refuri)s">%(text)s</a>'''
NAVLIST_TEMPLATE_BEGIN_BLOCK = u'''%(indent)s <ol>'''
NAVLIST_TEMPLATE_END_BLOCK = u'''%(indent)s </ol>
%(indent)s </li>'''
NAVLIST_INDENT = ' '
PACKAGE_DOC_TEMPLATE = u'''\
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" xml:lang="%(lang)s"
unique-identifier="%(uid)s"
prefix="ibooks: http://vocabulary.itunes.apple.com/rdf/ibooks/vocabulary-extensions-1.0/">
<metadata xmlns:opf="http://www.idpf.org/2007/opf"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:language>%(lang)s</dc:language>
<dc:title>%(title)s</dc:title>
<dc:description>%(description)s</dc:description>
<dc:creator>%(author)s</dc:creator>
<dc:contributor>%(contributor)s</dc:contributor>
<dc:publisher>%(publisher)s</dc:publisher>
<dc:rights>%(copyright)s</dc:rights>
<dc:identifier id="%(uid)s">%(id)s</dc:identifier>
<dc:date>%(date)s</dc:date>
<meta property="dcterms:modified">%(date)s</meta>
<meta property="ibooks:version">%(version)s</meta>
<meta property="ibooks:specified-fonts">true</meta>
<meta property="ibooks:binding">true</meta>
<meta property="ibooks:scroll-axis">%(ibook_scroll_axis)s</meta>
</metadata>
<manifest>
<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml" />
<item id="nav" href="nav.xhtml"\
media-type="application/xhtml+xml" properties="nav"/>
%(files)s
</manifest>
<spine toc="ncx" page-progression-direction="%(page_progression_direction)s">
%(spine)s
</spine>
<guide>
%(guide)s
</guide>
</package>
'''
# writing modes
PAGE_PROGRESSION_DIRECTIONS = {
'horizontal': 'ltr',
'vertical': 'rtl',
}
IBOOK_SCROLL_AXIS = {
'horizontal': 'vertical',
'vertical': 'horizontal',
}
THEME_WRITING_MODES = {
'vertical': 'vertical-rl',
'horizontal': 'horizontal-tb',
}
DOCTYPE = u'''<!DOCTYPE html>'''
# The epub3 publisher
class Epub3Builder(EpubBuilder):
"""
@@ -111,13 +58,7 @@ class Epub3Builder(EpubBuilder):
"""
name = 'epub'
navigation_doc_template = NAVIGATION_DOC_TEMPLATE
navlist_template = NAVLIST_TEMPLATE
navlist_template_has_child = NAVLIST_TEMPLATE_HAS_CHILD
navlist_template_begin_block = NAVLIST_TEMPLATE_BEGIN_BLOCK
navlist_template_end_block = NAVLIST_TEMPLATE_END_BLOCK
navlist_indent = NAVLIST_INDENT
content_template = PACKAGE_DOC_TEMPLATE
template_dir = path.join(package_dir, 'templates', 'epub3')
doctype = DOCTYPE
# Finish by building the epub file
@@ -132,77 +73,31 @@ class Epub3Builder(EpubBuilder):
self.build_toc(self.outdir, 'toc.ncx')
self.build_epub(self.outdir, self.config.epub_basename + '.epub')
def content_metadata(self, files, spine, guide):
# type: (List[unicode], List[unicode], List[unicode]) -> Dict
def content_metadata(self):
# type: () -> Dict
"""Create a dictionary with all metadata for the content.opf
file properly escaped.
"""
metadata = super(Epub3Builder, self).content_metadata(
files, spine, guide)
writing_mode = self.config.epub_writing_mode
metadata = super(Epub3Builder, self).content_metadata()
metadata['description'] = self.esc(self.config.epub_description)
metadata['contributor'] = self.esc(self.config.epub_contributor)
metadata['page_progression_direction'] = self._page_progression_direction()
metadata['ibook_scroll_axis'] = self._ibook_scroll_axis()
metadata['page_progression_direction'] = PAGE_PROGRESSION_DIRECTIONS.get(writing_mode)
metadata['ibook_scroll_axis'] = IBOOK_SCROLL_AXIS.get(writing_mode)
metadata['date'] = self.esc(datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"))
metadata['version'] = self.esc(self.config.version)
return metadata
def _page_progression_direction(self):
# type: () -> unicode
if self.config.epub_writing_mode == 'horizontal':
page_progression_direction = 'ltr'
elif self.config.epub_writing_mode == 'vertical':
page_progression_direction = 'rtl'
else:
page_progression_direction = 'default'
return page_progression_direction
def _ibook_scroll_axis(self):
# type: () -> unicode
if self.config.epub_writing_mode == 'horizontal':
scroll_axis = 'vertical'
elif self.config.epub_writing_mode == 'vertical':
scroll_axis = 'horizontal'
else:
scroll_axis = 'default'
return scroll_axis
def _css_writing_mode(self):
# type: () -> unicode
if self.config.epub_writing_mode == 'vertical':
editing_mode = 'vertical-rl'
else:
editing_mode = 'horizontal-tb'
return editing_mode
def prepare_writing(self, docnames):
# type: (Iterable[unicode]) -> None
super(Epub3Builder, self).prepare_writing(docnames)
self.globalcontext['theme_writing_mode'] = self._css_writing_mode()
def new_navlist(self, node, level, has_child):
# type: (nodes.Node, int, bool) -> unicode
"""Create a new entry in the toc from the node at given level."""
# XXX Modifies the node
self.tocid += 1
node['indent'] = self.navlist_indent * level
if has_child:
return self.navlist_template_has_child % node
else:
return self.navlist_template % node
def begin_navlist_block(self, level):
# type: (int) -> unicode
return self.navlist_template_begin_block % {
"indent": self.navlist_indent * level
}
def end_navlist_block(self, level):
# type: (int) -> unicode
return self.navlist_template_end_block % {"indent": self.navlist_indent * level}
writing_mode = self.config.epub_writing_mode
self.globalcontext['theme_writing_mode'] = THEME_WRITING_MODES.get(writing_mode)
def build_navlist(self, navnodes):
# type: (List[nodes.Node]) -> unicode
# type: (List[nodes.Node]) -> List[NavPoint]
"""Create the toc navigation structure.
This method is almost same as build_navpoints method in epub.py.
@@ -212,9 +107,9 @@ class Epub3Builder(EpubBuilder):
The difference from build_navpoints method is templates which are used
when generating navigation documents.
"""
navlist = []
level = 1
usenodes = []
navstack = [] # type: List[NavPoint]
navstack.append(NavPoint('', '', []))
level = 0
for node in navnodes:
if not node['text']:
continue
@@ -223,31 +118,33 @@ class Epub3Builder(EpubBuilder):
continue
if node['level'] > self.config.epub_tocdepth:
continue
usenodes.append(node)
for i, node in enumerate(usenodes):
curlevel = node['level']
if curlevel == level + 1:
navlist.append(self.begin_navlist_block(level))
while curlevel < level:
level -= 1
navlist.append(self.end_navlist_block(level))
level = curlevel
if i != len(usenodes) - 1 and usenodes[i + 1]['level'] > level:
has_child = True
navpoint = NavPoint(node['text'], node['refuri'], [])
if node['level'] == level:
navstack.pop()
navstack[-1].children.append(navpoint)
navstack.append(navpoint)
elif node['level'] == level + 1:
level += 1
navstack[-1].children.append(navpoint)
navstack.append(navpoint)
elif node['level'] < level:
while node['level'] < len(navstack):
navstack.pop()
level = node['level']
navstack[-1].children.append(navpoint)
navstack.append(navpoint)
else:
has_child = False
navlist.append(self.new_navlist(node, level, has_child))
while level != 1:
level -= 1
navlist.append(self.end_navlist_block(level))
return '\n'.join(navlist)
raise
return navstack[0].children
def navigation_doc_metadata(self, navlist):
# type: (unicode) -> Dict
# type: (List[NavPoint]) -> Dict
"""Create a dictionary with all metadata for the nav.xhtml file
properly escaped.
"""
metadata = {}
metadata = {} # type: Dict
metadata['lang'] = self.esc(self.config.epub_language)
metadata['toc_locale'] = self.esc(self.guide_titles['toc'])
metadata['navlist'] = navlist
@@ -268,9 +165,9 @@ class Epub3Builder(EpubBuilder):
# 'includehidden'
refnodes = self.refnodes
navlist = self.build_navlist(refnodes)
with codecs.open(path.join(outdir, outname), 'w', 'utf-8') as f: # type: ignore
f.write(self.navigation_doc_template % # type: ignore
self.navigation_doc_metadata(navlist))
copy_asset_file(path.join(self.template_dir, 'nav.xhtml_t'),
path.join(outdir, outname),
self.navigation_doc_metadata(navlist))
# Add nav.xhtml to epub file
if outname not in self.files:
@@ -284,7 +181,8 @@ def setup(app):
app.add_config_value('epub_description', '', 'epub3', string_classes)
app.add_config_value('epub_contributor', 'unknown', 'epub3', string_classes)
app.add_config_value('epub_writing_mode', 'horizontal', 'epub3', string_classes)
app.add_config_value('epub_writing_mode', 'horizontal', 'epub3',
ENUM('horizontal', 'vertical'))
return {
'version': 'builtin',

View File

@@ -1032,7 +1032,7 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder):
return tree
def assemble_toc_secnumbers(self):
# type: () -> Dict[unicode, Dict[Tuple[unicode, unicode], Tuple[int, ...]]]
# type: () -> Dict[unicode, Dict[unicode, Tuple[int, ...]]]
# Assemble toc_secnumbers to resolve section numbers on SingleHTML.
# Merge all secnumbers to single secnumber.
#
@@ -1042,15 +1042,16 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder):
#
# There are related codes in inline_all_toctres() and
# HTMLTranslter#add_secnumber().
new_secnumbers = {}
new_secnumbers = {} # type: Dict[unicode, Tuple[int, ...]]
for docname, secnums in iteritems(self.env.toc_secnumbers):
for id, secnum in iteritems(secnums):
new_secnumbers[(docname, id)] = secnum
alias = "%s/%s" % (docname, id)
new_secnumbers[alias] = secnum
return {self.config.master_doc: new_secnumbers}
def assemble_toc_fignumbers(self):
# type: () -> Dict[unicode, Dict[Tuple[unicode, unicode], Dict[unicode, Tuple[int, ...]]]] # NOQA
# type: () -> Dict[unicode, Dict[unicode, Dict[unicode, Tuple[int, ...]]]] # NOQA
# Assemble toc_fignumbers to resolve figure numbers on SingleHTML.
# Merge all fignumbers to single fignumber.
#
@@ -1060,13 +1061,14 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder):
#
# There are related codes in inline_all_toctres() and
# HTMLTranslter#add_fignumber().
new_fignumbers = {} # type: Dict[Tuple[unicode, unicode], Dict[unicode, Tuple[int, ...]]] # NOQA
new_fignumbers = {} # type: Dict[unicode, Dict[unicode, Tuple[int, ...]]]
# {u'foo': {'figure': {'id2': (2,), 'id1': (1,)}}, u'bar': {'figure': {'id1': (3,)}}}
for docname, fignumlist in iteritems(self.env.toc_fignumbers):
for figtype, fignums in iteritems(fignumlist):
new_fignumbers.setdefault((docname, figtype), {})
alias = "%s/%s" % (docname, figtype)
new_fignumbers.setdefault(alias, {})
for id, fignum in iteritems(fignums):
new_fignumbers[(docname, figtype)][id] = fignum
new_fignumbers[alias][id] = fignum
return {self.config.master_doc: new_fignumbers}

View File

@@ -171,7 +171,8 @@ class Config(object):
if getenv('SOURCE_DATE_EPOCH') is not None:
for k in ('copyright', 'epub_copyright'):
if k in config:
config[k] = copyright_year_re.sub('\g<1>%s' % format_date('%Y'), config[k])
config[k] = copyright_year_re.sub(r'\g<1>%s' % format_date('%Y'),
config[k])
def check_types(self):
# type: () -> None

View File

@@ -214,9 +214,9 @@ class LiteralIncludeReader(object):
lines = self.show_diff()
else:
filters = [self.pyobject_filter,
self.lines_filter,
self.start_filter,
self.end_filter,
self.lines_filter,
self.prepend_filter,
self.append_filter]
lines = self.read_file(self.filename)

View File

@@ -116,7 +116,7 @@ class PyXrefMixin(object):
def make_xrefs(self, rolename, domain, target, innernode=nodes.emphasis,
contnode=None):
# type: (unicode, unicode, unicode, nodes.Node, nodes.Node) -> List[nodes.Node]
delims = '(\s*[\[\]\(\),](?:\s*or\s)?\s*|\s+or\s+)'
delims = r'(\s*[\[\]\(\),](?:\s*or\s)?\s*|\s+or\s+)'
delims_re = re.compile(delims)
sub_targets = re.split(delims, target)

View File

@@ -45,9 +45,9 @@ logger = logging.getLogger(__name__)
# RE for option descriptions
option_desc_re = re.compile(r'((?:/|--|-|\+)?[-\.?@#_a-zA-Z0-9]+)(=?\s*.*)')
option_desc_re = re.compile(r'((?:/|--|-|\+)?[^\s=]+)(=?\s*.*)')
# RE for grammar tokens
token_re = re.compile('`(\w+)`', re.U)
token_re = re.compile(r'`(\w+)`', re.U)
class GenericObject(ObjectDescription):

View File

@@ -357,7 +357,7 @@ class Autosummary(Directive):
*items* is a list produced by :meth:`get_items`.
"""
table_spec = addnodes.tabular_col_spec()
table_spec['spec'] = 'p{0.5\linewidth}p{0.5\linewidth}'
table_spec['spec'] = r'p{0.5\linewidth}p{0.5\linewidth}'
table = autosummary_table('')
real_table = nodes.table('', classes=['longtable'])
@@ -388,7 +388,7 @@ class Autosummary(Directive):
for name, sig, summary, real_name in items:
qualifier = 'obj'
if 'nosignatures' not in self.options:
col1 = ':%s:`%s <%s>`\ %s' % (qualifier, name, real_name, rst.escape(sig)) # type: unicode # NOQA
col1 = ':%s:`%s <%s>`\\ %s' % (qualifier, name, real_name, rst.escape(sig)) # type: unicode # NOQA
else:
col1 = ':%s:`%s <%s>`' % (qualifier, name, real_name)
col2 = summary

View File

@@ -52,7 +52,7 @@ number2name.update(token.tok_name)
_eq = nodes.Leaf(token.EQUAL, '=')
emptyline_re = re.compile('^\s*(#.*)?$')
emptyline_re = re.compile(r'^\s*(#.*)?$')
class AttrDocVisitor(nodes.NodeVisitor):

View File

@@ -293,7 +293,7 @@ def emph_literal_role(typ, rawtext, text, lineno, inliner,
return [retnode], []
_abbr_re = re.compile('\((.*)\)$', re.S)
_abbr_re = re.compile(r'\((.*)\)$', re.S)
def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
<rootfiles>
<rootfile full-path="content.opf" media-type="application/oebps-package+xml"/>
</rootfiles>
</container>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="2.0"
unique-identifier="%(uid)s">
<metadata xmlns:opf="http://www.idpf.org/2007/opf"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:language>{{ lang }}</dc:language>
<dc:title>{{ title }}</dc:title>
<dc:creator opf:role="aut">{{ author }}</dc:creator>
<dc:publisher>{{ publisher }}</dc:publisher>
<dc:rights>{{ copyright }}</dc:rights>
<dc:identifier id="{{ uid }}" opf:scheme="{{ scheme }}">{{ id }}</dc:identifier>
<dc:date>{{ date }}</dc:date>
{%- if cover %}
<meta name="cover" content="{{ cover }}"/>
{%- endif %}
</metadata>
<manifest>
<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml" />
{%- for item in manifest_items %}
<item id="{{ item.id }}" href="{{ item.href }}" media-type="{{ item.media_type }}" />
{%- endfor %}
</manifest>
<spine toc="ncx">
{%- for spine in spines %}
{%- if spine.linear %}
<itemref idref="{{ spine.idref }}" />
{%- else %}
<itemref idref="{{ spine.idref }}" linear="no" />'''
{%- endif %}
{%- endfor %}
</spine>
<guide>
{%- for guide in guides %}
<reference type="{{ guide.type }}" title="{{ guide.title }}" href="{{ guide.uri }}" />'''
{%- endfor %}
</guide>
</package>

View File

@@ -0,0 +1 @@
application/epub+zip

View File

@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<ncx version="2005-1" xmlns="http://www.daisy.org/z3986/2005/ncx/">
<head>
<meta name="dtb:uid" content="{{ uid }}"/>
<meta name="dtb:depth" content="{{ level }}"/>
<meta name="dtb:totalPageCount" content="0"/>
<meta name="dtb:maxPageNumber" content="0"/>
</head>
<docTitle>
<text>{{ title }}</text>
</docTitle>
<navMap>
{{ navpoints }}
</navMap>
</ncx>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
<rootfiles>
<rootfile full-path="content.opf" media-type="application/oebps-package+xml"/>
</rootfiles>
</container>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" xml:lang="{{ lang }}"
unique-identifier="{{ uid }}"
prefix="ibooks: http://vocabulary.itunes.apple.com/rdf/ibooks/vocabulary-extensions-1.0/">
<metadata xmlns:opf="http://www.idpf.org/2007/opf"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:language>{{ lang }}</dc:language>
<dc:title>{{ title }}</dc:title>
<dc:description>{{ description }}</dc:description>
<dc:creator>{{ author }}</dc:creator>
<dc:contributor>{{ contributor }}</dc:contributor>
<dc:publisher>{{ publisher }}</dc:publisher>
<dc:rights>{{ copyright }}</dc:rights>
<dc:identifier id="{{ uid }}">{{ id }}</dc:identifier>
<dc:date>{{ date }}</dc:date>
<meta property="dcterms:modified">{{ date }}</meta>
<meta property="ibooks:version">{{ version }}</meta>
<meta property="ibooks:specified-fonts">true</meta>
<meta property="ibooks:binding">true</meta>
<meta property="ibooks:scroll-axis">{{ ibook_scroll_axis }}</meta>
{%- if cover %}
<meta name="cover" content="{{ cover }}"/>
{%- endif %}
</metadata>
<manifest>
<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml" />
<item id="nav" href="nav.xhtml" media-type="application/xhtml+xml" properties="nav"/>
{%- for item in manifest_items %}
<item id="{{ item.id }}" href="{{ item.href }}" media-type="{{ item.media_type }}" />
{%- endfor %}
</manifest>
<spine toc="ncx" page-progression-direction="{{ page_progression_direction }}">
{%- for spine in spines %}
{%- if spine.linear %}
<itemref idref="{{ spine.idref }}" />
{%- else %}
<itemref idref="{{ spine.idref }}" linear="no" />'''
{%- endif %}
{%- endfor %}
</spine>
<guide>
{%- for guide in guides %}
<reference type="{{ guide.type }}" title="{{ guide.title }}" href="{{ guide.uri }}" />'''
{%- endfor %}
</guide>
</package>

View File

@@ -0,0 +1 @@
application/epub+zip

View File

@@ -0,0 +1,26 @@
{%- macro toctree(navlist) -%}
<ol>
{%- for nav in navlist %}
<li>
<a href="{{ nav.refuri }}">{{ nav.text }}</a>
{%- if nav.children %}
{{ toctree(nav.children)|indent(4, true) }}
{%- endif %}
</li>
{%- endfor %}
</ol>
{%- endmacro -%}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:epub="http://www.idpf.org/2007/ops" lang="{{ lang }}" xml:lang="{{ lang }}">
<head>
<title>{{ toc_locale }}</title>
</head>
<body>
<nav epub:type="toc">
<h1>{{ toc_locale }}</h1>
{{ toctree(navlist)|indent(6, true) }}
</nav>
</body>
</html>

View File

@@ -0,0 +1,24 @@
{%- macro navPoints(navlist) %}
{%- for nav in navlist %}
<navPoint id="{{ nav.navpoint }}" playOrder="{{ nav.playorder }}">
<navLabel>
<text>{{ nav.text }}</text>
</navLabel>
<content src="{{ nav.refuri }}" />{{ navPoints(nav.children)|indent(2, true) }}
</navPoint>
{%- endfor %}
{%- endmacro -%}
<?xml version="1.0"?>
<ncx version="2005-1" xmlns="http://www.daisy.org/z3986/2005/ncx/">
<head>
<meta name="dtb:uid" content="{{ uid }}"/>
<meta name="dtb:depth" content="{{ level }}"/>
<meta name="dtb:totalPageCount" content="0"/>
<meta name="dtb:maxPageNumber" content="0"/>
</head>
<docTitle>
<text>{{ title }}</text>
</docTitle>
<navMap>{{ navPoints(navpoints)|indent(4, true) }}
</navMap>
</ncx>

View File

@@ -561,7 +561,7 @@ def encode_uri(uri):
def split_docinfo(text):
# type: (unicode) -> Sequence[unicode]
docinfo_re = re.compile('\A((?:\s*:\w+:.*?\n(?:[ \t]+.*?\n)*)+)', re.M)
docinfo_re = re.compile('\\A((?:\\s*:\\w+:.*?\n(?:[ \\t]+.*?\n)*)+)', re.M)
result = docinfo_re.split(text, 1) # type: ignore
if len(result) == 1:
return '', result[0]

View File

@@ -21,7 +21,8 @@ from docutils.parsers.rst import directives, roles
from sphinx.util import logging
logger = logging.getLogger(__name__)
report_re = re.compile('^(.+?:\d+): \((DEBUG|INFO|WARNING|ERROR|SEVERE)/(\d+)?\) (.+?)\n?$')
report_re = re.compile('^(.+?:\\d+): \\((DEBUG|INFO|WARNING|ERROR|SEVERE)/(\\d+)?\\) '
'(.+?)\n?$')
if False:

View File

@@ -65,8 +65,8 @@ def apply_source_workaround(node):
if isinstance(node, nodes.term):
# strip classifier from rawsource of term
for classifier in reversed(node.parent.traverse(nodes.classifier)):
node.rawsource = re.sub(
'\s*:\s*%s' % re.escape(classifier.astext()), '', node.rawsource)
node.rawsource = re.sub(r'\s*:\s*%s' % re.escape(classifier.astext()),
'', node.rawsource)
# workaround: recommonmark-0.2.0 doesn't set rawsource attribute
if not node.rawsource:

View File

@@ -11,7 +11,7 @@
import re
symbols_re = re.compile('([!-/:-@\[-`{-~])')
symbols_re = re.compile(r'([!-/:-@\[-`{-~])')
def escape(text):

View File

@@ -310,11 +310,9 @@ class HTMLTranslator(BaseTranslator):
elif isinstance(node.parent, nodes.section):
if self.builder.name == 'singlehtml':
docname = self.docnames[-1]
anchorname = '#' + node.parent['ids'][0]
if (docname, anchorname) not in self.builder.secnumbers:
anchorname = (docname, '') # try first heading which has no anchor
else:
anchorname = (docname, anchorname)
anchorname = "%s/#%s" % (docname, node.parent['ids'][0])
if anchorname not in self.builder.secnumbers:
anchorname = "%s/" % docname # try first heading which has no anchor
else:
anchorname = '#' + node.parent['ids'][0]
if anchorname not in self.builder.secnumbers:
@@ -329,7 +327,7 @@ class HTMLTranslator(BaseTranslator):
def append_fignumber(figtype, figure_id):
# type: (unicode, unicode) -> None
if self.builder.name == 'singlehtml':
key = (self.docnames[-1], figtype)
key = "%s/%s" % (self.docnames[-1], figtype)
else:
key = figtype

View File

@@ -371,7 +371,7 @@ class Table(object):
This is what LaTeX calls the 'preamble argument' of the used table environment.
.. note:: the ``\X`` column type specifier is defined in ``sphinx.sty``.
.. note:: the ``\\X`` column type specifier is defined in ``sphinx.sty``.
"""
if self.colspec:
return self.colspec
@@ -456,13 +456,13 @@ class TableCell(object):
def escape_abbr(text):
# type: (unicode) -> unicode
"""Adjust spacing after abbreviations."""
return re.sub('\.(?=\s|$)', '.\\@', text)
return re.sub(r'\.(?=\s|$)', r'.\@', text)
def rstdim_to_latexdim(width_str):
# type: (unicode) -> unicode
"""Convert `width_str` with rst length to LaTeX length."""
match = re.match('^(\d*\.?\d*)\s*(\S*)$', width_str)
match = re.match(r'^(\d*\.?\d*)\s*(\S*)$', width_str)
if not match:
raise ValueError
res = width_str
@@ -2244,7 +2244,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
def visit_line(self, node):
# type: (nodes.Node) -> None
self.body.append('\item[] ')
self.body.append(r'\item[] ')
def depart_line(self, node):
# type: (nodes.Node) -> None

View File

@@ -129,7 +129,7 @@ class ManualPageTranslator(BaseTranslator):
tmpl = (".TH \"%(title_upper)s\" \"%(manual_section)s\""
" \"%(date)s\" \"%(version)s\" \"%(manual_group)s\"\n"
".SH NAME\n"
"%(title)s \- %(subtitle)s\n")
"%(title)s \\- %(subtitle)s\n")
return tmpl % self._docinfo
def visit_start_of_file(self, node):

View File

@@ -469,7 +469,7 @@ class TexinfoTranslator(nodes.NodeVisitor):
def tex_image_length(self, width_str):
# type: (unicode) -> unicode
match = re.match('(\d*\.?\d*)\s*(\S*)', width_str)
match = re.match(r'(\d*\.?\d*)\s*(\S*)', width_str)
if not match:
# fallback
return width_str

View File

@@ -52,15 +52,15 @@
import re
xpath_tokenizer = re.compile(
"("
"'[^']*'|\"[^\"]*\"|"
"::|"
"//?|"
"\.\.|"
"\(\)|"
"[/.*:\[\]\(\)@=])|"
"((?:\{[^}]+\})?[^/:\[\]\(\)@=\s]+)|"
"\s+"
r"("
r"'[^']*'|\"[^\"]*\"|"
r"::|"
r"//?|"
r"\.\.|"
r"\(\)|"
r"[/.*:\[\]\(\)@=])|"
r"((?:\{[^}]+\})?[^/:\[\]\(\)@=\s]+)|"
r"\s+"
).findall
def prepare_tag(next, token):

View File

@@ -976,7 +976,7 @@ def _serialize_text(write, elem, encoding):
# invalid.
def register_namespace(prefix, uri):
if re.match("ns\d+$", prefix):
if re.match(r"ns\d+$", prefix):
raise ValueError("Prefix format reserved for internal use")
for k, v in _namespace_map.items():
if k == uri or v == prefix:

View File

@@ -172,13 +172,15 @@ Others
.. option:: +p
.. option:: --ObjC++
.. option:: --plugin.option
.. option:: create-auth-token
.. option:: arg
Link to :option:`perl +p`, :option:`--plugin.option`, :option:`create-auth-token` and :option:`arg`
Link to :option:`perl +p`, :option:`--ObjC++`, :option:`--plugin.option`, :option:`create-auth-token` and :option:`arg`
.. program:: hg

View File

@@ -3,7 +3,7 @@ Literal Includes with Line Numbers Matching
.. literalinclude:: literal.inc
:language: python
:pyobject: Bar
:pyobject: Foo
:lineno-match:
.. literalinclude:: literal.inc
@@ -16,6 +16,12 @@ Literal Includes with Line Numbers Matching
:start-after: pass
:lineno-match:
.. literalinclude:: literal.inc
:language: python
:start-after: Literally
:lines: 1,2
:lineno-match:
.. literalinclude:: literal.inc
:language: python
:start-at: class Bar:

View File

@@ -24,6 +24,7 @@ sys.path.insert(0, os.path.abspath(os.path.join(testroot, os.path.pardir)))
# filter warnings of test dependencies
warnings.filterwarnings('ignore', category=DeprecationWarning, module='site') # virtualenv
warnings.filterwarnings('ignore', category=ImportWarning, module='backports')
warnings.filterwarnings('ignore', category=ImportWarning, module='pytest_cov')
warnings.filterwarnings('ignore', category=PendingDeprecationWarning, module=r'_pytest\..*')
# check dependencies before testing

247
tests/test_build_epub.py Normal file
View File

@@ -0,0 +1,247 @@
# -*- coding: utf-8 -*-
"""
test_build_html
~~~~~~~~~~~~~~~
Test the HTML builder and check output against XPath.
:copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from xml.etree import ElementTree
import pytest
class EPUBElementTree(object):
"""Test helper for content.opf and tox.ncx"""
namespaces = {
'idpf': 'http://www.idpf.org/2007/opf',
'dc': 'http://purl.org/dc/elements/1.1/',
'ibooks': 'http://vocabulary.itunes.apple.com/rdf/ibooks/vocabulary-extensions-1.0/',
'ncx': 'http://www.daisy.org/z3986/2005/ncx/',
'xhtml': 'http://www.w3.org/1999/xhtml',
'epub': 'http://www.idpf.org/2007/ops'
}
def __init__(self, tree):
self.tree = tree
@classmethod
def fromstring(cls, string):
return cls(ElementTree.fromstring(string))
def find(self, match):
ret = self.tree.find(match, namespaces=self.namespaces)
if ret is not None:
return self.__class__(ret)
else:
return ret
def findall(self, match):
ret = self.tree.findall(match, namespaces=self.namespaces)
return [self.__class__(e) for e in ret]
def __getattr__(self, name):
return getattr(self.tree, name)
def __iter__(self):
for child in self.tree:
yield self.__class__(child)
@pytest.mark.sphinx('epub', testroot='basic')
def test_build_epub(app):
app.build()
assert (app.outdir / 'mimetype').text() == 'application/epub+zip'
assert (app.outdir / 'META-INF' / 'container.xml').exists()
# toc.ncx
toc = EPUBElementTree.fromstring((app.outdir / 'toc.ncx').text())
assert toc.find("./ncx:docTitle/ncx:text").text == 'Python documentation'
# toc.ncx / head
meta = list(toc.find("./ncx:head"))
assert meta[0].attrib == {'name': 'dtb:uid', 'content': 'unknown'}
assert meta[1].attrib == {'name': 'dtb:depth', 'content': '1'}
assert meta[2].attrib == {'name': 'dtb:totalPageCount', 'content': '0'}
assert meta[3].attrib == {'name': 'dtb:maxPageNumber', 'content': '0'}
# toc.ncx / navMap
navpoints = toc.findall("./ncx:navMap/ncx:navPoint")
assert len(navpoints) == 1
assert navpoints[0].attrib == {'id': 'navPoint1', 'playOrder': '1'}
assert navpoints[0].find("./ncx:content").attrib == {'src': 'index.xhtml'}
navlabel = navpoints[0].find("./ncx:navLabel/ncx:text")
assert navlabel.text == 'The basic Sphinx documentation for testing'
# content.opf
opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').text())
# content.opf / metadata
metadata = opf.find("./idpf:metadata")
assert metadata.find("./dc:language").text == 'en'
assert metadata.find("./dc:title").text == 'Python documentation'
assert metadata.find("./dc:description").text is None
assert metadata.find("./dc:creator").text == 'unknown'
assert metadata.find("./dc:contributor").text == 'unknown'
assert metadata.find("./dc:publisher").text == 'unknown'
assert metadata.find("./dc:rights").text is None
assert metadata.find("./idpf:meta[@property='ibooks:version']").text is None
assert metadata.find("./idpf:meta[@property='ibooks:specified-fonts']").text == 'true'
assert metadata.find("./idpf:meta[@property='ibooks:binding']").text == 'true'
assert metadata.find("./idpf:meta[@property='ibooks:scroll-axis']").text == 'vertical'
# content.opf / manifest
manifest = opf.find("./idpf:manifest")
items = list(manifest)
assert items[0].attrib == {'id': 'ncx',
'href': 'toc.ncx',
'media-type': 'application/x-dtbncx+xml'}
assert items[1].attrib == {'id': 'nav',
'href': 'nav.xhtml',
'media-type': 'application/xhtml+xml',
'properties': 'nav'}
assert items[2].attrib == {'id': 'epub-0',
'href': 'genindex.xhtml',
'media-type': 'application/xhtml+xml'}
assert items[3].attrib == {'id': 'epub-1',
'href': 'index.xhtml',
'media-type': 'application/xhtml+xml'}
for i, item in enumerate(items[2:]):
# items are named as epub-NN
assert item.get('id') == 'epub-%d' % i
# content.opf / spine
spine = opf.find("./idpf:spine")
itemrefs = list(spine)
assert spine.get('toc') == 'ncx'
assert spine.get('page-progression-direction') == 'ltr'
assert itemrefs[0].get('idref') == 'epub-1'
assert itemrefs[1].get('idref') == 'epub-0'
# content.opf / guide
reference = opf.find("./idpf:guide/idpf:reference")
assert reference.get('type') == 'toc'
assert reference.get('title') == 'Table of Contents'
assert reference.get('href') == 'index.xhtml'
# nav.xhtml
nav = EPUBElementTree.fromstring((app.outdir / 'nav.xhtml').text())
assert nav.attrib == {'lang': 'en',
'{http://www.w3.org/XML/1998/namespace}lang': 'en'}
assert nav.find("./xhtml:head/xhtml:title").text == 'Table of Contents'
# nav.xhtml / nav
navlist = nav.find("./xhtml:body/xhtml:nav")
toc = navlist.findall("./xhtml:ol/xhtml:li")
assert navlist.find("./xhtml:h1").text == 'Table of Contents'
assert len(toc) == 1
assert toc[0].find("./xhtml:a").get("href") == 'index.xhtml'
assert toc[0].find("./xhtml:a").text == 'The basic Sphinx documentation for testing'
@pytest.mark.sphinx('epub', testroot='footnotes',
confoverrides={'epub_cover': ('_images/rimg.png', None)})
def test_epub_cover(app):
app.build()
# content.opf / metadata
opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').text())
cover_image = opf.find("./idpf:manifest/idpf:item[@href='%s']" % app.config.epub_cover[0])
cover = opf.find("./idpf:metadata/idpf:meta[@name='cover']")
assert cover
assert cover.get('content') == cover_image.get('id')
@pytest.mark.sphinx('epub', testroot='toctree')
def test_nested_toc(app):
app.build()
# toc.ncx
toc = EPUBElementTree.fromstring((app.outdir / 'toc.ncx').text())
assert toc.find("./ncx:docTitle/ncx:text").text == 'Python documentation'
# toc.ncx / navPoint
def navinfo(elem):
label = elem.find("./ncx:navLabel/ncx:text")
content = elem.find("./ncx:content")
return (elem.get('id'), elem.get('playOrder'),
content.get('src'), label.text)
navpoints = toc.findall("./ncx:navMap/ncx:navPoint")
assert len(navpoints) == 4
assert navinfo(navpoints[0]) == ('navPoint1', '1', 'index.xhtml',
"Welcome to Sphinx Tests's documentation!")
assert navpoints[0].findall("./ncx:navPoint") == []
# toc.ncx / nested navPoints
assert navinfo(navpoints[1]) == ('navPoint2', '2', 'foo.xhtml', 'foo')
navchildren = navpoints[1].findall("./ncx:navPoint")
assert len(navchildren) == 4
assert navinfo(navchildren[0]) == ('navPoint3', '2', 'foo.xhtml', 'foo')
assert navinfo(navchildren[1]) == ('navPoint4', '3', 'quux.xhtml', 'quux')
assert navinfo(navchildren[2]) == ('navPoint5', '4', 'foo.xhtml#foo-1', 'foo.1')
assert navinfo(navchildren[3]) == ('navPoint8', '6', 'foo.xhtml#foo-2', 'foo.2')
# nav.xhtml / nav
def navinfo(elem):
anchor = elem.find("./xhtml:a")
return (anchor.get('href'), anchor.text)
nav = EPUBElementTree.fromstring((app.outdir / 'nav.xhtml').text())
toc = nav.findall("./xhtml:body/xhtml:nav/xhtml:ol/xhtml:li")
assert len(toc) == 4
assert navinfo(toc[0]) == ('index.xhtml',
"Welcome to Sphinx Tests's documentation!")
assert toc[0].findall("./xhtml:ol") == []
# nav.xhtml / nested toc
assert navinfo(toc[1]) == ('foo.xhtml', 'foo')
tocchildren = toc[1].findall("./xhtml:ol/xhtml:li")
assert len(tocchildren) == 3
assert navinfo(tocchildren[0]) == ('quux.xhtml', 'quux')
assert navinfo(tocchildren[1]) == ('foo.xhtml#foo-1', 'foo.1')
assert navinfo(tocchildren[2]) == ('foo.xhtml#foo-2', 'foo.2')
grandchild = tocchildren[1].findall("./xhtml:ol/xhtml:li")
assert len(grandchild) == 1
assert navinfo(grandchild[0]) == ('foo.xhtml#foo-1-1', 'foo.1-1')
@pytest.mark.sphinx('epub', testroot='basic')
def test_epub_writing_mode(app):
# horizontal (default)
app.build()
# horizontal / page-progression-direction
opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').text())
assert opf.find("./idpf:spine").get('page-progression-direction') == 'ltr'
# horizontal / ibooks:scroll-axis
metadata = opf.find("./idpf:metadata")
assert metadata.find("./idpf:meta[@property='ibooks:scroll-axis']").text == 'vertical'
# horizontal / writing-mode (CSS)
css = (app.outdir / '_static' / 'epub.css').text()
assert 'writing-mode: horizontal-tb;' in css
# vertical
app.config.epub_writing_mode = 'vertical'
(app.outdir / 'index.xhtml').unlink() # forcely rebuild
app.build()
# vertical / page-progression-direction
opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').text())
assert opf.find("./idpf:spine").get('page-progression-direction') == 'rtl'
# vertical / ibooks:scroll-axis
metadata = opf.find("./idpf:metadata")
assert metadata.find("./idpf:meta[@property='ibooks:scroll-axis']").text == 'horizontal'
# vertical / writing-mode (CSS)
css = (app.outdir / '_static' / 'epub.css').text()
assert 'writing-mode: vertical-rl;' in css

View File

@@ -346,7 +346,9 @@ def test_static_output(app):
(".//a[@class='reference internal'][@href='#cmdoption-perl-arg-p']/code/span",
'perl'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-arg-p']/code/span",
'\+p'),
'\\+p'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-objc']/code/span",
'--ObjC\\+\\+'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-plugin-option']/code/span",
'--plugin.option'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-arg-create-auth-token']"

View File

@@ -482,9 +482,9 @@ def test_footnote(app, status, warning):
assert '\\caption{Table caption \\sphinxfootnotemark[4]' in result
assert 'name \\sphinxfootnotemark[5]' in result
assert ('\\end{threeparttable}\n\\par\n\\endgroup\n%\n'
'\\begin{footnotetext}[4]\sphinxAtStartFootnote\n'
'\\begin{footnotetext}[4]\\sphinxAtStartFootnote\n'
'footnotes in table caption\n%\n\\end{footnotetext}%\n'
'\\begin{footnotetext}[5]\sphinxAtStartFootnote\n'
'\\begin{footnotetext}[5]\\sphinxAtStartFootnote\n'
'footnotes in table\n%\n\\end{footnotetext}') in result
@@ -507,18 +507,18 @@ def test_reference_in_caption_and_codeblock_in_footnote(app, status, warning):
'%\n\\begin{footnotetext}[4]\\sphinxAtStartFootnote\n'
'Footnote in section\n%\n\\end{footnotetext}') in result
assert ('\\caption{This is the figure caption with a footnote to '
'\\sphinxfootnotemark[6].}\label{\\detokenize{index:id27}}\end{figure}\n'
'\\sphinxfootnotemark[6].}\\label{\\detokenize{index:id27}}\\end{figure}\n'
'%\n\\begin{footnotetext}[6]\\sphinxAtStartFootnote\n'
'Footnote in caption\n%\n\\end{footnotetext}')in result
assert ('\\caption{footnote \\sphinxfootnotemark[7] '
'in caption of normal table}\\label{\\detokenize{index:id28}}') in result
assert ('\\caption{footnote \\sphinxfootnotemark[8] '
'in caption \sphinxfootnotemark[9] of longtable}') in result
assert ('\end{longtable}\n%\n\\begin{footnotetext}[8]'
'\sphinxAtStartFootnote\n'
'in caption \\sphinxfootnotemark[9] of longtable}') in result
assert ('\\end{longtable}\n%\n\\begin{footnotetext}[8]'
'\\sphinxAtStartFootnote\n'
'Foot note in longtable\n%\n\\end{footnotetext}' in result)
assert ('This is a reference to the code-block in the footnote:\n'
'{\hyperref[\\detokenize{index:codeblockinfootnote}]'
'{\\hyperref[\\detokenize{index:codeblockinfootnote}]'
'{\\sphinxcrossref{\\DUrole{std,std-ref}{I am in a footnote}}}}') in result
assert ('&\nThis is one more footnote with some code in it '
'\\sphinxfootnotemark[10].\n\\\\') in result
@@ -855,7 +855,7 @@ def test_latex_table_tabulars(app, status, warning):
# table having :align: option (tabular)
table = tables['table having :align: option (tabular)']
assert ('\\begingroup\n\\raggedright\n'
'\\begin{tabular}{|\X{30}{100}|\X{70}{100}|}\n' in table)
'\\begin{tabular}{|\\X{30}{100}|\\X{70}{100}|}\n' in table)
assert ('\\hline\n\\end{tabular}\n\\par\n\\endgroup' in table)
# table with tabularcolumn

View File

@@ -263,7 +263,7 @@ def test_code_block_caption_latex(app, status, warning):
latex = (app.outdir / 'Python.tex').text(encoding='utf-8')
caption = '\\sphinxSetupCaptionForVerbatim{caption \\sphinxstyleemphasis{test} rb}'
label = '\\def\\sphinxLiteralBlockLabel{\\label{\\detokenize{caption:id1}}}'
link = '\hyperref[\\detokenize{caption:name-test-rb}]' \
link = '\\hyperref[\\detokenize{caption:name-test-rb}]' \
'{Listing \\ref{\\detokenize{caption:name-test-rb}}}'
assert caption in latex
assert label in latex
@@ -393,9 +393,8 @@ def test_literal_include_lineno_match(app, status, warning):
html = (app.outdir / 'lineno_match.html').text(encoding='utf-8')
pyobject = (
'<td class="linenos"><div class="linenodiv"><pre>'
' 9\n'
'10\n'
'11</pre></div></td>')
'6\n'
'7</pre></div></td>')
assert pyobject in html
@@ -419,6 +418,12 @@ def test_literal_include_lineno_match(app, status, warning):
'14</pre></div></td>')
assert start_after in html
start_after_with_lines = (
'<td class="linenos"><div class="linenodiv"><pre>'
'2\n'
'3</pre></div></td>')
assert start_after_with_lines in html
start_at_end_at = (
'<td class="linenos"><div class="linenodiv"><pre>'
' 9\n'
@@ -459,7 +464,7 @@ def test_literalinclude_caption_latex(app, status, warning):
latex = (app.outdir / 'Python.tex').text(encoding='utf-8')
caption = '\\sphinxSetupCaptionForVerbatim{caption \\sphinxstylestrong{test} py}'
label = '\\def\\sphinxLiteralBlockLabel{\\label{\\detokenize{caption:id2}}}'
link = '\hyperref[\\detokenize{caption:name-test-py}]' \
link = '\\hyperref[\\detokenize{caption:name-test-py}]' \
'{Listing \\ref{\\detokenize{caption:name-test-py}}}'
assert caption in latex
assert label in latex

View File

@@ -520,12 +520,12 @@ def test_build_domain_cpp_with_add_function_parentheses_is_True(app, status, war
('', 'MyEnum')
]
parenPatterns = [
('ref function without parens ', 'paren_1\(\)'),
('ref function with parens ', 'paren_2\(\)'),
('ref function without parens ', r'paren_1\(\)'),
('ref function with parens ', r'paren_2\(\)'),
('ref function without parens, explicit title ', 'paren_3_title'),
('ref function with parens, explicit title ', 'paren_4_title'),
('ref op call without parens ', 'paren_5::operator\(\)\(\)'),
('ref op call with parens ', 'paren_6::operator\(\)\(\)'),
('ref op call without parens ', r'paren_5::operator\(\)\(\)'),
('ref op call with parens ', r'paren_6::operator\(\)\(\)'),
('ref op call without parens, explicit title ', 'paren_7_title'),
('ref op call with parens, explicit title ', 'paren_8_title')
]
@@ -566,8 +566,8 @@ def test_build_domain_cpp_with_add_function_parentheses_is_False(app, status, wa
('ref function with parens ', 'paren_2'),
('ref function without parens, explicit title ', 'paren_3_title'),
('ref function with parens, explicit title ', 'paren_4_title'),
('ref op call without parens ', 'paren_5::operator\(\)'),
('ref op call with parens ', 'paren_6::operator\(\)'),
('ref op call without parens ', r'paren_5::operator\(\)'),
('ref op call with parens ', r'paren_6::operator\(\)'),
('ref op call without parens, explicit title ', 'paren_7_title'),
('ref op call with parens, explicit title ', 'paren_8_title')
]

View File

@@ -20,8 +20,8 @@ def test_graphviz_html(app, status, warning):
app.builder.build_all()
content = (app.outdir / 'index.html').text()
html = ('<div class="figure" .*?>\s*<img .*?/>\s*<p class="caption">'
'<span class="caption-text">caption of graph</span>.*</p>\s*</div>')
html = (r'<div class="figure" .*?>\s*<img .*?/>\s*<p class="caption">'
r'<span class="caption-text">caption of graph</span>.*</p>\s*</div>')
assert re.search(html, content, re.S)
html = 'Hello <img .*?/>\n graphviz world'
@@ -30,8 +30,8 @@ def test_graphviz_html(app, status, warning):
html = '<img src=".*?" alt="digraph {\n bar -&gt; baz\n}" />'
assert re.search(html, content, re.M)
html = ('<div class="figure align-right" .*?>\s*<img .*?/>\s*<p class="caption">'
'<span class="caption-text">on right</span>.*</p>\s*</div>')
html = (r'<div class="figure align-right" .*?>\s*<img .*?/>\s*<p class="caption">'
r'<span class="caption-text">on right</span>.*</p>\s*</div>')
assert re.search(html, content, re.S)
@@ -41,16 +41,16 @@ def test_graphviz_latex(app, status, warning):
app.builder.build_all()
content = (app.outdir / 'SphinxTests.tex').text()
macro = ('\\\\begin{figure}\[htbp\]\n\\\\centering\n\\\\capstart\n\n'
'\\\\includegraphics{graphviz-\w+.pdf}\n'
macro = ('\\\\begin{figure}\\[htbp\\]\n\\\\centering\n\\\\capstart\n\n'
'\\\\includegraphics{graphviz-\\w+.pdf}\n'
'\\\\caption{caption of graph}\\\\label{.*}\\\\end{figure}')
assert re.search(macro, content, re.S)
macro = 'Hello \\\\includegraphics{graphviz-\w+.pdf} graphviz world'
macro = 'Hello \\\\includegraphics{graphviz-\\w+.pdf} graphviz world'
assert re.search(macro, content, re.S)
macro = ('\\\\begin{wrapfigure}{r}{0pt}\n\\\\centering\n'
'\\\\includegraphics{graphviz-\w+.pdf}\n'
'\\\\includegraphics{graphviz-\\w+.pdf}\n'
'\\\\caption{on right}\\\\label{.*}\\\\end{wrapfigure}')
assert re.search(macro, content, re.S)

View File

@@ -24,7 +24,7 @@ def test_inheritance_diagram_html(app, status, warning):
content = (app.outdir / 'index.html').text()
pattern = ('<div class="figure" id="id1">\n'
'<img src="_images/inheritance-\w+.png" alt="Inheritance diagram of test.Foo" '
'<img src="_images/inheritance-\\w+.png" alt="Inheritance diagram of test.Foo" '
'class="inheritance"/>\n<p class="caption"><span class="caption-text">'
'Test Foo!</span><a class="headerlink" href="#id1" '
'title="Permalink to this image">\xb6</a></p>')

View File

@@ -45,8 +45,8 @@ def test_imgmath_png(app, status, warning):
raise SkipTest('dvipng command "dvipng" is not available')
content = (app.outdir / 'index.html').text()
html = ('<div class="math">\s*<p>\s*<img src="_images/math/\w+.png"'
'\s*alt="a\^2\+b\^2=c\^2"/>\s*</p>\s*</div>')
html = (r'<div class="math">\s*<p>\s*<img src="_images/math/\w+.png"'
r'\s*alt="a\^2\+b\^2=c\^2"/>\s*</p>\s*</div>')
assert re.search(html, content, re.S)
@@ -61,8 +61,8 @@ def test_imgmath_svg(app, status, warning):
raise SkipTest('dvisvgm command "dvisvgm" is not available')
content = (app.outdir / 'index.html').text()
html = ('<div class="math">\s*<p>\s*<img src="_images/math/\w+.svg"'
'\s*alt="a\^2\+b\^2=c\^2"/>\s*</p>\s*</div>')
html = (r'<div class="math">\s*<p>\s*<img src="_images/math/\w+.svg"'
r'\s*alt="a\^2\+b\^2=c\^2"/>\s*</p>\s*</div>')
assert re.search(html, content, re.S)

View File

@@ -12,5 +12,5 @@ from sphinx.util.rst import escape
def test_escape():
assert escape(':ref:`id`') == '\:ref\:\`id\`'
assert escape('footnote [#]_') == 'footnote \[\#\]\_'
assert escape(':ref:`id`') == r'\:ref\:\`id\`'
assert escape('footnote [#]_') == r'footnote \[\#\]\_'

View File

@@ -29,9 +29,9 @@ def bump_version(path, version_info):
with open(path, 'r+') as f:
body = f.read()
body = re.sub("(?<=__version__ = ')[^']+", version, body)
body = re.sub("(?<=__released__ = ')[^']+", release, body)
body = re.sub("(?<=version_info = )\(.*\)", str(version_info), body)
body = re.sub(r"(?<=__version__ = ')[^']+", version, body)
body = re.sub(r"(?<=__released__ = ')[^']+", release, body)
body = re.sub(r"(?<=version_info = )\(.*\)", str(version_info), body)
f.seek(0)
f.truncate(0)
@@ -39,23 +39,23 @@ def bump_version(path, version_info):
def parse_version(version):
matched = re.search('^(\d+)\.(\d+)$', version)
matched = re.search(r'^(\d+)\.(\d+)$', version)
if matched:
major, minor = matched.groups()
return (int(major), int(minor), 0, 'final', 0)
matched = re.search('^(\d+)\.(\d+)\.(\d+)$', version)
matched = re.search(r'^(\d+)\.(\d+)\.(\d+)$', version)
if matched:
major, minor, rev = matched.groups()
return (int(major), int(minor), int(rev), 'final', 0)
matched = re.search('^(\d+)\.(\d+)\s*(a|b|alpha|beta)(\d+)$', version)
matched = re.search(r'^(\d+)\.(\d+)\s*(a|b|alpha|beta)(\d+)$', version)
if matched:
major, minor, typ, relver = matched.groups()
release = RELEASE_TYPE.get(typ, typ)
return (int(major), int(minor), 0, release, int(relver))
matched = re.search('^(\d+)\.(\d+)\.(\d+)\s*(a|b|alpha|beta)(\d+)$', version)
matched = re.search(r'^(\d+)\.(\d+)\.(\d+)\s*(a|b|alpha|beta)(\d+)$', version)
if matched:
major, minor, rev, typ, relver = matched.groups()
release = RELEASE_TYPE.get(typ, typ)
@@ -90,7 +90,7 @@ class Changes(object):
def fetch_version(self):
with open(self.path) as f:
version = f.readline().strip()
matched = re.search('^Release (.*) \((.*)\)$', version)
matched = re.search(r'^Release (.*) \((.*)\)$', version)
if matched is None:
raise RuntimeError('Unknown CHANGES format: %s' % version)