diff options
| author | Georg Brandl <georg@python.org> | 2011-09-22 10:42:09 +0200 |
|---|---|---|
| committer | Georg Brandl <georg@python.org> | 2011-09-22 10:42:09 +0200 |
| commit | c5599ea28eeac416ab4646a9ef1b9eb3d783f8b7 (patch) | |
| tree | 76a1db0ec536f130b4394c48be68fee820d8024b /sphinx/domains | |
| parent | ccef6db7cb04db18258c41a4fd5af6384525737a (diff) | |
| parent | 7cabc9f2a989a78753f9ddcbfd941dd3fc45e106 (diff) | |
| download | sphinx-c5599ea28eeac416ab4646a9ef1b9eb3d783f8b7.tar.gz | |
Merge with 1.0
Diffstat (limited to 'sphinx/domains')
| -rw-r--r-- | sphinx/domains/__init__.py | 29 | ||||
| -rw-r--r-- | sphinx/domains/c.py | 2 | ||||
| -rw-r--r-- | sphinx/domains/cpp.py | 91 | ||||
| -rw-r--r-- | sphinx/domains/javascript.py | 6 | ||||
| -rw-r--r-- | sphinx/domains/python.py | 107 | ||||
| -rw-r--r-- | sphinx/domains/rst.py | 9 | ||||
| -rw-r--r-- | sphinx/domains/std.py | 122 |
7 files changed, 257 insertions, 109 deletions
diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py index 1d04a593..c48568eb 100644 --- a/sphinx/domains/__init__.py +++ b/sphinx/domains/__init__.py @@ -66,9 +66,8 @@ class Index(object): self.domain = domain def generate(self, docnames=None): - """ - Return entries for the index given by *name*. If *docnames* is given, - restrict to entries referring to these docnames. + """Return entries for the index given by *name*. If *docnames* is + given, restrict to entries referring to these docnames. The return value is a tuple of ``(content, collapse)``, where *collapse* is a boolean that determines if sub-entries should start collapsed (for @@ -160,8 +159,7 @@ class Domain(object): self.objtypes_for_role = self._role2type.get def role(self, name): - """ - Return a role adapter function that always gives the registered + """Return a role adapter function that always gives the registered role its full name ('domain:name') as the first argument. """ if name in self._role_cache: @@ -177,8 +175,7 @@ class Domain(object): return role_adapter def directive(self, name): - """ - Return a directive adapter class that always gives the registered + """Return a directive adapter class that always gives the registered directive its full name ('domain:name') as ``self.name``. """ if name in self._directive_cache: @@ -197,21 +194,16 @@ class Domain(object): # methods that should be overwritten def clear_doc(self, docname): - """ - Remove traces of a document in the domain-specific inventories. - """ + """Remove traces of a document in the domain-specific inventories.""" pass def process_doc(self, env, docname, document): - """ - Process a document after it is read by the environment. - """ + """Process a document after it is read by the environment.""" pass def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): - """ - Resolve the ``pending_xref`` *node* with the given *typ* and *target*. + """Resolve the pending_xref *node* with the given *typ* and *target*. This method should return a new node, to replace the xref node, containing the *contnode* which is the markup content of the @@ -227,8 +219,7 @@ class Domain(object): pass def get_objects(self): - """ - Return an iterable of "object descriptions", which are tuples with + """Return an iterable of "object descriptions", which are tuples with five items: * `name` -- fully qualified name @@ -247,9 +238,7 @@ class Domain(object): return [] def get_type_name(self, type, primary=False): - """ - Return full name for given ObjType. - """ + """Return full name for given ObjType.""" if primary: return type.lname return _('%s %s') % (self.label, type.lname) diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 48fbb36f..b0dd332b 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -168,7 +168,7 @@ class CObject(ObjectDescription): indextext = self.get_index_text(name) if indextext: - self.indexnode['entries'].append(('single', indextext, name, name)) + self.indexnode['entries'].append(('single', indextext, name, '')) def before_content(self): self.typename_set = False diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index dac0106b..0d33f59d 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -21,6 +21,7 @@ from sphinx.domains import Domain, ObjType from sphinx.directives import ObjectDescription from sphinx.util.nodes import make_refnode from sphinx.util.compat import Directive +from sphinx.util.docfields import Field, GroupedField _identifier_re = re.compile(r'(~?\b[a-zA-Z_][a-zA-Z0-9_]*)\b') @@ -28,6 +29,8 @@ _whitespace_re = re.compile(r'\s+(?u)') _string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) _visibility_re = re.compile(r'\b(public|private|protected)\b') +_array_def_re = re.compile(r'\[\s*(.+?)?\s*\]') +_template_arg_re = re.compile(r'[^,>]+') _operator_re = re.compile(r'''(?x) \[\s*\] | \(\s*\) @@ -109,7 +112,7 @@ class DefinitionError(Exception): return self.description def __str__(self): - return unicode(self.encode('utf-8')) + return unicode(self).encode('utf-8') class DefExpr(object): @@ -131,17 +134,21 @@ class DefExpr(object): def __ne__(self, other): return not self.__eq__(other) + __hash__ = None + def clone(self): - """Close a definition expression node""" + """Clone a definition expression node.""" return deepcopy(self) def get_id(self): - """Returns the id for the node""" + """Return the id for the node.""" return u'' def get_name(self): - """Returns the name. Returns either `None` or a node with - a name you might call :meth:`split_owner` on. + """Return the name. + + Returns either `None` or a node with a name you might call + :meth:`split_owner` on. """ return None @@ -154,7 +161,7 @@ class DefExpr(object): return None, self def prefix(self, prefix): - """Prefixes a name node (a node returned by :meth:`get_name`).""" + """Prefix a name node (a node returned by :meth:`get_name`).""" raise NotImplementedError() def __str__(self): @@ -235,6 +242,18 @@ class TemplateDefExpr(PrimaryDefExpr): return u'%s<%s>' % (self.typename, u', '.join(map(unicode, self.args))) +class ConstantTemplateArgExpr(PrimaryDefExpr): + + def __init__(self, arg): + self.arg = arg + + def get_id(self): + return self.arg.replace(u' ', u'-') + + def __unicode__(self): + return unicode(self.arg) + + class WrappingDefExpr(DefExpr): def __init__(self, typename): @@ -269,6 +288,22 @@ class PtrDefExpr(WrappingDefExpr): return u'%s*' % self.typename +class ArrayDefExpr(WrappingDefExpr): + + def __init__(self, typename, size_hint=None): + WrappingDefExpr.__init__(self, typename) + self.size_hint = size_hint + + def get_id(self): + return self.typename.get_id() + u'A' + + def __unicode__(self): + return u'%s[%s]' % ( + self.typename, + self.size_hint is not None and unicode(self.size_hint) or u'' + ) + + class RefDefExpr(WrappingDefExpr): def get_id(self): @@ -523,8 +558,15 @@ class DefinitionParser(object): return CastOpDefExpr(type) def _parse_name(self): + return self._parse_name_or_template_arg(False) + + def _parse_name_or_template_arg(self, in_template): if not self.match(_identifier_re): - self.fail('expected name') + if not in_template: + self.fail('expected name') + if not self.match(_template_arg_re): + self.fail('expected name or constant template argument') + return ConstantTemplateArgExpr(self.matched_text.strip()) identifier = self.matched_text # strictly speaking, operators are not regular identifiers @@ -558,6 +600,8 @@ class DefinitionParser(object): expr = ConstDefExpr(expr) elif self.skip_string('*'): expr = PtrDefExpr(expr) + elif self.match(_array_def_re): + expr = ArrayDefExpr(expr, self.last_match.group(1)) elif self.skip_string('&'): expr = RefDefExpr(expr) else: @@ -591,8 +635,8 @@ class DefinitionParser(object): rv = ModifierDefExpr(NameDefExpr(typename), modifiers) return self._attach_crefptr(rv, is_const) - def _parse_type_expr(self): - typename = self._parse_name() + def _parse_type_expr(self, in_template=False): + typename = self._parse_name_or_template_arg(in_template) self.skip_ws() if not self.skip_string('<'): return typename @@ -641,7 +685,7 @@ class DefinitionParser(object): (result and not self.skip_string('::')) or \ self.eof: break - result.append(self._parse_type_expr()) + result.append(self._parse_type_expr(in_template)) if not result: self.fail('expected type') @@ -689,6 +733,13 @@ class DefinitionParser(object): self.fail('expected comma between arguments') self.skip_ws() + if self.skip_string('...'): + args.append(ArgumentDefExpr(None, '...', None)) + if self.skip_string(')'): + break + else: + self.fail('expected closing parenthesis after ellipses') + argtype = self._parse_type() argname = default = None self.skip_ws() @@ -788,6 +839,17 @@ class DefinitionParser(object): class CPPObject(ObjectDescription): """Description of a C++ language object.""" + doc_field_types = [ + GroupedField('parameter', label=l_('Parameters'), + names=('param', 'parameter', 'arg', 'argument'), + can_collapse=True), + GroupedField('exceptions', label=l_('Throws'), rolename='cpp:class', + names=('throws', 'throw', 'exception'), + can_collapse=True), + Field('returnvalue', label=l_('Returns'), has_arg=False, + names=('returns', 'return')), + ] + def attach_name(self, node, name): owner, name = name.split_owner() varname = unicode(name) @@ -829,7 +891,7 @@ class CPPObject(ObjectDescription): indextext = self.get_index_text(name) if indextext: - self.indexnode['entries'].append(('single', indextext, theid, name)) + self.indexnode['entries'].append(('single', indextext, theid, '')) def before_content(self): lastname = self.names and self.names[-1] @@ -977,8 +1039,9 @@ class CPPFunctionObject(CPPObject): class CPPCurrentNamespace(Directive): - """This directive is just to tell Sphinx that we're documenting - stuff in namespace foo. + """ + This directive is just to tell Sphinx that we're documenting stuff in + namespace foo. """ has_content = False @@ -1077,7 +1140,7 @@ class CPPDomain(Domain): node.line) return None - parent = node['cpp:parent'] + parent = node.get('cpp:parent', None) rv = _create_refnode(expr) if rv is not None or parent is None: diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index e53eb5fd..09d555ad 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -98,7 +98,7 @@ class JSObject(ObjectDescription): if indextext: self.indexnode['entries'].append(('single', indextext, fullname.replace('$', '_S_'), - fullname)) + '')) def get_index_text(self, objectname, name_obj): name, obj = name_obj @@ -128,11 +128,13 @@ class JSCallable(JSObject): can_collapse=True), Field('returnvalue', label=l_('Returns'), has_arg=False, names=('returns', 'return')), + Field('returntype', label=l_('Return type'), has_arg=False, + names=('rtype',)), ] class JSConstructor(JSCallable): - """Like a callable but with a different prefix""" + """Like a callable but with a different prefix.""" display_prefix = 'class ' diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 0220afd0..4004599c 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -88,6 +88,7 @@ class PyObject(ObjectDescription): option_spec = { 'noindex': directives.flag, 'module': directives.unchanged, + 'annotation': directives.unchanged, } doc_field_types = [ @@ -110,22 +111,21 @@ class PyObject(ObjectDescription): ] def get_signature_prefix(self, sig): - """ - May return a prefix to put before the object name in the signature. + """May return a prefix to put before the object name in the + signature. """ return '' def needs_arglist(self): - """ - May return true if an empty argument list is to be generated even if + """May return true if an empty argument list is to be generated even if the document contains none. """ return False def handle_signature(self, sig, signode): - """ - Transform a Python signature into RST nodes. - Returns (fully qualified name of the thing, classname if any). + """Transform a Python signature into RST nodes. + + Return (fully qualified name of the thing, classname if any). If inside a class, the current class name is handled intelligently: * it is stripped from the displayed name if present @@ -181,6 +181,8 @@ class PyObject(ObjectDescription): nodetext = modname + '.' signode += addnodes.desc_addname(nodetext, nodetext) + anno = self.options.get('annotation') + signode += addnodes.desc_name(name, name) if not arglist: if self.needs_arglist(): @@ -188,16 +190,19 @@ class PyObject(ObjectDescription): signode += addnodes.desc_parameterlist() if retann: signode += addnodes.desc_returns(retann, retann) + if anno: + signode += addnodes.desc_annotation(' ' + anno, ' ' + anno) return fullname, name_prefix + _pseudo_parse_arglist(signode, arglist) if retann: signode += addnodes.desc_returns(retann, retann) + if anno: + signode += addnodes.desc_annotation(' ' + anno, ' ' + anno) return fullname, name_prefix def get_index_text(self, modname, name): - """ - Return the text for the index entry of the object. - """ + """Return the text for the index entry of the object.""" raise NotImplementedError('must be implemented in subclasses') def add_target_and_index(self, name_cls, sig, signode): @@ -224,7 +229,7 @@ class PyObject(ObjectDescription): indextext = self.get_index_text(modname, name_cls) if indextext: self.indexnode['entries'].append(('single', indextext, - fullname, fullname)) + fullname, '')) def before_content(self): # needed for automatic qualification of members (reset in subclasses) @@ -360,6 +365,38 @@ class PyClassmember(PyObject): self.clsname_set = True +class PyDecoratorMixin(object): + """ + Mixin for decorator directives. + """ + def handle_signature(self, sig, signode): + ret = super(PyDecoratorMixin, self).handle_signature(sig, signode) + signode.insert(0, addnodes.desc_addname('@', '@')) + return ret + + def needs_arglist(self): + return False + + +class PyDecoratorFunction(PyDecoratorMixin, PyModulelevel): + """ + Directive to mark functions meant to be used as decorators. + """ + def run(self): + # a decorator function is a function after all + self.name = 'py:function' + return PyModulelevel.run(self) + + +class PyDecoratorMethod(PyDecoratorMixin, PyClassmember): + """ + Directive to mark methods meant to be used as decorators. + """ + def run(self): + self.name = 'py:method' + return PyClassmember.run(self) + + class PyModule(Directive): """ Directive to mark description of a new module. @@ -386,26 +423,17 @@ class PyModule(Directive): env.domaindata['py']['modules'][modname] = \ (env.docname, self.options.get('synopsis', ''), self.options.get('platform', ''), 'deprecated' in self.options) - # make a duplicate entry in 'objects' to facilitate searching for - # the module in PythonDomain.find_obj() + # make a duplicate entry in 'objects' to facilitate searching for the + # module in PythonDomain.find_obj() env.domaindata['py']['objects'][modname] = (env.docname, 'module') - targetnode = nodes.target('', '', ids=['module-' + modname], - ismod=True) + targetnode = nodes.target('', '', ids=['module-' + modname], ismod=True) self.state.document.note_explicit_target(targetnode) + # the platform and synopsis aren't printed; in fact, they are only used + # in the modindex currently ret.append(targetnode) - # XXX this behavior of the module directive is a mess... - if 'platform' in self.options: - platform = self.options['platform'] - node = nodes.paragraph() - node += nodes.emphasis('', _('Platforms: ')) - node += nodes.Text(platform, platform) - ret.append(node) - # the synopsis isn't printed; in fact, it is only used in the - # modindex currently - if not noindex: indextext = _('%s (module)') % modname inode = addnodes.index(entries=[('single', indextext, - 'module-' + modname, modname)]) + 'module-' + modname, '')]) ret.append(inode) return ret @@ -540,16 +568,18 @@ class PythonDomain(Domain): } directives = { - 'function': PyModulelevel, - 'data': PyModulelevel, - 'class': PyClasslike, - 'exception': PyClasslike, - 'method': PyClassmember, - 'classmethod': PyClassmember, - 'staticmethod': PyClassmember, - 'attribute': PyClassmember, - 'module': PyModule, - 'currentmodule': PyCurrentModule, + 'function': PyModulelevel, + 'data': PyModulelevel, + 'class': PyClasslike, + 'exception': PyClasslike, + 'method': PyClassmember, + 'classmethod': PyClassmember, + 'staticmethod': PyClassmember, + 'attribute': PyClassmember, + 'module': PyModule, + 'currentmodule': PyCurrentModule, + 'decorator': PyDecoratorFunction, + 'decoratormethod': PyDecoratorMethod, } roles = { 'data': PyXRefRole(), @@ -579,9 +609,8 @@ class PythonDomain(Domain): del self.data['modules'][modname] def find_obj(self, env, modname, classname, name, type, searchmode=0): - """ - Find a Python object for "name", perhaps using the given module and/or - classname. Returns a list of (name, object entry) tuples. + """Find a Python object for "name", perhaps using the given module + and/or classname. Returns a list of (name, object entry) tuples. """ # skip parens if name[-2:] == '()': diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py index 6b3e05ee..e67f827e 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)) + targetname, '')) def get_index_text(self, objectname, name): if self.objtype == 'directive': @@ -59,9 +59,10 @@ class ReSTMarkup(ObjectDescription): def parse_directive(d): - """ - Parses a directive signature. Returns (directive, arguments) string tuple. - if no arguments are given, returns (directive, ''). + """Parse a directive signature. + + Returns (directive, arguments) string tuple. If no arguments are given, + returns (directive, ''). """ dir = d.strip() if not dir.startswith('.'): diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 1fd015ac..9d5b0b0f 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -14,6 +14,7 @@ import unicodedata from docutils import nodes from docutils.parsers.rst import directives +from docutils.statemachine import ViewList from sphinx import addnodes from sphinx.roles import XRefRole @@ -60,7 +61,7 @@ class GenericObject(ObjectDescription): indextype = 'single' indexentry = self.indextemplate % (name,) self.indexnode['entries'].append((indextype, indexentry, - targetname, targetname)) + targetname, '')) self.env.domaindata['std']['objects'][self.objtype, name] = \ self.env.docname, targetname @@ -81,8 +82,8 @@ class EnvVarXRefRole(XRefRole): tgtid = 'index-%s' % env.new_serialno('index') indexnode = addnodes.index() indexnode['entries'] = [ - ('single', varname, tgtid, varname), - ('single', _('environment variable; %s') % varname, tgtid, varname) + ('single', varname, tgtid, ''), + ('single', _('environment variable; %s') % varname, tgtid, '') ] targetnode = nodes.target('', '', ids=[tgtid]) document.note_explicit_target(targetnode) @@ -117,7 +118,7 @@ class Target(Directive): indextype = indexentry[:colon].strip() indexentry = indexentry[colon+1:].strip() inode = addnodes.index(entries=[(indextype, indexentry, - targetname, targetname)]) + targetname, '')]) ret.insert(0, inode) name = self.name if ':' in self.name: @@ -160,7 +161,7 @@ class Cmdoption(ObjectDescription): self.indexnode['entries'].append( ('pair', _('%scommand line option; %s') % ((currprogram and currprogram + ' ' or ''), sig), - targetname, targetname)) + targetname, '')) self.env.domaindata['std']['progoptions'][currprogram, name] = \ self.env.docname, targetname @@ -206,8 +207,8 @@ class OptionXRefRole(XRefRole): class Glossary(Directive): """ - Directive to create a glossary with cross-reference targets - for :term: roles. + Directive to create a glossary with cross-reference targets for :term: + roles. """ has_content = True @@ -224,37 +225,100 @@ class Glossary(Directive): 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') + + # This directive implements a custom format of the reST definition list + # that allows multiple lines of terms before the definition. This is + # easy to parse since we know that the contents of the glossary *must + # be* a definition list. + + # first, collect single entries + entries = [] + in_definition = True + was_empty = True + messages = [] + for line, (source, lineno) in zip(self.content, self.content.items): + # empty line -> add to last definition + if not line: + if in_definition and entries: + entries[-1][1].append('', source, lineno) + was_empty = True + continue + # unindented line -> a term + if line and not line[0].isspace(): + # first term of definition + if in_definition: + if not was_empty: + messages.append(self.state.reporter.system_message( + 2, 'glossary term must be preceded by empty line', + source=source, line=lineno)) + entries.append(([(line, source, lineno)], ViewList())) + in_definition = False + # second term and following + else: + if was_empty: + messages.append(self.state.reporter.system_message( + 2, 'glossary terms must not be separated by empty ' + 'lines', source=source, line=lineno)) + entries[-1][0].append((line, source, lineno)) + else: + if not in_definition: + # first line of definition, determines indentation + in_definition = True + indent_len = len(line) - len(line.lstrip()) + entries[-1][1].append(line[indent_len:], source, lineno) + was_empty = False + + # now, parse all the entries into a big definition list 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() + for terms, definition in entries: + termtexts = [] + termnodes = [] + system_messages = [] + ids = [] + for line, source, lineno in terms: + # parse the term with inline markup + res = self.state.inline_text(line, lineno) + system_messages.extend(res[1]) + + # get a text-only representation of the term and register it + # as a cross-reference target + tmp = nodes.paragraph('', '', *res[0]) + termtext = tmp.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) + ids.append(new_id) objects['term', termtext.lower()] = env.docname, new_id + termtexts.append(termtext) # add an index entry too indexnode = addnodes.index() - indexnode['entries'] = [('single', termtext, new_id, termtext)] - li.insert(0, indexnode) - items.append((termtext, li)) + indexnode['entries'] = [('single', termtext, new_id, 'main')] + termnodes.append(indexnode) + termnodes.extend(res[0]) + termnodes.append(addnodes.termsep()) + # make a single "term" node with all the terms, separated by termsep + # nodes (remove the dangling trailing separator) + term = nodes.term('', '', *termnodes[:-1]) + term['ids'].extend(ids) + term['names'].extend(ids) + term += system_messages + + defnode = nodes.definition() + self.state.nested_parse(definition, definition.items[0][1], defnode) + + items.append((termtexts, + nodes.definition_list_item('', term, defnode))) + if 'sorted' in self.options: - items.sort(key=lambda x: unicodedata.normalize('NFD', x[0].lower())) - new_dl.extend(item[1] for item in items) - node.children = [new_dl] - return [node] + items.sort(key=lambda x: + unicodedata.normalize('NFD', x[0][0].lower())) + + dlist = nodes.definition_list() + dlist['classes'].append('glossary') + dlist.extend(item[1] for item in items) + node += dlist + return messages + [node] token_re = re.compile('`([a-z_][a-z0-9_]*)`') |
