classifier of glossary terms can be used for index entries grouping key. The classifier also be used for translation. See also :ref:glossary-directive.

This commit is contained in:
shimizukawa 2016-02-13 22:30:06 +09:00
parent 358a13ee72
commit e6a5a3a92e
20 changed files with 111 additions and 65 deletions

View File

@ -73,6 +73,8 @@ Features added
sections using its title. Thanks to Tadhg O'Higgins.
* #1854: Allow to choose Janome for Japanese splitter.
* #1853: support custom text splitter on html search with `language='ja'`.
* #2320: classifier of glossary terms can be used for index entries grouping key.
The classifier also be used for translation. See also :ref:`glossary-directive`.
Bugs fixed
----------

View File

@ -183,6 +183,24 @@ Glossary
(When the glossary is sorted, the first term determines the sort order.)
If you want to specify "grouping key" for general index entries, you can put a "key"
as "term : key". For example::
.. glossary::
term 1 : A
term 2 : B
Definition of both terms.
Note that "key" is used for grouping key as is.
The "key" isn't normalized; key "A" and "a" become different groups.
The whole characters in "key" is used instead of a first character; it is used for
"Combining Character Sequence" and "Surrogate Pairs" grouping key.
In i18n situation, you can specify "localized term : key" even if original text only
have "term" part. In this case, translated "localized term" will be categorized in
"key" group.
.. versionadded:: 0.6
You can now give the glossary directive a ``:sorted:`` flag that will
automatically sort the entries alphabetically.
@ -190,6 +208,8 @@ Glossary
.. versionchanged:: 1.1
Now supports multiple terms and inline markup in terms.
.. versionchanged:: 1.4
Index key for glossary term should be considered *experimental*.
Grammar production displays
---------------------------

View File

@ -400,7 +400,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
# XXX: modifies tree inline
# Logic modeled from themes/basic/genindex.html
for key, columns in tree:
for entryname, (links, subitems) in columns:
for entryname, (links, subitems, key_) in columns:
for (i, (ismain, link)) in enumerate(links):
m = self.refuri_re.match(link)
if m:

View File

@ -117,7 +117,7 @@ class I18nBuilder(Builder):
if 'index' in self.env.config.gettext_additional_targets:
# Extract translatable messages from index entries.
for node, entries in traverse_translatable_index(doctree):
for typ, msg, tid, main in entries:
for typ, msg, tid, main, key_ in entries:
for m in split_index_msg(typ, msg):
if typ == 'pair' and m in pairindextypes.values():
# avoid built-in translated message was incorporated

View File

@ -512,7 +512,7 @@ class StandaloneHTMLBuilder(Builder):
indexcounts = []
for _k, entries in genindex:
indexcounts.append(sum(1 + len(subitems)
for _, (_, subitems) in entries))
for _, (_, subitems, _) in entries))
genindexcontext = dict(
genindexentries = genindex,

View File

@ -299,7 +299,7 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder):
write_index(subitem[0], subitem[1], [])
f.write('</UL>')
for (key, group) in index:
for title, (refs, subitems) in group:
for title, (refs, subitems, key_) in group:
write_index(title, refs, subitems)
f.write('</UL>\n')
finally:

View File

@ -148,7 +148,7 @@ class QtHelpBuilder(StandaloneHTMLBuilder):
keywords = []
index = self.env.create_index(self, group_entries=False)
for (key, group) in index:
for title, (refs, subitems) in group:
for title, (refs, subitems, key_) in group:
keywords.extend(self.build_keywords(title, refs, subitems))
keywords = u'\n'.join(keywords)

View File

@ -206,7 +206,7 @@ class CObject(ObjectDescription):
indextext = self.get_index_text(name)
if indextext:
self.indexnode['entries'].append(('single', indextext,
targetname, ''))
targetname, '', None))
def before_content(self):
self.typename_set = False

View File

@ -3677,7 +3677,7 @@ class CPPObject(ObjectDescription):
name = text_type(ast.symbol.get_full_nested_name()).lstrip(':')
indexText = self.get_index_text(name)
self.indexnode['entries'].append(('single', indexText, newestId, ''))
self.indexnode['entries'].append(('single', indexText, newestId, '', None))
if newestId not in self.state.document.ids:
# if the name is not unique, the first one will win

View File

