From e6a5a3a92e938fcd75866b4227db9e0524d58f7c Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Sat, 13 Feb 2016 22:30:06 +0900 Subject: [PATCH] classifier of glossary terms can be used for index entries grouping key. The classifier also be used for translation. See also :ref:`glossary-directive`. --- CHANGES | 2 ++ doc/markup/para.rst | 20 ++++++++++++ sphinx/builders/epub.py | 2 +- sphinx/builders/gettext.py | 2 +- sphinx/builders/html.py | 2 +- sphinx/builders/htmlhelp.py | 2 +- sphinx/builders/qthelp.py | 2 +- sphinx/domains/c.py | 2 +- sphinx/domains/cpp.py | 2 +- sphinx/domains/javascript.py | 2 +- sphinx/domains/python.py | 4 +-- sphinx/domains/rst.py | 2 +- sphinx/domains/std.py | 51 +++++++++++++++++++------------ sphinx/environment.py | 49 ++++++++++++++++------------- sphinx/roles.py | 7 +++-- sphinx/themes/basic/genindex.html | 2 +- sphinx/transforms.py | 13 +++++--- sphinx/util/nodes.py | 6 ++-- sphinx/writers/latex.py | 2 +- sphinx/writers/texinfo.py | 2 +- 20 files changed, 111 insertions(+), 65 deletions(-) diff --git a/CHANGES b/CHANGES index 9310b45a2..d93935e32 100644 --- a/CHANGES +++ b/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 ---------- diff --git a/doc/markup/para.rst b/doc/markup/para.rst index cc03a9f13..ba2cc52f5 100644 --- a/doc/markup/para.rst +++ b/doc/markup/para.rst @@ -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 --------------------------- diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py index fde2cbc12..8c0109264 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -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: diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py index 21c692f3f..fce6c8cc1 100644 --- a/sphinx/builders/gettext.py +++ b/sphinx/builders/gettext.py @@ -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 diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 03199f3b9..899c17d98 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -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, diff --git a/sphinx/builders/htmlhelp.py b/sphinx/builders/htmlhelp.py index 2f06cb0e1..f4003c4c9 100644 --- a/sphinx/builders/htmlhelp.py +++ b/sphinx/builders/htmlhelp.py @@ -299,7 +299,7 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder): write_index(subitem[0], subitem[1], []) f.write('') 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('\n') finally: diff --git a/sphinx/builders/qthelp.py b/sphinx/builders/qthelp.py index f6cfa84c5..0a7e85c92 100644 --- a/sphinx/builders/qthelp.py +++ b/sphinx/builders/qthelp.py @@ -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) diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 23ed04d05..c7fd0681e 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -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 diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 10602931d..a932227a6 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -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 diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index f7e2703e4..b5f64022a 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -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 diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index ba5998084..1639d8288 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -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 diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py index f0ad66ddc..b11c9450b 100644 --- a/sphinx/domains/rst.py +++ b/sphinx/domains/rst.py @@ -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': diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 974f428aa..501d6ea30 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -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) diff --git a/sphinx/environment.py b/sphinx/environment.py index 2d653289c..53d54ba7b 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -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,14 +1868,17 @@ 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])) - # now calculate the key - letter = unicodedata.normalize('NFD', k[0])[0].upper() - if letter in letters: - return letter + 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: + return letter + else: + # get all other symbols under one heading + return _('Symbols') else: - # get all other symbols under one heading - return _('Symbols') + return v[2] return [(key_, list(group)) for (key_, group) in groupby(newlist, keyfunc2)] diff --git a/sphinx/roles.py b/sphinx/roles.py index 59d69a1f1..a6583a6ea 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -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) diff --git a/sphinx/themes/basic/genindex.html b/sphinx/themes/basic/genindex.html index 69c4ec564..fac7f1899 100644 --- a/sphinx/themes/basic/genindex.html +++ b/sphinx/themes/basic/genindex.html @@ -46,7 +46,7 @@ {%- for column in entries|slice(2) if column %}
- {%- for entryname, (links, subitems) in column %} + {%- for entryname, (links, subitems, _) in column %} {{ indexentries(entryname, links) }} {%- if subitems %}
diff --git a/sphinx/transforms.py b/sphinx/transforms.py index 0d22020af..a5d44d73f 100644 --- a/sphinx/transforms.py +++ b/sphinx/transforms.py @@ -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 diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index ccea95777..98f84f2bf 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -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 diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 6eb287ce9..151db3854 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -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' diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index c29b9dda0..360b7d56d 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -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)