Georg Brandl avatar Georg Brandl committed 3f5b9f7

#454: Add more index markup capabilities: marking see/seealso entries, and main entries for a given key.

Comments (0)

Files changed (21)

 
 * #521: Added :confval:`linkcheck_ignore` config value.
 
+* #454: Add more index markup capabilities: marking see/seealso entries,
+  and main entries for a given key.
+
 * #516: Added new value of the :confval:`latex_show_urls` option to
   show the URLs in footnotes.
 

doc/markup/misc.rst

       Likewise, ``triple: module; search; path`` is a shortcut that creates
       three index entries, which are ``module; search path``, ``search; path,
       module`` and ``path; module search``.
+   see
+      ``see: entry; other`` creates an index entry that refers from ``entry`` to
+      ``other``.
+   seealso
+      Like ``see``, but inserts "see also" instead of "see".
    module, keyword, operator, object, exception, statement, builtin
       These all create two index entries.  For example, ``module: hashlib``
       creates the entries ``module; hashlib`` and ``hashlib; module``.  (These
       are Python-specific and therefore deprecated.)
 
+   You can mark up "main" index entries by prefixing them with an exclamation
+   mark.  The references to "main" entries are emphasized in the generated
+   index.  For example, if two pages contain ::
+
+      .. index:: Python
+
+   and one page contains ::
+
+      .. index:: ! Python
+
+   then the backlink to the latter page is emphasized among the three backlinks.
+
    For index directives containing only "single" entries, there is a shorthand
    notation::
 
 
    This creates four index entries.
 
+   .. versionchanged:: 1.1
+      Added ``see`` and ``seealso`` types, as well as marking main entries.
+
 .. rst:role:: index
 
    While the :rst:dir:`index` directive is a block-level markup and links to the

sphinx/builders/epub.py

         # Logic modeled from themes/basic/genindex.html
         for key, columns in tree:
             for entryname, (links, subitems) in columns:
-                for (i, link) in enumerate(links):
+                for (i, (ismain, link)) in enumerate(links):
                     m = _refuri_re.match(link)
                     if m:
-                        links[i] = self.fix_fragment(m.group(1), m.group(2))
+                        links[i] = (ismain,
+                                    self.fix_fragment(m.group(1), m.group(2)))
                 for subentryname, subentrylinks in subitems:
-                    for (i, link) in enumerate(subentrylinks):
+                    for (i, (ismain, link)) in enumerate(subentrylinks):
                         m = _refuri_re.match(link)
                         if m:
-                            subentrylinks[i] = \
-                                self.fix_fragment(m.group(1), m.group(2))
+                            subentrylinks[i] = (ismain,
+                                self.fix_fragment(m.group(1), m.group(2)))
 
     def handle_page(self, pagename, addctx, templatename='page.html',
                     outfilename=None, event_arg=None):

sphinx/domains/c.py

 
         indextext = self.get_index_text(name)
         if indextext:
-            self.indexnode['entries'].append(('single', indextext, name, name))
+            self.indexnode['entries'].append(('single', indextext, name, ''))
 
     def before_content(self):
         self.typename_set = False

sphinx/domains/cpp.py

 
         indextext = self.get_index_text(name)
         if indextext:
-            self.indexnode['entries'].append(('single', indextext, name, name))
+            self.indexnode['entries'].append(('single', indextext, theid, ''))
 
     def before_content(self):
         lastname = self.names and self.names[-1]

sphinx/domains/javascript.py

         indextext = self.get_index_text(objectname, name_obj)
         if indextext:
             self.indexnode['entries'].append(('single', indextext,
-                                              fullname, fullname))
+                                              fullname, ''))
 
     def get_index_text(self, objectname, name_obj):
         name, obj = name_obj

sphinx/domains/python.py

         indextext = self.get_index_text(modname, name_cls)
         if indextext:
             self.indexnode['entries'].append(('single', indextext,
-                                              fullname, fullname))
+                                              fullname, ''))
 
     def before_content(self):
         # needed for automatic qualification of members (reset in subclasses)
         if not noindex:
             indextext = _('%s (module)') % modname
             inode = addnodes.index(entries=[('single', indextext,
-                                             'module-' + modname, modname)])
+                                             'module-' + modname, '')])
             ret.append(inode)
         return ret
 

