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

This commit is contained in:
Georg Brandl 2011-01-07 19:00:29 +01:00
parent 98d884da6e
commit bf767d5222
21 changed files with 228 additions and 145 deletions

View File

@ -46,6 +46,9 @@ Release 1.1 (in development)
* #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.

View File

@ -113,11 +113,28 @@ mainly contained in information units, such as the language reference.
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::
@ -125,6 +142,9 @@ mainly contained in information units, such as the language reference.
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

View File

@ -289,16 +289,17 @@ class EpubBuilder(StandaloneHTMLBuilder):
# 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):

View File

@ -168,7 +168,7 @@ class CObject(ObjectDescription):
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

View File

@ -852,7 +852,7 @@ class CPPObject(ObjectDescription):
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]

View File

@ -96,7 +96,7 @@ class JSObject(ObjectDescription):
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

View File

@ -221,7 +221,7 @@ class PyObject(ObjectDescription):
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)
@ -399,7 +399,7 @@ class PyModule(Directive):
if not noindex:
indextext = _('%s (module)') % modname
inode = addnodes.index(entries=[('single', indextext,
'module-' + modname, modname)])
'module-' + modname, '')])
ret.append(inode)
return ret

View File

@ -48,7 +48,7 @@ class ReSTMarkup(ObjectDescription):
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':

View File

@ -61,7 +61,7 @@ class GenericObject(ObjectDescription):
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
@ -82,8 +82,8 @@ class EnvVarXRefRole(XRefRole):
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)
@ -118,7 +118,7 @@ class Target(Directive):
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:
@ -161,7 +161,7 @@ class Cmdoption(ObjectDescription):
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
@ -293,8 +293,8 @@ class Glossary(Directive):
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

View File

@ -37,7 +37,7 @@ from docutils.transforms import Transform
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
@ -1484,56 +1484,50 @@ class BuildEnvironment:
"""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

View File

@ -170,6 +170,7 @@ versionlabels = {
'deprecated': l_('Deprecated since version %s'),
}
# XXX Python specific
pairindextypes = {
'module': l_('module'),
'keyword': l_('keyword'),

View File

@ -170,8 +170,8 @@ def indexmarkup_role(typ, rawtext, etext, lineno, inliner,
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:
@ -190,8 +190,7 @@ def indexmarkup_role(typ, rawtext, etext, lineno, inliner,
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:
@ -282,7 +281,13 @@ def index_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
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)

View File

@ -175,23 +175,6 @@
}
% 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]{}

View File

@ -7,31 +7,48 @@
: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 %}

View File

@ -7,39 +7,57 @@
: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 %}

View File

@ -285,6 +285,14 @@ def rpartition(s, t):
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()

View File

@ -74,17 +74,22 @@ def split_explicit_title(text):
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:
@ -92,15 +97,19 @@ def process_index_entry(entry, targetid):
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

View File

@ -23,6 +23,7 @@ from sphinx import addnodes
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
@ -1066,26 +1067,33 @@ class LaTeXTranslator(nodes.NodeVisitor):
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):

View File

@ -277,6 +277,8 @@ Index markup
double: entry; double
triple: index; entry; triple
keyword: with
see: from; to
seealso: fromalso; toalso
Invalid index markup...
@ -285,6 +287,11 @@ Invalid index markup...
pair:
keyword:
.. index::
!Main, !Other
!single: entry; pair
:index:`!Main`
.. _ölabel:

View File

@ -49,7 +49,7 @@ http://sphinx.pocoo.org/domains.html
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; '
"""
@ -232,6 +232,14 @@ HTML_XPATH = {
'_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:

View File

@ -30,6 +30,7 @@ latex_warnfile = StringIO()
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):