mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Support the image directive.
This commit is contained in:
parent
649ce723c1
commit
ba47f283f6
3
CHANGES
3
CHANGES
@ -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.
|
||||
|
||||
|
13
doc/rest.rst
13
doc/rest.rst
@ -205,6 +205,19 @@ The directive content follows after a blank line and is indented relative to the
|
||||
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
|
||||
---------
|
||||
|
||||
|
@ -75,7 +75,7 @@ class Builder(object):
|
||||
|
||||
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 @@ class Builder(object):
|
||||
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 @@ class Builder(object):
|
||||
"""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 @@ class Builder(object):
|
||||
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 @@ class StandaloneHTMLBuilder(Builder):
|
||||
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 @@ class StandaloneHTMLBuilder(Builder):
|
||||
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 @@ class LaTeXBuilder(Builder):
|
||||
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):
|
||||
|
@ -352,7 +352,7 @@ def desc_directive(desctype, arguments, options, content, lineno,
|
||||
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',
|
||||
|
@ -57,7 +57,7 @@ default_settings = {
|
||||
|
||||
# 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 @@ class BuildEnvironment:
|
||||
# (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 @@ class BuildEnvironment:
|
||||
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 @@ class BuildEnvironment:
|
||||
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 @@ class BuildEnvironment:
|
||||
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 @@ class BuildEnvironment:
|
||||
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 @@ class BuildEnvironment:
|
||||
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 @@ class BuildEnvironment:
|
||||
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 @@ class BuildEnvironment:
|
||||
# -------
|
||||
# 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 @@ class BuildEnvironment:
|
||||
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('', '')
|
||||
|
@ -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 @@ class HTMLTranslator(BaseTranslator):
|
||||
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
|
||||
|
@ -40,6 +40,15 @@ FOOTER = r'''
|
||||
\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 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
||||
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 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
||||
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):
|
||||
|
@ -120,6 +120,8 @@ def xfileref_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
# 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'):
|
||||
|
Loading…
Reference in New Issue
Block a user