sphinx/domains/rst.py

         indextext = self.get_index_text(self.objtype, name)
         if indextext:
             self.indexnode['entries'].append(('single', indextext,
-                                              targetname, targetname))
+                                              targetname, ''))
 
     def get_index_text(self, objectname, name):
         if self.objtype == 'directive':

sphinx/domains/std.py

                 indextype = 'single'
                 indexentry = self.indextemplate % (name,)
             self.indexnode['entries'].append((indextype, indexentry,
-                                              targetname, targetname))
+                                              targetname, ''))
         self.env.domaindata['std']['objects'][self.objtype, name] = \
             self.env.docname, targetname
 
         tgtid = 'index-%s' % env.new_serialno('index')
         indexnode = addnodes.index()
         indexnode['entries'] = [
-            ('single', varname, tgtid, varname),
-            ('single', _('environment variable; %s') % varname, tgtid, varname)
+            ('single', varname, tgtid, ''),
+            ('single', _('environment variable; %s') % varname, tgtid, '')
         ]
         targetnode = nodes.target('', '', ids=[tgtid])
         document.note_explicit_target(targetnode)
                 indextype = indexentry[:colon].strip()
                 indexentry = indexentry[colon+1:].strip()
             inode = addnodes.index(entries=[(indextype, indexentry,
-                                             targetname, targetname)])
+                                             targetname, '')])
             ret.insert(0, inode)
         name = self.name
         if ':' in self.name:
         self.indexnode['entries'].append(
             ('pair', _('%scommand line option; %s') %
              ((currprogram and currprogram + ' ' or ''), sig),
-             targetname, targetname))
+             targetname, ''))
         self.env.domaindata['std']['progoptions'][currprogram, name] = \
             self.env.docname, targetname
 
                 termtexts.append(termtext)
                 # add an index entry too
                 indexnode = addnodes.index()
-                indexnode['entries'] = [('single', termtext, new_id, termtext)]
-                termnodes += indexnode
+                indexnode['entries'] = [('single', termtext, new_id, 'main')]
+                termnodes.append(indexnode)
                 termnodes.extend(res[0])
                 termnodes.append(addnodes.termsep())
             # make a single "term" node with all the terms, separated by termsep

sphinx/environment.py

 from docutils.transforms.parts import ContentsFilter
 
 from sphinx import addnodes
-from sphinx.util import url_re, get_matching_docs, docname_join, \
+from sphinx.util import url_re, get_matching_docs, docname_join, split_into, \
      FilenameUniqDict
 from sphinx.util.nodes import clean_astext, make_refnode, extract_messages
 from sphinx.util.osutil import movefile, SEP, ustrftime
         """Create the real index from the collected index entries."""
         new = {}
 
-        def add_entry(word, subword, dic=new):
+        def add_entry(word, subword, link=True, dic=new):
             entry = dic.get(word)
             if not entry:
                 dic[word] = entry = [[], {}]
             if subword:
-                add_entry(subword, '', dic=entry[1])
-            else:
+                add_entry(subword, '', link=link, dic=entry[1])
+            elif link:
                 try:
-                    entry[0].append(builder.get_relative_uri('genindex', fn)
-                                    + '#' + tid)
+                    uri = builder.get_relative_uri('genindex', fn) + '#' + tid
                 except NoUri:
                     pass
+                else:
+                    entry[0].append((main, uri))
 
         for fn, entries in self.indexentries.iteritems():
             # new entry types must be listed in directives/other.py!
