[Python-checkins] r61890 - in doctools/trunk: CHANGES doc/rest.rst sphinx/builder.py sphinx/directives.py sphinx/environment.py sphinx/htmlwriter.py sphinx/latexwriter.py sphinx/roles.py
georg.brandl
python-checkins at python.org
Tue Mar 25 13:32:03 CET 2008
Author: georg.brandl
Date: Tue Mar 25 13:32:03 2008
New Revision: 61890
Modified:
doctools/trunk/CHANGES
doctools/trunk/doc/rest.rst
doctools/trunk/sphinx/builder.py
doctools/trunk/sphinx/directives.py
doctools/trunk/sphinx/environment.py
doctools/trunk/sphinx/htmlwriter.py
doctools/trunk/sphinx/latexwriter.py
doctools/trunk/sphinx/roles.py
Log:
Support the image directive.
Modified: doctools/trunk/CHANGES
==============================================================================
--- doctools/trunk/CHANGES (original)
+++ doctools/trunk/CHANGES Tue Mar 25 13:32:03 2008
@@ -1,6 +1,9 @@
Changes in trunk
================
+* sphinx.htmlwriter, sphinx.latexwriter: Support the ``.. image::``
+ directive by copying image files to the output directory.
+
* sphinx.environment: Take dependent files into account when collecting
the set of outdated sources.
Modified: doctools/trunk/doc/rest.rst
==============================================================================
--- doctools/trunk/doc/rest.rst (original)
+++ doctools/trunk/doc/rest.rst Tue Mar 25 13:32:03 2008
@@ -205,6 +205,19 @@
directive start.
+Images
+------
+
+reST supports an image directive, used like so::
+
+ .. image:: filename
+ (options)
+
+When used within Sphinx, the ``filename`` given must be relative to the source
+file, and Sphinx will automatically copy image files over to a subdirectory of
+the output directory on building.
+
+
Footnotes
---------
Modified: doctools/trunk/sphinx/builder.py
==============================================================================
--- doctools/trunk/sphinx/builder.py (original)
+++ doctools/trunk/sphinx/builder.py Tue Mar 25 13:32:03 2008
@@ -75,7 +75,7 @@
def init_templates(self):
"""Call if you need Jinja templates in the builder."""
- # lazily import this, maybe other builders won't need it
+ # lazily import this, other builders won't need it
from sphinx._jinja import Environment, SphinxFileSystemLoader
# load templates
@@ -103,14 +103,18 @@
raise NotImplementedError
def get_relative_uri(self, from_, to, typ=None):
- """Return a relative URI between two source filenames.
- May raise environment.NoUri if there's no way to return a
- sensible URI."""
+ """
+ Return a relative URI between two source filenames. May raise environment.NoUri
+ if there's no way to return a sensible URI.
+ """
return relative_uri(self.get_target_uri(from_),
self.get_target_uri(to, typ))
def get_outdated_docs(self):
- """Return a list of output files that are outdated."""
+ """
+ Return an iterable of output files that are outdated, or a string describing
+ what an update build will build.
+ """
raise NotImplementedError
def status_iterator(self, iterable, summary, colorfunc):
@@ -173,7 +177,7 @@
"""Only rebuild files changed or added since last build."""
to_build = self.get_outdated_docs()
if isinstance(to_build, str):
- self.build([], to_build)
+ self.build(['__all__'], to_build)
else:
to_build = list(to_build)
self.build(to_build,
@@ -211,7 +215,7 @@
self.info(bold('checking consistency...'))
self.env.check_consistency()
else:
- if not docnames:
+ if method == 'update' and not docnames:
self.info(bold('no targets are out of date.'))
return
@@ -341,6 +345,7 @@
destination = StringOutput(encoding='utf-8')
doctree.settings = self.docsettings
+ self.imgpath = relative_uri(self.get_target_uri(docname), '_images')
self.docwriter.write(doctree, destination)
self.docwriter.assemble_parts()
@@ -474,8 +479,19 @@
self.info(' index', nonl=1)
self.handle_page('index', {'indextemplate': indextemplate}, 'index.html')
- # copy static files
self.info()
+
+ # copy image files
+ if self.env.images:
+ self.info(bold('copying images...'), nonl=1)
+ ensuredir(path.join(self.outdir, '_images'))
+ for src, dest in self.env.images.iteritems():
+ self.info(' '+src, nonl=1)
+ shutil.copyfile(path.join(self.srcdir, src),
+ path.join(self.outdir, '_images', dest))
+ self.info()
+
+ # copy static files
self.info(bold('copying static files...'))
ensuredir(path.join(self.outdir, 'static'))
staticdirnames = [path.join(path.dirname(__file__), 'static')] + \
@@ -796,6 +812,15 @@
return largetree
def finish(self):
+ # copy image files
+ if self.env.images:
+ self.info(bold('copying images...'), nonl=1)
+ for src, dest in self.env.images.iteritems():
+ self.info(' '+src, nonl=1)
+ shutil.copyfile(path.join(self.srcdir, src),
+ path.join(self.outdir, dest))
+ self.info()
+
self.info(bold('copying TeX support files...'))
staticdirname = path.join(path.dirname(__file__), 'texinputs')
for filename in os.listdir(staticdirname):
Modified: doctools/trunk/sphinx/directives.py
==============================================================================
--- doctools/trunk/sphinx/directives.py (original)
+++ doctools/trunk/sphinx/directives.py Tue Mar 25 13:32:03 2008
@@ -352,7 +352,7 @@
signode['ids'].append(fullname)
signode['first'] = (not names)
state.document.note_explicit_target(signode)
- env.note_descref(fullname, desctype)
+ env.note_descref(fullname, desctype, lineno)
names.append(name)
env.note_index_entry('single',
Modified: doctools/trunk/sphinx/environment.py
==============================================================================
--- doctools/trunk/sphinx/environment.py (original)
+++ doctools/trunk/sphinx/environment.py Tue Mar 25 13:32:03 2008
@@ -57,7 +57,7 @@
# This is increased every time a new environment attribute is added
# to properly invalidate pickle files.
-ENV_VERSION = 19
+ENV_VERSION = 20
def walk_depth(node, depth, maxdepth):
@@ -251,6 +251,7 @@
# (type, string, target, aliasname)
self.versionchanges = {} # version -> list of
# (type, docname, lineno, module, descname, content)
+ self.images = {} # absolute path -> unique filename
# These are set while parsing a file
self.docname = None # current document name
@@ -269,9 +270,11 @@
self._warnfunc = func
self.settings['warning_stream'] = RedirStream(func)
- def warn(self, docname, msg):
+ def warn(self, docname, msg, lineno=None):
if docname:
- self._warnfunc(self.doc2path(docname) + ':: ' + msg)
+ if lineno is None:
+ lineno = ''
+ self._warnfunc('%s:%s: %s' % (self.doc2path(docname), lineno, msg))
else:
self._warnfunc('GLOBAL:: ' + msg)
@@ -420,6 +423,12 @@
self.warn(None, 'master file %s not found' %
self.doc2path(config.master_doc))
+ # remove all non-existing images from inventory
+ for imgsrc in self.images.keys():
+ if not os.access(path.join(self.srcdir, imgsrc), os.R_OK):
+ del self.images[imgsrc]
+
+
# --------- SINGLE FILE BUILDING -------------------------------------------
def read_doc(self, docname, src_path=None, save_parsed=True, app=None):
@@ -436,6 +445,7 @@
settings_overrides=self.settings,
reader=MyStandaloneReader())
self.process_dependencies(docname, doctree)
+ self.process_images(docname, doctree)
self.process_metadata(docname, doctree)
self.create_title_from(docname, doctree)
self.note_labels_from(docname, doctree)
@@ -482,11 +492,37 @@
deps = doctree.settings.record_dependencies
if not deps:
return
- basename = path.dirname(self.doc2path(docname, base=None))
+ docdir = path.dirname(self.doc2path(docname, base=None))
for dep in deps.list:
- dep = path.join(basename, dep)
+ dep = path.join(docdir, dep)
self.dependencies.setdefault(docname, set()).add(dep)
+ def process_images(self, docname, doctree):
+ """
+ Process and rewrite image URIs.
+ """
+ docdir = path.dirname(self.doc2path(docname, base=None))
+ for node in doctree.traverse(nodes.image):
+ imguri = node['uri']
+ if imguri.find('://') != -1:
+ self.warn(docname, 'Nonlocal image URI found: %s' % imguri, node.line)
+ else:
+ imgpath = path.normpath(path.join(docdir, imguri))
+ node['uri'] = imgpath
+ self.dependencies.setdefault(docname, set()).add(imgpath)
+ if not os.access(path.join(self.srcdir, imgpath), os.R_OK):
+ self.warn(docname, 'Image file not readable: %s' % imguri, node.line)
+ if imgpath in self.images:
+ continue
+ names = set(self.images.values())
+ uniquename = path.basename(imgpath)
+ base, ext = path.splitext(uniquename)
+ i = 0
+ while uniquename in names:
+ i += 1
+ uniquename = '%s%s%s' % (base, i, ext)
+ self.images[imgpath] = uniquename
+
def process_metadata(self, docname, doctree):
"""
Process the docinfo part of the doctree as metadata.
@@ -527,6 +563,8 @@
if not explicit:
continue
labelid = document.nameids[name]
+ if labelid is None:
+ continue
node = document.ids[labelid]
if name.isdigit() or node.has_key('refuri') or \
node.tagname.startswith('desc_'):
@@ -535,7 +573,8 @@
continue
if name in self.labels:
self.warn(docname, 'duplicate label %s, ' % name +
- 'other instance in %s' % self.doc2path(self.labels[name][0]))
+ 'other instance in %s' % self.doc2path(self.labels[name][0]),
+ node.line)
self.anonlabels[name] = docname, labelid
if not isinstance(node, nodes.section):
# anonymous-only labels
@@ -616,11 +655,12 @@
# -------
# these are called from docutils directives and therefore use self.docname
#
- def note_descref(self, fullname, desctype):
+ def note_descref(self, fullname, desctype, line):
if fullname in self.descrefs:
self.warn(self.docname,
'duplicate canonical description name %s, ' % fullname +
- 'other instance in %s' % self.doc2path(self.descrefs[fullname][0]))
+ 'other instance in %s' % self.doc2path(self.descrefs[fullname][0]),
+ line)
self.descrefs[fullname] = (self.docname, desctype)
def note_module(self, modname, synopsis, platform, deprecated):
@@ -780,7 +820,8 @@
docname, labelid = self.reftargets.get((typ, target), ('', ''))
if not docname:
if typ == 'term':
- self.warn(fromdocname, 'term not in glossary: %s' % target)
+ self.warn(fromdocname, 'term not in glossary: %s' % target,
+ node.line)
newnode = contnode
else:
newnode = nodes.reference('', '')
Modified: doctools/trunk/sphinx/htmlwriter.py
==============================================================================
--- doctools/trunk/sphinx/htmlwriter.py (original)
+++ doctools/trunk/sphinx/htmlwriter.py Tue Mar 25 13:32:03 2008
@@ -10,6 +10,7 @@
"""
import sys
+from os import path
from docutils import nodes
from docutils.writers.html4css1 import Writer, HTMLTranslator as BaseTranslator
@@ -246,6 +247,15 @@
def depart_highlightlang(self, node):
pass
+ # overwritten
+ def visit_image(self, node):
+ olduri = node['uri']
+ # rewrite the URI if the environment knows about it
+ if olduri in self.builder.env.images:
+ node['uri'] = path.join(self.builder.imgpath,
+ self.builder.env.images[olduri])
+ BaseTranslator.visit_image(self, node)
+
def visit_toctree(self, node):
# this only happens when formatting a toc from env.tocs -- in this
# case we don't want to include the subtree
Modified: doctools/trunk/sphinx/latexwriter.py
==============================================================================
--- doctools/trunk/sphinx/latexwriter.py (original)
+++ doctools/trunk/sphinx/latexwriter.py Tue Mar 25 13:32:03 2008
@@ -40,6 +40,15 @@
\end{document}
'''
+GRAPHICX = r'''
+%% Check if we are compiling under latex or pdflatex.
+\ifx\pdftexversion\undefined
+ \usepackage{graphicx}
+\else
+ \usepackage[pdftex]{graphicx}
+\fi
+'''
+
class LaTeXWriter(writers.Writer):
@@ -118,11 +127,14 @@
self.first_document = 1
self.this_is_the_title = 1
self.literal_whitespace = 0
+ self.need_graphicx = 0
def astext(self):
return (HEADER % self.options) + \
(self.options['modindex'] and '\\makemodindex\n' or '') + \
- self.highlighter.get_stylesheet() + '\n\n' + \
+ self.highlighter.get_stylesheet() + \
+ (self.need_graphicx and GRAPHICX or '') + \
+ '\n\n' + \
u''.join(self.body) + \
(self.options['modindex'] and '\\printmodindex\n' or '') + \
(FOOTER % self.options)
@@ -498,6 +510,49 @@
def depart_module(self, node):
pass
+ def visit_image(self, node):
+ self.need_graphicx = 1
+ attrs = node.attributes
+ pre = [] # in reverse order
+ post = []
+ include_graphics_options = ""
+ inline = isinstance(node.parent, nodes.TextElement)
+ if attrs.has_key('scale'):
+ # Could also be done with ``scale`` option to
+ # ``\includegraphics``; doing it this way for consistency.
+ pre.append('\\scalebox{%f}{' % (attrs['scale'] / 100.0,))
+ post.append('}')
+ if attrs.has_key('width'):
+ include_graphics_options = '[width=%s]' % attrs['width']
+ if attrs.has_key('align'):
+ align_prepost = {
+ # By default latex aligns the top of an image.
+ (1, 'top'): ('', ''),
+ (1, 'middle'): ('\\raisebox{-0.5\\height}{', '}'),
+ (1, 'bottom'): ('\\raisebox{-\\height}{', '}'),
+ (0, 'center'): ('{\\hfill', '\\hfill}'),
+ # These 2 don't exactly do the right thing. The image should
+ # be floated alongside the paragraph. See
+ # http://www.w3.org/TR/html4/struct/objects.html#adef-align-IMG
+ (0, 'left'): ('{', '\\hfill}'),
+ (0, 'right'): ('{\\hfill', '}'),}
+ try:
+ pre.append(align_prepost[inline, attrs['align']][0])
+ post.append(align_prepost[inline, attrs['align']][1])
+ except KeyError:
+ pass
+ if not inline:
+ pre.append('\n')
+ post.append('\n')
+ pre.reverse()
+ self.body.extend(pre)
+ # XXX: for now, don't fiddle around with graphics formats
+ uri = self.builder.env.images.get(node['uri'], node['uri'])
+ self.body.append('\\includegraphics%s{%s}' % (include_graphics_options, uri))
+ self.body.extend(post)
+ def depart_image(self, node):
+ pass
+
def visit_note(self, node):
self.body.append('\n\\begin{notice}[note]')
def depart_note(self, node):
Modified: doctools/trunk/sphinx/roles.py
==============================================================================
--- doctools/trunk/sphinx/roles.py (original)
+++ doctools/trunk/sphinx/roles.py Tue Mar 25 13:32:03 2008
@@ -120,6 +120,8 @@
# we want a cross-reference, create the reference node
pnode = addnodes.pending_xref(rawtext, reftype=typ, refcaption=False,
modname=env.currmodule, classname=env.currclass)
+ # we may need the line number for warnings
+ pnode.line = lineno
innertext = text
# special actions for Python object cross-references
if typ in ('data', 'exc', 'func', 'class', 'const', 'attr', 'meth', 'mod'):
More information about the Python-checkins
mailing list