@ -97,7 +97,7 @@ class JSObject(ObjectDescription):
if indextext:
self.indexnode['entries'].append(('single', indextext,
fullname.replace('$', '_S_'),
''))
'', None))
def get_index_text(self, objectname, name_obj):
name, obj = name_obj

View File

@ -257,7 +257,7 @@ class PyObject(ObjectDescription):
indextext = self.get_index_text(modname, name_cls)
if indextext:
self.indexnode['entries'].append(('single', indextext,
fullname, ''))
fullname, '', None))
def before_content(self):
# needed for automatic qualification of members (reset in subclasses)
@ -462,7 +462,7 @@ class PyModule(Directive):
ret.append(targetnode)
indextext = _('%s (module)') % modname
inode = addnodes.index(entries=[('single', indextext,
'module-' + modname, '')])
'module-' + modname, '', None)])
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, '', None))
def get_index_text(self, objectname, name):
if self.objtype == 'directive':

View File

@ -64,7 +64,7 @@ class GenericObject(ObjectDescription):
indextype = 'single'
indexentry = self.indextemplate % (name,)
self.indexnode['entries'].append((indextype, indexentry,
targetname, ''))
targetname, '', None))
self.env.domaindata['std']['objects'][self.objtype, name] = \
self.env.docname, targetname
@ -85,8 +85,8 @@ class EnvVarXRefRole(XRefRole):
tgtid = 'index-%s' % env.new_serialno('index')
indexnode = addnodes.index()
indexnode['entries'] = [
('single', varname, tgtid, ''),
('single', _('environment variable; %s') % varname, tgtid, '')
('single', varname, tgtid, '', None),
('single', _('environment variable; %s') % varname, tgtid, '', None)
]
targetnode = nodes.target('', '', ids=[tgtid])
document.note_explicit_target(targetnode)
@ -184,7 +184,7 @@ class Cmdoption(ObjectDescription):
self.indexnode['entries'].append(
('pair', _('%scommand line option; %s') %
((currprogram and currprogram + ' ' or ''), sig),
targetname, ''))
targetname, '', None))
class Program(Directive):
@ -214,11 +214,23 @@ class OptionXRefRole(XRefRole):
return title, target
def register_term_to_glossary(env, node, new_id=None):
def split_term_classifiers(line):
# split line into a term and classifiers. if no classifier, None is used..
parts = re.split(' +: +', line) + [None]
return parts
def make_glossary_term(env, textnodes, index_key, source, lineno, new_id=None):
# get a text-only representation of the term and register it
# as a cross-reference target
term = nodes.term('', '', *textnodes)
term.source = source
term.line = lineno
gloss_entries = env.temp_data.setdefault('gloss_entries', set())
objects = env.domaindata['std']['objects']
termtext = node.astext()
termtext = term.astext()
if new_id is None:
new_id = nodes.make_id('term-' + termtext)
if new_id in gloss_entries:
@ -228,11 +240,13 @@ def register_term_to_glossary(env, node, new_id=None):
# add an index entry too
indexnode = addnodes.index()
indexnode['entries'] = [('single', termtext, new_id, 'main')]
indexnode.source, indexnode.line = node.source, node.line
node.append(indexnode)
node['ids'].append(new_id)
node['names'].append(new_id)
indexnode['entries'] = [('single', termtext, new_id, 'main', index_key)]
indexnode.source, indexnode.line = term.source, term.line
term.append(indexnode)
term['ids'].append(new_id)
term['names'].append(new_id)
return term
class Glossary(Directive):
@ -316,16 +330,15 @@ class Glossary(Directive):
termnodes = []
system_messages = []
for line, source, lineno in terms:
parts = split_term_classifiers(line)
# parse the term with inline markup
res = self.state.inline_text(line, lineno)
system_messages.extend(res[1])
# classifiers (parts[1:]) will not be shown on doctree
textnodes, sysmsg = self.state.inline_text(parts[0], lineno)
# get a text-only representation of the term and register it
# as a cross-reference target
term = nodes.term('', '', *res[0])
term.source = source
term.line = lineno
register_term_to_glossary(env, term)
# use first classifier as a index key
term = make_glossary_term(env, textnodes, parts[1], source, lineno)
term.rawsource = line
system_messages.extend(sysmsg)
termtexts.append(term.astext())
termnodes.append(term)