-            for type, value, tid, alias in entries:
-                if type == 'single':
-                    try:
-                        entry, subentry = value.split(';', 1)
-                    except ValueError:
-                        entry, subentry = value, ''
-                    if not entry:
-                        self.warn(fn, 'invalid index entry %r' % value)
-                        continue
-                    add_entry(entry.strip(), subentry.strip())
-                elif type == 'pair':
-                    try:
-                        first, second = map(lambda x: x.strip(),
-                                            value.split(';', 1))
-                        if not first or not second:
-                            raise ValueError
-                    except ValueError:
-                        self.warn(fn, 'invalid pair index entry %r' % value)
-                        continue
-                    add_entry(first, second)
-                    add_entry(second, first)
-                elif type == 'triple':
-                    try:
-                        first, second, third = map(lambda x: x.strip(),
-                                                   value.split(';', 2))
-                        if not first or not second or not third:
-                            raise ValueError
-                    except ValueError:
-                        self.warn(fn, 'invalid triple index entry %r' % value)
-                        continue
-                    add_entry(first, second+' '+third)
-                    add_entry(second, third+', '+first)
-                    add_entry(third, first+' '+second)
-                else:
-                    self.warn(fn, 'unknown index entry type %r' % type)
+            for type, value, tid, main in entries:
+                try:
+                    if type == 'single':
+                        try:
+                            entry, subentry = split_into(2, 'single', value)
+                        except ValueError:
+                            entry, = split_into(1, 'single', value)
+                            subentry = ''
+                        add_entry(entry, subentry)
+                    elif type == 'pair':
+                        first, second = split_into(2, 'pair', value)
+                        add_entry(first, second)
+                        add_entry(second, first)
+                    elif type == 'triple':
+                        first, second, third = split_into(3, 'triple', value)
+                        add_entry(first, second+' '+third)
+                        add_entry(second, third+', '+first)
+                        add_entry(third, first+' '+second)
+                    elif type == 'see':
+                        first, second = split_into(2, 'see', value)
+                        add_entry(first, _('see %s') % second, link=False)
+                    elif type == 'seealso':
+                        first, second = split_into(2, 'see', value)
+                        add_entry(first, _('see also %s') % second, link=False)
+                    else:
+                        self.warn(fn, 'unknown index entry type %r' % type)
+                except ValueError, err:
+                    self.warn(fn, str(err))
 
         # sort the index entries; put all symbols at the front, even those
         # following the letters in ASCII, this is where the chr(127) comes from

sphinx/locale/__init__.py

     'deprecated':     l_('Deprecated since version %s'),
 }
 
+# XXX Python specific
 pairindextypes = {
     'module':    l_('module'),
     'keyword':   l_('keyword'),
     inliner.document.note_explicit_target(targetnode)
     if typ == 'pep':
         indexnode['entries'] = [
-            ('single', _('Python Enhancement Proposals!PEP %s') % text,
-             targetid, 'PEP %s' % text)]
+            ('single', _('Python Enhancement Proposals; PEP %s') % text,
+             targetid, '')]
         anchor = ''
         anchorindex = text.find('#')
         if anchorindex > 0:
         rn += sn
         return [indexnode, targetnode, rn], []
     elif typ == 'rfc':
-        indexnode['entries'] = [('single', 'RFC; RFC %s' % text,
-                                 targetid, 'RFC %s' % text)]
+        indexnode['entries'] = [('single', 'RFC; RFC %s' % text, targetid, '')]
         anchor = ''
         anchorindex = text.find('#')
         if anchorindex > 0:
         entries = process_index_entry(target, targetid)
     # otherwise we just create a "single" entry
     else:
-        entries = [('single', target, targetid, target)]
+        # but allow giving main entry
+        main = ''
+        if target.startswith('!'):
+            target = target[1:]
+            title = title[1:]
+            main = 'main'
+        entries = [('single', target, targetid, main)]
     indexnode = addnodes.index()
     indexnode['entries'] = entries
     textnode = nodes.Text(title, title)

sphinx/texinputs/sphinx.sty

 }
 
 
