Commits

georg.brandl  committed d301ad2

Rebuild all HTML files in case of a template change.
Also, record image docnames in order to be able to delete image records from the env.

  • Participants
  • Parent commits e3ae6e2

Comments (0)

Files changed (7)

 * sphinx.ext.autodoc: Record files from which docstrings are included
   as dependencies.
 
+* sphinx.builder: Rebuild all HTML files in case of a template change.
+
 * sphinx.builder: Handle unavailability of TOC relations (previous/
   next chapter) more gracefully in the HTML builder.
 

File doc/templating.rst

 that you can overwrite only specific blocks within a template, customizing it
 while also keeping the changes at a minimum.
 
-Inheritance is done via two directives, ``extends`` and ``block``.
+Inheritance is done via two (Jinja) directives, ``extends`` and ``block``.
 
 .. template path
    blocks

File sphinx/builder.py

 from docutils.readers.doctree import Reader as DoctreeReader
 
 from sphinx import addnodes
-from sphinx.util import (get_matching_docs, ensuredir, relative_uri, SEP, os_path)
+from sphinx.util import (get_matching_docs, mtimes_of_files,
+                         ensuredir, relative_uri, SEP, os_path)
 from sphinx.htmlhelp import build_hhx
 from sphinx.htmlwriter import HTMLWriter, HTMLTranslator, SmartyPantsHTMLTranslator
 from sphinx.latexwriter import LaTeXWriter
         base_templates_path = path.join(path.dirname(__file__), 'templates')
         ext_templates_path = [path.join(self.srcdir, dir)
                               for dir in self.config.templates_path]
+        self.templates_path = [base_templates_path] + ext_templates_path
         loader = SphinxFileSystemLoader(base_templates_path, ext_templates_path)
         self.jinja_env = Environment(loader=loader,
                                      # disable traceback, more likely that something
     Builds standalone HTML docs.
     """
     name = 'html'
-
     copysource = True
+    out_suffix = '.html'
+    indexer_format = 'json'
 
     def init(self):
         """Load templates."""
         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():
+            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))
         # dump the search index
         self.handle_finish()
 
-    # --------- these are overwritten by the Pickle builder
-
-    def get_target_uri(self, docname, typ=None):
-        return docname + '.html'
-
     def get_outdated_docs(self):
+        template_mtime = max(mtimes_of_files(self.templates_path, '.html'))
         for docname in self.env.found_docs:
-            targetname = self.env.doc2path(docname, self.outdir, '.html')
+            if docname not in self.env.all_docs:
+                yield docname
+                continue
+            targetname = self.env.doc2path(docname, self.outdir, self.out_suffix)
             try:
                 targetmtime = path.getmtime(targetname)
-            except:
+            except Exception:
                 targetmtime = 0
-            if docname not in self.env.all_docs:
+            srcmtime = max(path.getmtime(self.env.doc2path(docname)), template_mtime)
+            if srcmtime > targetmtime:
                 yield docname
-            elif path.getmtime(self.env.doc2path(docname)) > targetmtime:
-                yield docname
-
 
     def load_indexer(self, docnames):
         try:
-            f = open(path.join(self.outdir, 'searchindex.json'), 'r')
+            f = open(path.join(self.outdir, 'searchindex.'+self.indexer_format), 'r')
             try:
-                self.indexer.load(f, 'json')
+                self.indexer.load(f, self.indexer_format)
             finally:
                 f.close()
         except (IOError, OSError):
         if self.indexer is not None and title:
             self.indexer.feed(pagename, title, doctree)
 
+    # --------- these are overwritten by the Pickle builder
+
+    def get_target_uri(self, docname, typ=None):
+        return docname + '.html'
+
     def handle_page(self, pagename, addctx, templatename='page.html'):
         ctx = self.globalcontext.copy()
         ctx['current_page_name'] = pagename
     Builds HTML docs without rendering templates.
     """
     name = 'pickle'
+    out_suffix = '.fpickle'
+    indexer_format = 'pickle'
 
     def init(self):
         self.init_translator_class()
 
-    def get_outdated_docs(self):
-        for docname in self.env.found_docs:
-            targetname = self.env.doc2path(docname, self.outdir, '.fpickle')
-            try:
-                targetmtime = path.getmtime(targetname)
-            except:
-                targetmtime = 0
-            if path.getmtime(self.env.doc2path(docname)) > targetmtime:
-                yield docname
-
     def get_target_uri(self, docname, typ=None):
         if docname == 'index':
             return ''
             return docname[:-5] # up to sep
         return docname + SEP
 
-    def load_indexer(self, docnames):
-        try:
-            f = open(path.join(self.outdir, 'searchindex.pickle'), 'r')
-            try:
-                self.indexer.load(f, 'pickle')
-            finally:
-                f.close()
-        except (IOError, OSError):
-            pass
-        # delete all entries for files that will be rebuilt
-        self.indexer.prune(set(self.env.all_docs) - set(docnames))
-
-    def index_page(self, pagename, doctree, title):
-        # only index pages with title
-        if self.indexer is not None and title:
-            self.indexer.feed(pagename, title, doctree)
-
     def handle_page(self, pagename, ctx, templatename='page.html'):
         ctx['current_page_name'] = pagename
         sidebarfile = self.config.html_sidebars.get(pagename, '')
         # copy image files
         if self.env.images:
             self.info(bold('copying images...'), nonl=1)
-            for src, dest in self.env.images.iteritems():
+            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))