View File

@ -1049,7 +1049,7 @@ class BuildEnvironment:
entries = self.indexentries[docname] = []
for node in document.traverse(addnodes.index):
try:
for type, value, tid, main in node['entries']:
for type, value, tid, main, index_key in node['entries']:
split_index_msg(type, value)
except ValueError as exc:
self.warn_node(exc, node)
@ -1771,16 +1771,16 @@ class BuildEnvironment:
"""Create the real index from the collected index entries."""
new = {}
def add_entry(word, subword, link=True, dic=new):
def add_entry(word, subword, link=True, dic=new, key=None):
# Force the word to be unicode if it's a ASCII bytestring.
# This will solve problems with unicode normalization later.
# For instance the RFC role will add bytestrings at the moment
word = text_type(word)
entry = dic.get(word)
if not entry:
dic[word] = entry = [[], {}]
dic[word] = entry = [[], {}, key]
if subword:
add_entry(subword, '', link=link, dic=entry[1])
add_entry(subword, '', link=link, dic=entry[1], key=key)
elif link:
try:
uri = builder.get_relative_uri('genindex', fn) + '#' + tid
@ -1792,7 +1792,7 @@ class BuildEnvironment:
for fn, entries in iteritems(self.indexentries):
# new entry types must be listed in directives/other.py!
for type, value, tid, main in entries:
for type, value, tid, main, index_key in entries:
try:
if type == 'single':
try:
@ -1800,22 +1800,24 @@ class BuildEnvironment:
except ValueError:
entry, = split_into(1, 'single', value)
subentry = ''
add_entry(entry, subentry)
add_entry(entry, subentry, key=index_key)
elif type == 'pair':
first, second = split_into(2, 'pair', value)
add_entry(first, second)
add_entry(second, first)
add_entry(first, second, key=index_key)
add_entry(second, first, key=index_key)
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)
add_entry(first, second+' '+third, key=index_key)
add_entry(second, third+', '+first, key=index_key)
add_entry(third, first+' '+second, key=index_key)
elif type == 'see':
first, second = split_into(2, 'see', value)
add_entry(first, _('see %s') % second, link=False)
add_entry(first, _('see %s') % second, link=False,
key=index_key)
elif type == 'seealso':
first, second = split_into(2, 'see', value)
add_entry(first, _('see also %s') % second, link=False)
add_entry(first, _('see also %s') % second, link=False,
key=index_key)
else:
self.warn(fn, 'unknown index entry type %r' % type)
except ValueError as err:
@ -1844,7 +1846,7 @@ class BuildEnvironment:
oldsubitems = None
i = 0
while i < len(newlist):
key, (targets, subitems) = newlist[i]
key, (targets, subitems, _key) = newlist[i]
# cannot move if it has subitems; structure gets too complex
if not subitems:
m = _fixre.match(key)
@ -1852,7 +1854,7 @@ class BuildEnvironment:
if oldkey == m.group(1):
# prefixes match: add entry as subitem of the
# previous entry
oldsubitems.setdefault(m.group(2), [[], {}])[0].\
oldsubitems.setdefault(m.group(2), [[], {}, _key])[0].\
extend(targets)
del newlist[i]
continue
@ -1866,7 +1868,8 @@ class BuildEnvironment:
def keyfunc2(item, letters=string.ascii_uppercase + '_'):
# hack: mutating the subitems dicts to a list in the keyfunc
k, v = item
v[1] = sorted((si, se) for (si, (se, void)) in iteritems(v[1]))
v[1] = sorted((si, se) for (si, (se, void, void)) in iteritems(v[1]))
if v[2] is None:
# now calculate the key
letter = unicodedata.normalize('NFD', k[0])[0].upper()
if letter in letters:
@ -1874,6 +1877,8 @@ class BuildEnvironment:
else:
# get all other symbols under one heading
return _('Symbols')
else:
return v[2]
return [(key_, list(group))
for (key_, group) in groupby(newlist, keyfunc2)]

View File

