diff options
Diffstat (limited to 'sphinx/domains/std.py')
| -rw-r--r-- | sphinx/domains/std.py | 497 |
1 files changed, 497 insertions, 0 deletions
diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py new file mode 100644 index 00000000..48c96640 --- /dev/null +++ b/sphinx/domains/std.py @@ -0,0 +1,497 @@ +# -*- coding: utf-8 -*- +""" + sphinx.domains.std + ~~~~~~~~~~~~~~~~~~ + + The standard domain. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from docutils import nodes +from docutils.parsers.rst import directives + +from sphinx import addnodes +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.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*.*?)(?=,\s+(?:/|-|--)|$)') + + +class GenericObject(ObjectDescription): + """ + A generic x-ref directive registered with Sphinx.add_object_type(). + """ + indextemplate = '' + parse_node = None + + def handle_signature(self, sig, signode): + if self.parse_node: + name = self.parse_node(self.env, sig, signode) + else: + signode.clear() + signode += addnodes.desc_name(sig, sig) + # normalize whitespace like XRefRole does + name = ws_re.sub('', sig) + return name + + def add_target_and_index(self, name, sig, signode): + targetname = '%s-%s' % (self.objtype, name) + signode['ids'].append(targetname) + self.state.document.note_explicit_target(signode) + if self.indextemplate: + colon = self.indextemplate.find(':') + if colon != -1: + indextype = self.indextemplate[:colon].strip() + indexentry = self.indextemplate[colon+1:].strip() % (name,) + else: + indextype = 'single' + indexentry = self.indextemplate % (name,) + self.indexnode['entries'].append((indextype, indexentry, + targetname, targetname)) + self.env.domaindata['std']['objects'][self.objtype, name] = \ + self.env.docname, targetname + + +class EnvVar(GenericObject): + indextemplate = l_('environment variable; %s') + + +class EnvVarXRefRole(XRefRole): + """ + Cross-referencing role for environment variables (adds an index entry). + """ + + def result_nodes(self, document, env, node, is_ref): + if not is_ref: + return [node], [] + varname = node['reftarget'] + tgtid = 'index-%s' % env.new_serialno('index') + indexnode = addnodes.index() + indexnode['entries'] = [ + ('single', varname, tgtid, varname), + ('single', _('environment variable; %s') % varname, tgtid, varname) + ] + targetnode = nodes.target('', '', ids=[tgtid]) + document.note_explicit_target(targetnode) + return [indexnode, targetnode, node], [] + + +class Target(Directive): + """ + Generic target for user-defined cross-reference types. + """ + indextemplate = '' + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {} + + def run(self): + env = self.state.document.settings.env + # normalize whitespace in fullname like XRefRole does + fullname = ws_re.sub(' ', self.arguments[0].strip()) + targetname = '%s-%s' % (self.name, fullname) + node = nodes.target('', '', ids=[targetname]) + self.state.document.note_explicit_target(node) + ret = [node] + if self.indextemplate: + indexentry = self.indextemplate % (fullname,) + indextype = 'single' + colon = indexentry.find(':') + if colon != -1: + indextype = indexentry[:colon].strip() + indexentry = indexentry[colon+1:].strip() + inode = addnodes.index(entries=[(indextype, indexentry, + targetname, targetname)]) + ret.insert(0, inode) + name = self.name + if ':' in self.name: + _, name = self.name.split(':', 1) + env.domaindata['std']['objects'][name, fullname] = \ + env.docname, targetname + return ret + + +class Cmdoption(ObjectDescription): + """ + Description of a command-line option (.. cmdoption). + """ + + def handle_signature(self, sig, signode): + """Transform an option description into RST nodes.""" + count = 0 + firstname = '' + for m in option_desc_re.finditer(sig): + optname, args = m.groups() + if count: + signode += addnodes.desc_addname(', ', ', ') + signode += addnodes.desc_name(optname, optname) + signode += addnodes.desc_addname(args, args) + if not count: + firstname = optname + count += 1 + if not firstname: + raise ValueError + return firstname + + def add_target_and_index(self, name, sig, signode): + targetname = name.replace('/', '-') + currprogram = self.env.temp_data.get('std:program') + if currprogram: + targetname = '-' + currprogram + targetname + targetname = 'cmdoption' + targetname + signode['ids'].append(targetname) + self.state.document.note_explicit_target(signode) + self.indexnode['entries'].append( + ('pair', _('%scommand line option; %s') % + ((currprogram and currprogram + ' ' or ''), sig), + targetname, targetname)) + self.env.domaindata['std']['progoptions'][currprogram, name] = \ + self.env.docname, targetname + + +class Program(Directive): + """ + Directive to name the program for which options are documented. + """ + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {} + + def run(self): + env = self.state.document.settings.env + program = ws_re.sub('-', self.arguments[0].strip()) + if program == 'None': + env.temp_data['std:program'] = None + else: + env.temp_data['std:program'] = program + return [] + + +class OptionXRefRole(XRefRole): + innernodeclass = addnodes.literal_emphasis + + 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 = re.split(' (?=-|--|/)', title, 1) + program = ws_re.sub('-', program) + target = target.strip() + elif ' ' in target: + program, target = re.split(' (?=-|--|/)', target, 1) + program = ws_re.sub('-', program) + refnode['refprogram'] = program + return title, target + + +class Glossary(Directive): + """ + Directive to create a glossary with cross-reference targets + for :term: roles. + """ + + has_content = True + required_arguments = 0 + optional_arguments = 0 + final_argument_whitespace = False + option_spec = { + 'sorted': directives.flag, + } + + def run(self): + env = self.state.document.settings.env + objects = env.domaindata['std']['objects'] + gloss_entries = env.temp_data.setdefault('gloss_entries', set()) + node = addnodes.glossary() + node.document = self.state.document + self.state.nested_parse(self.content, self.content_offset, node) + + # the content should be definition lists + dls = [child for child in node + if isinstance(child, nodes.definition_list)] + # now, extract definition terms to enable cross-reference creation + new_dl = nodes.definition_list() + new_dl['classes'].append('glossary') + items = [] + for dl in dls: + for li in dl.children: + if not li.children or not isinstance(li[0], nodes.term): + continue + termtext = li.children[0].astext() + new_id = 'term-' + nodes.make_id(termtext) + if new_id in gloss_entries: + new_id = 'term-' + str(len(gloss_entries)) + gloss_entries.add(new_id) + li[0]['names'].append(new_id) + li[0]['ids'].append(new_id) + objects['term', termtext.lower()] = env.docname, new_id + # add an index entry too + indexnode = addnodes.index() + indexnode['entries'] = [('single', termtext, new_id, termtext)] + li.insert(0, indexnode) + items.append((termtext, li)) + if 'sorted' in self.options: + items.sort(key=lambda x: x[0].lower()) + new_dl.extend(item[1] for item in items) + node.children = [new_dl] + return [node] + + +token_re = re.compile('`([a-z_][a-z0-9_]*)`') + +def token_xrefs(text): + retnodes = [] + pos = 0 + for m in token_re.finditer(text): + if m.start() > pos: + txt = text[pos:m.start()] + retnodes.append(nodes.Text(txt, txt)) + refnode = addnodes.pending_xref( + m.group(1), reftype='token', refdomain='std', reftarget=m.group(1)) + refnode += nodes.literal(m.group(1), m.group(1), classes=['xref']) + retnodes.append(refnode) + pos = m.end() + if pos < len(text): + retnodes.append(nodes.Text(text[pos:], text[pos:])) + return retnodes + + +class ProductionList(Directive): + """ + Directive to list grammar productions. + """ + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {} + + def run(self): + env = self.state.document.settings.env + objects = env.domaindata['std']['objects'] + node = addnodes.productionlist() + messages = [] + i = 0 + + for rule in self.arguments[0].split('\n'): + if i == 0 and ':' not in rule: + # production group + continue + i += 1 + try: + name, tokens = rule.split(':', 1) + except ValueError: + break + subnode = addnodes.production() + subnode['tokenname'] = name.strip() + if subnode['tokenname']: + idname = 'grammar-token-%s' % subnode['tokenname'] + if idname not in self.state.document.ids: + subnode['ids'].append(idname) + self.state.document.note_implicit_target(subnode, subnode) + objects['token', subnode['tokenname']] = env.docname, idname + subnode.extend(token_xrefs(tokens)) + node.append(subnode) + return [node] + messages + + +class StandardDomain(Domain): + """ + Domain for all objects that don't fit into another domain or are added + via the application interface. + """ + + name = 'std' + label = 'Default' + + object_types = { + 'term': ObjType(l_('glossary term'), 'term', searchprio=-1), + 'token': ObjType(l_('grammar token'), 'token', searchprio=-1), + 'label': ObjType(l_('reference label'), 'ref', searchprio=-1), + 'envvar': ObjType(l_('environment variable'), 'envvar'), + 'cmdoption': ObjType(l_('program option'), 'option'), + } + + directives = { + 'program': Program, + 'cmdoption': Cmdoption, # old name for backwards compatibility + 'option': Cmdoption, + 'envvar': EnvVar, + 'glossary': Glossary, + 'productionlist': ProductionList, + } + roles = { + 'option': OptionXRefRole(innernodeclass=addnodes.literal_emphasis), + 'envvar': EnvVarXRefRole(), + # links to tokens in grammar productions + 'token': XRefRole(), + # links to terms in glossary + 'term': XRefRole(lowercase=True, innernodeclass=nodes.emphasis), + # links to headings or arbitrary labels + 'ref': XRefRole(lowercase=True, innernodeclass=nodes.emphasis), + # links to labels, without a different title + 'keyword': XRefRole(), + } + + initial_data = { + 'progoptions': {}, # (program, name) -> docname, labelid + 'objects': {}, # (type, name) -> docname, labelid + 'labels': { # labelname -> docname, labelid, sectionname + 'genindex': ('genindex', '', l_('Index')), + 'modindex': ('py-modindex', '', l_('Module Index')), + 'search': ('search', '', l_('Search Page')), + }, + 'anonlabels': { # labelname -> docname, labelid + 'genindex': ('genindex', ''), + 'modindex': ('py-modindex', ''), + 'search': ('search', ''), + }, + } + + def clear_doc(self, docname): + for key, (fn, _) in self.data['progoptions'].items(): + if fn == docname: + del self.data['progoptions'][key] + for key, (fn, _) in self.data['objects'].items(): + if fn == docname: + del self.data['objects'][key] + for key, (fn, _, _) in self.data['labels'].items(): + if fn == docname: + del self.data['labels'][key] + for key, (fn, _) in self.data['anonlabels'].items(): + if fn == docname: + del self.data['anonlabels'][key] + + def process_doc(self, env, docname, document): + labels, anonlabels = self.data['labels'], self.data['anonlabels'] + for name, explicit in document.nametypes.iteritems(): + 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_'): + # ignore footnote labels, labels automatically generated from a + # link and object descriptions + continue + if name in labels: + env.warn(docname, 'duplicate label %s, ' % name + + 'other instance in ' + env.doc2path(labels[name][0]), + node.line) + anonlabels[name] = docname, labelid + if node.tagname == 'section': + sectname = clean_astext(node[0]) # node[0] == title node + elif node.tagname == 'figure': + 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 resolve_xref(self, env, fromdocname, builder, + typ, target, node, contnode): + if typ == 'ref': + #refdoc = node.get('refdoc', fromdocname) + if node['refexplicit']: + # reference to anonymous label; the reference uses + # the supplied link caption + docname, labelid = self.data['anonlabels'].get(target, ('','')) + sectname = node.astext() + # XXX warn somehow if not resolved by intersphinx + #if not docname: + # env.warn(refdoc, 'undefined label: %s' % + # target, node.line) + else: + # reference to named label; the final node will + # contain the section name after the label + docname, labelid, sectname = self.data['labels'].get(target, + ('','','')) + # XXX warn somehow if not resolved by intersphinx + #if not docname: + # env.warn(refdoc, + # 'undefined label: %s' % target + ' -- if you ' + # 'don\'t give a link caption the label must ' + # 'precede a section header.', node.line) + if not docname: + return None + newnode = nodes.reference('', '') + 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 + elif typ == 'keyword': + # keywords are oddballs: they are referenced by named labels + docname, labelid, _ = self.data['labels'].get(target, ('','','')) + if not docname: + #env.warn(refdoc, 'unknown keyword: %s' % target) + return None + else: + return make_refnode(builder, fromdocname, docname, + labelid, contnode) + elif typ == 'option': + progname = node['refprogram'] + docname, labelid = self.data['progoptions'].get((progname, target), + ('', '')) + if not docname: + return None + else: + return make_refnode(builder, fromdocname, docname, + labelid, contnode) + else: + docname, labelid = self.data['objects'].get((typ, target), ('', '')) + if not docname: + if typ == 'term': + env.warn(node.get('refdoc', fromdocname), + 'term not in glossary: %s' % target, node.line) + return None + else: + return make_refnode(builder, fromdocname, docname, + labelid, contnode) + + def get_objects(self): + for (prog, option), info in self.data['progoptions'].iteritems(): + yield (option, option, 'option', info[0], info[1], 1) + for (type, name), info in self.data['objects'].iteritems(): + yield (name, name, type, info[0], info[1], + self.object_types[type].attrs['searchprio']) + for name, info in self.data['labels'].iteritems(): + yield (name, info[2], 'label', info[0], info[1], -1) |