File sphinx/environment.py

     'sectsubtitle_xform': False,
 }
 
-# This is increased every time a new environment attribute is added
-# to properly invalidate pickle files.
-ENV_VERSION = 20
+# This is increased every time an environment attribute is added
+# or changed to properly invalidate pickle files.
+ENV_VERSION = 21
 
 
 def walk_depth(node, depth, maxdepth):
                                     # (type, string, target, aliasname)
         self.versionchanges = {}    # version -> list of
                                     # (type, docname, lineno, module, descname, content)
-        self.images = {}            # absolute path -> unique filename
+        self.images = {}            # absolute path -> (docnames, unique filename)
 
         # These are set while parsing a file
         self.docname = None         # current document name
             self.titles.pop(docname, None)
             self.tocs.pop(docname, None)
             self.toc_num_entries.pop(docname, None)
+            self.filemodules.pop(docname, None)
+            self.indexentries.pop(docname, None)
 
             for subfn, fnset in self.files_to_rebuild.iteritems():
                 fnset.discard(docname)
             for fullname, (fn, _) in self.descrefs.items():
                 if fn == docname:
                     del self.descrefs[fullname]
-            self.filemodules.pop(docname, None)
             for modname, (fn, _, _, _) in self.modules.items():
                 if fn == docname:
                     del self.modules[modname]
             for key, (fn, _) in self.reftargets.items():
                 if fn == docname:
                     del self.reftargets[key]
-            self.indexentries.pop(docname, None)
             for version, changes in self.versionchanges.items():
                 new = [change for change in changes if change[1] != docname]
                 changes[:] = new
+            for fullpath, (docs, _) in self.images.items():
+                docs.discard(docname)
+                if not docs:
+                    del self.images[fullpath]
 
     def doc2path(self, docname, base=True, suffix=None):
         """
         """
         Process and rewrite image URIs.
         """
+        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):
             imguri = node['uri']
                 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)
                     continue
-                names = set(self.images.values())
                 uniquename = path.basename(imgpath)
                 base, ext = path.splitext(uniquename)
                 i = 0
-                while uniquename in names:
+                while uniquename in existing_names:
                     i += 1
                     uniquename = '%s%s%s' % (base, i, ext)
-                self.images[imgpath] = uniquename
+                self.images[imgpath] = (set([docname]), uniquename)
+                existing_names.add(uniquename)
 
     def process_metadata(self, docname, doctree):
         """

File sphinx/htmlwriter.py

         # 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])
+                                    self.builder.env.images[olduri][1])
         BaseTranslator.visit_image(self, node)
 
     def visit_toctree(self, node):

File sphinx/latexwriter.py

         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'])
+        if node['uri'] in self.builder.env.images:
+            uri = self.builder.env.images[node['uri']][1]
+        else:
+            uri = node['uri']
         self.body.append('\\includegraphics%s{%s}' % (include_graphics_options, uri))
         self.body.extend(post)
     def depart_image(self, node):

File sphinx/util/__init__.py

     """
     Get all file names (without suffix) matching a suffix in a
     directory, recursively.
+
+    Exclude files in *exclude*, prune directories in *prune*.
     """
     pattern = '*' + suffix
     # dirname is a normalized absolute path.
             yield qualified_name
 
 
+def mtimes_of_files(dirnames, suffix):
+    for dirname in dirnames:
+        for root, dirs, files in os.walk(dirname):
+            for sfile in files:
+                if sfile.endswith(suffix):
+                    try:
+                        yield path.getmtime(path.join(root, sfile))
+                    except EnvironmentError:
+                        pass
+
+
 def shorten_result(text='', keywords=[], maxlen=240, fuzz=60):
     if not text:
         text = ''