Commits

Anonymous committed 6a7d8ab

Support app.add_crossref_type().

Comments (0)

Files changed (7)

+Changes in trunk
+================
+
+* sphinx.application: Support a new method, ``add_crossref_type``.
+  It works like ``add_description_unit`` but the directive will only
+  create a target and no output.
+
+
 Release 0.1.61950 (Mar 26, 2008)
 ================================
 
+.. _changes:
+
 Changes in Sphinx
 *****************
 

doc/ext/appapi.rst

    source, *role* the role function (see the `Docutils documentation
    <http://docutils.sourceforge.net/docs/howto/rst-roles.html>`_ on details).
 
-.. method:: Sphinx.add_description_unit(directivename, rolename, indexdesc='', parse_node=None)
+.. method:: Sphinx.add_description_unit(directivename, rolename, indextemplate='', parse_node=None, ref_nodeclass=None)
 
    This method is a very convenient way to add a new type of information that
    can be cross-referenced.  It will do this:
 
    * Create a new directive (called *directivename*) for a :term:`description
-     unit`.  It will automatically add index entries if *indexdesc* is nonempty.
+     unit`.  It will automatically add index entries if *indextemplate* is
+     nonempty; if given, it must contain exactly one instance of ``%s``.  See
+     the example below for how the template will be interpreted.
    * Create a new role (called *rolename*) to cross-reference to these
      description units.
    * If you provide *parse_node*, it must be a function that takes a string and
 
    For example, if you have this call in a custom Sphinx extension::
 
-      app.add_description_unit('directive', 'dir', 'directive')
+      app.add_description_unit('directive', 'dir', 'pair: %s; directive')
 
    you can use this markup in your documents::
 
 
       See also the :dir:`function` directive.
 
+   For the directive, an index entry will be generated as if you had prepended ::
+
+      .. index:: pair: function; directive
+
+   The reference node will be of class ``literal`` (so it will be rendered in a
+   proportional font, as appropriate for code) unless you give the *ref_nodeclass*
+   argument, which must be a docutils node class (most useful are
+   ``docutils.nodes.emphasis`` or ``docutils.nodes.strong`` -- you can also use
+   ``docutils.nodes.generated`` if you want no further text decoration).
+
    For the role content, you have the same options as for standard Sphinx roles
    (see :ref:`xref-syntax`).
 
+.. method:: Sphinx.add_crossref_type(directivename, rolename, indextemplate='', ref_nodeclass=None)
+
+   This method is very similar to :meth:`add_description_unit` except that the
+   directive it generates must be empty, and will produce no output.
+
+   That means that you can add semantic targets to your sources, and refer to
+   them using custom roles instead of generic ones (like :role:`ref`).  Example
+   call::
+
+      app.add_crossref_type('topic', 'topic', 'single: %s', docutils.nodes.emphasis)
+
+   Example usage::
+
+      .. topic:: application API
+
+      The application API
+      -------------------
+
+      <...>
+
+      See also :topic:`this section <application API>`.
+
+   (Of course, the element following the ``topic`` directive needn't be a
+   section.)
+
 .. method:: Sphinx.connect(event, callback)
 
    Register *callback* to be called when *event* is emitted.  For details on

doc/ext/doctest.rst

    They will be respected when the test is run, but stripped from presentation
    output.
 
-   .. versionadded:: 0.1.61798
-      Removal of ``<BLANKLINE>`` and inline options in presentation output.
-
 
 .. directive:: .. testcode:: [group]
 
 
 .. XXX web app
 
+.. note::
+
+   While Sphinx' version is still *0.1.revision*, no mention will be made in the
+   docs if a feature is added or changed.  The :ref:`Changelog <changes>` is the
+   only source of version annotations.
+
+
 Prerequisites
 -------------
 

sphinx/application.py

 from docutils.parsers.rst import directives, roles
 
 import sphinx
-from sphinx.roles import xfileref_role
+from sphinx.roles import xfileref_role, innernodetypes
 from sphinx.config import Config
 from sphinx.builder import builtin_builders
-from sphinx.directives import desc_directive, additional_xref_types
+from sphinx.directives import desc_directive, target_directive, additional_xref_types
 from sphinx.util.console import bold
 
 
     def add_role(self, name, role):
         roles.register_canonical_role(name, role)
 
-    def add_description_unit(self, directivename, rolename, indexdesc='',
-                             parse_node=None):
-        additional_xref_types[directivename] = (rolename, indexdesc, parse_node)
+    def add_description_unit(self, directivename, rolename, indextemplate='',
+                             parse_node=None, ref_nodeclass=None):
+        additional_xref_types[directivename] = (rolename, indextemplate, parse_node)
         directives.register_directive(directivename, desc_directive)
         roles.register_canonical_role(rolename, xfileref_role)
+        if ref_nodeclass is not None:
+            innernodetypes[rolename] = ref_nodeclass
+
+    def add_crossref_type(self, directivename, rolename, indextemplate='',
+                          ref_nodeclass=None):
+        additional_xref_types[directivename] = (rolename, indextemplate, None)
+        directives.register_directive(directivename, target_directive)
+        roles.register_canonical_role(rolename, xfileref_role)
+        if ref_nodeclass is not None:
+            innernodetypes[rolename] = ref_nodeclass

sphinx/directives.py

 from sphinx import addnodes
 from sphinx.util.compat import make_admonition
 
+ws_re = re.compile(r'\s+')
+
 # ------ index markup --------------------------------------------------------------
 
 entrytypes = [
                 continue
             else:
                 # another registered generic x-ref directive
-                rolename, indextext, parse_node = additional_xref_types[desctype]
+                rolename, indextemplate, parse_node = additional_xref_types[desctype]
                 if parse_node:
                     fullname = parse_node(env, sig, signode)
                 else:
                     signode.clear()
                     signode += addnodes.desc_name(sig, sig)
-                    fullname = sig
+                    # normalize whitespace like xfileref_role does
+                    fullname = ws_re.sub('', sig)
                 if not noindex:
                     targetname = '%s-%s' % (rolename, fullname)
                     signode['ids'].append(targetname)
                     state.document.note_explicit_target(signode)
-                    if indextext:
-                        env.note_index_entry('pair',
-                                             '%s; %s' % (indextext, fullname),
+                    if indextemplate:
+                        indexentry = indextemplate % (fullname,)
+                        indextype = 'single'
+                        colon = indexentry.find(':')
+                        if colon != -1:
+                            indextype = indexentry[:colon].strip()
+                            indexentry = indexentry[colon+1:].strip()
+                        env.note_index_entry(indextype, indexentry,
                                              targetname, targetname)
                     env.note_reftarget(rolename, fullname, targetname)
                 # don't use object indexing below
 for _name in desctypes:
     directives.register_directive(_name, desc_directive)
 
-# Generic cross-reference types; they can be registered in the application
+# Generic cross-reference types; they can be registered in the application;
+# the directives are either desc_directive or target_directive
 additional_xref_types = {
-    # directive name: (role name, index text)
+    # directive name: (role name, index text, function to parse the desc node)
     'envvar': ('envvar', 'environment variable', None),
 }
 
 
+# ------ target --------------------------------------------------------------------
+
+def target_directive(targettype, arguments, options, content, lineno,
+                     content_offset, block_text, state, state_machine):
+    """Generic target for user-defined cross-reference types."""
+    env = state.document.settings.env
+    rolename, indextemplate, _ = additional_xref_types[targettype]
+    # normalize whitespace in fullname like xfileref_role does
+    fullname = ws_re.sub('', arguments[0].strip())
+    targetname = '%s-%s' % (rolename, fullname)
+    node = nodes.target('', '', ids=[targetname])
+    state.document.note_explicit_target(node)
+    if indextemplate:
+        indexentry = indextemplate % (fullname,)
+        indextype = 'single'
+        colon = indexentry.find(':')
+        if colon != -1:
+            indextype = indexentry[:colon].strip()
+            indexentry = indexentry[colon+1:].strip()
+        env.note_index_entry(indextype, indexentry, targetname, targetname)
+    env.note_reftarget(rolename, fullname, targetname)
+    return [node]
+
+target_directive.content = 0
+target_directive.arguments = (1, 0, 1)
+
+
 # ------ versionadded/versionchanged -----------------------------------------------
 
 def version_directive(name, arguments, options, content, lineno,