-% Index-entry generation support.
-%
-
-% Command to generate two index entries (using subentries)
-\newcommand{\indexii}[2]{\index{#1!#2}\index{#2!#1}}
-
-% And three entries (using only one level of subentries)
-\newcommand{\indexiii}[3]{\index{#1!#2 #3}\index{#2!#3, #1}\index{#3!#1 #2}}
-
-% And four (again, using only one level of subentries)
-\newcommand{\indexiv}[4]{
-\index{#1!#2 #3 #4}
-\index{#2!#3 #4, #1}
-\index{#3!#4, #1 #2}
-\index{#4!#1 #2 #3}
-}
-
 % \moduleauthor{name}{email}
 \newcommand{\moduleauthor}[2]{}
 

sphinx/themes/basic/genindex-single.html

     :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
     :license: BSD, see LICENSE for details.
 #}
+{% macro indexentries(firstname, links) %}
+  <dt>
+  {%- if links -%}
+    <a href="{{ links[0][1] }}">  
+    {%- if links[0][0] %}<strong>{% endif -%}
+    {{ firstname|e }}
+    {%- if links[0][0] %}</strong>{% endif -%}
+    </a>
+
+    {%- for ismain, link in links[1:] -%}
+      , <a href="{{ link }}">{% if ismain %}<strong>{% endif -%}
+      [{{ loop.index }}]
+      {%- if ismain %}</strong>{% endif -%}
+      </a>
+    {%- endfor %}
+  {%- else %}
+    {{ firstname|e }}
+  {%- endif %}
+  </dt>
+{% endmacro %}
+
 {% extends "layout.html" %}
 {% set title = _('Index') %}
 {% block body %}
 
-   <h1 id="index">{% trans key=key %}Index &ndash; {{ key }}{% endtrans %}</h1>
+<h1 id="index">{% trans key=key %}Index &ndash; {{ key }}{% endtrans %}</h1>
 
 <table width="100%" class="indextable"><tr>
   {%- for column in entries|slice(2) if column %}
   <td width="33%" valign="top"><dl>
-  {%- for entryname, (links, subitems) in column %}
-    <dt>{% if links %}<a href="{{ links[0] }}">{{ entryname|e }}</a>
-      {%- for link in links[1:] %}, <a href="{{ link }}">[{{ loop.index }}]</a>{% endfor %}
-      {%- else %}{{ entryname|e }}{% endif %}</dt>
-    {%- if subitems %}
-    <dd><dl>
-    {%- for subentryname, subentrylinks in subitems %}
-      <dt><a href="{{ subentrylinks[0] }}">{{ subentryname|e }}</a>
-      {%- for link in subentrylinks[1:] %}, <a href="{{ link }}">[{{ loop.index }}]</a>{% endfor -%}
-      </dt>
+    {%- for entryname, (links, subitems) in column %}
+      {{ indexentries(entryname, links) }}
+      {%- if subitems %}
+      <dd><dl>
+      {%- for subentryname, subentrylinks in subitems %}
+        {{ indexentries(subentryname, subentrylinks) }}
+      {%- endfor %}
+      </dl></dd>
+      {%- endif -%}
     {%- endfor %}
-  </dl></dd>
-  {%- endif -%}
-{%- endfor %}
-</dl></td>
-{%- endfor %}
+  </dl></td>
+  {%- endfor %}
 </tr></table>
 
 {% endblock %}

sphinx/themes/basic/genindex.html

     :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
     :license: BSD, see LICENSE for details.
 #}
+{% macro indexentries(firstname, links) %}
+  <dt>
+  {%- if links -%}
+    <a href="{{ links[0][1] }}">  
+    {%- if links[0][0] %}<strong>{% endif -%}
+    {{ firstname|e }}
+    {%- if links[0][0] %}</strong>{% endif -%}
+    </a>
+
+    {%- for ismain, link in links[1:] -%}
+      , <a href="{{ link }}">{% if ismain %}<strong>{% endif -%}
+      [{{ loop.index }}]
+      {%- if ismain %}</strong>{% endif -%}
+      </a>
+    {%- endfor %}
+  {%- else %}
+    {{ firstname|e }}
+  {%- endif %}
+  </dt>
+{% endmacro %}
+
 {% extends "layout.html" %}
 {% set title = _('Index') %}
 {% block body %}
 
-   <h1 id="index">{{ _('Index') }}</h1>
+<h1 id="index">{{ _('Index') }}</h1>
 
-   <div class="genindex-jumpbox">
-   {% for key, dummy in genindexentries -%}
-   <a href="#{{ key }}"><strong>{{ key }}</strong></a> {% if not loop.last %}| {% endif %}
-   {%- endfor %}
-   </div>
+<div class="genindex-jumpbox">
+ {% for key, dummy in genindexentries -%}
+ <a href="#{{ key }}"><strong>{{ key }}</strong></a>
+ {% if not loop.last %}| {% endif %}
+ {%- endfor %}
+</div>
 
-   {%- for key, entries in genindexentries %}
+{%- for key, entries in genindexentries %}
 <h2 id="{{ key }}">{{ key }}</h2>
 <table width="100%" class="indextable genindextable"><tr>
   {%- for column in entries|slice(2) if column %}
   <td width="33%" valign="top"><dl>
-  {%- for entryname, (links, subitems) in column %}
-    <dt>{% if links %}<a href="{{ links[0] }}">{{ entryname|e }}</a>
-      {%- for link in links[1:] %}, <a href="{{ link }}">[{{ loop.index }}]</a>{% endfor %}
-      {%- else %}{{ entryname|e }}{% endif %}</dt>
-    {%- if subitems %}
-    <dd><dl>
-    {%- for subentryname, subentrylinks in subitems %}
-      <dt><a href="{{ subentrylinks[0] }}">{{ subentryname|e }}</a>
-      {%- for link in subentrylinks[1:] %}, <a href="{{ link }}">[{{ loop.index }}]</a>{% endfor -%}
-      </dt>
+    {%- for entryname, (links, subitems) in column %}
+      {{ indexentries(entryname, links) }}
+      {%- if subitems %}
+      <dd><dl>
+      {%- for subentryname, subentrylinks in subitems %}
+        {{ indexentries(subentryname, subentrylinks) }}
+      {%- endfor %}
+      </dl></dd>
+      {%- endif -%}
     {%- endfor %}
-  </dl></dd>
-  {%- endif -%}
-{%- endfor %}
-</dl></td>
-{%- endfor %}
+  </dl></td>
+  {%- endfor %}
 </tr></table>
 {% endfor %}
 

sphinx/util/__init__.py

     return '', s
 
 
+def split_into(n, type, value):
+    """Split an index entry into a given number of parts at semicolons."""
+    parts = map(lambda x: x.strip(), value.split(';', n-1))
+    if sum(1 for part in parts if part) < n:
+        raise ValueError('invalid %s index entry %r' % (type, value))
+    return parts
+
+
 def format_exception_cut_frames(x=1):
     """Format an exception with traceback, but only the last x frames."""
     typ, val, tb = sys.exc_info()

sphinx/util/nodes.py

 
 
 indextypes = [
-    'single', 'pair', 'double', 'triple',
+    'single', 'pair', 'double', 'triple', 'see', 'seealso',
 ]
 
 def process_index_entry(entry, targetid):
     indexentries = []
     entry = entry.strip()
+    oentry = entry
+    main = ''
+    if entry.startswith('!'):
+        main = 'main'
+        entry = entry[1:].lstrip()
     for type in pairindextypes:
         if entry.startswith(type+':'):
             value = entry[len(type)+1:].strip()
             value = pairindextypes[type] + '; ' + value
-            indexentries.append(('pair', value, targetid, value))
+            indexentries.append(('pair', value, targetid, main))
             break
     else:
         for type in indextypes:
                 value = entry[len(type)+1:].strip()
                 if type == 'double':
                     type = 'pair'
-                indexentries.append((type, value, targetid, value))
+                indexentries.append((type, value, targetid, main))
                 break
         # shorthand notation for single entries
         else:
-            for value in entry.split(','):
+            for value in oentry.split(','):
                 value = value.strip()
+                main = ''
+                if value.startswith('!'):
+                    main = 'main'
+                    value = value[1:].lstrip()
                 if not value:
                     continue
-                indexentries.append(('single', value, targetid, value))
+                indexentries.append(('single', value, targetid, main))
     return indexentries
 
 

sphinx/writers/latex.py

 from sphinx import highlighting
 from sphinx.errors import SphinxError
 from sphinx.locale import admonitionlabels, versionlabels, _
+from sphinx.util import split_into
 from sphinx.util.osutil import ustrftime
 from sphinx.util.pycompat import any
 from sphinx.util.texescape import tex_escape_map, tex_replace_map
         if not node.get('inline', True):
             self.body.append('\n')
         entries = node['entries']
-        for type, string, tid, _ in entries:
-            if type == 'single':
-                self.body.append(r'\index{%s}' %
-                                 scre.sub('!', self.encode(string)))
-            elif type == 'pair':
-                parts = tuple(self.encode(x.strip())
-                              for x in string.split(';', 1))
-                try:
-                    self.body.append(r'\indexii{%s}{%s}' % parts)
-                except TypeError:
-                    self.builder.warn('invalid pair index entry %r' % string)
-            elif type == 'triple':
-                parts = tuple(self.encode(x.strip())
-                              for x in string.split(';', 2))
-                try:
-                    self.body.append(r'\indexiii{%s}{%s}{%s}' % parts)
-                except TypeError:
-                    self.builder.warn('invalid triple index entry %r' % string)
-            else:
-                self.builder.warn('unknown index entry type %s found' % type)
+        for type, string, tid, ismain in entries:
+            m = ''
+            if ismain:
+                m = '|textbf'
+            try:
+                if type == 'single':
+                    p = scre.sub('!', self.encode(string))
+                    self.body.append(r'\index{%s%s}' % (p, m))
+                elif type == 'pair':
+                    p1, p2 = map(self.encode, split_into(2, 'pair', string))
+                    self.body.append(r'\index{%s!%s%s}\index{%s!%s%s}' %
+                                     (p1, p2, m,  p2, p1, m))
+                elif type == 'triple':
+                    p1, p2, p3 = map(self.encode, split_into(3, 'triple', string))
+                    self.body.append(
+                        r'\index{%s!%s %s%s}\index{%s!%s, %s%s}\index{%s!%s %s%s}' %
+                        (p1, p2, p3, m,  p2, p3, p1, m,  p3, p1, p2, m))
+                elif type == 'see':
+                    p1, p2 = map(self.encode, split_into(2, 'see', string))
+                    self.body.append(r'\index{%s|see{%s}}' % (p1, p2))
+                elif type == 'seealso':
+                    p1, p2 = map(self.encode, split_into(2, 'seealso', string))
+                    self.body.append(r'\index{%s|see{%s}}' % (p1, p2))
+                else:
+                    self.builder.warn('unknown index entry type %s found' % type)
+            except ValueError, err:
+                self.builder.warn(str(err))
         raise nodes.SkipNode
 
     def visit_raw(self, node):

tests/root/markup.txt

    double: entry; double
    triple: index; entry; triple
    keyword: with
+   see: from; to
+   seealso: fromalso; toalso
 
 Invalid index markup...
 
    pair:
    keyword:
 
+.. index::
+   !Main, !Other
+   !single: entry; pair
+
+:index:`!Main`
 
 .. _ölabel:
 

tests/test_build_html.py

 
 HTML_WARNINGS = ENV_WARNINGS + """\
 %(root)s/images.txt:20: WARNING: no matching candidate for image URI u'foo.\\*'
-%(root)s/markup.txt:: WARNING: invalid index entry u''
+%(root)s/markup.txt:: WARNING: invalid single index entry u''
 %(root)s/markup.txt:: WARNING: invalid pair index entry u''
 %(root)s/markup.txt:: WARNING: invalid pair index entry u'keyword; '
 """
     '_static/statictmpl.html': [
         (".//project", 'Sphinx <Tests>'),
     ],
+    'genindex.html': [
+        # index entries
+        (".//a/strong", "Main"),
+        (".//a/strong", "[1]"),
+        (".//a/strong", "Other"),
+        (".//a", "entry"),
+        (".//dt/a", "double"),
+    ]
 }
 
 if pygments:

tests/test_build_latex.py

 LATEX_WARNINGS = ENV_WARNINGS + """\
 None:None: WARNING: no matching candidate for image URI u'foo.\\*'
 WARNING: invalid pair index entry u''
+WARNING: invalid pair index entry u'keyword; '
 """
 
 if sys.version_info >= (3, 0):
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.