@ -191,7 +191,7 @@ def indexmarkup_role(typ, rawtext, text, lineno, inliner,
if typ == 'pep':
indexnode['entries'] = [
('single', _('Python Enhancement Proposals; PEP %s') % target,
targetid, '')]
targetid, '', None)]
anchor = ''
anchorindex = target.find('#')
if anchorindex > 0:
@ -212,7 +212,8 @@ def indexmarkup_role(typ, rawtext, text, lineno, inliner,
rn += sn
return [indexnode, targetnode, rn], []
elif typ == 'rfc':
indexnode['entries'] = [('single', 'RFC; RFC %s' % target, targetid, '')]
indexnode['entries'] = [
('single', 'RFC; RFC %s' % target, targetid, '', None)]
anchor = ''
anchorindex = target.find('#')
if anchorindex > 0:
@ -317,7 +318,7 @@ def index_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
target = target[1:]
title = title[1:]
main = 'main'
entries = [('single', target, targetid, main)]
entries = [('single', target, targetid, main, None)]
indexnode = addnodes.index()
indexnode['entries'] = entries
set_role_source_info(inliner, lineno, indexnode)

View File

@ -46,7 +46,7 @@
<table style="width: 100%" class="indextable genindextable"><tr>
{%- for column in entries|slice(2) if column %}
<td style="width: 33%" valign="top"><dl>
{%- for entryname, (links, subitems) in column %}
{%- for entryname, (links, subitems, _) in column %}
{{ indexentries(entryname, links) }}
{%- if subitems %}
<dd><dl>

View File

@ -27,7 +27,7 @@ from sphinx.util.nodes import (
from sphinx.util.osutil import ustrftime
from sphinx.util.i18n import find_catalog
from sphinx.util.pycompat import indent
from sphinx.domains.std import register_term_to_glossary
from sphinx.domains.std import make_glossary_term, split_term_classifiers
default_substitutions = set([
@ -340,7 +340,12 @@ class Locale(Transform):
for _id in node['names']:
if _id in gloss_entries:
gloss_entries.remove(_id)
register_term_to_glossary(env, patch, _id)
parts = split_term_classifiers(msgstr)
patch = publish_msgstr(
env.app, parts[0], source, node.line, env.config, settings)
patch = make_glossary_term(
env, patch, parts[1], source, node.line, _id)
node['ids'] = patch['ids']
node['names'] = patch['names']
processed = True
@ -521,7 +526,7 @@ class Locale(Transform):
# Extract and translate messages for index entries.
for node, entries in traverse_translatable_index(self.document):
new_entries = []
for type, msg, tid, main in entries:
for type, msg, tid, main, key_ in entries:
msg_parts = split_index_msg(type, msg)
msgstr_parts = []
for part in msg_parts:
@ -530,7 +535,7 @@ class Locale(Transform):
msgstr = part
msgstr_parts.append(msgstr)
new_entries.append((type, ';'.join(msgstr_parts), tid, main))
new_entries.append((type, ';'.join(msgstr_parts), tid, main, None))
node['raw_entries'] = entries
node['entries'] = new_entries

View File

@ -213,7 +213,7 @@ def process_index_entry(entry, targetid):
if entry.startswith(type+':'):
value = entry[len(type)+1:].strip()
value = pairindextypes[type] + '; ' + value
indexentries.append(('pair', value, targetid, main))
indexentries.append(('pair', value, targetid, main, None))
break
else:
for type in indextypes:
@ -221,7 +221,7 @@ def process_index_entry(entry, targetid):
value = entry[len(type)+1:].strip()
if type == 'double':
type = 'pair'
indexentries.append((type, value, targetid, main))
indexentries.append((type, value, targetid, main, None))
break
# shorthand notation for single entries
else:
@ -233,7 +233,7 @@ def process_index_entry(entry, targetid):
value = value[1:].lstrip()
if not value:
continue
indexentries.append(('single', value, targetid, main))
indexentries.append(('single', value, targetid, main, None))
return indexentries

View File

@ -1531,7 +1531,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
if not node.get('inline', True):
self.body.append('\n')
entries = node['entries']
for type, string, tid, ismain in entries:
for type, string, tid, ismain, key_ in entries:
m = ''
if ismain:
m = '|textbf'

View File

@ -1302,7 +1302,7 @@ class TexinfoTranslator(nodes.NodeVisitor):
else:
self.body.append('\n')
for entry in node['entries']:
typ, text, tid, text2 = entry
typ, text, tid, text2, key_ = entry
text = self.escape_menu(text)
self.body.append('@geindex %s\n' % text)