From e1e999bdb00dabbcf82c8e3d594bb7bc91d0978d Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 4 May 2008 17:22:31 +0000 Subject: [PATCH] Split directives into a subpackage. --- sphinx/directives.py | 856 ---------------------------------- sphinx/directives/__init__.py | 14 + sphinx/directives/code.py | 91 ++++ sphinx/directives/desc.py | 434 +++++++++++++++++ sphinx/directives/other.py | 355 ++++++++++++++ 5 files changed, 894 insertions(+), 856 deletions(-) delete mode 100644 sphinx/directives.py create mode 100644 sphinx/directives/__init__.py create mode 100644 sphinx/directives/code.py create mode 100644 sphinx/directives/desc.py create mode 100644 sphinx/directives/other.py diff --git a/sphinx/directives.py b/sphinx/directives.py deleted file mode 100644 index 06cb63637..000000000 --- a/sphinx/directives.py +++ /dev/null @@ -1,856 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sphinx.directives - ~~~~~~~~~~~~~~~~~ - - Handlers for additional ReST directives. - - :copyright: 2007-2008 by Georg Brandl. - :license: BSD. -""" - -import re -import sys -import string -import posixpath -from os import path - -from docutils import nodes -from docutils.parsers.rst import directives - -from sphinx import addnodes -from sphinx.util import patfilter -from sphinx.roles import caption_ref_re -from sphinx.util.compat import make_admonition - -ws_re = re.compile(r'\s+') - -# ------ index markup -------------------------------------------------------------- - -entrytypes = [ - 'single', 'pair', 'triple', 'module', 'keyword', 'operator', - 'object', 'exception', 'statement', 'builtin', -] - -def index_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - arguments = arguments[0].split('\n') - env = state.document.settings.env - targetid = 'index-%s' % env.index_num - env.index_num += 1 - targetnode = nodes.target('', '', ids=[targetid]) - state.document.note_explicit_target(targetnode) - indexnode = addnodes.index() - indexnode['entries'] = ne = [] - for entry in arguments: - entry = entry.strip() - for type in entrytypes: - if entry.startswith(type+':'): - value = entry[len(type)+1:].strip() - env.note_index_entry(type, value, targetid, value) - ne.append((type, value, targetid, value)) - break - # shorthand notation for single entries - else: - for value in entry.split(','): - env.note_index_entry('single', value.strip(), targetid, value.strip()) - ne.append(('single', value.strip(), targetid, value.strip())) - return [indexnode, targetnode] - -index_directive.arguments = (1, 0, 1) -directives.register_directive('index', index_directive) - -# ------ information units --------------------------------------------------------- - -def desc_index_text(desctype, currmodule, name): - if desctype == 'function': - if not currmodule: - return '%s() (built-in function)' % name - return '%s() (in module %s)' % (name, currmodule) - elif desctype == 'data': - if not currmodule: - return '%s (built-in variable)' % name - return '%s (in module %s)' % (name, currmodule) - elif desctype == 'class': - return '%s (class in %s)' % (name, currmodule) - elif desctype == 'exception': - return name - elif desctype == 'method': - try: - clsname, methname = name.rsplit('.', 1) - except ValueError: - if currmodule: - return '%s() (in module %s)' % (name, currmodule) - else: - return '%s()' % name - if currmodule: - return '%s() (%s.%s method)' % (methname, currmodule, clsname) - else: - return '%s() (%s method)' % (methname, clsname) - elif desctype == 'attribute': - try: - clsname, attrname = name.rsplit('.', 1) - except ValueError: - if currmodule: - return '%s (in module %s)' % (name, currmodule) - else: - return name - if currmodule: - return '%s (%s.%s attribute)' % (attrname, currmodule, clsname) - else: - return '%s (%s attribute)' % (attrname, clsname) - elif desctype == 'opcode': - return '%s (opcode)' % name - elif desctype == 'cfunction': - return '%s (C function)' % name - elif desctype == 'cmember': - return '%s (C member)' % name - elif desctype == 'cmacro': - return '%s (C macro)' % name - elif desctype == 'ctype': - return '%s (C type)' % name - elif desctype == 'cvar': - return '%s (C variable)' % name - else: - raise ValueError("unhandled descenv: %s" % desctype) - - -# ------ functions to parse a Python or C signature and create desc_* nodes. - -py_sig_re = re.compile(r'''^([\w.]*\.)? # class names - (\w+) \s* # thing name - (?: \((.*)\) )? $ # optionally arguments - ''', re.VERBOSE) - -py_paramlist_re = re.compile(r'([\[\],])') # split at '[', ']' and ',' - -def parse_py_signature(signode, sig, desctype, env): - """ - 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 - * it is added to the full name (return value) if not present - """ - m = py_sig_re.match(sig) - if m is None: - raise ValueError - classname, name, arglist = m.groups() - - add_module = True - if env.currclass: - if classname and classname.startswith(env.currclass): - fullname = classname + name - # class name is given again in the signature - classname = classname[len(env.currclass):].lstrip('.') - add_module = False - elif classname: - # class name is given in the signature, but different - fullname = env.currclass + '.' + classname + name - else: - # class name is not given in the signature - fullname = env.currclass + '.' + name - add_module = False - else: - fullname = classname and classname + name or name - - if classname: - signode += addnodes.desc_classname(classname, classname) - # exceptions are a special case, since they are documented in the - # 'exceptions' module. - elif add_module and env.config.add_module_names and \ - env.currmodule and env.currmodule != 'exceptions': - nodetext = env.currmodule + '.' - signode += addnodes.desc_classname(nodetext, nodetext) - - signode += addnodes.desc_name(name, name) - if not arglist: - if desctype in ('function', 'method'): - # for callables, add an empty parameter list - signode += addnodes.desc_parameterlist() - return fullname, classname - signode += addnodes.desc_parameterlist() - - stack = [signode[-1]] - for token in py_paramlist_re.split(arglist): - if token == '[': - opt = addnodes.desc_optional() - stack[-1] += opt - stack.append(opt) - elif token == ']': - try: - stack.pop() - except IndexError: - raise ValueError - elif not token or token == ',' or token.isspace(): - pass - else: - token = token.strip() - stack[-1] += addnodes.desc_parameter(token, token) - if len(stack) != 1: - raise ValueError - return fullname, classname - - -c_sig_re = re.compile( - r'''^([^(]*?) # return type - ([\w:]+) \s* # thing name (colon allowed for C++ class names) - (?: \((.*)\) )? $ # optionally arguments - ''', re.VERBOSE) -c_funcptr_sig_re = re.compile( - r'''^([^(]+?) # return type - (\( [^()]+ \)) \s* # name in parentheses - \( (.*) \) $ # arguments - ''', re.VERBOSE) -c_funcptr_name_re = re.compile(r'^\(\s*\*\s*(.*?)\s*\)$') - -# RE to split at word boundaries -wsplit_re = re.compile(r'(\W+)') - -# These C types aren't described in the reference, so don't try to create -# a cross-reference to them -stopwords = set(('const', 'void', 'char', 'int', 'long', 'FILE', 'struct')) - -def parse_c_type(node, ctype): - # add cross-ref nodes for all words - for part in filter(None, wsplit_re.split(ctype)): - tnode = nodes.Text(part, part) - if part[0] in string.letters+'_' and part not in stopwords: - pnode = addnodes.pending_xref( - '', reftype='ctype', reftarget=part, modname=None, classname=None) - pnode += tnode - node += pnode - else: - node += tnode - -def parse_c_signature(signode, sig, desctype): - """Transform a C (or C++) signature into RST nodes.""" - # first try the function pointer signature regex, it's more specific - m = c_funcptr_sig_re.match(sig) - if m is None: - m = c_sig_re.match(sig) - if m is None: - raise ValueError('no match') - rettype, name, arglist = m.groups() - - signode += addnodes.desc_type("", "") - parse_c_type(signode[-1], rettype) - signode += addnodes.desc_name(name, name) - # clean up parentheses from canonical name - m = c_funcptr_name_re.match(name) - if m: - name = m.group(1) - if not arglist: - if desctype == 'cfunction': - # for functions, add an empty parameter list - signode += addnodes.desc_parameterlist() - return name - - paramlist = addnodes.desc_parameterlist() - arglist = arglist.replace('`', '').replace('\\ ', '') # remove markup - # this messes up function pointer types, but not too badly ;) - args = arglist.split(',') - for arg in args: - arg = arg.strip() - param = addnodes.desc_parameter('', '', noemph=True) - try: - ctype, argname = arg.rsplit(' ', 1) - except ValueError: - # no argument name given, only the type - parse_c_type(param, arg) - else: - parse_c_type(param, ctype) - param += nodes.emphasis(' '+argname, ' '+argname) - paramlist += param - signode += paramlist - return name - - -opcode_sig_re = re.compile(r'(\w+(?:\+\d)?)\s*\((.*)\)') - -def parse_opcode_signature(signode, sig): - """Transform an opcode signature into RST nodes.""" - m = opcode_sig_re.match(sig) - if m is None: - raise ValueError - opname, arglist = m.groups() - signode += addnodes.desc_name(opname, opname) - paramlist = addnodes.desc_parameterlist() - signode += paramlist - paramlist += addnodes.desc_parameter(arglist, arglist) - return opname.strip() - - -option_desc_re = re.compile( - r'(/|-|--)([-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)') - -def parse_option_desc(signode, sig): - """Transform an option description into RST nodes.""" - count = 0 - firstname = '' - for m in option_desc_re.finditer(sig): - prefix, optname, args = m.groups() - if count: - signode += addnodes.desc_classname(', ', ', ') - signode += addnodes.desc_name(prefix+optname, prefix+optname) - signode += addnodes.desc_classname(args, args) - if not count: - firstname = optname - count += 1 - if not firstname: - raise ValueError - return firstname - - -def desc_directive(desctype, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - env = state.document.settings.env - node = addnodes.desc() - node['desctype'] = desctype - - noindex = ('noindex' in options) - node['noindex'] = noindex - # remove backslashes to support (dummy) escapes; helps Vim's highlighting - signatures = map(lambda s: s.strip().replace('\\', ''), arguments[0].split('\n')) - names = [] - clsname = None - for i, sig in enumerate(signatures): - # add a signature node for each signature in the current unit - # and add a reference target for it - sig = sig.strip() - signode = addnodes.desc_signature(sig, '') - signode['first'] = False - node.append(signode) - try: - if desctype in ('function', 'data', 'class', 'exception', - 'method', 'attribute'): - name, clsname = parse_py_signature(signode, sig, desctype, env) - elif desctype in ('cfunction', 'cmember', 'cmacro', 'ctype', 'cvar'): - name = parse_c_signature(signode, sig, desctype) - elif desctype == 'opcode': - name = parse_opcode_signature(signode, sig) - elif desctype == 'cmdoption': - optname = parse_option_desc(signode, sig) - if not noindex: - targetname = 'cmdoption-' + optname - signode['ids'].append(targetname) - state.document.note_explicit_target(signode) - env.note_index_entry('pair', 'command line option; %s' % sig, - targetname, targetname) - env.note_reftarget('option', optname, targetname) - continue - elif desctype == 'describe': - signode.clear() - signode += addnodes.desc_name(sig, sig) - continue - else: - # another registered generic x-ref directive - rolename, indextemplate, parse_node = additional_xref_types[desctype] - if parse_node: - fullname = parse_node(env, sig, signode) - else: - signode.clear() - signode += addnodes.desc_name(sig, sig) - # normalize whitespace like xfileref_role does - fullname = ws_re.sub('', sig) - if not noindex: - targetname = '%s-%s' % (rolename, fullname) - signode['ids'].append(targetname) - state.document.note_explicit_target(signode) - if indextemplate: - indexentry = indextemplate % (fullname,) - indextype = 'single' - colon = indexentry.find(':') - if colon != -1: - indextype = indexentry[:colon].strip() - indexentry = indexentry[colon+1:].strip() - env.note_index_entry(indextype, indexentry, - targetname, targetname) - env.note_reftarget(rolename, fullname, targetname) - # don't use object indexing below - continue - except ValueError, err: - # signature parsing failed - signode.clear() - signode += addnodes.desc_name(sig, sig) - continue # we don't want an index entry here - # only add target and index entry if this is the first description of the - # function name in this desc block - if not noindex and name not in names: - fullname = (env.currmodule and env.currmodule + '.' or '') + name - # note target - if fullname not in state.document.ids: - signode['names'].append(fullname) - signode['ids'].append(fullname) - signode['first'] = (not names) - state.document.note_explicit_target(signode) - env.note_descref(fullname, desctype, lineno) - names.append(name) - - env.note_index_entry('single', - desc_index_text(desctype, env.currmodule, name), - fullname, fullname) - - subnode = addnodes.desc_content() - # needed for automatic qualification of members - clsname_set = False - if desctype in ('class', 'exception') and names: - env.currclass = names[0] - clsname_set = True - elif desctype in ('method', 'attribute') and clsname and not env.currclass: - env.currclass = clsname.strip('.') - clsname_set = True - # needed for association of version{added,changed} directives - if names: - env.currdesc = names[0] - state.nested_parse(content, content_offset, subnode) - if clsname_set: - env.currclass = None - env.currdesc = None - node.append(subnode) - return [node] - -desc_directive.content = 1 -desc_directive.arguments = (1, 0, 1) -desc_directive.options = {'noindex': directives.flag} - -desctypes = [ - # the Python ones - 'function', - 'data', - 'class', - 'method', - 'attribute', - 'exception', - # the C ones - 'cfunction', - 'cmember', - 'cmacro', - 'ctype', - 'cvar', - # the odd one - 'opcode', - # for command line options - 'cmdoption', - # the generic one - 'describe', - 'envvar', -] - -for _name in desctypes: - directives.register_directive(_name, desc_directive) - -# Generic cross-reference types; they can be registered in the application; -# the directives are either desc_directive or target_directive -additional_xref_types = { - # directive name: (role name, index text, function to parse the desc node) - 'envvar': ('envvar', 'environment variable; %s', None), -} - - -# ------ target -------------------------------------------------------------------- - -def target_directive(targettype, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - """Generic target for user-defined cross-reference types.""" - env = state.document.settings.env - rolename, indextemplate, _ = additional_xref_types[targettype] - # normalize whitespace in fullname like xfileref_role does - fullname = ws_re.sub('', arguments[0].strip()) - targetname = '%s-%s' % (rolename, fullname) - node = nodes.target('', '', ids=[targetname]) - state.document.note_explicit_target(node) - if indextemplate: - indexentry = indextemplate % (fullname,) - indextype = 'single' - colon = indexentry.find(':') - if colon != -1: - indextype = indexentry[:colon].strip() - indexentry = indexentry[colon+1:].strip() - env.note_index_entry(indextype, indexentry, targetname, targetname) - env.note_reftarget(rolename, fullname, targetname) - return [node] - -target_directive.content = 0 -target_directive.arguments = (1, 0, 1) - - -# ------ versionadded/versionchanged ----------------------------------------------- - -def version_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - node = addnodes.versionmodified() - node['type'] = name - node['version'] = arguments[0] - if len(arguments) == 2: - inodes, messages = state.inline_text(arguments[1], lineno+1) - node.extend(inodes) - if content: - state.nested_parse(content, content_offset, node) - ret = [node] + messages - else: - ret = [node] - env = state.document.settings.env - env.note_versionchange(node['type'], node['version'], node, lineno) - return ret - -version_directive.arguments = (1, 1, 1) -version_directive.content = 1 - -directives.register_directive('deprecated', version_directive) -directives.register_directive('versionadded', version_directive) -directives.register_directive('versionchanged', version_directive) - - -# ------ see also ------------------------------------------------------------------ - -def seealso_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - rv = make_admonition( - addnodes.seealso, name, ['See also'], options, content, - lineno, content_offset, block_text, state, state_machine) - return rv - -seealso_directive.content = 1 -seealso_directive.arguments = (0, 0, 0) -directives.register_directive('seealso', seealso_directive) - - -# ------ production list (for the reference) --------------------------------------- - -token_re = re.compile('`([a-z_]+)`') - -def token_xrefs(text, env): - 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)) - refnode['reftype'] = 'token' - refnode['reftarget'] = m.group(1) - refnode['modname'] = env.currmodule - refnode['classname'] = env.currclass - 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 - -def productionlist_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - env = state.document.settings.env - node = addnodes.productionlist() - messages = [] - i = 0 - - for rule in 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 state.document.ids: - subnode['ids'].append(idname) - state.document.note_implicit_target(subnode, subnode) - env.note_reftarget('token', subnode['tokenname'], idname) - subnode.extend(token_xrefs(tokens, env)) - node.append(subnode) - return [node] + messages - -productionlist_directive.content = 0 -productionlist_directive.arguments = (1, 0, 1) -directives.register_directive('productionlist', productionlist_directive) - -# ------ section metadata ---------------------------------------------------------- - -def module_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - env = state.document.settings.env - modname = arguments[0].strip() - env.currmodule = modname - env.note_module(modname, options.get('synopsis', ''), - options.get('platform', ''), - 'deprecated' in options) - modulenode = addnodes.module() - modulenode['modname'] = modname - modulenode['synopsis'] = options.get('synopsis', '') - targetnode = nodes.target('', '', ids=['module-' + modname]) - state.document.note_explicit_target(targetnode) - ret = [modulenode, targetnode] - if 'platform' in options: - modulenode['platform'] = options['platform'] - node = nodes.paragraph() - node += nodes.emphasis('Platforms: ', 'Platforms: ') - node += nodes.Text(options['platform'], options['platform']) - ret.append(node) - # the synopsis isn't printed; in fact, it is only used in the modindex currently - env.note_index_entry('single', '%s (module)' % modname, 'module-' + modname, - modname) - return ret - -module_directive.arguments = (1, 0, 0) -module_directive.options = {'platform': lambda x: x, - 'synopsis': lambda x: x, - 'deprecated': directives.flag} -directives.register_directive('module', module_directive) - - -def currentmodule_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - # This directive is just to tell people that we're documenting - # stuff in module foo, but links to module foo won't lead here. - env = state.document.settings.env - modname = arguments[0].strip() - env.currmodule = modname - return [] - -currentmodule_directive.arguments = (1, 0, 0) -directives.register_directive('currentmodule', currentmodule_directive) - - -def author_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - # Show authors only if the show_authors option is on - env = state.document.settings.env - if not env.config.show_authors: - return [] - para = nodes.paragraph() - emph = nodes.emphasis() - para += emph - if name == 'sectionauthor': - text = 'Section author: ' - elif name == 'moduleauthor': - text = 'Module author: ' - else: - text = 'Author: ' - emph += nodes.Text(text, text) - inodes, messages = state.inline_text(arguments[0], lineno) - emph.extend(inodes) - return [para] + messages - -author_directive.arguments = (1, 0, 1) -directives.register_directive('sectionauthor', author_directive) -directives.register_directive('moduleauthor', author_directive) - - -# ------ toctree directive --------------------------------------------------------- - -def toctree_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - env = state.document.settings.env - suffix = env.config.source_suffix - dirname = posixpath.dirname(env.docname) - glob = 'glob' in options - - ret = [] - subnode = addnodes.toctree() - includefiles = [] - includetitles = {} - all_docnames = env.found_docs.copy() - # don't add the currently visited file in catch-all patterns - all_docnames.remove(env.docname) - for entry in content: - if not entry: - continue - if not glob: - # look for explicit titles and documents ("Some Title "). - m = caption_ref_re.match(entry) - if m: - docname = m.group(2) - includetitles[docname] = m.group(1) - else: - docname = entry - # remove suffixes (backwards compatibility) - if docname.endswith(suffix): - docname = docname[:-len(suffix)] - # absolutize filenames - docname = posixpath.normpath(posixpath.join(dirname, docname)) - if docname not in env.found_docs: - ret.append(state.document.reporter.warning( - 'toctree references unknown document %r' % docname, line=lineno)) - else: - includefiles.append(docname) - else: - patname = posixpath.normpath(posixpath.join(dirname, entry)) - docnames = sorted(patfilter(all_docnames, patname)) - for docname in docnames: - all_docnames.remove(docname) # don't include it again - includefiles.append(docname) - if not docnames: - ret.append(state.document.reporter.warning( - 'toctree glob pattern %r didn\'t match any documents' % entry, - line=lineno)) - subnode['includefiles'] = includefiles - subnode['includetitles'] = includetitles - subnode['maxdepth'] = options.get('maxdepth', -1) - ret.append(subnode) - return ret - -toctree_directive.content = 1 -toctree_directive.options = {'maxdepth': int, 'glob': directives.flag} -directives.register_directive('toctree', toctree_directive) - - -# ------ centered directive --------------------------------------------------------- - -def centered_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - if not arguments: - return [] - subnode = addnodes.centered() - inodes, messages = state.inline_text(arguments[0], lineno) - subnode.extend(inodes) - return [subnode] + messages - -centered_directive.arguments = (1, 0, 1) -directives.register_directive('centered', centered_directive) - - -# ------ highlight directive -------------------------------------------------------- - -def highlightlang_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - if 'linenothreshold' in options: - try: - linenothreshold = int(options['linenothreshold']) - except Exception: - linenothreshold = 10 - else: - linenothreshold = sys.maxint - return [addnodes.highlightlang(lang=arguments[0].strip(), - linenothreshold=linenothreshold)] - -highlightlang_directive.content = 0 -highlightlang_directive.arguments = (1, 0, 0) -highlightlang_directive.options = {'linenothreshold': directives.unchanged} -directives.register_directive('highlight', highlightlang_directive) -directives.register_directive('highlightlang', highlightlang_directive) # old name - - -# ------ code-block directive ------------------------------------------------------- - -def codeblock_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - code = u'\n'.join(content) - literal = nodes.literal_block(code, code) - literal['language'] = arguments[0] - literal['linenos'] = 'linenos' in options - return [literal] - -codeblock_directive.content = 1 -codeblock_directive.arguments = (1, 0, 0) -codeblock_directive.options = {'linenos': directives.flag} -directives.register_directive('code-block', codeblock_directive) -directives.register_directive('sourcecode', codeblock_directive) - - -# ------ literalinclude directive --------------------------------------------------- - -def literalinclude_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - """Like .. include:: :literal:, but only warns if the include file is not found.""" - if not state.document.settings.file_insertion_enabled: - return [state.document.reporter.warning('File insertion disabled', line=lineno)] - env = state.document.settings.env - rel_fn = arguments[0] - source_dir = path.dirname(path.abspath(state_machine.input_lines.source( - lineno - state_machine.input_offset - 1))) - fn = path.normpath(path.join(source_dir, rel_fn)) - - try: - f = open(fn) - text = f.read() - f.close() - except (IOError, OSError): - retnode = state.document.reporter.warning( - 'Include file %r not found or reading it failed' % arguments[0], line=lineno) - else: - retnode = nodes.literal_block(text, text, source=fn) - retnode.line = 1 - if options.get('language', ''): - retnode['language'] = options['language'] - if 'linenos' in options: - retnode['linenos'] = True - state.document.settings.env.note_dependency(rel_fn) - return [retnode] - -literalinclude_directive.options = {'linenos': directives.flag, - 'language': directives.unchanged} -literalinclude_directive.content = 0 -literalinclude_directive.arguments = (1, 0, 0) -directives.register_directive('literalinclude', literalinclude_directive) - - -# ------ glossary directive --------------------------------------------------------- - -def glossary_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - """Glossary with cross-reference targets for :dfn: roles.""" - env = state.document.settings.env - node = addnodes.glossary() - state.nested_parse(content, 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 - for dl in dls: - dl['classes'].append('glossary') - 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 env.gloss_entries: - new_id = 'term-' + str(len(env.gloss_entries)) - env.gloss_entries.add(new_id) - li[0]['names'].append(new_id) - li[0]['ids'].append(new_id) - state.document.settings.env.note_reftarget('term', termtext.lower(), - new_id) - return [node] - -glossary_directive.content = 1 -glossary_directive.arguments = (0, 0, 0) -directives.register_directive('glossary', glossary_directive) - - -# ------ acks directive ------------------------------------------------------------- - -def acks_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - node = addnodes.acks() - state.nested_parse(content, content_offset, node) - if len(node.children) != 1 or not isinstance(node.children[0], nodes.bullet_list): - return [state.document.reporter.warning('.. acks content is not a list', - line=lineno)] - return [node] - -acks_directive.content = 1 -acks_directive.arguments = (0, 0, 0) -directives.register_directive('acks', acks_directive) - - -# ------ tabularcolumns directive --------------------------------------------------- - -def tabularcolumns_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - # support giving explicit tabulary column definition to latex - node = addnodes.tabular_col_spec() - node['spec'] = arguments[0] - return [node] - -tabularcolumns_directive.content = 0 -tabularcolumns_directive.arguments = (1, 0, 1) -directives.register_directive('tabularcolumns', tabularcolumns_directive) - diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py new file mode 100644 index 000000000..462b2cffc --- /dev/null +++ b/sphinx/directives/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +""" + sphinx.directives + ~~~~~~~~~~~~~~~~~ + + Handlers for additional ReST directives. + + :copyright: 2008 by Georg Brandl. + :license: BSD. +""" + +from sphinx.directives.desc import * +from sphinx.directives.code import * +from sphinx.directives.other import * diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py new file mode 100644 index 000000000..7e81f0724 --- /dev/null +++ b/sphinx/directives/code.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +""" + sphinx.directives.code + ~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: 2007-2008 by Georg Brandl. + :license: BSD. +""" + +import sys +from os import path + +from docutils import nodes +from docutils.parsers.rst import directives + +from sphinx import addnodes + + +# ------ highlight directive -------------------------------------------------------- + +def highlightlang_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + if 'linenothreshold' in options: + try: + linenothreshold = int(options['linenothreshold']) + except Exception: + linenothreshold = 10 + else: + linenothreshold = sys.maxint + return [addnodes.highlightlang(lang=arguments[0].strip(), + linenothreshold=linenothreshold)] + +highlightlang_directive.content = 0 +highlightlang_directive.arguments = (1, 0, 0) +highlightlang_directive.options = {'linenothreshold': directives.unchanged} +directives.register_directive('highlight', highlightlang_directive) +directives.register_directive('highlightlang', highlightlang_directive) # old name + + +# ------ code-block directive ------------------------------------------------------- + +def codeblock_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + code = u'\n'.join(content) + literal = nodes.literal_block(code, code) + literal['language'] = arguments[0] + literal['linenos'] = 'linenos' in options + return [literal] + +codeblock_directive.content = 1 +codeblock_directive.arguments = (1, 0, 0) +codeblock_directive.options = {'linenos': directives.flag} +directives.register_directive('code-block', codeblock_directive) +directives.register_directive('sourcecode', codeblock_directive) + + +# ------ literalinclude directive --------------------------------------------------- + +def literalinclude_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + """Like .. include:: :literal:, but only warns if the include file is not found.""" + if not state.document.settings.file_insertion_enabled: + return [state.document.reporter.warning('File insertion disabled', line=lineno)] + env = state.document.settings.env + rel_fn = arguments[0] + source_dir = path.dirname(path.abspath(state_machine.input_lines.source( + lineno - state_machine.input_offset - 1))) + fn = path.normpath(path.join(source_dir, rel_fn)) + + try: + f = open(fn) + text = f.read() + f.close() + except (IOError, OSError): + retnode = state.document.reporter.warning( + 'Include file %r not found or reading it failed' % arguments[0], line=lineno) + else: + retnode = nodes.literal_block(text, text, source=fn) + retnode.line = 1 + if options.get('language', ''): + retnode['language'] = options['language'] + if 'linenos' in options: + retnode['linenos'] = True + state.document.settings.env.note_dependency(rel_fn) + return [retnode] + +literalinclude_directive.options = {'linenos': directives.flag, + 'language': directives.unchanged} +literalinclude_directive.content = 0 +literalinclude_directive.arguments = (1, 0, 0) +directives.register_directive('literalinclude', literalinclude_directive) diff --git a/sphinx/directives/desc.py b/sphinx/directives/desc.py new file mode 100644 index 000000000..8fb9993b8 --- /dev/null +++ b/sphinx/directives/desc.py @@ -0,0 +1,434 @@ +# -*- coding: utf-8 -*- +""" + sphinx.directives.desc + ~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: 2007-2008 by Georg Brandl. + :license: BSD. +""" + +import re +import string + +from docutils import nodes +from docutils.parsers.rst import directives + +from sphinx import addnodes + + +ws_re = re.compile(r'\s+') + +# ------ information units --------------------------------------------------------- + +def desc_index_text(desctype, currmodule, name): + if desctype == 'function': + if not currmodule: + return '%s() (built-in function)' % name + return '%s() (in module %s)' % (name, currmodule) + elif desctype == 'data': + if not currmodule: + return '%s (built-in variable)' % name + return '%s (in module %s)' % (name, currmodule) + elif desctype == 'class': + return '%s (class in %s)' % (name, currmodule) + elif desctype == 'exception': + return name + elif desctype == 'method': + try: + clsname, methname = name.rsplit('.', 1) + except ValueError: + if currmodule: + return '%s() (in module %s)' % (name, currmodule) + else: + return '%s()' % name + if currmodule: + return '%s() (%s.%s method)' % (methname, currmodule, clsname) + else: + return '%s() (%s method)' % (methname, clsname) + elif desctype == 'attribute': + try: + clsname, attrname = name.rsplit('.', 1) + except ValueError: + if currmodule: + return '%s (in module %s)' % (name, currmodule) + else: + return name + if currmodule: + return '%s (%s.%s attribute)' % (attrname, currmodule, clsname) + else: + return '%s (%s attribute)' % (attrname, clsname) + elif desctype == 'opcode': + return '%s (opcode)' % name + elif desctype == 'cfunction': + return '%s (C function)' % name + elif desctype == 'cmember': + return '%s (C member)' % name + elif desctype == 'cmacro': + return '%s (C macro)' % name + elif desctype == 'ctype': + return '%s (C type)' % name + elif desctype == 'cvar': + return '%s (C variable)' % name + else: + raise ValueError("unhandled descenv: %s" % desctype) + + +# ------ functions to parse a Python or C signature and create desc_* nodes. + +py_sig_re = re.compile(r'''^([\w.]*\.)? # class names + (\w+) \s* # thing name + (?: \((.*)\) )? $ # optionally arguments + ''', re.VERBOSE) + +py_paramlist_re = re.compile(r'([\[\],])') # split at '[', ']' and ',' + +def parse_py_signature(signode, sig, desctype, env): + """ + 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 + * it is added to the full name (return value) if not present + """ + m = py_sig_re.match(sig) + if m is None: + raise ValueError + classname, name, arglist = m.groups() + + add_module = True + if env.currclass: + if classname and classname.startswith(env.currclass): + fullname = classname + name + # class name is given again in the signature + classname = classname[len(env.currclass):].lstrip('.') + add_module = False + elif classname: + # class name is given in the signature, but different + fullname = env.currclass + '.' + classname + name + else: + # class name is not given in the signature + fullname = env.currclass + '.' + name + add_module = False + else: + fullname = classname and classname + name or name + + if classname: + signode += addnodes.desc_classname(classname, classname) + # exceptions are a special case, since they are documented in the + # 'exceptions' module. + elif add_module and env.config.add_module_names and \ + env.currmodule and env.currmodule != 'exceptions': + nodetext = env.currmodule + '.' + signode += addnodes.desc_classname(nodetext, nodetext) + + signode += addnodes.desc_name(name, name) + if not arglist: + if desctype in ('function', 'method'): + # for callables, add an empty parameter list + signode += addnodes.desc_parameterlist() + return fullname, classname + signode += addnodes.desc_parameterlist() + + stack = [signode[-1]] + for token in py_paramlist_re.split(arglist): + if token == '[': + opt = addnodes.desc_optional() + stack[-1] += opt + stack.append(opt) + elif token == ']': + try: + stack.pop() + except IndexError: + raise ValueError + elif not token or token == ',' or token.isspace(): + pass + else: + token = token.strip() + stack[-1] += addnodes.desc_parameter(token, token) + if len(stack) != 1: + raise ValueError + return fullname, classname + + +c_sig_re = re.compile( + r'''^([^(]*?) # return type + ([\w:]+) \s* # thing name (colon allowed for C++ class names) + (?: \((.*)\) )? $ # optionally arguments + ''', re.VERBOSE) +c_funcptr_sig_re = re.compile( + r'''^([^(]+?) # return type + (\( [^()]+ \)) \s* # name in parentheses + \( (.*) \) $ # arguments + ''', re.VERBOSE) +c_funcptr_name_re = re.compile(r'^\(\s*\*\s*(.*?)\s*\)$') + +# RE to split at word boundaries +wsplit_re = re.compile(r'(\W+)') + +# These C types aren't described in the reference, so don't try to create +# a cross-reference to them +stopwords = set(('const', 'void', 'char', 'int', 'long', 'FILE', 'struct')) + +def parse_c_type(node, ctype): + # add cross-ref nodes for all words + for part in filter(None, wsplit_re.split(ctype)): + tnode = nodes.Text(part, part) + if part[0] in string.letters+'_' and part not in stopwords: + pnode = addnodes.pending_xref( + '', reftype='ctype', reftarget=part, modname=None, classname=None) + pnode += tnode + node += pnode + else: + node += tnode + +def parse_c_signature(signode, sig, desctype): + """Transform a C (or C++) signature into RST nodes.""" + # first try the function pointer signature regex, it's more specific + m = c_funcptr_sig_re.match(sig) + if m is None: + m = c_sig_re.match(sig) + if m is None: + raise ValueError('no match') + rettype, name, arglist = m.groups() + + signode += addnodes.desc_type("", "") + parse_c_type(signode[-1], rettype) + signode += addnodes.desc_name(name, name) + # clean up parentheses from canonical name + m = c_funcptr_name_re.match(name) + if m: + name = m.group(1) + if not arglist: + if desctype == 'cfunction': + # for functions, add an empty parameter list + signode += addnodes.desc_parameterlist() + return name + + paramlist = addnodes.desc_parameterlist() + arglist = arglist.replace('`', '').replace('\\ ', '') # remove markup + # this messes up function pointer types, but not too badly ;) + args = arglist.split(',') + for arg in args: + arg = arg.strip() + param = addnodes.desc_parameter('', '', noemph=True) + try: + ctype, argname = arg.rsplit(' ', 1) + except ValueError: + # no argument name given, only the type + parse_c_type(param, arg) + else: + parse_c_type(param, ctype) + param += nodes.emphasis(' '+argname, ' '+argname) + paramlist += param + signode += paramlist + return name + + +opcode_sig_re = re.compile(r'(\w+(?:\+\d)?)\s*\((.*)\)') + +def parse_opcode_signature(signode, sig): + """Transform an opcode signature into RST nodes.""" + m = opcode_sig_re.match(sig) + if m is None: + raise ValueError + opname, arglist = m.groups() + signode += addnodes.desc_name(opname, opname) + paramlist = addnodes.desc_parameterlist() + signode += paramlist + paramlist += addnodes.desc_parameter(arglist, arglist) + return opname.strip() + + +option_desc_re = re.compile( + r'(/|-|--)([-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)') + +def parse_option_desc(signode, sig): + """Transform an option description into RST nodes.""" + count = 0 + firstname = '' + for m in option_desc_re.finditer(sig): + prefix, optname, args = m.groups() + if count: + signode += addnodes.desc_classname(', ', ', ') + signode += addnodes.desc_name(prefix+optname, prefix+optname) + signode += addnodes.desc_classname(args, args) + if not count: + firstname = optname + count += 1 + if not firstname: + raise ValueError + return firstname + + +def desc_directive(desctype, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + env = state.document.settings.env + node = addnodes.desc() + node['desctype'] = desctype + + noindex = ('noindex' in options) + node['noindex'] = noindex + # remove backslashes to support (dummy) escapes; helps Vim's highlighting + signatures = map(lambda s: s.strip().replace('\\', ''), arguments[0].split('\n')) + names = [] + clsname = None + for i, sig in enumerate(signatures): + # add a signature node for each signature in the current unit + # and add a reference target for it + sig = sig.strip() + signode = addnodes.desc_signature(sig, '') + signode['first'] = False + node.append(signode) + try: + if desctype in ('function', 'data', 'class', 'exception', + 'method', 'attribute'): + name, clsname = parse_py_signature(signode, sig, desctype, env) + elif desctype in ('cfunction', 'cmember', 'cmacro', 'ctype', 'cvar'): + name = parse_c_signature(signode, sig, desctype) + elif desctype == 'opcode': + name = parse_opcode_signature(signode, sig) + elif desctype == 'cmdoption': + optname = parse_option_desc(signode, sig) + if not noindex: + targetname = 'cmdoption-' + optname + signode['ids'].append(targetname) + state.document.note_explicit_target(signode) + env.note_index_entry('pair', 'command line option; %s' % sig, + targetname, targetname) + env.note_reftarget('option', optname, targetname) + continue + elif desctype == 'describe': + signode.clear() + signode += addnodes.desc_name(sig, sig) + continue + else: + # another registered generic x-ref directive + rolename, indextemplate, parse_node = additional_xref_types[desctype] + if parse_node: + fullname = parse_node(env, sig, signode) + else: + signode.clear() + signode += addnodes.desc_name(sig, sig) + # normalize whitespace like xfileref_role does + fullname = ws_re.sub('', sig) + if not noindex: + targetname = '%s-%s' % (rolename, fullname) + signode['ids'].append(targetname) + state.document.note_explicit_target(signode) + if indextemplate: + indexentry = indextemplate % (fullname,) + indextype = 'single' + colon = indexentry.find(':') + if colon != -1: + indextype = indexentry[:colon].strip() + indexentry = indexentry[colon+1:].strip() + env.note_index_entry(indextype, indexentry, + targetname, targetname) + env.note_reftarget(rolename, fullname, targetname) + # don't use object indexing below + continue + except ValueError, err: + # signature parsing failed + signode.clear() + signode += addnodes.desc_name(sig, sig) + continue # we don't want an index entry here + # only add target and index entry if this is the first description of the + # function name in this desc block + if not noindex and name not in names: + fullname = (env.currmodule and env.currmodule + '.' or '') + name + # note target + if fullname not in state.document.ids: + signode['names'].append(fullname) + signode['ids'].append(fullname) + signode['first'] = (not names) + state.document.note_explicit_target(signode) + env.note_descref(fullname, desctype, lineno) + names.append(name) + + env.note_index_entry('single', + desc_index_text(desctype, env.currmodule, name), + fullname, fullname) + + subnode = addnodes.desc_content() + # needed for automatic qualification of members + clsname_set = False + if desctype in ('class', 'exception') and names: + env.currclass = names[0] + clsname_set = True + elif desctype in ('method', 'attribute') and clsname and not env.currclass: + env.currclass = clsname.strip('.') + clsname_set = True + # needed for association of version{added,changed} directives + if names: + env.currdesc = names[0] + state.nested_parse(content, content_offset, subnode) + if clsname_set: + env.currclass = None + env.currdesc = None + node.append(subnode) + return [node] + +desc_directive.content = 1 +desc_directive.arguments = (1, 0, 1) +desc_directive.options = {'noindex': directives.flag} + +desctypes = [ + # the Python ones + 'function', + 'data', + 'class', + 'method', + 'attribute', + 'exception', + # the C ones + 'cfunction', + 'cmember', + 'cmacro', + 'ctype', + 'cvar', + # the odd one + 'opcode', + # for command line options + 'cmdoption', + # the generic one + 'describe', + 'envvar', +] + +for _name in desctypes: + directives.register_directive(_name, desc_directive) + +# Generic cross-reference types; they can be registered in the application; +# the directives are either desc_directive or target_directive +additional_xref_types = { + # directive name: (role name, index text, function to parse the desc node) + 'envvar': ('envvar', 'environment variable; %s', None), +} + + +# ------ target -------------------------------------------------------------------- + +def target_directive(targettype, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + """Generic target for user-defined cross-reference types.""" + env = state.document.settings.env + rolename, indextemplate, _ = additional_xref_types[targettype] + # normalize whitespace in fullname like xfileref_role does + fullname = ws_re.sub('', arguments[0].strip()) + targetname = '%s-%s' % (rolename, fullname) + node = nodes.target('', '', ids=[targetname]) + state.document.note_explicit_target(node) + if indextemplate: + indexentry = indextemplate % (fullname,) + indextype = 'single' + colon = indexentry.find(':') + if colon != -1: + indextype = indexentry[:colon].strip() + indexentry = indexentry[colon+1:].strip() + env.note_index_entry(indextype, indexentry, targetname, targetname) + env.note_reftarget(rolename, fullname, targetname) + return [node] + +target_directive.content = 0 +target_directive.arguments = (1, 0, 1) diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py new file mode 100644 index 000000000..29fd12f8e --- /dev/null +++ b/sphinx/directives/other.py @@ -0,0 +1,355 @@ +# -*- coding: utf-8 -*- +""" + sphinx.directives.other + ~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: 2007-2008 by Georg Brandl. + :license: BSD. +""" + +import re +import posixpath + +from docutils import nodes +from docutils.parsers.rst import directives + +from sphinx import addnodes +from sphinx.util import patfilter +from sphinx.roles import caption_ref_re +from sphinx.util.compat import make_admonition + + +# ------ the TOC tree --------------------------------------------------------------- + +def toctree_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + env = state.document.settings.env + suffix = env.config.source_suffix + dirname = posixpath.dirname(env.docname) + glob = 'glob' in options + + ret = [] + subnode = addnodes.toctree() + includefiles = [] + includetitles = {} + all_docnames = env.found_docs.copy() + # don't add the currently visited file in catch-all patterns + all_docnames.remove(env.docname) + for entry in content: + if not entry: + continue + if not glob: + # look for explicit titles and documents ("Some Title "). + m = caption_ref_re.match(entry) + if m: + docname = m.group(2) + includetitles[docname] = m.group(1) + else: + docname = entry + # remove suffixes (backwards compatibility) + if docname.endswith(suffix): + docname = docname[:-len(suffix)] + # absolutize filenames + docname = posixpath.normpath(posixpath.join(dirname, docname)) + if docname not in env.found_docs: + ret.append(state.document.reporter.warning( + 'toctree references unknown document %r' % docname, line=lineno)) + else: + includefiles.append(docname) + else: + patname = posixpath.normpath(posixpath.join(dirname, entry)) + docnames = sorted(patfilter(all_docnames, patname)) + for docname in docnames: + all_docnames.remove(docname) # don't include it again + includefiles.append(docname) + if not docnames: + ret.append(state.document.reporter.warning( + 'toctree glob pattern %r didn\'t match any documents' % entry, + line=lineno)) + subnode['includefiles'] = includefiles + subnode['includetitles'] = includetitles + subnode['maxdepth'] = options.get('maxdepth', -1) + ret.append(subnode) + return ret + +toctree_directive.content = 1 +toctree_directive.options = {'maxdepth': int, 'glob': directives.flag} +directives.register_directive('toctree', toctree_directive) + + +# ------ section metadata ---------------------------------------------------------- + +def module_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + env = state.document.settings.env + modname = arguments[0].strip() + env.currmodule = modname + env.note_module(modname, options.get('synopsis', ''), + options.get('platform', ''), + 'deprecated' in options) + modulenode = addnodes.module() + modulenode['modname'] = modname + modulenode['synopsis'] = options.get('synopsis', '') + targetnode = nodes.target('', '', ids=['module-' + modname]) + state.document.note_explicit_target(targetnode) + ret = [modulenode, targetnode] + if 'platform' in options: + modulenode['platform'] = options['platform'] + node = nodes.paragraph() + node += nodes.emphasis('Platforms: ', 'Platforms: ') + node += nodes.Text(options['platform'], options['platform']) + ret.append(node) + # the synopsis isn't printed; in fact, it is only used in the modindex currently + env.note_index_entry('single', '%s (module)' % modname, 'module-' + modname, + modname) + return ret + +module_directive.arguments = (1, 0, 0) +module_directive.options = {'platform': lambda x: x, + 'synopsis': lambda x: x, + 'deprecated': directives.flag} +directives.register_directive('module', module_directive) + + +def currentmodule_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + # This directive is just to tell people that we're documenting + # stuff in module foo, but links to module foo won't lead here. + env = state.document.settings.env + modname = arguments[0].strip() + env.currmodule = modname + return [] + +currentmodule_directive.arguments = (1, 0, 0) +directives.register_directive('currentmodule', currentmodule_directive) + + +def author_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + # Show authors only if the show_authors option is on + env = state.document.settings.env + if not env.config.show_authors: + return [] + para = nodes.paragraph() + emph = nodes.emphasis() + para += emph + if name == 'sectionauthor': + text = 'Section author: ' + elif name == 'moduleauthor': + text = 'Module author: ' + else: + text = 'Author: ' + emph += nodes.Text(text, text) + inodes, messages = state.inline_text(arguments[0], lineno) + emph.extend(inodes) + return [para] + messages + +author_directive.arguments = (1, 0, 1) +directives.register_directive('sectionauthor', author_directive) +directives.register_directive('moduleauthor', author_directive) + + +# ------ index markup -------------------------------------------------------------- + +entrytypes = [ + 'single', 'pair', 'triple', 'module', 'keyword', 'operator', + 'object', 'exception', 'statement', 'builtin', +] + +def index_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + arguments = arguments[0].split('\n') + env = state.document.settings.env + targetid = 'index-%s' % env.index_num + env.index_num += 1 + targetnode = nodes.target('', '', ids=[targetid]) + state.document.note_explicit_target(targetnode) + indexnode = addnodes.index() + indexnode['entries'] = ne = [] + for entry in arguments: + entry = entry.strip() + for type in entrytypes: + if entry.startswith(type+':'): + value = entry[len(type)+1:].strip() + env.note_index_entry(type, value, targetid, value) + ne.append((type, value, targetid, value)) + break + # shorthand notation for single entries + else: + for value in entry.split(','): + env.note_index_entry('single', value.strip(), targetid, value.strip()) + ne.append(('single', value.strip(), targetid, value.strip())) + return [indexnode, targetnode] + +index_directive.arguments = (1, 0, 1) +directives.register_directive('index', index_directive) + +# ------ versionadded/versionchanged ----------------------------------------------- + +def version_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + node = addnodes.versionmodified() + node['type'] = name + node['version'] = arguments[0] + if len(arguments) == 2: + inodes, messages = state.inline_text(arguments[1], lineno+1) + node.extend(inodes) + if content: + state.nested_parse(content, content_offset, node) + ret = [node] + messages + else: + ret = [node] + env = state.document.settings.env + env.note_versionchange(node['type'], node['version'], node, lineno) + return ret + +version_directive.arguments = (1, 1, 1) +version_directive.content = 1 + +directives.register_directive('deprecated', version_directive) +directives.register_directive('versionadded', version_directive) +directives.register_directive('versionchanged', version_directive) + + +# ------ see also ------------------------------------------------------------------ + +def seealso_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + rv = make_admonition( + addnodes.seealso, name, ['See also'], options, content, + lineno, content_offset, block_text, state, state_machine) + return rv + +seealso_directive.content = 1 +seealso_directive.arguments = (0, 0, 0) +directives.register_directive('seealso', seealso_directive) + + +# ------ production list (for the reference) --------------------------------------- + +token_re = re.compile('`([a-z_]+)`') + +def token_xrefs(text, env): + 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)) + refnode['reftype'] = 'token' + refnode['reftarget'] = m.group(1) + refnode['modname'] = env.currmodule + refnode['classname'] = env.currclass + 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 + +def productionlist_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + env = state.document.settings.env + node = addnodes.productionlist() + messages = [] + i = 0 + + for rule in 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 state.document.ids: + subnode['ids'].append(idname) + state.document.note_implicit_target(subnode, subnode) + env.note_reftarget('token', subnode['tokenname'], idname) + subnode.extend(token_xrefs(tokens, env)) + node.append(subnode) + return [node] + messages + +productionlist_directive.content = 0 +productionlist_directive.arguments = (1, 0, 1) +directives.register_directive('productionlist', productionlist_directive) + + +# ------ glossary directive --------------------------------------------------------- + +def glossary_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + """Glossary with cross-reference targets for :dfn: roles.""" + env = state.document.settings.env + node = addnodes.glossary() + state.nested_parse(content, 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 + for dl in dls: + dl['classes'].append('glossary') + 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 env.gloss_entries: + new_id = 'term-' + str(len(env.gloss_entries)) + env.gloss_entries.add(new_id) + li[0]['names'].append(new_id) + li[0]['ids'].append(new_id) + state.document.settings.env.note_reftarget('term', termtext.lower(), + new_id) + return [node] + +glossary_directive.content = 1 +glossary_directive.arguments = (0, 0, 0) +directives.register_directive('glossary', glossary_directive) + + +# ------ miscellaneous markup ------------------------------------------------------- + +def centered_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + if not arguments: + return [] + subnode = addnodes.centered() + inodes, messages = state.inline_text(arguments[0], lineno) + subnode.extend(inodes) + return [subnode] + messages + +centered_directive.arguments = (1, 0, 1) +directives.register_directive('centered', centered_directive) + + +def acks_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + node = addnodes.acks() + state.nested_parse(content, content_offset, node) + if len(node.children) != 1 or not isinstance(node.children[0], nodes.bullet_list): + return [state.document.reporter.warning('.. acks content is not a list', + line=lineno)] + return [node] + +acks_directive.content = 1 +acks_directive.arguments = (0, 0, 0) +directives.register_directive('acks', acks_directive) + + +def tabularcolumns_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + # support giving explicit tabulary column definition to latex + node = addnodes.tabular_col_spec() + node['spec'] = arguments[0] + return [node] + +tabularcolumns_directive.content = 0 +tabularcolumns_directive.arguments = (1, 0, 1) +directives.register_directive('tabularcolumns', tabularcolumns_directive)