Commits

georg.brandl  committed a10e656

Add "env-updated" and "missing-reference" events.
Write inventory file as part of the HTML build.

  • Participants
  • Parent commits da5a02f

Comments (0)

Files changed (7)

 * sphinx.doc.autodoc has a new event ``autodoc-process-signature`` that
   allows tuning function signature introspection.
 
+* Added new events: ``env-updated`` and ``missing-reference``.
+
 
 Release 0.4.2 (Jul 29, 2008)
 ============================
 Sphinx TODO
 ===========
 
-- literal search mode
+- RSS generation
+- files for downloading
 - specify node visit functions when adding nodes to app
 - allow extensions to add static files
 - decide which static files to include
 - verbose option
 - remove redundant <ul>s in tocs
 - autoattribute in autodoc
-- range and object options for literalinclude
+- section, range and object options for literalinclude
+- literal search mode
 - option for compact module index
 - HTML section numbers?
 - "seealso" links to external examples, see http://svn.python.org/projects/sandbox/trunk/seealso/ and http://effbot.org/zone/idea-seealso.htm

File doc/ext/appapi.rst

 
 .. method:: Sphinx.emit(event, *arguments)
 
-   Emit *event* and pass *arguments* to the callback functions.  Do not emit
-   core Sphinx events in extensions!
+   Emit *event* and pass *arguments* to the callback functions.  Return the
+   return values of all callbacks as a list.  Do not emit core Sphinx events
+   in extensions!
+
+.. method:: Sphinx.emit_firstresult(event, *arguments)
+
+   Emit *event* and pass *arguments* to the callback functions.  Return the
+   result of the first callback that doesn't return ``None`` (and don't call
+   the rest of the callbacks).
+
+   .. versionadded:: 0.5
 
 
 .. exception:: ExtensionError
 
 .. event:: builder-inited (app)
 
-   Emitted the builder object has been created.
+   Emitted when the builder object has been created.  It is available as
+   ``app.builder``.
 
 .. event:: doctree-read (app, doctree)
 
    Emitted when a doctree has been parsed and read by the environment, and is
-   about to be pickled.
+   about to be pickled.  The *doctree* can be modified in-place.
 
+.. event:: missing-reference (app, env, node, contnode)
+
+   Emitted when a cross-reference to a Python module or object cannot be
+   resolved.  If the event handler can resolve the reference, it should return a
+   new docutils node to be inserted in the document tree in place of the node
+   *node*.  Usually this node is a :class:`reference` node containing *contnode*
+   as a child.
+
+   :param env: The build environment (``app.builder.env``).
+   :param node: The :class:`pending_xref` node to be resolved.  Its attributes
+      ``reftype``, ``reftarget``, ``modname`` and ``classname`` attributes
+      determine the type and target of the reference.
+   :param contnode: The node that carries the text and formatting inside the
+      future reference and should be a child of the returned reference node.
+
+   .. versionadded:: 0.5
+   
 .. event:: doctree-resolved (app, doctree, docname)
 
    Emitted when a doctree has been "resolved" by the environment, that is, all
-   references and TOCs have been inserted.
+   references have been resolved and TOCs have been inserted.
 
+.. event:: env-updated (app, env)
+
+   Emitted when the :meth:`update` method of the build environment has
+   completed, that is, the environment and all doctrees are now up-to-date.
+
+   .. versionadded:: 0.5
+   
 .. event:: page-context (app, pagename, templatename, context, doctree)
 
    Emitted when the HTML builder has created a context dictionary to render a

File sphinx/application.py

                 result.append(callback(self, *args))
         return result
 
+    def emit_firstresult(self, event, *args):
+        for result in self.emit(event, *args):
+            if result is not None:
+                return result
+        return None
+
     # registering addon parts
 
     def add_builder(self, builder):

File sphinx/builder.py

 
 import os
 import time
+import gzip
 import codecs
 import shutil
 import cPickle as pickle
 
 ENV_PICKLE_FILENAME = 'environment.pickle'
 LAST_BUILD_FILENAME = 'last_build'
