diff options
Diffstat (limited to 'sphinx/domains/std.py')
-rw-r--r-- | sphinx/domains/std.py | 220 |
1 files changed, 153 insertions, 67 deletions
diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 9a7937aa..03228da2 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -12,6 +12,7 @@ import re import unicodedata +from six import iteritems from docutils import nodes from docutils.parsers.rst import directives from docutils.statemachine import ViewList @@ -21,13 +22,15 @@ from sphinx.roles import XRefRole from sphinx.locale import l_, _ from sphinx.domains import Domain, ObjType from sphinx.directives import ObjectDescription -from sphinx.util import ws_re +from sphinx.util import ws_re, get_figtype from sphinx.util.nodes import clean_astext, make_refnode from sphinx.util.compat import Directive # RE for option descriptions -option_desc_re = re.compile(r'((?:/|-|--)?[-_a-zA-Z0-9]+)(\s*.*)') +option_desc_re = re.compile(r'((?:/|--|-|\+)?[-?@#_a-zA-Z0-9]+)(=?\s*.*)') +# RE for grammar tokens +token_re = re.compile('`(\w+)`', re.U) class GenericObject(ObjectDescription): @@ -143,8 +146,9 @@ class Cmdoption(ObjectDescription): self.env.warn( self.env.docname, 'Malformed option description %r, should ' - 'look like "opt", "-opt args", "--opt args" or ' - '"/opt args"' % potential_option, self.lineno) + 'look like "opt", "-opt args", "--opt args", ' + '"/opt args" or "+opt args"' % potential_option, + self.lineno) continue optname, args = m.groups() if count: @@ -162,7 +166,7 @@ class Cmdoption(ObjectDescription): return firstname def add_target_and_index(self, firstname, sig, signode): - currprogram = self.env.temp_data.get('std:program') + currprogram = self.env.ref_context.get('std:program') for optname in signode.get('allnames', []): targetname = optname.replace('/', '-') if not targetname.startswith('-'): @@ -197,36 +201,19 @@ class Program(Directive): env = self.state.document.settings.env program = ws_re.sub('-', self.arguments[0].strip()) if program == 'None': - env.temp_data['std:program'] = None + env.ref_context.pop('std:program', None) else: - env.temp_data['std:program'] = program + env.ref_context['std:program'] = program return [] class OptionXRefRole(XRefRole): - innernodeclass = addnodes.literal_emphasis - - def _split(self, text, refnode, env): - try: - program, target = re.split(' (?=-|--|/)', text, 1) - except ValueError: - env.warn_node('Malformed :option: %r, does not contain option ' - 'marker - or -- or /' % text, refnode) - return None, text - else: - program = ws_re.sub('-', program) - return program, target - def process_link(self, env, refnode, has_explicit_title, title, target): - program = env.temp_data.get('std:program') - if not has_explicit_title: - if ' ' in title and not (title.startswith('/') or - title.startswith('-')): - program, target = self._split(title, refnode, env) - target = target.strip() - elif ' ' in target: - program, target = self._split(target, refnode, env) - refnode['refprogram'] = program + # validate content + if not re.match('(.+ )?[-/+]', target): + env.warn_node('Malformed :option: %r, does not contain option ' + 'marker - or -- or / or +' % target, refnode) + refnode['std:program'] = env.ref_context.get('std:program') return title, target @@ -326,7 +313,7 @@ class Glossary(Directive): else: messages.append(self.state.reporter.system_message( 2, 'glossary seems to be misformatted, check ' - 'indentation', source=source, line=lineno)) + 'indentation', source=source, line=lineno)) else: if not in_definition: # first line of definition, determines indentation @@ -337,7 +324,7 @@ class Glossary(Directive): else: messages.append(self.state.reporter.system_message( 2, 'glossary seems to be misformatted, check ' - 'indentation', source=source, line=lineno)) + 'indentation', source=source, line=lineno)) was_empty = False # now, parse all the entries into a big definition list @@ -358,7 +345,7 @@ class Glossary(Directive): tmp.source = source tmp.line = lineno new_id, termtext, new_termnodes = \ - make_termnodes_from_paragraph_node(env, tmp) + make_termnodes_from_paragraph_node(env, tmp) ids.append(new_id) termtexts.append(termtext) termnodes.extend(new_termnodes) @@ -385,8 +372,6 @@ class Glossary(Directive): return messages + [node] -token_re = re.compile('`(\w+)`', re.U) - def token_xrefs(text): retnodes = [] pos = 0 @@ -471,7 +456,7 @@ class StandardDomain(Domain): 'productionlist': ProductionList, } roles = { - 'option': OptionXRefRole(innernodeclass=addnodes.literal_emphasis), + 'option': OptionXRefRole(), 'envvar': EnvVarXRefRole(), # links to tokens in grammar productions 'token': XRefRole(), @@ -481,6 +466,9 @@ class StandardDomain(Domain): # links to headings or arbitrary labels 'ref': XRefRole(lowercase=True, innernodeclass=nodes.emphasis, warn_dangling=True), + # links to labels of numbered figures, tables and code-blocks + 'numref': XRefRole(lowercase=True, + warn_dangling=True), # links to labels, without a different title 'keyword': XRefRole(warn_dangling=True), } @@ -504,34 +492,50 @@ class StandardDomain(Domain): 'term': 'term not in glossary: %(target)s', 'ref': 'undefined label: %(target)s (if the link has no caption ' 'the label must precede a section header)', + 'numref': 'undefined label: %(target)s', 'keyword': 'unknown keyword: %(target)s', } def clear_doc(self, docname): - for key, (fn, _) in self.data['progoptions'].items(): + for key, (fn, _) in list(self.data['progoptions'].items()): if fn == docname: del self.data['progoptions'][key] - for key, (fn, _) in self.data['objects'].items(): + for key, (fn, _) in list(self.data['objects'].items()): if fn == docname: del self.data['objects'][key] - for key, (fn, _, _) in self.data['labels'].items(): + for key, (fn, _, _) in list(self.data['labels'].items()): if fn == docname: del self.data['labels'][key] - for key, (fn, _) in self.data['anonlabels'].items(): + for key, (fn, _) in list(self.data['anonlabels'].items()): if fn == docname: del self.data['anonlabels'][key] + def merge_domaindata(self, docnames, otherdata): + # XXX duplicates? + for key, data in otherdata['progoptions'].items(): + if data[0] in docnames: + self.data['progoptions'][key] = data + for key, data in otherdata['objects'].items(): + if data[0] in docnames: + self.data['objects'][key] = data + for key, data in otherdata['labels'].items(): + if data[0] in docnames: + self.data['labels'][key] = data + for key, data in otherdata['anonlabels'].items(): + if data[0] in docnames: + self.data['anonlabels'][key] = data + def process_doc(self, env, docname, document): labels, anonlabels = self.data['labels'], self.data['anonlabels'] - for name, explicit in document.nametypes.iteritems(): + for name, explicit in iteritems(document.nametypes): if not explicit: continue labelid = document.nameids[name] if labelid is None: continue node = document.ids[labelid] - if name.isdigit() or node.has_key('refuri') or \ - node.tagname.startswith('desc_'): + if name.isdigit() or 'refuri' in node or \ + node.tagname.startswith('desc_'): # ignore footnote labels, labels automatically generated from a # link and object descriptions continue @@ -540,7 +544,7 @@ class StandardDomain(Domain): 'in ' + env.doc2path(labels[name][0]), node) anonlabels[name] = docname, labelid if node.tagname == 'section': - sectname = clean_astext(node[0]) # node[0] == title node + sectname = clean_astext(node[0]) # node[0] == title node elif node.tagname == 'figure': for n in node: if n.tagname == 'caption': @@ -548,6 +552,13 @@ class StandardDomain(Domain): break else: continue + elif node.tagname == 'image' and node.parent.tagname == 'figure': + for n in node.parent: + if n.tagname == 'caption': + sectname = clean_astext(n) + break + else: + continue elif node.tagname == 'table': for n in node: if n.tagname == 'title': @@ -555,52 +566,105 @@ class StandardDomain(Domain): break else: continue + elif node.tagname == 'container' and node.get('literal_block'): + for n in node: + if n.tagname == 'caption': + sectname = clean_astext(n) + break + else: + continue else: # anonymous-only labels continue labels[name] = docname, labelid, sectname + def build_reference_node(self, fromdocname, builder, + docname, labelid, sectname, + **options): + nodeclass = options.pop('nodeclass', nodes.reference) + newnode = nodeclass('', '', internal=True, **options) + innernode = nodes.emphasis(sectname, sectname) + if docname == fromdocname: + newnode['refid'] = labelid + else: + # set more info in contnode; in case the + # get_relative_uri call raises NoUri, + # the builder will then have to resolve these + contnode = addnodes.pending_xref('') + contnode['refdocname'] = docname + contnode['refsectname'] = sectname + newnode['refuri'] = builder.get_relative_uri( + fromdocname, docname) + if labelid: + newnode['refuri'] += '#' + labelid + newnode.append(innernode) + return newnode + def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): if typ == 'ref': if node['refexplicit']: # reference to anonymous label; the reference uses # the supplied link caption - docname, labelid = self.data['anonlabels'].get(target, ('','')) + docname, labelid = self.data['anonlabels'].get(target, ('', '')) sectname = node.astext() else: # reference to named label; the final node will # contain the section name after the label docname, labelid, sectname = self.data['labels'].get(target, - ('','','')) + ('', '', '')) + if not docname: + return None + + return self.build_reference_node(fromdocname, builder, + docname, labelid, sectname) + elif typ == 'numref': + docname, labelid = self.data['anonlabels'].get(target, ('', '')) if not docname: return None - newnode = nodes.reference('', '', internal=True) - innernode = nodes.emphasis(sectname, sectname) - if docname == fromdocname: - newnode['refid'] = labelid + + if env.config.numfig is False: + env.warn(fromdocname, 'numfig is disabled. :numref: is ignored.') + return contnode + + try: + target = env.get_doctree(docname).ids[labelid] + figtype = get_figtype(target) + figure_id = target['ids'][0] + fignumber = env.toc_fignumbers[docname][figtype][figure_id] + except (KeyError, IndexError): + return None + + title = contnode.astext() + if labelid == title: + prefix = env.config.numfig_prefix.get(figtype, '') + title = prefix.replace('%s', '#') + newtitle = prefix % '.'.join(map(str, fignumber)) else: - # set more info in contnode; in case the - # get_relative_uri call raises NoUri, - # the builder will then have to resolve these - contnode = addnodes.pending_xref('') - contnode['refdocname'] = docname - contnode['refsectname'] = sectname - newnode['refuri'] = builder.get_relative_uri( - fromdocname, docname) - if labelid: - newnode['refuri'] += '#' + labelid - newnode.append(innernode) - return newnode + newtitle = title.replace('#', '.'.join(map(str, fignumber))) + + return self.build_reference_node(fromdocname, builder, + docname, labelid, newtitle, + nodeclass=addnodes.number_reference, + title=title) elif typ == 'keyword': # keywords are oddballs: they are referenced by named labels - docname, labelid, _ = self.data['labels'].get(target, ('','','')) + docname, labelid, _ = self.data['labels'].get(target, ('', '', '')) if not docname: return None return make_refnode(builder, fromdocname, docname, labelid, contnode) elif typ == 'option': - progname = node.get('refprogram', '') + target = target.strip() + # most obvious thing: we are a flag option without program + if target.startswith(('-', '/', '+')): + progname = node.get('std:program') + else: + try: + progname, target = re.split(r' (?=-|--|/|\+)', target, 1) + except ValueError: + return None + progname = ws_re.sub('-', progname.strip()) docname, labelid = self.data['progoptions'].get((progname, target), ('', '')) if not docname: @@ -620,17 +684,39 @@ class StandardDomain(Domain): return make_refnode(builder, fromdocname, docname, labelid, contnode) + def resolve_any_xref(self, env, fromdocname, builder, target, + node, contnode): + results = [] + ltarget = target.lower() # :ref: lowercases its target automatically + for role in ('ref', 'option'): # do not try "keyword" + res = self.resolve_xref(env, fromdocname, builder, role, + ltarget if role == 'ref' else target, + node, contnode) + if res: + results.append(('std:' + role, res)) + # all others + for objtype in self.object_types: + key = (objtype, target) + if objtype == 'term': + key = (objtype, ltarget) + if key in self.data['objects']: + docname, labelid = self.data['objects'][key] + results.append(('std:' + self.role_for_objtype(objtype), + make_refnode(builder, fromdocname, docname, + labelid, contnode))) + return results + def get_objects(self): - for (prog, option), info in self.data['progoptions'].iteritems(): + for (prog, option), info in iteritems(self.data['progoptions']): yield (option, option, 'option', info[0], info[1], 1) - for (type, name), info in self.data['objects'].iteritems(): + for (type, name), info in iteritems(self.data['objects']): yield (name, name, type, info[0], info[1], self.object_types[type].attrs['searchprio']) - for name, info in self.data['labels'].iteritems(): + for name, info in iteritems(self.data['labels']): yield (name, info[2], 'label', info[0], info[1], -1) # add anonymous-only labels as well non_anon_labels = set(self.data['labels']) - for name, info in self.data['anonlabels'].iteritems(): + for name, info in iteritems(self.data['anonlabels']): if name not in non_anon_labels: yield (name, name, 'label', info[0], info[1], -1) |