summaryrefslogtreecommitdiff
path: root/sphinx/domains/std.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/domains/std.py')
-rw-r--r--sphinx/domains/std.py220
1 files changed, 153 insertions, 67 deletions
diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py
index 9a7937aa..1cd71c41 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_node = env.get_doctree(docname).ids[labelid]
+ figtype = get_figtype(target_node)
+ figure_id = target_node['ids'][0]
+ fignumber = env.toc_fignumbers[docname][figtype][figure_id]
+ except (KeyError, IndexError):
+ return None
+
+ title = contnode.astext()
+ if target == title:
+ prefix = env.config.numfig_format.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)