diff options
| author | Georg Brandl <georg@python.org> | 2014-01-20 21:21:33 +0100 |
|---|---|---|
| committer | Georg Brandl <georg@python.org> | 2014-01-20 21:21:33 +0100 |
| commit | 930d996dbe5bcccbb0d86de5940376b70ed7c3db (patch) | |
| tree | e8936ce5ad95fa2225c42886ec4c1be99c1a5f55 /sphinx | |
| parent | d3db46164e65f031aee3f5059b0bfc7cc22bb41e (diff) | |
| parent | fb6c6f5bfe4379c6e680a9757c84b7eebc8443d3 (diff) | |
| download | sphinx-930d996dbe5bcccbb0d86de5940376b70ed7c3db.tar.gz | |
merge with stable
Diffstat (limited to 'sphinx')
49 files changed, 1665 insertions, 499 deletions
diff --git a/sphinx/__init__.py b/sphinx/__init__.py index 21086cf8..c1e9e38d 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -15,12 +15,12 @@ import sys from os import path -__version__ = '1.2.1+' -__released__ = '1.2.1' # used when Sphinx builds its own docs +__version__ = '1.3a0' +__released__ = '1.3a0' # used when Sphinx builds its own docs # version info for better programmatic use # possible values for 3rd element: 'alpha', 'beta', 'rc', 'final' # 'final' has 0 as the last element -version_info = (1, 2, 1, 'final', 0) +version_info = (1, 3, 0, 'alpha', 0) package_dir = path.abspath(path.dirname(__file__)) @@ -42,8 +42,9 @@ if '+' in __version__ or 'pre' in __version__: def main(argv=sys.argv): """Sphinx build "main" command-line entry.""" - if sys.version_info[:3] < (2, 5, 0): - sys.stderr.write('Error: Sphinx requires at least Python 2.5 to run.\n') + if (sys.version_info[:3] < (2, 6, 0) or + (3, 0, 0) <= sys.version_info[:3] < (3, 2, 0)): + sys.stderr.write('Error: Sphinx requires at least Python 2.6 to run.\n') return 1 try: from sphinx import cmdline diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index d8dca2ce..e3f6d7d6 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -168,6 +168,11 @@ class literal_emphasis(nodes.emphasis): applied (e.g. smartypants for HTML output). """ +class literal_strong(nodes.strong): + """Node that behaves like `strong`, but further text processors are not + applied (e.g. smartypants for HTML output). + """ + class abbreviation(nodes.Inline, nodes.TextElement): """Node for abbreviations with explanations.""" diff --git a/sphinx/application.py b/sphinx/application.py index 3807f542..c9626b3b 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -15,6 +15,7 @@ import os import sys import types import posixpath +import traceback from os import path from cStringIO import StringIO @@ -112,8 +113,6 @@ class Sphinx(object): if self.confdir is None: self.confdir = self.srcdir - # backwards compatibility: activate old C markup - self.setup_extension('sphinx.ext.oldcmarkup') # load all user-given extension modules for extension in self.config.extensions: self.setup_extension(extension) @@ -122,7 +121,7 @@ class Sphinx(object): self.config.setup(self) # now that we know all config values, collect them from conf.py - self.config.init_values() + self.config.init_values(self.warn) # check the Sphinx version if requested if self.config.needs_sphinx and \ @@ -322,6 +321,7 @@ class Sphinx(object): try: mod = __import__(extension, None, None, ['setup']) except ImportError, err: + self.verbose('Original exception:\n' + traceback.format_exc()) raise ExtensionError('Could not import extension %s' % extension, err) if not hasattr(mod, 'setup'): diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py index 1dfcc704..35061b79 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -12,7 +12,6 @@ import os import re -import sys import time import codecs import zipfile @@ -750,12 +749,5 @@ class EpubBuilder(StandaloneHTMLBuilder): zipfile.ZIP_STORED) for file in projectfiles: fp = path.join(outdir, file) - if sys.version_info < (2, 6): - # When zipile.ZipFile.write call with unicode filename, ZipFile - # encode filename to 'utf-8' (only after Python-2.6). - if isinstance(file, unicode): - # OEBPS Container Format (OCF) 2.0.1 specification require - # "File Names MUST be UTF-8 encoded". - file = file.encode('utf-8') epub.write(fp, file, zipfile.ZIP_DEFLATED) epub.close() diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index beb8d3ca..638d667d 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -16,11 +16,7 @@ import codecs import posixpath import cPickle as pickle from os import path -try: - from hashlib import md5 -except ImportError: - # 2.4 compatibility - from md5 import md5 +from hashlib import md5 from docutils import nodes from docutils.io import DocTreeInput, StringOutput @@ -35,7 +31,7 @@ from sphinx.util.osutil import SEP, os_path, relative_uri, ensuredir, \ movefile, ustrftime, copyfile from sphinx.util.nodes import inline_all_toctrees from sphinx.util.matching import patmatch, compile_matchers -from sphinx.util.pycompat import any, b +from sphinx.util.pycompat import b from sphinx.errors import SphinxError from sphinx.locale import _ from sphinx.search import js_index @@ -583,10 +579,7 @@ class StandaloneHTMLBuilder(Builder): # then, copy over all user-supplied static files staticentries = [path.join(self.confdir, spath) for spath in self.config.html_static_path] - matchers = compile_matchers( - self.config.exclude_patterns + - ['**/' + d for d in self.config.exclude_dirnames] - ) + matchers = compile_matchers(self.config.exclude_patterns) for entry in staticentries: if not path.exists(entry): self.warn('html_static_path entry %r does not exist' % entry) @@ -1100,8 +1093,4 @@ class JSONHTMLBuilder(SerializingHTMLBuilder): searchindex_filename = 'searchindex.json' def init(self): - if jsonimpl.json is None: - raise SphinxError( - 'The module simplejson (or json in Python >= 2.6) ' - 'is not available. The JSONHTMLBuilder builder will not work.') SerializingHTMLBuilder.init(self) diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 6cbe361b..171e68d5 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -10,7 +10,6 @@ """ import re -import sys import Queue import socket import threading @@ -110,7 +109,7 @@ class CheckExternalLinksBuilder(Builder): def check_thread(self): kwargs = {} - if sys.version_info > (2, 5) and self.app.config.linkcheck_timeout: + if self.app.config.linkcheck_timeout: kwargs['timeout'] = self.app.config.linkcheck_timeout def check(): diff --git a/sphinx/cmdline.py b/sphinx/cmdline.py index 99001a28..97f9718b 100644 --- a/sphinx/cmdline.py +++ b/sphinx/cmdline.py @@ -181,14 +181,11 @@ def main(argv): print >>sys.stderr, ('Error: -D option argument must be ' 'in the form name=value.') return 1 - try: - val = int(val) - except ValueError: - if likely_encoding and isinstance(val, bytes): - try: - val = val.decode(likely_encoding) - except UnicodeError: - pass + if likely_encoding and isinstance(val, bytes): + try: + val = val.decode(likely_encoding) + except UnicodeError: + pass confoverrides[key] = val elif opt == '-A': try: diff --git a/sphinx/config.py b/sphinx/config.py index 8b15bdf3..ace5f2a6 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -51,10 +51,6 @@ class Config(object): source_suffix = ('.rst', 'env'), source_encoding = ('utf-8-sig', 'env'), exclude_patterns = ([], 'env'), - # the next three are all deprecated now - unused_docs = ([], 'env'), - exclude_trees = ([], 'env'), - exclude_dirnames = ([], 'env'), default_role = (None, 'env'), add_function_parentheses = (True, 'env'), add_module_names = (True, 'env'), @@ -214,8 +210,11 @@ class Config(object): self.overrides = overrides self.values = Config.config_values.copy() config = {} - if "extensions" in overrides: - config["extensions"] = overrides["extensions"] + if 'extensions' in overrides: + if isinstance(overrides['extensions'], (str, unicode)): + config['extensions'] = overrides.pop('extensions').split(',') + else: + config['extensions'] = overrides.pop('extensions') if dirname is not None: config_file = path.join(dirname, filename) config['__file__'] = config_file @@ -248,12 +247,36 @@ class Config(object): 'Please use Unicode strings, e.g. %r.' % (name, u'Content') ) - def init_values(self): + def init_values(self, warn): config = self._raw_config for valname, value in self.overrides.iteritems(): if '.' in valname: realvalname, key = valname.split('.', 1) config.setdefault(realvalname, {})[key] = value + continue + elif valname not in self.values: + warn('unknown config value %r in override, ignoring' % valname) + continue + defvalue = self.values[valname][0] + if isinstance(value, (str, unicode)): + if isinstance(defvalue, dict): + warn('cannot override dictionary config setting %r, ' + 'ignoring (use %r to set individual elements)' % + (valname, valname + '.key=value')) + continue + elif isinstance(defvalue, list): + config[valname] = value.split(',') + elif isinstance(defvalue, (int, long)): + try: + config[valname] = int(value) + except ValueError: + warn('invalid number %r for config value %r, ignoring' + % (value, valname)) + elif defvalue is not None and not isinstance(defvalue, (str, unicode)): + warn('cannot override config setting %r with unsupported type, ' + 'ignoring' % valname) + else: + config[valname] = value else: config[valname] = value for name in config: diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py index 4d43e5ff..6900ea6b 100644 --- a/sphinx/directives/code.py +++ b/sphinx/directives/code.py @@ -56,7 +56,9 @@ class CodeBlock(Directive): final_argument_whitespace = False option_spec = { 'linenos': directives.flag, + 'lineno-start': int, 'emphasize-lines': directives.unchanged_required, + 'filename': directives.unchanged_required, } def run(self): @@ -75,9 +77,16 @@ class CodeBlock(Directive): literal = nodes.literal_block(code, code) literal['language'] = self.arguments[0] - literal['linenos'] = 'linenos' in self.options + filename = self.options.get('filename') + if filename: + literal['filename'] = filename + literal['linenos'] = 'linenos' in self.options or \ + 'lineno-start' in self.options + extra_args = literal['highlight_args'] = {} if hl_lines is not None: - literal['highlight_args'] = {'hl_lines': hl_lines} + extra_args['hl_lines'] = hl_lines + if 'lineno-start' in self.options: + extra_args['linenostart'] = self.options['lineno-start'] set_source_info(self, literal) return [literal] @@ -95,6 +104,7 @@ class LiteralInclude(Directive): final_argument_whitespace = True option_spec = { 'linenos': directives.flag, + 'lineno-start': int, 'tab-width': int, 'language': directives.unchanged_required, 'encoding': directives.encoding, @@ -105,6 +115,7 @@ class LiteralInclude(Directive): 'prepend': directives.unchanged_required, 'append': directives.unchanged_required, 'emphasize-lines': directives.unchanged_required, + 'filename': directives.unchanged, } def run(self): @@ -204,10 +215,18 @@ class LiteralInclude(Directive): set_source_info(self, retnode) if self.options.get('language', ''): retnode['language'] = self.options['language'] - if 'linenos' in self.options: - retnode['linenos'] = True + retnode['linenos'] = 'linenos' in self.options or \ + 'lineno-start' in self.options + filename = self.options.get('filename') + if filename is not None: + if not filename: + filename = self.arguments[0] + retnode['filename'] = filename + extra_args = retnode['highlight_args'] = {} if hl_lines is not None: - retnode['highlight_args'] = {'hl_lines': hl_lines} + extra_args['hl_lines'] = hl_lines + if 'lineno-start' in self.options: + extra_args['linenostart'] = self.options['lineno-start'] env.note_dependency(rel_filename) return [retnode] diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index a7ed7557..6714e838 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -81,6 +81,23 @@ def _pseudo_parse_arglist(signode, arglist): signode += paramlist +# This override allows our inline type specifiers to behave like :class: link +# when it comes to handling "." and "~" prefixes. +class PyTypedField(TypedField): + def make_xref(self, rolename, domain, target, innernode=nodes.emphasis): + result = super(PyTypedField, self).make_xref(rolename, domain, target, + innernode) + if target.startswith('.'): + result['reftarget'] = target[1:] + result['refspecific'] = True + result[0][0] = nodes.Text(target[1:]) + if target.startswith('~'): + result['reftarget'] = target[1:] + title = target.split('.')[-1] + result[0][0] = nodes.Text(title) + return result + + class PyObject(ObjectDescription): """ Description of a general Python object. @@ -92,7 +109,7 @@ class PyObject(ObjectDescription): } doc_field_types = [ - TypedField('parameter', label=l_('Parameters'), + PyTypedField('parameter', label=l_('Parameters'), names=('param', 'parameter', 'arg', 'argument', 'keyword', 'kwarg', 'kwparam'), typerolename='obj', typenames=('paramtype', 'type'), diff --git a/sphinx/environment.py b/sphinx/environment.py index c84f5752..ca9a213e 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -340,9 +340,6 @@ class BuildEnvironment: matchers = compile_matchers( config.exclude_patterns[:] + config.html_extra_path + - config.exclude_trees + - [d + config.source_suffix for d in config.unused_docs] + - ['**/' + d for d in config.exclude_dirnames] + ['**/_sources', '.#*'] ) self.found_docs = set(get_matching_docs( diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index d07014fa..77862ff2 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -29,7 +29,7 @@ from sphinx.util.nodes import nested_parse_with_titles from sphinx.util.compat import Directive from sphinx.util.inspect import getargspec, isdescriptor, safe_getmembers, \ safe_getattr, safe_repr, is_builtin_class_method -from sphinx.util.pycompat import base_exception, class_types +from sphinx.util.pycompat import class_types from sphinx.util.docstrings import prepare_docstring @@ -70,6 +70,35 @@ class Options(dict): return None +class _MockModule(object): + """Used by autodoc_mock_imports.""" + def __init__(self, *args, **kwargs): + pass + + def __call__(self, *args, **kwargs): + return _MockModule() + + @classmethod + def __getattr__(cls, name): + if name in ('__file__', '__path__'): + return '/dev/null' + elif name[0] == name[0].upper(): + # Not very good, we assume Uppercase names are classes... + mocktype = type(name, (), {}) + mocktype.__module__ = __name__ + return mocktype + else: + return _MockModule() + +def mock_import(modname): + if '.' in modname: + pkg, _n, mods = modname.rpartition('.') + mock_import(pkg) + mod = _MockModule() + sys.modules[modname] = mod + return mod + + ALL = object() INSTANCEATTR = object() @@ -332,6 +361,9 @@ class Documenter(object): self.modname, '.'.join(self.objpath)) try: dbg('[autodoc] import %s', self.modname) + for modname in self.env.config.autodoc_mock_imports: + dbg('[autodoc] adding a mock module %s!', self.modname) + mock_import(modname) __import__(self.modname) parent = None obj = self.module = sys.modules[self.modname] @@ -1131,7 +1163,7 @@ class ExceptionDocumenter(ClassDocumenter): @classmethod def can_document_member(cls, member, membername, isattr, parent): return isinstance(member, class_types) and \ - issubclass(member, base_exception) + issubclass(member, BaseException) class DataDocumenter(ModuleLevelDocumenter): @@ -1454,6 +1486,7 @@ def setup(app): app.add_config_value('autodoc_member_order', 'alphabetic', True) app.add_config_value('autodoc_default_flags', [], True) app.add_config_value('autodoc_docstring_signature', True, True) + app.add_config_value('autodoc_mock_imports', [], True) app.add_event('autodoc-process-docstring') app.add_event('autodoc-process-signature') app.add_event('autodoc-skip-member') diff --git a/sphinx/ext/napoleon/__init__.py b/sphinx/ext/napoleon/__init__.py new file mode 100644 index 00000000..d3b6f754 --- /dev/null +++ b/sphinx/ext/napoleon/__init__.py @@ -0,0 +1,375 @@ +# -*- coding: utf-8 -*- +""" + sphinx.ext.napoleon + ~~~~~~~~~~~~~~~~~~~ + + Support for NumPy and Google style docstrings. + + :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import sys +from sphinx.ext.napoleon.docstring import GoogleDocstring, NumpyDocstring + + +class Config(object): + """Sphinx napoleon extension settings in `conf.py`. + + Listed below are all the settings used by napoleon and their default + values. These settings can be changed in the Sphinx `conf.py` file. Make + sure that both "sphinx.ext.autodoc" and "sphinx.ext.napoleon" are + enabled in `conf.py`:: + + # conf.py + + # Add any Sphinx extension module names here, as strings + extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon'] + + # Napoleon settings + napoleon_google_docstring = True + napoleon_numpy_docstring = True + napoleon_include_private_with_doc = False + napoleon_include_special_with_doc = True + napoleon_use_admonition_for_examples = False + napoleon_use_admonition_for_notes = False + napoleon_use_admonition_for_references = False + napoleon_use_ivar = False + napoleon_use_param = False + napoleon_use_rtype = False + + .. _Google style: + http://google-styleguide.googlecode.com/svn/trunk/pyguide.html + .. _NumPy style: + https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt + + Attributes + ---------- + napoleon_google_docstring : bool, defaults to True + True to parse `Google style`_ docstrings. False to disable support + for Google style docstrings. + napoleon_numpy_docstring : bool, defaults to True + True to parse `NumPy style`_ docstrings. False to disable support + for NumPy style docstrings. + napoleon_include_private_with_doc : bool, defaults to False + True to include private members (like ``_membername``) with docstrings + in the documentation. False to fall back to Sphinx's default behavior. + + **If True**:: + + def _included(self): + \"\"\" + This will be included in the docs because it has a docstring + \"\"\" + pass + + def _skipped(self): + # This will NOT be included in the docs + pass + + napoleon_include_special_with_doc : bool, defaults to True + True to include special members (like ``__membername__``) with + docstrings in the documentation. False to fall back to Sphinx's + default behavior. + + **If True**:: + + def __str__(self): + \"\"\" + This will be included in the docs because it has a docstring + \"\"\" + return unicode(self).encode('utf-8') + + def __unicode__(self): + # This will NOT be included in the docs + return unicode(self.__class__.__name__) + + napoleon_use_admonition_for_examples : bool, defaults to False + True to use the ``.. admonition::`` directive for the **Example** and + **Examples** sections. False to use the ``.. rubric::`` directive + instead. One may look better than the other depending on what HTML + theme is used. + + This `NumPy style`_ snippet will be converted as follows:: + + Example + ------- + This is just a quick example + + **If True**:: + + .. admonition:: Example + + This is just a quick example + + **If False**:: + + .. rubric:: Example + + This is just a quick example + + napoleon_use_admonition_for_notes : bool, defaults to False + True to use the ``.. admonition::`` directive for **Notes** sections. + False to use the ``.. rubric::`` directive instead. + + Note + ---- + The singular **Note** section will always be converted to a + ``.. note::`` directive. + + See Also + -------- + :attr:`napoleon_use_admonition_for_examples` + + napoleon_use_admonition_for_references : bool, defaults to False + True to use the ``.. admonition::`` directive for **References** + sections. False to use the ``.. rubric::`` directive instead. + + See Also + -------- + :attr:`napoleon_use_admonition_for_examples` + + napoleon_use_ivar : bool, defaults to False + True to use the ``:ivar:`` role for instance variables. False to use + the ``.. attribute::`` directive instead. + + This `NumPy style`_ snippet will be converted as follows:: + + Attributes + ---------- + attr1 : int + Description of `attr1` + + **If True**:: + + :ivar attr1: Description of `attr1` + :vartype attr1: int + + **If False**:: + + .. attribute:: attr1 + :annotation: int + + Description of `attr1` + + napoleon_use_param : bool, defaults to False + True to use a ``:param:`` role for each function parameter. False to + use a single ``:parameters:`` role for all the parameters. + + This `NumPy style`_ snippet will be converted as follows:: + + Parameters + ---------- + arg1 : str + Description of `arg1` + arg2 : int, optional + Description of `arg2`, defaults to 0 + + **If True**:: + + :param arg1: Description of `arg1` + :type arg1: str + :param arg2: Description of `arg2`, defaults to 0 + :type arg2: int, optional + + **If False**:: + + :parameters: * **arg1** (*str*) -- + Description of `arg1` + * **arg2** (*int, optional*) -- + Description of `arg2`, defaults to 0 + + napoleon_use_rtype : bool, defaults to False + True to use the ``:rtype:`` role for the return type. False to output + the return type inline with the description. + + This `NumPy style`_ snippet will be converted as follows:: + + Returns + ------- + bool + True if successful, False otherwise + + **If True**:: + + :returns: True if successful, False otherwise + :rtype: bool + + **If False**:: + + :returns: *bool* -- True if successful, False otherwise + + """ + _config_values = { + 'napoleon_google_docstring': (True, 'env'), + 'napoleon_numpy_docstring': (True, 'env'), + 'napoleon_include_private_with_doc': (False, 'env'), + 'napoleon_include_special_with_doc': (True, 'env'), + 'napoleon_use_admonition_for_examples': (False, 'env'), + 'napoleon_use_admonition_for_notes': (False, 'env'), + 'napoleon_use_admonition_for_references': (False, 'env'), + 'napoleon_use_ivar': (False, 'env'), + 'napoleon_use_param': (False, 'env'), + 'napoleon_use_rtype': (False, 'env'), + } + + def __init__(self, **settings): + for name, (default, rebuild) in self._config_values.iteritems(): + setattr(self, name, default) + for name, value in settings.iteritems(): + setattr(self, name, value) + + +def setup(app): + """Sphinx extension setup function. + + When the extension is loaded, Sphinx imports this module and executes + the ``setup()`` function, which in turn notifies Sphinx of everything + the extension offers. + + Parameters + ---------- + app : sphinx.application.Sphinx + Application object representing the Sphinx process + + See Also + -------- + The Sphinx documentation on `Extensions`_, the `Extension Tutorial`_, and + the `Extension API`_. + + .. _Extensions: http://sphinx-doc.org/extensions.html + .. _Extension Tutorial: http://sphinx-doc.org/ext/tutorial.html + .. _Extension API: http://sphinx-doc.org/ext/appapi.html + + """ + from sphinx.application import Sphinx + if not isinstance(app, Sphinx): + return # probably called by tests + + app.connect('autodoc-process-docstring', _process_docstring) + app.connect('autodoc-skip-member', _skip_member) + + for name, (default, rebuild) in Config._config_values.iteritems(): + app.add_config_value(name, default, rebuild) + + +def _process_docstring(app, what, name, obj, options, lines): + """Process the docstring for a given python object. + + Called when autodoc has read and processed a docstring. `lines` is a list + of docstring lines that `_process_docstring` modifies in place to change + what Sphinx outputs. + + The following settings in conf.py control what styles of docstrings will + be parsed: + + * ``napoleon_google_docstring`` -- parse Google style docstrings + * ``napoleon_numpy_docstring`` -- parse NumPy style docstrings + + Parameters + ---------- + app : sphinx.application.Sphinx + Application object representing the Sphinx process. + what : str + A string specifying the type of the object to which the docstring + belongs. Valid values: "module", "class", "exception", "function", + "method", "attribute". + name : str + The fully qualified name of the object. + obj : module, class, exception, function, method, or attribute + The object to which the docstring belongs. + options : sphinx.ext.autodoc.Options + The options given to the directive: an object with attributes + inherited_members, undoc_members, show_inheritance and noindex that + are True if the flag option of same name was given to the auto + directive. + lines : list of str + The lines of the docstring, see above. + + .. note:: `lines` is modified *in place* + + """ + result_lines = lines + if app.config.napoleon_numpy_docstring: + docstring = NumpyDocstring(result_lines, app.config, app, what, name, + obj, options) + result_lines = docstring.lines() + if app.config.napoleon_google_docstring: + docstring = GoogleDocstring(result_lines, app.config, app, what, name, + obj, options) + result_lines = docstring.lines() + lines[:] = result_lines[:] + + +def _skip_member(app, what, name, obj, skip, options): + """Determine if private and special class members are included in docs. + + The following settings in conf.py determine if private and special class + members are included in the generated documentation: + + * ``napoleon_include_private_with_doc`` -- + include private members if they have docstrings + * ``napoleon_include_special_with_doc`` -- + include special members if they have docstrings + + Parameters + ---------- + app : sphinx.application.Sphinx + Application object representing the Sphinx process + what : str + A string specifying the type of the object to which the member + belongs. Valid values: "module", "class", "exception", "function", + "method", "attribute". + name : str + The name of the member. + obj : module, class, exception, function, method, or attribute. + For example, if the member is the __init__ method of class A, then + `obj` will be `A.__init__`. + skip : bool + A boolean indicating if autodoc will skip this member if `_skip_member` + does not override the decision + options : sphinx.ext.autodoc.Options + The options given to the directive: an object with attributes + inherited_members, undoc_members, show_inheritance and noindex that + are True if the flag option of same name was given to the auto + directive. + + Returns + ------- + bool + True if the member should be skipped during creation of the docs, + False if it should be included in the docs. + + """ + has_doc = getattr(obj, '__doc__', False) + is_member = (what == 'class' or what == 'exception' or what == 'module') + if name != '__weakref__' and name != '__init__' and has_doc and is_member: + if what == 'class' or what == 'exception': + if sys.version_info[0] < 3: + cls = getattr(obj, 'im_class', getattr(obj, '__objclass__', + None)) + cls_is_owner = (cls and hasattr(cls, name) and + name in cls.__dict__) + elif sys.version_info[1] >= 3 and hasattr(obj, '__qualname__'): + cls_path, _, _ = obj.__qualname__.rpartition('.') + if cls_path: + import importlib + import functools + + mod = importlib.import_module(obj.__module__) + cls = functools.reduce(getattr, cls_path.split('.'), mod) + cls_is_owner = (cls and hasattr(cls, name) and + name in cls.__dict__) + else: + cls_is_owner = False + else: + cls_is_owner = True + + if what == 'module' or cls_is_owner: + is_special = name.startswith('__') and name.endswith('__') + is_private = not is_special and name.startswith('_') + inc_special = app.config.napoleon_include_special_with_doc + inc_private = app.config.napoleon_include_private_with_doc + if (is_special and inc_special) or (is_private and inc_private): + return False + return skip diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py new file mode 100644 index 00000000..d6c73db8 --- /dev/null +++ b/sphinx/ext/napoleon/docstring.py @@ -0,0 +1,714 @@ +# -*- coding: utf-8 -*- +""" + sphinx.ext.napoleon.docstring + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + + Classes for docstring parsing and formatting. + + + :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re +import sys +from sphinx.ext.napoleon.iterators import modify_iter + + +if sys.version_info[0] >= 3: + basestring = str + xrange = range + + +_directive_regex = re.compile(r'\.\. \S+::') +_field_parens_regex = re.compile(r'\s*(\w+)\s*\(\s*(.+?)\s*\)') + + +class GoogleDocstring(object): + """Parse Google style docstrings. + + Convert Google style docstrings to reStructuredText. + + Parameters + ---------- + docstring : str or list of str + The docstring to parse, given either as a string or split into + individual lines. + config : sphinx.ext.napoleon.Config or sphinx.config.Config, optional + The configuration settings to use. If not given, defaults to the + config object on `app`; or if `app` is not given defaults to the + a new `sphinx.ext.napoleon.Config` object. + + See Also + -------- + :class:`sphinx.ext.napoleon.Config` + + Other Parameters + ---------------- + app : sphinx.application.Sphinx, optional + Application object representing the Sphinx process. + what : str, optional + A string specifying the type of the object to which the docstring + belongs. Valid values: "module", "class", "exception", "function", + "method", "attribute". + name : str, optional + The fully qualified name of the object. + obj : module, class, exception, function, method, or attribute + The object to which the docstring belongs. + options : sphinx.ext.autodoc.Options, optional + The options given to the directive: an object with attributes + inherited_members, undoc_members, show_inheritance and noindex that + are True if the flag option of same name was given to the auto + directive. + + Example + ------- + >>> from sphinx.ext.napoleon import Config + >>> config = Config(napoleon_use_param=True, napoleon_use_rtype=True) + >>> docstring = '''One line summary. + ... + ... Extended description. + ... + ... Args: + ... arg1(int): Description of `arg1` + ... arg2(str): Description of `arg2` + ... Returns: + ... str: Description of return value. + ... ''' + >>> print(GoogleDocstring(docstring, config)) + One line summary. + <BLANKLINE> + Extended description. + <BLANKLINE> + :param arg1: Description of `arg1` + :type arg1: int + :param arg2: Description of `arg2` + :type arg2: str + <BLANKLINE> + :returns: Description of return value. + :rtype: str + + """ + def __init__(self, docstring, config=None, app=None, what='', name='', + obj=None, options=None): + self._config = config + self._app = app + if not self._config: + from sphinx.ext.napoleon import Config + self._config = self._app and self._app.config or Config() + self._what = what + self._name = name + self._obj = obj + self._opt = options + if isinstance(docstring, basestring): + docstring = docstring.splitlines() + self._lines = docstring + self._line_iter = modify_iter(docstring, modifier=lambda s: s.rstrip()) + self._parsed_lines = [] + self._is_in_section = False + self._section_indent = 0 + if not hasattr(self, '_directive_sections'): + self._directive_sections = [] + if not hasattr(self, '_sections'): + self._sections = { + 'args': self._parse_parameters_section, + 'arguments': self._parse_parameters_section, + 'attributes': self._parse_attributes_section, + 'example': self._parse_examples_section, + 'examples': self._parse_examples_section, + 'keyword args': self._parse_keyword_arguments_section, + 'keyword arguments': self._parse_keyword_arguments_section, + 'methods': self._parse_methods_section, + 'note': self._parse_note_section, + 'notes': self._parse_notes_section, + 'other parameters': self._parse_other_parameters_section, + 'parameters': self._parse_parameters_section, + 'return': self._parse_returns_section, + 'returns': self._parse_returns_section, + 'raises': self._parse_raises_section, + 'references': self._parse_references_section, + 'see also': self._parse_see_also_section, + 'warning': self._parse_warning_section, + 'warnings': self._parse_warning_section, + 'warns': self._parse_warns_section, + 'yields': self._parse_yields_section, + } + self._parse() + + def __str__(self): + """Return the parsed docstring in reStructuredText format. + + Returns + ------- + str + UTF-8 encoded version of the docstring. + + """ + if sys.version_info[0] >= 3: + return self.__unicode__() + else: + return self.__unicode__().encode('utf8') + + def __unicode__(self): + """Return the parsed docstring in reStructuredText format. + + Returns + ------- + unicode + Unicode version of the docstring. + + """ + return u'\n'.join(self.lines()) + + def lines(self): + """Return the parsed lines of the docstring in reStructuredText format. + + Returns + ------- + list of str + The lines of the docstring in a list. + + """ + return self._parsed_lines + + def _consume_indented_block(self, indent=1): + lines = [] + line = self._line_iter.peek() + while(not self._is_section_break() + and (not line or self._is_indented(line, indent))): + lines.append(self._line_iter.next()) + line = self._line_iter.peek() + return lines + + def _consume_contiguous(self): + lines = [] + while (self._line_iter.has_next() + and self._line_iter.peek() + and not self._is_section_header()): + lines.append(self._line_iter.next()) + return lines + + def _consume_empty(self): + lines = [] + line = self._line_iter.peek() + while self._line_iter.has_next() and not line: + lines.append(self._line_iter.next()) + line = self._line_iter.peek() + return lines + + def _consume_field(self, parse_type=True, prefer_type=False): + line = self._line_iter.next() + _name, _, _desc = line.partition(':') + _name, _type, _desc = _name.strip(), '', _desc.strip() + match = _field_parens_regex.match(_name) + if parse_type and match: + _name = match.group(1) + _type = match.group(2) + if prefer_type and not _type: + _type, _name = _name, _type + indent = self._get_indent(line) + 1 + _desc = [_desc] + self._dedent(self._consume_indented_block(indent)) + _desc = self.__class__(_desc, self._config).lines() + return _name, _type, _desc + + def _consume_fields(self, parse_type=True, prefer_type=False): + self._consume_empty() + fields = [] + while not self._is_section_break(): + _name, _type, _desc = self._consume_field(parse_type, prefer_type) + if _name or _type or _desc: + fields.append((_name, _type, _desc,)) + return fields + + def _consume_returns_section(self): + lines = self._dedent(self._consume_to_next_section()) + if lines: + if ':' in lines[0]: + _type, _, _desc = lines[0].partition(':') + _name, _type, _desc = '', _type.strip(), _desc.strip() + match = _field_parens_regex.match(_type) + if match: + _name = match.group(1) + _type = match.group(2) + lines[0] = _desc + _desc = lines + else: + _name, _type, _desc = '', '', lines + _desc = self.__class__(_desc, self._config).lines() + return [(_name, _type, _desc,)] + else: + return [] + + def _consume_section_header(self): + section = self._line_iter.next() + stripped_section = section.strip(':') + if stripped_section.lower() in self._sections: + section = stripped_section + return section + + def _consume_to_next_section(self): + self._consume_empty() + lines = [] + while not self._is_section_break(): + lines.append(self._line_iter.next()) + return lines + self._consume_empty() + + def _dedent(self, lines, full=False): + if full: + return [line.lstrip() for line in lines] + else: + min_indent = self._get_min_indent(lines) + return [line[min_indent:] for line in lines] + + def _format_admonition(self, admonition, lines): + lines = self._strip_empty(lines) + if len(lines) == 1: + return ['.. %s:: %s' % (admonition, lines[0].strip()), ''] + elif lines: + lines = self._indent(self._dedent(lines), 3) + return ['.. %s::' % admonition, ''] + lines + [''] + else: + return ['.. %s::' % admonition, ''] + + def _format_block(self, prefix, lines, padding=None): + if lines: + if padding is None: + padding = ' ' * len(prefix) + result_lines = [] + for i, line in enumerate(lines): + if line: + if i == 0: + result_lines.append(prefix + line) + else: + result_lines.append(padding + line) + else: + result_lines.append('') + return result_lines + else: + return [prefix] + + def _format_field(self, _name, _type, _desc): + separator = any([s for s in _desc]) and ' --' or '' + if _name: + if _type: + field = ['**%s** (*%s*)%s' % (_name, _type, separator)] + else: + field = ['**%s**%s' % (_name, separator)] + elif _type: + field = ['*%s*%s' % (_type, separator)] + else: + field = [] + return field + _desc + + def _format_fields(self, field_type, fields): + field_type = ':%s:' % field_type.strip() + padding = ' ' * len(field_type) + multi = len(fields) > 1 + lines = [] + for _name, _type, _desc in fields: + field = self._format_field(_name, _type, _desc) + if multi: + if lines: + lines.extend(self._format_block(padding + ' * ', field)) + else: + lines.extend(self._format_block(field_type + ' * ', field)) + else: + lines.extend(self._format_block(field_type + ' ', field)) + return lines + + def _get_current_indent(self, peek_ahead=0): + line = self._line_iter.peek(peek_ahead + 1)[peek_ahead] + while line != self._line_iter.sentinel: + if line: + return self._get_indent(line) + peek_ahead += 1 + line = self._line_iter.peek(peek_ahead + 1)[peek_ahead] + return 0 + + def _get_indent(self, line): + for i, s in enumerate(line): + if not s.isspace(): + return i + return len(line) + + def _get_min_indent(self, lines): + min_indent = None + for line in lines: + if line: + indent = self._get_indent(line) + if min_indent is None: + min_indent = indent + elif indent < min_indent: + min_indent = indent + return min_indent or 0 + + def _indent(self, lines, n=4): + return [(' ' * n) + line for line in lines] + + def _is_indented(self, line, indent=1): + for i, s in enumerate(line): + if i >= indent: + return True + elif not s.isspace(): + return False + return False + + def _is_section_header(self): + section = self._line_iter.peek().lower() + if section.strip(':') in self._sections: + header_indent = self._get_indent(section) + section_indent = self._get_current_indent(peek_ahead=1) + return section_indent > header_indent + elif self._directive_sections: + if _directive_regex.match(section): + for directive_section in self._directive_sections: + if section.startswith(directive_section): + return True + return False + + def _is_section_break(self): + line = self._line_iter.peek() + return (not self._line_iter.has_next() + or self._is_section_header() + or (self._is_in_section + and line + and not self._is_indented(line, self._section_indent))) + + def _parse(self): + self._parsed_lines = self._consume_empty() + while self._line_iter.has_next(): + if self._is_section_header(): + try: + section = self._consume_section_header() + self._is_in_section = True + self._section_indent = self._get_current_indent() + if _directive_regex.match(section): + lines = [section] + self._consume_to_next_section() + else: + lines = self._sections[section.lower()](section) + finally: + self._is_in_section = False + self._section_indent = 0 + else: + if not self._parsed_lines: + lines = self._consume_contiguous() + self._consume_empty() + else: + lines = self._consume_to_next_section() + self._parsed_lines.extend(lines) + + def _parse_attributes_section(self, section): + lines = [] + for _name, _type, _desc in self._consume_fields(): + if self._config.napoleon_use_ivar: + field = ':ivar %s: ' % _name + lines.extend(self._format_block(field, _desc)) + if _type: + lines.append(':vartype %s: %s' % (_name, _type)) + else: + lines.append('.. attribute:: ' + _name) + if _type: + lines.append(' :annotation: ' + _type) + if _desc: + lines.extend([''] + self._indent(_desc, 3)) + lines.append('') + if self._config.napoleon_use_ivar: + lines.append('') + return lines + + def _parse_examples_section(self, section): + use_admonition = self._config.napoleon_use_admonition_for_examples + return self._parse_generic_section(section, use_admonition) + + def _parse_generic_section(self, section, use_admonition): + lines = self._strip_empty(self._consume_to_next_section()) + lines = self._dedent(lines) + if use_admonition: + header = '.. admonition:: %s' % section + lines = self._indent(lines, 3) + else: + header = '.. rubric:: %s' % section + if lines: + return [header, ''] + lines + [''] + else: + return [header, ''] + + def _parse_keyword_arguments_section(self, section): + return self._format_fields('Keyword Arguments', self._consume_fields()) + + def _parse_methods_section(self, section): + lines = [] + for _name, _, _desc in self._consume_fields(parse_type=False): + lines.append('.. method:: %s' % _name) + if _desc: + lines.extend([''] + self._indent(_desc, 3)) + lines.append('') + return lines + + def _parse_note_section(self, section): + lines = self._consume_to_next_section() + return self._format_admonition('note', lines) + + def _parse_notes_section(self, section): + use_admonition = self._config.napoleon_use_admonition_for_notes + return self._parse_generic_section('Notes', use_admonition) + + def _parse_other_parameters_section(self, section): + return self._format_fields('Other Parameters', self._consume_fields()) + + def _parse_parameters_section(self, section): + fields = self._consume_fields() + if self._config.napoleon_use_param: + lines = [] + for _name, _type, _desc in fields: + field = ':param %s: ' % _name + lines.extend(self._format_block(field, _desc)) + if _type: + lines.append(':type %s: %s' % (_name, _type)) + return lines + [''] + else: + return self._format_fields('Parameters', fields) + + def _parse_raises_section(self, section): + fields = self._consume_fields() + field_type = ':raises:' + padding = ' ' * len(field_type) + multi = len(fields) > 1 + lines = [] + for _name, _type, _desc in fields: + sep = _desc and ' -- ' or '' + if _name: + if ' ' in _name: + _name = '**%s**' % _name + else: + _name = ':exc:`%s`' % _name + if _type: + field = ['%s (*%s*)%s' % (_name, _type, sep)] + else: + field = ['%s%s' % (_name, sep)] + elif _type: + field = ['*%s*%s' % (_type, sep)] + else: + field = [] + field = field + _desc + if multi: + if lines: + lines.extend(self._format_block(padding + ' * ', field)) + else: + lines.extend(self._format_block(field_type + ' * ', field)) + else: + lines.extend(self._format_block(field_type + ' ', field)) + return lines + + def _parse_references_section(self, section): + use_admonition = self._config.napoleon_use_admonition_for_references + return self._parse_generic_section('References', use_admonition) + + def _parse_returns_section(self, section): + fields = self._consume_returns_section() + multi = len(fields) > 1 + if multi: + use_rtype = False + else: + use_rtype = self._config.napoleon_use_rtype + + lines = [] + for _name, _type, _desc in fields: + if use_rtype: + field = self._format_field(_name, '', _desc) + else: + field = self._format_field(_name, _type, _desc) + + if multi: + if lines: + lines.extend(self._format_block(' * ', field)) + else: + lines.extend(self._format_block(':returns: * ', field)) + else: + lines.extend(self._format_block(':returns: ', field)) + if _type and use_rtype: + lines.append(':rtype: %s' % _type) + return lines + + def _parse_see_also_section(self, section): + lines = self._consume_to_next_section() + return self._format_admonition('seealso', lines) + + def _parse_warning_section(self, section): + lines = self._consume_to_next_section() + return self._format_admonition('warning', lines) + + def _parse_warns_section(self, section): + return self._format_fields('Warns', self._consume_fields()) + + def _parse_yields_section(self, section): + fields = self._consume_fields(prefer_type=True) + return self._format_fields('Yields', fields) + + def _strip_empty(self, lines): + if lines: + start = -1 + for i, line in enumerate(lines): + if line: + start = i + break + if start == -1: + lines = [] + end = -1 + for i in reversed(xrange(len(lines))): + line = lines[i] + if line: + end = i + break + if start > 0 or end + 1 < len(lines): + lines = lines[start:end + 1] + return lines + + +class NumpyDocstring(GoogleDocstring): + """Parse NumPy style docstrings. + + Convert NumPy style docstrings to reStructuredText. + + Parameters + ---------- + docstring : str or list of str + The docstring to parse, given either as a string or split into + individual lines. + config : sphinx.ext.napoleon.Config or sphinx.config.Config, optional + The configuration settings to use. If not given, defaults to the + config object on `app`; or if `app` is not given defaults to the + a new `sphinx.ext.napoleon.Config` object. + + See Also + -------- + :class:`sphinx.ext.napoleon.Config` + + Other Parameters + ---------------- + app : sphinx.application.Sphinx, optional + Application object representing the Sphinx process. + what : str, optional + A string specifying the type of the object to which the docstring + belongs. Valid values: "module", "class", "exception", "function", + "method", "attribute". + name : str, optional + The fully qualified name of the object. + obj : module, class, exception, function, method, or attribute + The object to which the docstring belongs. + options : sphinx.ext.autodoc.Options, optional + The options given to the directive: an object with attributes + inherited_members, undoc_members, show_inheritance and noindex that + are True if the flag option of same name was given to the auto + directive. + + Example + ------- + >>> from sphinx.ext.napoleon import Config + >>> config = Config(napoleon_use_param=True, napoleon_use_rtype=True) + >>> docstring = '''One line summary. + ... + ... Extended description. + ... + ... Parameters + ... ---------- + ... arg1 : int + ... Description of `arg1` + ... arg2 : str + ... Description of `arg2` + ... Returns + ... ------- + ... str + ... Description of return value. + ... ''' + >>> print(NumpyDocstring(docstring, config)) + One line summary. + <BLANKLINE> + Extended description. + <BLANKLINE> + :param arg1: Description of `arg1` + :type arg1: int + :param arg2: Description of `arg2` + :type arg2: str + <BLANKLINE> + :returns: Description of return value. + :rtype: str + + Methods + ------- + __str__() + Return the parsed docstring in reStructuredText format. + + Returns + ------- + str + UTF-8 encoded version of the docstring. + + __unicode__() + Return the parsed docstring in reStructuredText format. + + Returns + ------- + unicode + Unicode version of the docstring. + + lines() + Return the parsed lines of the docstring in reStructuredText format. + + Returns + ------- + list of str + The lines of the docstring in a list. + + """ + def __init__(self, docstring, config=None, app=None, what='', name='', + obj=None, options=None): + self._directive_sections = ['.. index::'] + super(NumpyDocstring, self).__init__(docstring, config, app, what, + name, obj, options) + + def _consume_field(self, parse_type=True, prefer_type=False): + line = self._line_iter.next() + if parse_type: + _name, _, _type = line.partition(':') + else: + _name, _type = line, '' + _name, _type = _name.strip(), _type.strip() + if prefer_type and not _type: + _type, _name = _name, _type + indent = self._get_indent(line) + _desc = self._dedent(self._consume_indented_block(indent + 1)) + _desc = self.__class__(_desc, self._config).lines() + return _name, _type, _desc + + def _consume_returns_section(self): + return self._consume_fields(prefer_type=True) + + def _consume_section_header(self): + section = self._line_iter.next() + if not _directive_regex.match(section): + # Consume the header underline + self._line_iter.next() + return section + + def _is_section_break(self): + line1, line2 = self._line_iter.peek(2) + return (not self._line_iter.has_next() + or self._is_section_header() + or ['', ''] == [line1, line2] + or (self._is_in_section + and line1 + and not self._is_indented(line1, self._section_indent))) + + def _is_section_header(self): + section, underline = self._line_iter.peek(2) + section = section.lower() + if section in self._sections and isinstance(underline, basestring): + pattern = r'[=\-`:\'"~^_*+#<>]{' + str(len(section)) + r'}$' + return bool(re.match(pattern, underline)) + elif self._directive_sections: + if _directive_regex.match(section): + for directive_section in self._directive_sections: + if section.startswith(directive_section): + return True + return False diff --git a/sphinx/ext/napoleon/iterators.py b/sphinx/ext/napoleon/iterators.py new file mode 100644 index 00000000..2f1904da --- /dev/null +++ b/sphinx/ext/napoleon/iterators.py @@ -0,0 +1,244 @@ +# -*- coding: utf-8 -*- +""" + sphinx.ext.napoleon.iterators + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + + A collection of helpful iterators. + + + :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import collections +import sys + + +if sys.version_info[0] >= 3: + callable = lambda o: hasattr(o, '__call__') + + +class peek_iter(object): + """An iterator object that supports peeking ahead. + + Parameters + ---------- + o : iterable or callable + `o` is interpreted very differently depending on the presence of + `sentinel`. + + If `sentinel` is not given, then `o` must be a collection object + which supports either the iteration protocol or the sequence protocol. + + If `sentinel` is given, then `o` must be a callable object. + + sentinel : any value, optional + If given, the iterator will call `o` with no arguments for each + call to its `next` method; if the value returned is equal to + `sentinel`, :exc:`StopIteration` will be raised, otherwise the + value will be returned. + + See Also + -------- + `peek_iter` can operate as a drop in replacement for the built-in + `iter <http://docs.python.org/2/library/functions.html#iter>`_ function. + + Attributes + ---------- + sentinel + The value used to indicate the iterator is exhausted. If `sentinel` + was not given when the `peek_iter` was instantiated, then it will + be set to a new object instance: ``object()``. + + """ + def __init__(self, *args): + """__init__(o, sentinel=None)""" + self._iterable = iter(*args) + self._cache = collections.deque() + if len(args) == 2: + self.sentinel = args[1] + else: + self.sentinel = object() + + def __iter__(self): + return self + + def __next__(self, n=None): + # note: prevent 2to3 to transform self.next() in next(self) which + # causes an infinite loop ! + return getattr(self, 'next')(n) + + def _fillcache(self, n): + """Cache `n` items. If `n` is 0 or None, then 1 item is cached.""" + if not n: + n = 1 + try: + while len(self._cache) < n: + self._cache.append(self._iterable.next()) + except StopIteration: + while len(self._cache) < n: + self._cache.append(self.sentinel) + + def has_next(self): + """Determine if iterator is exhausted. + + Returns + ------- + bool + True if iterator has more items, False otherwise. + + Note + ---- + Will never raise :exc:`StopIteration`. + + """ + return self.peek() != self.sentinel + + def next(self, n=None): + """Get the next item or `n` items of the iterator. + + Parameters + ---------- + n : int or None + The number of items to retrieve. Defaults to None. + + Returns + ------- + item or list of items + The next item or `n` items of the iterator. If `n` is None, the + item itself is returned. If `n` is an int, the items will be + returned in a list. If `n` is 0, an empty list is returned. + + Raises + ------ + StopIteration + Raised if the iterator is exhausted, even if `n` is 0. + + """ + self._fillcache(n) + if not n: + if self._cache[0] == self.sentinel: + raise StopIteration + if n is None: + result = self._cache.popleft() + else: + result = [] + else: + if self._cache[n - 1] == self.sentinel: + raise StopIteration + result = [self._cache.popleft() for i in range(n)] + return result + + def peek(self, n=None): + """Preview the next item or `n` items of the iterator. + + The iterator is not advanced when peek is called. + + Returns + ------- + item or list of items + The next item or `n` items of the iterator. If `n` is None, the + item itself is returned. If `n` is an int, the items will be + returned in a list. If `n` is 0, an empty list is returned. + + If the iterator is exhausted, `peek_iter.sentinel` is returned, + or placed as the last item in the returned list. + + Note + ---- + Will never raise :exc:`StopIteration`. + + """ + self._fillcache(n) + if n is None: + result = self._cache[0] + else: + result = [self._cache[i] for i in range(n)] + return result + + +class modify_iter(peek_iter): + """An iterator object that supports modifying items as they are returned. + + Parameters + ---------- + o : iterable or callable + `o` is interpreted very differently depending on the presence of + `sentinel`. + + If `sentinel` is not given, then `o` must be a collection object + which supports either the iteration protocol or the sequence protocol. + + If `sentinel` is given, then `o` must be a callable object. + + sentinel : any value, optional + If given, the iterator will call `o` with no arguments for each + call to its `next` method; if the value returned is equal to + `sentinel`, :exc:`StopIteration` will be raised, otherwise the + value will be returned. + + modifier : callable, optional + The function that will be used to modify each item returned by the + iterator. `modifier` should take a single argument and return a + single value. Defaults to ``lambda x: x``. + + If `sentinel` is not given, `modifier` must be passed as a keyword + argument. + + Attributes + ---------- + modifier : callable + `modifier` is called with each item in `o` as it is iterated. The + return value of `modifier` is returned in lieu of the item. + + Values returned by `peek` as well as `next` are affected by + `modifier`. However, `modify_iter.sentinel` is never passed through + `modifier`; it will always be returned from `peek` unmodified. + + Example + ------- + >>> a = [" A list ", + ... " of strings ", + ... " with ", + ... " extra ", + ... " whitespace. "] + >>> modifier = lambda s: s.strip().replace('with', 'without') + >>> for s in modify_iter(a, modifier=modifier): + ... print('"%s"' % s) + "A list" + "of strings" + "without" + "extra" + "whitespace." + + """ + def __init__(self, *args, **kwargs): + """__init__(o, sentinel=None, modifier=lambda x: x)""" + if 'modifier' in kwargs: + self.modifier = kwargs['modifier'] + elif len(args) > 2: + self.modifier = args[2] + args = args[:2] + else: + self.modifier = lambda x: x + if not callable(self.modifier): + raise TypeError('modify_iter(o, modifier): ' + 'modifier must be callable') + super(modify_iter, self).__init__(*args) + + def _fillcache(self, n): + """Cache `n` modified items. If `n` is 0 or None, 1 item is cached. + + Each item returned by the iterator is passed through the + `modify_iter.modified` function before being cached. + + """ + if not n: + n = 1 + try: + while len(self._cache) < n: + self._cache.append(self.modifier(self._iterable.next())) + except StopIteration: + while len(self._cache) < n: + self._cache.append(self.sentinel) diff --git a/sphinx/ext/oldcmarkup.py b/sphinx/ext/oldcmarkup.py deleted file mode 100644 index aa10246b..00000000 --- a/sphinx/ext/oldcmarkup.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sphinx.ext.oldcmarkup - ~~~~~~~~~~~~~~~~~~~~~ - - Extension for compatibility with old C markup (directives and roles). - - :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -from docutils.parsers.rst import directives - -from sphinx.util.compat import Directive - -_warned_oldcmarkup = False -WARNING_MSG = 'using old C markup; please migrate to new-style markup ' \ - '(e.g. c:function instead of cfunction), see ' \ - 'http://sphinx-doc.org/domains.html' - - -class OldCDirective(Directive): - has_content = True - required_arguments = 1 - optional_arguments = 0 - final_argument_whitespace = True - option_spec = { - 'noindex': directives.flag, - 'module': directives.unchanged, - } - - def run(self): - env = self.state.document.settings.env - if not env.app._oldcmarkup_warned: - self.state_machine.reporter.warning(WARNING_MSG, line=self.lineno) - env.app._oldcmarkup_warned = True - newname = 'c:' + self.name[1:] - newdir = env.lookup_domain_element('directive', newname)[0] - return newdir(newname, self.arguments, self.options, - self.content, self.lineno, self.content_offset, - self.block_text, self.state, self.state_machine).run() - - -def old_crole(typ, rawtext, text, lineno, inliner, options={}, content=[]): - env = inliner.document.settings.env - if not typ: - typ = env.config.default_role - if not env.app._oldcmarkup_warned: - inliner.reporter.warning(WARNING_MSG, line=lineno) - env.app._oldcmarkup_warned = True - newtyp = 'c:' + typ[1:] - newrole = env.lookup_domain_element('role', newtyp)[0] - return newrole(newtyp, rawtext, text, lineno, inliner, options, content) - - -def setup(app): - app._oldcmarkup_warned = False - app.add_directive('cfunction', OldCDirective) - app.add_directive('cmember', OldCDirective) - app.add_directive('cmacro', OldCDirective) - app.add_directive('ctype', OldCDirective) - app.add_directive('cvar', OldCDirective) - app.add_role('cdata', old_crole) - app.add_role('cfunc', old_crole) - app.add_role('cmacro', old_crole) - app.add_role('ctype', old_crole) - app.add_role('cmember', old_crole) diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py index de5d2b9f..253ae07d 100644 --- a/sphinx/ext/todo.py +++ b/sphinx/ext/todo.py @@ -171,4 +171,3 @@ def setup(app): app.connect('doctree-read', process_todos) app.connect('doctree-resolved', process_todo_nodes) app.connect('env-purge-doc', purge_todos) - diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py index 2c95dec0..c30c9ef3 100644 --- a/sphinx/highlighting.py +++ b/sphinx/highlighting.py @@ -63,12 +63,6 @@ _LATEX_STYLES = r''' \newcommand\PYGZcb{\char`\}} ''' -parsing_exceptions = (SyntaxError, UnicodeEncodeError) -if sys.version_info < (2, 5): - # Python <= 2.4 raises MemoryError when parsing an - # invalid encoding cookie - parsing_exceptions += MemoryError, - class PygmentsBridge(object): # Set these attributes if you want to have different Pygments formatters @@ -131,10 +125,6 @@ class PygmentsBridge(object): # lines beginning with "..." are probably placeholders for suite src = re.sub(r"(?m)^(\s*)" + mark + "(.)", r"\1"+ mark + r"# \2", src) - # if we're using 2.5, use the with statement - if sys.version_info >= (2, 5): - src = 'from __future__ import with_statement\n' + src - if sys.version_info < (3, 0) and isinstance(src, unicode): # Non-ASCII chars will only occur in string literals # and comments. If we wanted to give them to the parser @@ -143,18 +133,12 @@ class PygmentsBridge(object): # just replace all non-ASCII characters. src = src.encode('ascii', 'replace') - if (3, 0) <= sys.version_info < (3, 2): - # Python 3.1 can't process '\r' as linesep. - # `parser.suite("print('hello')\r\n")` cause error. - if '\r\n' in src: - src = src.replace('\r\n', '\n') - if parser is None: return True try: parser.suite(src) - except parsing_exceptions: + except (SyntaxError, UnicodeEncodeError): return False else: return True diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py index 2c0708f9..54e79da6 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -17,7 +17,7 @@ from sphinx.errors import PycodeError from sphinx.pycode import nodes from sphinx.pycode.pgen2 import driver, token, tokenize, parse, literals from sphinx.util import get_module_source, detect_encoding -from sphinx.util.pycompat import next, StringIO, BytesIO, TextIOWrapper +from sphinx.util.pycompat import StringIO, BytesIO, TextIOWrapper from sphinx.util.docstrings import prepare_docstring, prepare_commentdoc diff --git a/sphinx/pycode/pgen2/parse.c b/sphinx/pycode/pgen2/parse.c index e09f5058..96fa6c8b 100644 --- a/sphinx/pycode/pgen2/parse.c +++ b/sphinx/pycode/pgen2/parse.c @@ -353,95 +353,6 @@ static void __Pyx_RaiseArgtupleInvalid(const char* func_name, int exact, static int __Pyx_ParseOptionalKeywords(PyObject *kwds, PyObject **argnames[], PyObject *kwds2, PyObject *values[], Py_ssize_t num_pos_args, const char* function_name); /*proto*/ -#if PY_VERSION_HEX < 0x02050000 -#ifndef PyAnySet_CheckExact - -#define PyAnySet_CheckExact(ob) \ - ((ob)->ob_type == &PySet_Type || \ - (ob)->ob_type == &PyFrozenSet_Type) - -#define PySet_New(iterable) \ - PyObject_CallFunctionObjArgs((PyObject *)&PySet_Type, (iterable), NULL) - -#define Pyx_PyFrozenSet_New(iterable) \ - PyObject_CallFunctionObjArgs((PyObject *)&PyFrozenSet_Type, (iterable), NULL) - -#define PySet_Size(anyset) \ - PyObject_Size((anyset)) - -#define PySet_Contains(anyset, key) \ - PySequence_Contains((anyset), (key)) - -#define PySet_Pop(set) \ - PyObject_CallMethod(set, (char *)"pop", NULL) - -static INLINE int PySet_Clear(PyObject *set) { - PyObject *ret = PyObject_CallMethod(set, (char *)"clear", NULL); - if (!ret) return -1; - Py_DECREF(ret); return 0; -} - -static INLINE int PySet_Discard(PyObject *set, PyObject *key) { - PyObject *ret = PyObject_CallMethod(set, (char *)"discard", (char *)"O", key); - if (!ret) return -1; - Py_DECREF(ret); return 0; -} - -static INLINE int PySet_Add(PyObject *set, PyObject *key) { - PyObject *ret = PyObject_CallMethod(set, (char *)"add", (char *)"O", key); - if (!ret) return -1; - Py_DECREF(ret); return 0; -} - -#endif /* PyAnySet_CheckExact (<= Py2.4) */ - -#if PY_VERSION_HEX < 0x02040000 -#ifndef Py_SETOBJECT_H -#define Py_SETOBJECT_H - -static PyTypeObject *__Pyx_PySet_Type = NULL; -static PyTypeObject *__Pyx_PyFrozenSet_Type = NULL; - -#define PySet_Type (*__Pyx_PySet_Type) -#define PyFrozenSet_Type (*__Pyx_PyFrozenSet_Type) - -#define PyAnySet_Check(ob) \ - (PyAnySet_CheckExact(ob) || \ - PyType_IsSubtype((ob)->ob_type, &PySet_Type) || \ - PyType_IsSubtype((ob)->ob_type, &PyFrozenSet_Type)) - -#define PyFrozenSet_CheckExact(ob) ((ob)->ob_type == &PyFrozenSet_Type) - -static int __Pyx_Py23SetsImport(void) { - PyObject *sets=0, *Set=0, *ImmutableSet=0; - - sets = PyImport_ImportModule((char *)"sets"); - if (!sets) goto bad; - Set = PyObject_GetAttrString(sets, (char *)"Set"); - if (!Set) goto bad; - ImmutableSet = PyObject_GetAttrString(sets, (char *)"ImmutableSet"); - if (!ImmutableSet) goto bad; - Py_DECREF(sets); - - __Pyx_PySet_Type = (PyTypeObject*) Set; - __Pyx_PyFrozenSet_Type = (PyTypeObject*) ImmutableSet; - - return 0; - - bad: - Py_XDECREF(sets); - Py_XDECREF(Set); - Py_XDECREF(ImmutableSet); - return -1; -} - -#else -static int __Pyx_Py23SetsImport(void) { return 0; } -#endif /* !Py_SETOBJECT_H */ -#endif /* < Py2.4 */ -#endif /* < Py2.5 */ - - static INLINE PyObject *__Pyx_GetItemInt_Generic(PyObject *o, PyObject* j) { PyObject *r; if (!j) return NULL; diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index d9449f74..fd358532 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -11,6 +11,7 @@ import sys, os, time, re from os import path +from io import open TERM_ENCODING = getattr(sys.stdin, 'encoding', None) @@ -21,7 +22,6 @@ from sphinx.util.osutil import make_filename from sphinx.util.console import purple, bold, red, turquoise, \ nocolor, color_terminal from sphinx.util import texescape -from sphinx.util.pycompat import open # function to get input from terminal -- overridden by the test suite try: @@ -99,7 +99,10 @@ release = '%(release_str)s' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = %(language)r # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: @@ -320,7 +323,7 @@ epub_copyright = u'%(copyright_str)s' #epub_theme = 'epub' # The language of the text. It defaults to the language option -# or en if the language is not set. +# or 'en' if the language is not set. #epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. @@ -1018,6 +1021,7 @@ def ask_user(d): * author: author names * version: version of project * release: release of project + * language: document language * suffix: source file suffix * master: master document name * epub: use epub (bool) @@ -1084,6 +1088,18 @@ just set both to the same value.''' if 'release' not in d: do_prompt(d, 'release', 'Project release', d['version']) + if 'language' not in d: + print ''' +If the documents are to be written in a language other than English, +you can select a language here by its language code. Sphinx will then +translate text that it generates into that language. + +For a list of supported codes, see +http://sphinx-doc.org/config.html#confval-language.''' + do_prompt(d, 'language', 'Project language', 'en') + if d['language'] == 'en': + d['language'] = None + if 'suffix' not in d: print ''' The file name suffix for source files. Commonly, this is either ".txt" diff --git a/sphinx/roles.py b/sphinx/roles.py index 6703b6b8..3b858588 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -22,15 +22,15 @@ from sphinx.util.nodes import split_explicit_title, process_index_entry, \ generic_docroles = { - 'command' : nodes.strong, + 'command' : addnodes.literal_strong, 'dfn' : nodes.emphasis, 'kbd' : nodes.literal, 'mailheader' : addnodes.literal_emphasis, - 'makevar' : nodes.strong, + 'makevar' : addnodes.literal_strong, 'manpage' : addnodes.literal_emphasis, 'mimetype' : addnodes.literal_emphasis, 'newsgroup' : addnodes.literal_emphasis, - 'program' : nodes.strong, # XXX should be an x-ref + 'program' : addnodes.literal_strong, # XXX should be an x-ref 'regexp' : nodes.literal, } diff --git a/sphinx/texinputs/Makefile b/sphinx/texinputs/Makefile index 6b87ad88..5e6030c0 100644 --- a/sphinx/texinputs/Makefile +++ b/sphinx/texinputs/Makefile @@ -9,6 +9,10 @@ ARCHIVEPRREFIX = # Additional LaTeX options LATEXOPTS = +LATEX = latex +PDFLATEX = pdflatex +MAKEINDEX = makeindex + all: $(ALLPDF) all-pdf: $(ALLPDF) all-dvi: $(ALLDVI) @@ -43,20 +47,20 @@ bz2: tar # The number of LaTeX runs is quite conservative, but I don't expect it # to get run often, so the little extra time won't hurt. %.dvi: %.tex - latex $(LATEXOPTS) '$<' - latex $(LATEXOPTS) '$<' - latex $(LATEXOPTS) '$<' - -makeindex -s python.ist '$(basename $<).idx' - latex $(LATEXOPTS) '$<' - latex $(LATEXOPTS) '$<' + $(LATEX) $(LATEXOPTS) '$<' + $(LATEX) $(LATEXOPTS) '$<' + $(LATEX) $(LATEXOPTS) '$<' + -$(MAKEINDEX) -s python.ist '$(basename $<).idx' + $(LATEX) $(LATEXOPTS) '$<' + $(LATEX) $(LATEXOPTS) '$<' %.pdf: %.tex - pdflatex $(LATEXOPTS) '$<' - pdflatex $(LATEXOPTS) '$<' - pdflatex $(LATEXOPTS) '$<' - -makeindex -s python.ist '$(basename $<).idx' - pdflatex $(LATEXOPTS) '$<' - pdflatex $(LATEXOPTS) '$<' + $(PDFLATEX) $(LATEXOPTS) '$<' + $(PDFLATEX) $(LATEXOPTS) '$<' + $(PDFLATEX) $(LATEXOPTS) '$<' + -$(MAKEINDEX) -s python.ist '$(basename $<).idx' + $(PDFLATEX) $(LATEXOPTS) '$<' + $(PDFLATEX) $(LATEXOPTS) '$<' clean: rm -f *.dvi *.log *.ind *.aux *.toc *.syn *.idx *.out *.ilg *.pla diff --git a/sphinx/themes/agogo/layout.html b/sphinx/themes/agogo/layout.html index fde19dbc..f2ddb217 100644 --- a/sphinx/themes/agogo/layout.html +++ b/sphinx/themes/agogo/layout.html @@ -11,7 +11,7 @@ {%- extends "basic/layout.html" %} {% block header %} - <div class="header-wrapper"> + <div class="header-wrapper" role="banner"> <div class="header"> {%- if logo %} <p class="logo"><a href="{{ pathto(master_doc) }}"> @@ -22,7 +22,7 @@ <div class="headertitle"><a href="{{ pathto(master_doc) }}">{{ shorttitle|e }}</a></div> {%- endblock %} - <div class="rel"> + <div class="rel" role="navigation" aria-label="related navigation"> {%- for rellink in rellinks|reverse %} <a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags|e }}" {{ accesskey(rellink[2]) }}>{{ rellink[3] }}</a> @@ -47,16 +47,18 @@ {{ toctree() }} {%- endblock %} {%- block sidebarsearch %} - <h3 style="margin-top: 1.5em;">{{ _('Search') }}</h3> - <form class="search" action="{{ pathto('search') }}" method="get"> - <input type="text" name="q" /> - <input type="submit" value="{{ _('Go') }}" /> - <input type="hidden" name="check_keywords" value="yes" /> - <input type="hidden" name="area" value="default" /> - </form> - <p class="searchtip" style="font-size: 90%"> - {{ _('Enter search terms or a module, class or function name.') }} - </p> + <div role="search"> + <h3 style="margin-top: 1.5em;">{{ _('Search') }}</h3> + <form class="search" action="{{ pathto('search') }}" method="get"> + <input type="text" name="q" /> + <input type="submit" value="{{ _('Go') }}" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + {{ _('Enter search terms or a module, class or function name.') }} + </p> + </div> {%- endblock %} </div> <div class="clearer"></div> @@ -68,16 +70,20 @@ <div class="footer-wrapper"> <div class="footer"> <div class="left"> - {%- for rellink in rellinks|reverse %} - <a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags|e }}" - {{ accesskey(rellink[2]) }}>{{ rellink[3] }}</a> - {%- if not loop.last %}{{ reldelim2 }}{% endif %} - {%- endfor %} - {%- if show_source and has_source and sourcename %} - <br/> - <a href="{{ pathto('_sources/' + sourcename, true)|e }}" - rel="nofollow">{{ _('Show Source') }}</a> - {%- endif %} + <div role="navigation" aria-label="related navigaton"> + {%- for rellink in rellinks|reverse %} + <a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags|e }}" + {{ accesskey(rellink[2]) }}>{{ rellink[3] }}</a> + {%- if not loop.last %}{{ reldelim2 }}{% endif %} + {%- endfor %} + </div> + <div role="note" aria-label="source link"> + {%- if show_source and has_source and sourcename %} + <br/> + <a href="{{ pathto('_sources/' + sourcename, true)|e }}" + rel="nofollow">{{ _('Show Source') }}</a> + {%- endif %} + </div> </div> <div class="right"> diff --git a/sphinx/themes/agogo/static/agogo.css_t b/sphinx/themes/agogo/static/agogo.css_t index 3fb81178..4c7eb378 100644 --- a/sphinx/themes/agogo/static/agogo.css_t +++ b/sphinx/themes/agogo/static/agogo.css_t @@ -462,3 +462,10 @@ div.viewcode-block:target { border-top: 1px solid #ac9; border-bottom: 1px solid #ac9; } + +div.code-block-filename { + background-color: #ddd; + color: #333; + padding: 2px 5px; + font-size: small; +} diff --git a/sphinx/themes/basic/layout.html b/sphinx/themes/basic/layout.html index 9e4e39a1..68a070b3 100644 --- a/sphinx/themes/basic/layout.html +++ b/sphinx/themes/basic/layout.html @@ -25,7 +25,7 @@ {%- endif %} {%- macro relbar() %} - <div class="related"> + <div class="related" role="navigation" aria-label="related navigation"> <h3>{{ _('Navigation') }}</h3> <ul> {%- for rellink in rellinks %} @@ -47,7 +47,7 @@ {%- macro sidebar() %} {%- if render_sidebar %} - <div class="sphinxsidebar"> + <div class="sphinxsidebar" role="navigation" aria-label="main navigation"> <div class="sphinxsidebarwrapper"> {%- block sidebarlogo %} {%- if logo %} @@ -152,7 +152,7 @@ {%- endblock %} {%- block extrahead %} {% endblock %} </head> - <body> + <body role="document"> {%- block header %}{% endblock %} {%- block relbar1 %}{{ relbar() }}{% endblock %} @@ -166,7 +166,7 @@ {%- if render_sidebar %} <div class="bodywrapper"> {%- endif %} - <div class="body"> + <div class="body" role="main"> {% block body %} {% endblock %} </div> {%- if render_sidebar %} @@ -183,7 +183,7 @@ {%- block relbar2 %}{{ relbar() }}{% endblock %} {%- block footer %} - <div class="footer"> + <div class="footer" role="contentinfo"> {%- if show_copyright %} {%- if hasdoc('copyright') %} {% trans path=pathto('copyright'), copyright=copyright|e %}© <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %} diff --git a/sphinx/themes/basic/searchbox.html b/sphinx/themes/basic/searchbox.html index 1495701a..420e0121 100644 --- a/sphinx/themes/basic/searchbox.html +++ b/sphinx/themes/basic/searchbox.html @@ -8,7 +8,7 @@ :license: BSD, see LICENSE for details. #} {%- if pagename != "search" and builder != "singlehtml" %} -<div id="searchbox" style="display: none"> +<div id="searchbox" style="display: none" role="search"> <h3>{{ _('Quick search') }}</h3> <form class="search" action="{{ pathto('search') }}" method="get"> <input type="text" name="q" /> diff --git a/sphinx/themes/basic/sourcelink.html b/sphinx/themes/basic/sourcelink.html index 08232efc..fbbae064 100644 --- a/sphinx/themes/basic/sourcelink.html +++ b/sphinx/themes/basic/sourcelink.html @@ -8,9 +8,11 @@ :license: BSD, see LICENSE for details. #} {%- if show_source and has_source and sourcename %} - <h3>{{ _('This Page') }}</h3> - <ul class="this-page-menu"> - <li><a href="{{ pathto('_sources/' + sourcename, true)|e }}" - rel="nofollow">{{ _('Show Source') }}</a></li> - </ul> + <div role="note" aria-label="source link"> + <h3>{{ _('This Page') }}</h3> + <ul class="this-page-menu"> + <li><a href="{{ pathto('_sources/' + sourcename, true)|e }}" + rel="nofollow">{{ _('Show Source') }}</a></li> + </ul> + </div> {%- endif %} diff --git a/sphinx/themes/basic/static/basic.css_t b/sphinx/themes/basic/static/basic.css_t index d54e7f4e..a3255ebd 100644 --- a/sphinx/themes/basic/static/basic.css_t +++ b/sphinx/themes/basic/static/basic.css_t @@ -471,6 +471,20 @@ table.highlighttable td { padding: 0 0.5em 0 0.5em; } +div.code-block-filename { + padding: 2px 5px; + font-size: small; +} + +div.code-block-filename tt { + background-color: transparent; +} + +div.code-block-filename + pre, +div.code-block-filename + div.highlight > pre { + margin-top: 0; +} + tt.descname { background-color: transparent; font-weight: bold; diff --git a/sphinx/themes/default/static/default.css_t b/sphinx/themes/default/static/default.css_t index 5db77108..cdc1b782 100644 --- a/sphinx/themes/default/static/default.css_t +++ b/sphinx/themes/default/static/default.css_t @@ -308,3 +308,8 @@ div.viewcode-block:target { border-top: 1px solid #ac9; border-bottom: 1px solid #ac9; } + +div.code-block-filename { + color: #efefef; + background-color: #1c4e63; +} diff --git a/sphinx/themes/haiku/layout.html b/sphinx/themes/haiku/layout.html index 0c6b41e7..a98d89c6 100644 --- a/sphinx/themes/haiku/layout.html +++ b/sphinx/themes/haiku/layout.html @@ -32,7 +32,7 @@ {% endmacro %} {% block content %} - <div class="header"> + <div class="header" role="banner"> {%- block haikuheader %} {%- if theme_full_logo != "false" %} <a href="{{ pathto('index') }}"> @@ -48,7 +48,7 @@ {%- endif %} {%- endblock %} </div> - <div class="topnav"> + <div class="topnav" role="navigation" aria-label="top navigation"> {{ nav() }} </div> <div class="content"> @@ -60,7 +60,7 @@ {%- endif %}#} {% block body %}{% endblock %} </div> - <div class="bottomnav"> + <div class="bottomnav" role="navigation" aria-label="bottom navigation"> {{ nav() }} </div> {% endblock %} diff --git a/sphinx/themes/nature/static/nature.css_t b/sphinx/themes/nature/static/nature.css_t index 3c492034..e4f4d2ed 100644 --- a/sphinx/themes/nature/static/nature.css_t +++ b/sphinx/themes/nature/static/nature.css_t @@ -243,3 +243,9 @@ div.viewcode-block:target { border-top: 1px solid #ac9; border-bottom: 1px solid #ac9; } + +div.code-block-filename { + background-color: #ddd; + color: #222; + border: 1px solid #C6C9CB; +} diff --git a/sphinx/themes/pyramid/layout.html b/sphinx/themes/pyramid/layout.html index 8780ceae..318a3662 100644 --- a/sphinx/themes/pyramid/layout.html +++ b/sphinx/themes/pyramid/layout.html @@ -10,7 +10,7 @@ {% block header %} {%- if logo %} -<div class="header"> +<div class="header" role="banner"> <div class="logo"> <a href="{{ pathto(master_doc) }}"> <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/> diff --git a/sphinx/themes/pyramid/static/pyramid.css_t b/sphinx/themes/pyramid/static/pyramid.css_t index c4e94908..c724a493 100644 --- a/sphinx/themes/pyramid/static/pyramid.css_t +++ b/sphinx/themes/pyramid/static/pyramid.css_t @@ -340,3 +340,8 @@ tt.xref { font-weight: normal; font-style: normal; } + +div.code-block-filename { + background-color: #ddd; + color: #222; +} diff --git a/sphinx/themes/scrolls/layout.html b/sphinx/themes/scrolls/layout.html index 48e5e4e4..0961808e 100644 --- a/sphinx/themes/scrolls/layout.html +++ b/sphinx/themes/scrolls/layout.html @@ -20,7 +20,7 @@ <h1 class="heading"><a href="{{ pathto('index') }}" title="back to the documentation overview"><span>{{ title|striptags|e }}</span></a></h1> </div> - <div class="relnav"> + <div class="relnav" role="navigation" aria-label="related navigation"> {%- if prev %} <a href="{{ prev.link|e }}">« {{ prev.title }}</a> | {%- endif %} @@ -31,7 +31,7 @@ </div> <div id="contentwrapper"> {%- if display_toc %} - <div id="toc"> + <div id="toc" role="navigation" aria-label="table of contents navigation"> <h3>{{ _('Table Of Contents') }}</h3> {{ toc }} </div> diff --git a/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t b/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t index af498257..1d7c5796 100644 --- a/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t +++ b/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t @@ -337,3 +337,9 @@ div.viewcode-block:target { border-top: 1px solid #ac9; border-bottom: 1px solid #ac9; } + +div.code-block-filename { + background-color: #ddd; + color: #222; + border: 1px solid #ccc; +} diff --git a/sphinx/themes/traditional/static/traditional.css_t b/sphinx/themes/traditional/static/traditional.css_t index fff411ef..cbea798c 100644 --- a/sphinx/themes/traditional/static/traditional.css_t +++ b/sphinx/themes/traditional/static/traditional.css_t @@ -698,3 +698,7 @@ div.viewcode-block:target { margin: -1px -10px; padding: 0 10px; } + +div.code-block-filename { + background-color: #cceeff; +} diff --git a/sphinx/transforms.py b/sphinx/transforms.py index 53bbadb0..e44a3d3e 100644 --- a/sphinx/transforms.py +++ b/sphinx/transforms.py @@ -23,7 +23,6 @@ from sphinx.util import split_index_msg from sphinx.util.nodes import traverse_translatable_index, extract_messages from sphinx.util.osutil import ustrftime, find_catalog from sphinx.util.compat import docutils_version -from sphinx.util.pycompat import all from sphinx.domains.std import ( make_term_from_paragraph_node, make_termnodes_from_paragraph_node, diff --git a/sphinx/util/docfields.py b/sphinx/util/docfields.py index 6fd8ba95..150bf3a1 100644 --- a/sphinx/util/docfields.py +++ b/sphinx/util/docfields.py @@ -99,7 +99,8 @@ class GroupedField(Field): return Field.make_field(self, types, domain, items[0]) for fieldarg, content in items: par = nodes.paragraph() - par += self.make_xref(self.rolename, domain, fieldarg, nodes.strong) + par += self.make_xref(self.rolename, domain, fieldarg, + addnodes.literal_strong) par += nodes.Text(' -- ') par += content listnode += nodes.list_item('', par) @@ -137,7 +138,8 @@ class TypedField(GroupedField): def make_field(self, types, domain, items): def handle_item(fieldarg, content): par = nodes.paragraph() - par += self.make_xref(self.rolename, domain, fieldarg, nodes.strong) + par += self.make_xref(self.rolename, domain, fieldarg, + addnodes.literal_strong) if fieldarg in types: par += nodes.Text(' (') # NOTE: using .pop() here to prevent a single type node to be diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 61061a9a..c7556d05 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -55,7 +55,7 @@ if sys.version_info >= (3, 0): raise TypeError('%r is not a Python function' % func) return inspect.getfullargspec(func) -elif sys.version_info >= (2, 5): +else: # 2.6, 2.7 from functools import partial def getargspec(func): """Like inspect.getargspec but supports functools.partial as well.""" @@ -86,12 +86,7 @@ elif sys.version_info >= (2, 5): del func_defaults[i] except IndexError: pass - if sys.version_info >= (2, 6): - return inspect.ArgSpec(args, varargs, varkw, func_defaults) - else: - return (args, varargs, varkw, func_defaults) -else: - getargspec = inspect.getargspec + return inspect.ArgSpec(args, varargs, varkw, func_defaults) def isdescriptor(x): diff --git a/sphinx/util/jsonimpl.py b/sphinx/util/jsonimpl.py index aa0ea825..de846b24 100644 --- a/sphinx/util/jsonimpl.py +++ b/sphinx/util/jsonimpl.py @@ -10,27 +10,15 @@ """ import UserString +import json -try: - import json - # json-py's json module has no JSONEncoder; this will raise AttributeError - # if json-py is imported instead of the built-in json module - JSONEncoder = json.JSONEncoder -except (ImportError, AttributeError): - try: - import simplejson as json - JSONEncoder = json.JSONEncoder - except ImportError: - json = None - JSONEncoder = object - - -class SphinxJSONEncoder(JSONEncoder): + +class SphinxJSONEncoder(json.JSONEncoder): """JSONEncoder subclass that forces translation proxies.""" def default(self, obj): if isinstance(obj, UserString.UserString): return unicode(obj) - return JSONEncoder.default(self, obj) + return json.JSONEncoder.default(self, obj) def dump(obj, fp, *args, **kwds): diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index 87717771..a5c461a6 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -69,6 +69,9 @@ def ensuredir(path): raise +# This function is same as os.walk of Python2.6, 2.7, 3.2, 3.3 except a +# customization that check UnicodeError. +# The customization obstacle to replace the function with the os.walk. def walk(top, topdown=True, followlinks=False): """Backport of os.walk from 2.6, where the *followlinks* argument was added. @@ -155,9 +158,8 @@ else: def safe_relpath(path, start=None): - from sphinx.util.pycompat import relpath try: - return relpath(path, start) + return os.path.relpath(path, start) except ValueError: return path @@ -171,14 +173,13 @@ def find_catalog(docname, compaction): def find_catalog_files(docname, srcdir, locale_dirs, lang, compaction): - from sphinx.util.pycompat import relpath if not(lang and locale_dirs): return [] domain = find_catalog(docname, compaction) files = [gettext.find(domain, path.join(srcdir, dir_), [lang]) for dir_ in locale_dirs] - files = [relpath(f, srcdir) for f in files if f] + files = [path.relpath(f, srcdir) for f in files if f] return files diff --git a/sphinx/util/pycompat.py b/sphinx/util/pycompat.py index 1e5ea314..9941dc0c 100644 --- a/sphinx/util/pycompat.py +++ b/sphinx/util/pycompat.py @@ -11,7 +11,6 @@ import sys import codecs -import encodings # ------------------------------------------------------------------------------ # Python 2/3 compatibility @@ -48,6 +47,7 @@ if sys.version_info >= (3, 0): # try to match ParseError details with SyntaxError details raise SyntaxError(err.msg, (filepath, lineno, offset, err.value)) return unicode(tree) + from itertools import zip_longest # Python 3 name else: # Python 2 @@ -69,6 +69,8 @@ else: # error handler import locale sys_encoding = locale.getpreferredencoding() + # use Python 3 name + from itertools import izip_longest as zip_longest def execfile_(filepath, _globals): @@ -81,8 +83,8 @@ def execfile_(filepath, _globals): finally: f.close() - # py25,py26,py31 accept only LF eol instead of CRLF - if sys.version_info[:2] in ((2, 5), (2, 6), (3, 1)): + # py26 accept only LF eol instead of CRLF + if sys.version_info[:2] == (2, 6): source = source.replace(b('\r\n'), b('\n')) # compile to a code object, handle syntax errors @@ -100,178 +102,7 @@ def execfile_(filepath, _globals): exec code in _globals -try: - from html import escape as htmlescape -except ImportError: +if sys.version_info >= (3, 2): + from html import escape as htmlescape # >= Python 3.2 +else: # 2.6, 2.7, 3.1 from cgi import escape as htmlescape - -# ------------------------------------------------------------------------------ -# Missing builtins and itertools in Python < 2.6 - -if sys.version_info >= (2, 6): - # Python >= 2.6 - next = next - - from itertools import product - try: - from itertools import zip_longest # Python 3 name - except ImportError: - from itertools import izip_longest as zip_longest - - import os - relpath = os.path.relpath - del os - - import io - open = io.open - -else: - # Python < 2.6 - from itertools import izip, repeat, chain - - # this is on Python 2, where the method is called "next" (it is refactored - # to __next__ by 2to3, but in that case never executed) - def next(iterator): - return iterator.next() - - # These replacement functions have been taken from the Python 2.6 - # itertools documentation. - def product(*args, **kwargs): - pools = map(tuple, args) * kwargs.get('repeat', 1) - result = [[]] - for pool in pools: - result = [x + [y] for x in result for y in pool] - for prod in result: - yield tuple(prod) - - def zip_longest(*args, **kwds): - # zip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D- - fillvalue = kwds.get('fillvalue') - def sentinel(counter = ([fillvalue]*(len(args)-1)).pop): - yield counter() # yields the fillvalue, or raises IndexError - fillers = repeat(fillvalue) - iters = [chain(it, sentinel(), fillers) for it in args] - try: - for tup in izip(*iters): - yield tup - except IndexError: - pass - - from os.path import curdir - def relpath(path, start=curdir): - """Return a relative version of a path""" - from os.path import sep, abspath, commonprefix, join, pardir - - if not path: - raise ValueError("no path specified") - - start_list = abspath(start).split(sep) - path_list = abspath(path).split(sep) - - # Work out how much of the filepath is shared by start and path. - i = len(commonprefix([start_list, path_list])) - - rel_list = [pardir] * (len(start_list)-i) + path_list[i:] - if not rel_list: - return start - return join(*rel_list) - del curdir - - from types import MethodType - def open(filename, mode='r', *args, **kw): - newline = kw.pop('newline', None) - mode = mode.replace('t', '') - f = codecs.open(filename, mode, *args, **kw) - if newline is not None: - f._write = f.write - def write(self, text): - text = text.replace(u'\r\n', u'\n').replace(u'\n', newline) - self._write(text) - f.write = MethodType(write, f) - return f - - -# ------------------------------------------------------------------------------ -# Missing builtins and codecs in Python < 2.5 - -if sys.version_info >= (2, 5): - # Python >= 2.5 - base_exception = BaseException - any = any - all = all - -else: - # Python 2.4 - base_exception = Exception - - def all(gen): - for i in gen: - if not i: - return False - return True - - def any(gen): - for i in gen: - if i: - return True - return False - - # Python 2.4 doesn't know the utf-8-sig encoding, so deliver it here - - def my_search_function(encoding): - norm_encoding = encodings.normalize_encoding(encoding) - if norm_encoding != 'utf_8_sig': - return None - return (encode, decode, StreamReader, StreamWriter) - - codecs.register(my_search_function) - - # begin code copied from utf_8_sig.py in Python 2.6 - - def encode(input, errors='strict'): - return (codecs.BOM_UTF8 + - codecs.utf_8_encode(input, errors)[0], len(input)) - - def decode(input, errors='strict'): - prefix = 0 - if input[:3] == codecs.BOM_UTF8: - input = input[3:] - prefix = 3 - (output, consumed) = codecs.utf_8_decode(input, errors, True) - return (output, consumed+prefix) - - class StreamWriter(codecs.StreamWriter): - def reset(self): - codecs.StreamWriter.reset(self) - try: - del self.encode - except AttributeError: - pass - - def encode(self, input, errors='strict'): - self.encode = codecs.utf_8_encode - return encode(input, errors) - - class StreamReader(codecs.StreamReader): - def reset(self): - codecs.StreamReader.reset(self) - try: - del self.decode - except AttributeError: - pass - - def decode(self, input, errors='strict'): - if len(input) < 3: - if codecs.BOM_UTF8.startswith(input): - # not enough data to decide if this is a BOM - # => try again on the next call - return (u"", 0) - elif input[:3] == codecs.BOM_UTF8: - self.decode = codecs.utf_8_decode - (output, consumed) = codecs.utf_8_decode(input[3:],errors) - return (output, consumed+3) - # (else) no BOM present - self.decode = codecs.utf_8_decode - return codecs.utf_8_decode(input, errors) - - # end code copied from utf_8_sig.py diff --git a/sphinx/versioning.py b/sphinx/versioning.py index a16751bb..ccab41d4 100644 --- a/sphinx/versioning.py +++ b/sphinx/versioning.py @@ -11,8 +11,9 @@ """ from uuid import uuid4 from operator import itemgetter +from itertools import product -from sphinx.util.pycompat import product, zip_longest, all +from sphinx.util.pycompat import zip_longest # anything below that ratio is considered equal/changed diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py index 2d68564f..1e0c9f77 100644 --- a/sphinx/writers/html.py +++ b/sphinx/writers/html.py @@ -273,6 +273,9 @@ class HTMLTranslator(BaseTranslator): **highlight_args) starttag = self.starttag(node, 'div', suffix='', CLASS='highlight-%s' % lang) + if node.has_key('filename'): + starttag += '<div class="code-block-filename"><tt>%s</tt></div>' % ( + node['filename'],) self.body.append(starttag + highlighted + '</div>\n') raise nodes.SkipNode @@ -514,6 +517,11 @@ class HTMLTranslator(BaseTranslator): def depart_literal_emphasis(self, node): return self.depart_emphasis(node) + def visit_literal_strong(self, node): + return self.visit_strong(node) + def depart_literal_strong(self, node): + return self.depart_strong(node) + def visit_abbreviation(self, node): attrs = {} if node.hasattr('explanation'): @@ -624,6 +632,14 @@ class SmartyPantsHTMLTranslator(HTMLTranslator): self.depart_emphasis(node) self.no_smarty -= 1 + def visit_literal_strong(self, node): + self.no_smarty += 1 + self.visit_strong(node) + + def depart_literal_strong(self, node): + self.depart_strong(node) + self.no_smarty -= 1 + def visit_desc_signature(self, node): self.no_smarty += 1 HTMLTranslator.visit_desc_signature(self, node) diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 3c1a50f8..61aa5828 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -25,7 +25,6 @@ from sphinx.errors import SphinxError from sphinx.locale import admonitionlabels, _ from sphinx.util import split_into from sphinx.util.osutil import ustrftime -from sphinx.util.pycompat import any from sphinx.util.texescape import tex_escape_map, tex_replace_map from sphinx.util.smartypants import educate_quotes_latex @@ -1247,6 +1246,13 @@ class LaTeXTranslator(nodes.NodeVisitor): def depart_strong(self, node): self.body.append('}') + def visit_literal_strong(self, node): + self.body.append(r'\textbf{\texttt{') + self.no_contractions += 1 + def depart_literal_strong(self, node): + self.body.append('}}') + self.no_contractions -= 1 + def visit_abbreviation(self, node): abbr = node.astext() self.body.append(r'\textsc{') @@ -1331,6 +1337,11 @@ class LaTeXTranslator(nodes.NodeVisitor): highlight_args['force'] = True if 'linenos' in node: linenos = node['linenos'] + filename = node.get('filename') + if filename: + self.body.append('\n{\\colorbox[rgb]{0.9,0.9,0.9}' + '{\\makebox[\\textwidth][l]' + '{\\small\\texttt{%s}}}}\n' % (filename,)) def warner(msg): self.builder.warn(msg, (self.curfilestack[-1], node.line)) hlcode = self.highlighter.highlight_block(code, lang, warn=warner, diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py index bc4da17a..0aa0058c 100644 --- a/sphinx/writers/manpage.py +++ b/sphinx/writers/manpage.py @@ -304,6 +304,11 @@ class ManualPageTranslator(BaseTranslator): def depart_literal_emphasis(self, node): return self.depart_emphasis(node) + def visit_literal_strong(self, node): + return self.visit_strong(node) + def depart_literal_strong(self, node): + return self.depart_strong(node) + def visit_abbreviation(self, node): pass def depart_abbreviation(self, node): diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index 7b70485a..a930a525 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -1207,6 +1207,11 @@ class TexinfoTranslator(nodes.NodeVisitor): def depart_literal_emphasis(self, node): self.body.append('}') + def visit_literal_strong(self, node): + self.body.append('@code{') + def depart_literal_strong(self, node): + self.body.append('}') + def visit_index(self, node): # terminate the line but don't prevent paragraph breaks if isinstance(node.parent, nodes.paragraph): diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py index 57a40b29..a174bc26 100644 --- a/sphinx/writers/text.py +++ b/sphinx/writers/text.py @@ -762,6 +762,11 @@ class TextTranslator(nodes.NodeVisitor): def depart_strong(self, node): self.add_text('**') + def visit_literal_strong(self, node): + self.add_text('**') + def depart_literal_strong(self, node): + self.add_text('**') + def visit_abbreviation(self, node): self.add_text('') def depart_abbreviation(self, node): |
