diff --git a/CHANGES b/CHANGES index af43d2d73..f9a5b5557 100644 --- a/CHANGES +++ b/CHANGES @@ -38,6 +38,9 @@ New features added - The directories in the `html_static_path` can now contain subdirectories. +* The image directive now supports specifying the extension as ``.*``, + which makes the builder select the one that matches best. + * The new config value `exclude_trees` can be used to exclude whole subtrees from the search for source files. diff --git a/TODO b/TODO index 4ea32900d..15d1c5c3a 100644 --- a/TODO +++ b/TODO @@ -13,12 +13,3 @@ Sphinx - "often used" combo box in sidebar - source file cross-references? -Web App -******* - -- fix /download - -- discuss and debug comments system -- prepare for databases other than sqlite for comments -- add search via Xapian or Nucular (Python indexer - nucular.sf.net) -- optionally have a contents tree view in the sidebar (AJAX based)? diff --git a/doc/rest.rst b/doc/rest.rst index f2813fb83..946f54f55 100644 --- a/doc/rest.rst +++ b/doc/rest.rst @@ -72,7 +72,7 @@ or roman numerals, such as :: A. First item B. Second item - + Nested lists are possible, but be aware that they must be separated from the parent list items by blank lines:: @@ -214,12 +214,27 @@ Images reST supports an image directive, used like so:: - .. image:: filename + .. image:: gnu.png (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. +When used within Sphinx, the file name given (here ``gnu.png``) must be relative +to the source file, and Sphinx will automatically copy image files over to a +subdirectory of the output directory on building (e.g. the ``_static`` directory +for HTML output.) + +Sphinx extends the standard docutils behavior by allowing an asterisk for the +extension:: + + .. image:: gnu.* + +Sphinx then searches for all images matching the provided pattern and determines +their type. Each builder then chooses the best image out of these candidates. +For instance, if the file name ``gnu.*`` was given and two files :file:`gnu.pdf` +and :file:`gnu.png` existed in the source tree, the LaTeX builder would choose +the former, while the HTML builder would prefer the latter. + +.. versionchanged:: 0.4 + Added the support for file names ending in an asterisk. Footnotes diff --git a/sphinx/builder.py b/sphinx/builder.py index 333321ebe..b868841d4 100644 --- a/sphinx/builder.py +++ b/sphinx/builder.py @@ -62,6 +62,9 @@ class Builder(object): self.info = app.info self.config = app.config + # images that need to be copied over (source -> dest) + self.images = {} + # if None, this is set in load_env() self.env = env self.freshenv = freshenv @@ -118,6 +121,28 @@ class Builder(object): if l == 0: self.info() + supported_image_types = [] + + def post_process_images(self, doctree): + """ + Pick the best candidate for all image URIs. + """ + for node in doctree.traverse(nodes.image): + uri = node['candidates'].get('*', None) + if not uri: + for imgtype in self.supported_image_types: + uri = node['candidates'].get(imgtype, None) + if uri: + node['uri'] = uri + break + else: + self.warn('%s:%s: %s' % + (node.source, node.lineno, + 'No matching candidate for uri: %(uri)s' % node)) + continue + if uri in self.env.images: + self.images[uri] = self.env.images[uri][1] + # build methods def load_env(self): @@ -271,6 +296,8 @@ class StandaloneHTMLBuilder(Builder): copysource = True out_suffix = '.html' indexer_format = 'json' + supported_image_types = ['image/svg+xml', 'image/png', 'image/gif', + 'image/jpeg'] def init(self): """Load templates.""" @@ -323,7 +350,7 @@ class StandaloneHTMLBuilder(Builder): favicon = self.config.html_favicon and \ path.basename(self.config.html_favicon) or '' - if os.path.splitext(favicon)[1] != '.ico': + if favicon and os.path.splitext(favicon)[1] != '.ico': self.warn('html_favicon is not an .ico file') if not isinstance(self.config.html_use_opensearch, basestring): @@ -407,6 +434,7 @@ class StandaloneHTMLBuilder(Builder): ) def write_doc(self, docname, doctree): + self.post_process_images(doctree) destination = StringOutput(encoding='utf-8') doctree.settings = self.docsettings @@ -504,10 +532,10 @@ class StandaloneHTMLBuilder(Builder): self.info() # copy image files - if self.env.images: + if self.images: self.info(bold('copying images...'), nonl=1) ensuredir(path.join(self.outdir, '_images')) - for src, (_, dest) in self.env.images.iteritems(): + for src, dest in self.images.iteritems(): self.info(' '+src, nonl=1) shutil.copyfile(path.join(self.srcdir, src), path.join(self.outdir, '_images', dest)) @@ -636,6 +664,8 @@ class PickleHTMLBuilder(StandaloneHTMLBuilder): name = 'pickle' out_suffix = '.fpickle' indexer_format = 'pickle' + supported_image_types = ('image/svg+xml', 'image/png', 'image/gif', + 'image/jpeg') def init(self): self.init_translator_class() @@ -711,6 +741,7 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder): # don't copy the reST source copysource = False + supported_image_types = ['image/png', 'image/gif', 'image/jpeg'] def init(self): StandaloneHTMLBuilder.init(self) @@ -726,6 +757,8 @@ class LaTeXBuilder(Builder): Builds LaTeX output to create PDF. """ name = 'latex' + supported_image_types = ['application/pdf', 'image/png', 'image/gif', + 'image/jpeg'] def init(self): self.docnames = [] @@ -787,6 +820,7 @@ class LaTeXBuilder(Builder): self.info("processing " + targetname + "... ", nonl=1) doctree = self.assemble_doctree(docname, toctree_only, appendices=(docclass == 'manual') and appendices or []) + self.post_process_images(doctree) self.info("writing... ", nonl=1) doctree.settings = docsettings doctree.settings.author = author @@ -852,9 +886,9 @@ class LaTeXBuilder(Builder): def finish(self): # copy image files - if self.env.images: + if self.images: self.info(bold('copying images...'), nonl=1) - for src, (_, dest) in self.env.images.iteritems(): + for src, dest in self.images.iteritems(): self.info(' '+src, nonl=1) shutil.copyfile(path.join(self.srcdir, src), path.join(self.outdir, dest)) diff --git a/sphinx/environment.py b/sphinx/environment.py index 1043aca23..eacc8f96d 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -14,9 +14,11 @@ import os import time import heapq import types +import imghdr import difflib import cPickle as pickle from os import path +from glob import glob from string import uppercase from itertools import izip, groupby try: @@ -511,25 +513,43 @@ class BuildEnvironment: existing_names = set(v[1] for v in self.images.itervalues()) docdir = path.dirname(self.doc2path(docname, base=None)) for node in doctree.traverse(nodes.image): + # Map the mimetype to the corresponding image. The writer may + # choose the best image from these candidates. The special key * is + # set if there is only single candiate to be used by a writer. + node['candidates'] = candidates = {} imguri = node['uri'] if imguri.find('://') != -1: self.warn(docname, 'Nonlocal image URI found: %s' % imguri, node.line) + candidates['*'] = imguri + continue + imgpath = path.normpath(path.join(docdir, imguri)) + if imgpath.endswith(os.extsep + '*'): + for filename in glob(imgpath): + basename, ext = os.path.splitext(filename) + if ext == '.pdf': + candidates['application/pdf'] = filename + elif ext == '.svg': + candidates['image/svg+xml'] = filename + else: + imgtype = imghdr.what(filename) + if imgtype: + candidates['image/' + imgtype] = filename 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: - self.images[imgpath][0].add(docname) + candidates['*'] = imgpath + for img in candidates.itervalues(): + self.dependencies.setdefault(docname, set()).add(img) + if not os.access(path.join(self.srcdir, img), os.R_OK): + self.warn(docname, 'Image file not readable: %s' % img, node.line) + if img in self.images: + self.images[img][0].add(docname) continue - uniquename = path.basename(imgpath) + uniquename = path.basename(img) base, ext = path.splitext(uniquename) i = 0 while uniquename in existing_names: i += 1 uniquename = '%s%s%s' % (base, i, ext) - self.images[imgpath] = (set([docname]), uniquename) + self.images[img] = (set([docname]), uniquename) existing_names.add(uniquename) def process_metadata(self, docname, doctree): diff --git a/sphinx/htmlwriter.py b/sphinx/htmlwriter.py index 5a061067c..e4a80767d 100644 --- a/sphinx/htmlwriter.py +++ b/sphinx/htmlwriter.py @@ -250,9 +250,9 @@ class HTMLTranslator(BaseTranslator): def visit_image(self, node): olduri = node['uri'] # rewrite the URI if the environment knows about it - if olduri in self.builder.env.images: + if olduri in self.builder.images: node['uri'] = posixpath.join(self.builder.imgpath, - self.builder.env.images[olduri][1]) + self.builder.images[olduri]) BaseTranslator.visit_image(self, node) def visit_toctree(self, node): diff --git a/sphinx/latexwriter.py b/sphinx/latexwriter.py index 485fd4e88..fb6525062 100644 --- a/sphinx/latexwriter.py +++ b/sphinx/latexwriter.py @@ -630,8 +630,8 @@ class LaTeXTranslator(nodes.NodeVisitor): pre.append('\n') post.append('\n') pre.reverse() - if node['uri'] in self.builder.env.images: - uri = self.builder.env.images[node['uri']][1] + if node['uri'] in self.builder.images: + uri = self.builder.images[node['uri']] else: uri = node['uri'] if uri.find('://') != -1: