mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
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:
parent
358a13ee72
commit
e6a5a3a92e
2
CHANGES
2
CHANGES
@ -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
|
||||
----------
|
||||
|
@ -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
|
||||
---------------------------
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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':
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)]
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user