+INVENTORY_FILENAME = 'inventory.txt.gz'
+
 
 class Builder(object):
     """
             return
         if not self.freshenv:
             try:
-                self.info(bold('trying to load pickled env... '), nonl=True)
+                self.info(bold('loading pickled environment... '), nonl=True)
                 self.env = BuildEnvironment.frompickle(self.config,
                     path.join(self.doctreedir, ENV_PICKLE_FILENAME))
                 self.info('done')
         iterator = self.env.update(self.config, self.srcdir, self.doctreedir, self.app)
         # the first item in the iterator is a summary message
         self.info(iterator.next())
-        for docname in self.status_iterator(iterator, 'reading... ', purple):
+        for docname in self.status_iterator(iterator, 'reading sources... ', purple):
             updated_docnames.append(docname)
             # nothing further to do, the environment has already done the reading
         for warning in warnings:
 
         if updated_docnames:
             # save the environment
-            self.info(bold('pickling the env... '), nonl=True)
+            self.info(bold('pickling environment... '), nonl=True)
             self.env.topickle(path.join(self.doctreedir, ENV_PICKLE_FILENAME))
             self.info('done')
 
             # global actions
-            self.info(bold('checking consistency...'))
+            self.info(bold('checking consistency... '), nonl=True)
             self.env.check_consistency()
+            self.info('done')
         else:
             if method == 'update' and not docnames:
                 self.info(bold('no targets are out of date.'))
         self.write(docnames, updated_docnames, method)
 
         # finish (write static files etc.)
-        self.info(bold('finishing... '))
         self.finish()
         if self.app._warncount:
             self.info(bold('build succeeded, %s warning%s.' %
                 docnames.add(tocdocname)
         docnames.add(self.config.master_doc)
 
+        self.info(bold('preparing documents... '), nonl=True)
         self.prepare_writing(docnames)
+        self.info('done')
 
         # write target files
         warnings = []
 
         # copy image files
         if self.images:
-            self.info(bold('copying images...'), nonl=1)
+            self.info(bold('copying images...'), nonl=True)
             ensuredir(path.join(self.outdir, '_images'))
             for src, dest in self.images.iteritems():
                 self.info(' '+src, nonl=1)
             self.info()
 
         # copy static files
-        self.info(bold('copying static files...'))
+        self.info(bold('copying static files... '), nonl=True)
         ensuredir(path.join(self.outdir, '_static'))
         staticdirnames = [path.join(path.dirname(__file__), 'static')] + \
                          [path.join(self.confdir, spath)
         f = open(path.join(self.outdir, '_static', 'pygments.css'), 'w')
         f.write(PygmentsBridge('html', self.config.pygments_style).get_stylesheet())
         f.close()
+        self.info('done')
 
         # dump the search index
         self.handle_finish()
             shutil.copyfile(self.env.doc2path(pagename), source_name)
 
     def handle_finish(self):
-        self.info(bold('dumping search index...'))
+        self.info(bold('dumping search index... '), nonl=True)
         self.indexer.prune(self.env.all_docs)
         f = open(path.join(self.outdir, self.searchindex_filename), 'wb')
         try:
             self.indexer.dump(f, self.indexer_format)
         finally:
             f.close()
+        self.info('done')
+
+        self.info(bold('dumping object inventory... '), nonl=True)
+        f = gzip.open(path.join(self.outdir, INVENTORY_FILENAME), 'w')
+        try:
+            for modname, info in self.env.modules.iteritems():
+                f.write('%s mod %s\n' % (modname, self.get_target_uri(info[0])))
+            for refname, (docname, desctype) in self.env.descrefs.iteritems():
+                f.write('%s %s %s\n' % (refname, desctype, self.get_target_uri(docname)))
+        finally:
+            f.close()
+        self.info('done')
 
 
 class SerializingHTMLBuilder(StandaloneHTMLBuilder):
             shutil.copyfile(path.join(self.confdir, self.config.latex_logo),
                             path.join(self.outdir, logobase))
 
-        self.info(bold('copying TeX support files...'))
+        self.info(bold('copying TeX support files... '), nonl=True)
         staticdirname = path.join(path.dirname(__file__), 'texinputs')
         for filename in os.listdir(staticdirname):
             if not filename.startswith('.'):
                 shutil.copyfile(path.join(staticdirname, filename),
                                 path.join(self.outdir, filename))
+        self.info('done')
 
 
 class ChangesBuilder(Builder):

File sphinx/environment.py

             if not os.access(path.join(self.srcdir, imgsrc), os.R_OK):
                 del self.images[imgsrc]
 
+        if app:
+            app.emit('env-updated', self)
+
 
     # --------- SINGLE FILE READING --------------------------------------------
 
                            'meth', 'cfunc', 'cdata', 'ctype', 'cmacro', 'obj'))
 
     def resolve_references(self, doctree, fromdocname, builder):
+        reftarget_roles = set(('token', 'term', 'option'))
+        # add all custom xref types too
+        reftarget_roles.update(i[0] for i in additional_xref_types.values())
+
         for node in doctree.traverse(addnodes.pending_xref):
             contnode = node[0].deepcopy()
             newnode = None
             typ = node['reftype']
             target = node['reftarget']
 
-            reftarget_roles = set(('token', 'term', 'option'))
-            # add all custom xref types too
-            reftarget_roles.update(i[0] for i in additional_xref_types.values())
-
             try:
                 if typ == 'ref':
                     if node['refcaption']:
                     # because the anchor is generally below the heading which is ugly
                     # but can't be helped easily
                     anchor = ''
-                    if not docname or docname == fromdocname:
+                    if not docname:
+                        newnode = builder.app.emit_firstresult('missing-reference',
+                                                               self, node, contnode)
+                        if not newnode:
+                            newnode = contnode
+                    elif docname == fromdocname:
                         # don't link to self
                         newnode = contnode
                     else:
                     name, desc = self.find_desc(modname, clsname,
                                                 target, typ, searchorder)
                     if not desc:
-                        newnode = contnode
+                        newnode = builder.app.emit_firstresult('missing-reference',
+                                                               self, node, contnode)
+                        if not newnode:
+                            newnode = contnode
                     else:
                         newnode = nodes.reference('', '')
                         if desc[0] == fromdocname:

File sphinx/ext/autodoc.py

                 args = None
                 err = e
 
-        results = self.env.app.emit('autodoc-process-signature',
-                                    what, name, obj, self.options, args, retann)
-        for result in results:
-            if result:
-                args, retann = result
+        result = self.env.app.emit_firstresult('autodoc-process-signature', what,
+                                               name, obj, self.options, args, retann)
+        if result:
+            args, retann = result
 
         if args is not None:
             return '%s%s' % (args, retann or '')