From d7caa3fa20cbf3ab87612e2479a64fb1c9a16bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Zapke-Gr=C3=BCndemann?= Date: Thu, 19 Sep 2013 21:48:54 +0200 Subject: [PATCH 01/50] Explain that only tags set via command-line or tags.add() are available --- doc/config.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/config.rst b/doc/config.rst index 70ebacb455..ae41594e08 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -43,7 +43,8 @@ Important points to note: * There is a special object named ``tags`` available in the config file. It can be used to query and change the tags (see :ref:`tags`). Use ``tags.has('tag')`` to query, ``tags.add('tag')`` and ``tags.remove('tag')`` - to change. + to change. Only tags set via the ``-t`` command-line option or via + ``tags.add('tag')`` can be queried using ``tags.has('tag')``. General configuration From 05ba0766b3c43a5d2af774cef7e05d236164935c Mon Sep 17 00:00:00 2001 From: "M. Nasimul Haque" Date: Thu, 26 Sep 2013 00:56:16 +0100 Subject: [PATCH 02/50] Add filename option to code-block Prepend the given filename to the code block --- sphinx/directives/code.py | 4 ++++ sphinx/themes/agogo/static/agogo.css_t | 14 ++++++++++++++ sphinx/themes/default/static/default.css_t | 13 +++++++++++++ sphinx/themes/haiku/static/haiku.css_t | 4 ++++ sphinx/themes/nature/static/nature.css_t | 14 ++++++++++++++ sphinx/themes/pyramid/static/pyramid.css_t | 13 +++++++++++++ sphinx/themes/sphinxdoc/static/sphinxdoc.css_t | 14 ++++++++++++++ sphinx/themes/traditional/static/traditional.css_t | 12 ++++++++++++ sphinx/writers/html.py | 3 +++ sphinx/writers/latex.py | 5 +++++ 10 files changed, 96 insertions(+) diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py index 4d43e5ff6e..8364721ea0 100644 --- a/sphinx/directives/code.py +++ b/sphinx/directives/code.py @@ -57,6 +57,7 @@ class CodeBlock(Directive): option_spec = { 'linenos': directives.flag, 'emphasize-lines': directives.unchanged_required, + 'filename': directives.unchanged_required, } def run(self): @@ -75,6 +76,9 @@ class CodeBlock(Directive): literal = nodes.literal_block(code, code) literal['language'] = self.arguments[0] + filename = self.options.get('filename') + if filename: + literal['filename'] = filename literal['linenos'] = 'linenos' in self.options if hl_lines is not None: literal['highlight_args'] = {'hl_lines': hl_lines} diff --git a/sphinx/themes/agogo/static/agogo.css_t b/sphinx/themes/agogo/static/agogo.css_t index 3fb81178c5..e22ce16dbb 100644 --- a/sphinx/themes/agogo/static/agogo.css_t +++ b/sphinx/themes/agogo/static/agogo.css_t @@ -462,3 +462,17 @@ 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-family: monospace; + font-size: small; +} + +div.code-block-filename + pre, +div.code-block-filename + div.highlight, +div.code-block-filename + div.highlight > pre { + margin-top: 0; +} diff --git a/sphinx/themes/default/static/default.css_t b/sphinx/themes/default/static/default.css_t index 5db7710890..c1f4447720 100644 --- a/sphinx/themes/default/static/default.css_t +++ b/sphinx/themes/default/static/default.css_t @@ -308,3 +308,16 @@ div.viewcode-block:target { border-top: 1px solid #ac9; border-bottom: 1px solid #ac9; } + +div.code-block-filename { + background-color: #1c4e63; + color: #efefef; + padding: 2px 5px; + font-family: monospace; + font-size: small; +} + +div.code-block-filename + pre, +div.code-block-filename + div.highlight > pre { + margin-top: 0; +} diff --git a/sphinx/themes/haiku/static/haiku.css_t b/sphinx/themes/haiku/static/haiku.css_t index 4108125bd5..c3655dfc7c 100644 --- a/sphinx/themes/haiku/static/haiku.css_t +++ b/sphinx/themes/haiku/static/haiku.css_t @@ -369,3 +369,7 @@ div.viewcode-block:target { margin: -1px -10px; padding: 0 12px; } + +div.code-block-filename { + font-family: monospace; +} diff --git a/sphinx/themes/nature/static/nature.css_t b/sphinx/themes/nature/static/nature.css_t index 3c49203419..8808ee128e 100644 --- a/sphinx/themes/nature/static/nature.css_t +++ b/sphinx/themes/nature/static/nature.css_t @@ -243,3 +243,17 @@ 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; + padding: 2px 5px; + font-family: monospace; + font-size: small; +} + +div.code-block-filename + pre, +div.code-block-filename + div.highlight > pre { + margin-top: 0; +} diff --git a/sphinx/themes/pyramid/static/pyramid.css_t b/sphinx/themes/pyramid/static/pyramid.css_t index c4e9490884..4e7bacc329 100644 --- a/sphinx/themes/pyramid/static/pyramid.css_t +++ b/sphinx/themes/pyramid/static/pyramid.css_t @@ -340,3 +340,16 @@ tt.xref { font-weight: normal; font-style: normal; } + +div.code-block-filename { + background-color: #ddd; + color: #222; + padding: 2px 5px; + font-family: monospace; + font-size: small; +} + +div.code-block-filename + pre, +div.code-block-filename + div.highlight > pre { + margin-top: 0; +} diff --git a/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t b/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t index af49825776..021240d75f 100644 --- a/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t +++ b/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t @@ -337,3 +337,17 @@ 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; + padding: 2px 5px; + font-family: monospace; + font-size: small; +} + +div.code-block-filename + pre, +div.code-block-filename + div.highlight > pre { + margin-top: 0; +} diff --git a/sphinx/themes/traditional/static/traditional.css_t b/sphinx/themes/traditional/static/traditional.css_t index 6c6bd5f5a8..95114473cb 100644 --- a/sphinx/themes/traditional/static/traditional.css_t +++ b/sphinx/themes/traditional/static/traditional.css_t @@ -702,3 +702,15 @@ div.viewcode-block:target { margin: -1px -10px; padding: 0 10px; } + +div.code-block-filename { + background-color: #cceeff; + padding: 2px 5px; + font-family: monospace; + font-size: small; +} + +div.code-block-filename + pre, +div.code-block-filename + div.highlight > pre { + margin-top: 0; +} diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py index c6747f858a..5508d2104f 100644 --- a/sphinx/writers/html.py +++ b/sphinx/writers/html.py @@ -256,6 +256,9 @@ class HTMLTranslator(BaseTranslator): **highlight_args) starttag = self.starttag(node, 'div', suffix='', CLASS='highlight-%s' % lang) + if node.has_key('filename'): + starttag += '
%s
' % ( + node['filename'],) self.body.append(starttag + highlighted + '\n') raise nodes.SkipNode diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index ce92edfc32..df4d5d6058 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -1333,6 +1333,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, From a8f65e9d3c8b3ffb16b0ad5004b420a60d1c6a60 Mon Sep 17 00:00:00 2001 From: guibog Date: Wed, 23 Oct 2013 16:36:45 +0800 Subject: [PATCH 03/50] autodoc extension: add autodoc_mock_imports config value --- doc/ext/autodoc.rst | 10 +++++++++ sphinx/ext/autodoc.py | 32 +++++++++++++++++++++++++++ tests/root/autodoc.txt | 2 ++ tests/root/autodoc_missing_imports.py | 9 ++++++++ tests/root/conf.py | 7 ++++++ 5 files changed, 60 insertions(+) create mode 100644 tests/root/autodoc_missing_imports.py diff --git a/doc/ext/autodoc.rst b/doc/ext/autodoc.rst index c92fe0c4ef..c37328f543 100644 --- a/doc/ext/autodoc.rst +++ b/doc/ext/autodoc.rst @@ -195,6 +195,10 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, .. versionadded:: 1.2 + * Add a list of modules in the :confval:`autodoc_mock_imports` to prevent + import errors to halt the building process when some external dependencies + are not importable at build time. + .. rst:directive:: autofunction autodata @@ -335,6 +339,12 @@ There are also new config values that you can set: .. versionadded:: 1.1 +.. confval:: autodoc_mock_imports + + This value contains a list of modules to be mocked up. This is useful when + some external dependencies are not met at build time and break the building + process. + Docstring preprocessing ----------------------- diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index 348c072c44..a556edc8fb 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -70,6 +70,26 @@ class Options(dict): return None +class _MockModule(object): + 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() + + ALL = object() INSTANCEATTR = object() @@ -332,6 +352,8 @@ class Documenter(object): self.modname, '.'.join(self.objpath)) try: dbg('[autodoc] import %s', self.modname) + for modname in self.env.config.autodoc_mock_imports: + self._mock_import(modname) __import__(self.modname) parent = None obj = self.module = sys.modules[self.modname] @@ -361,6 +383,15 @@ class Documenter(object): self.env.note_reread() return False + def _mock_import(self, modname): + if '.' in modname: + pkg, _n, mods = modname.rpartition('.') + self._mock_import(pkg) + mod = _MockModule() + sys.modules[modname] = mod + return mod + + def get_real_modname(self): """Get the real module name of an object to document. @@ -1428,6 +1459,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/tests/root/autodoc.txt b/tests/root/autodoc.txt index d4b3404c44..aa0dffba15 100644 --- a/tests/root/autodoc.txt +++ b/tests/root/autodoc.txt @@ -45,3 +45,5 @@ Just testing a few autodoc possibilities... :members: ca1, ia1 Specific members (2 total) + +.. automodule:: autodoc_missing_imports diff --git a/tests/root/autodoc_missing_imports.py b/tests/root/autodoc_missing_imports.py new file mode 100644 index 0000000000..7a71734526 --- /dev/null +++ b/tests/root/autodoc_missing_imports.py @@ -0,0 +1,9 @@ + +import missing_module +from missing_module import missing_name +import missing_package1.missing_module1 +from missing_package2 import missing_module2 +from missing_package3.missing_module3 import missing_name + +class TestAutodoc(object): + """TestAutodoc docstring.""" diff --git a/tests/root/conf.py b/tests/root/conf.py index 8025ba33cb..215cebf9af 100644 --- a/tests/root/conf.py +++ b/tests/root/conf.py @@ -71,6 +71,13 @@ autosummary_generate = ['autosummary'] extlinks = {'issue': ('http://bugs.python.org/issue%s', 'issue '), 'pyurl': ('http://python.org/%s', None)} +autodoc_mock_imports = [ + 'missing_module', + 'missing_package1.missing_module1', + 'missing_package2.missing_module2', + 'missing_package3.missing_module3', +] + # modify tags from conf.py tags.add('confpytag') From a7b6eb95ff997e89067ccd503bb8671ac435658a Mon Sep 17 00:00:00 2001 From: tom prince Date: Tue, 29 Oct 2013 16:40:43 -0600 Subject: [PATCH 04/50] Make latex commands customizable. --- sphinx/texinputs/Makefile | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/sphinx/texinputs/Makefile b/sphinx/texinputs/Makefile index 6b87ad8814..5e6030c0d4 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 From 900ba65ab4bae75df5f566b21bf51745d949c13a Mon Sep 17 00:00:00 2001 From: Sascha Peilicke Date: Wed, 13 Nov 2013 15:33:25 +0100 Subject: [PATCH 05/50] BuildDoc shouldn't fail on Unicode paths. Sub-classes of sphinx.setup_command.BuildDoc may choose to call finalize_options in their run() method again for various reasons. However, currently this fails with py2.7 because of http://bugs.python.org/issue19570. Since it is unlikely that the upstream issue will be solved, a workaround is to re-implement distutils' Command._ensure_stringlike to support Unicode strings. --- sphinx/setup_command.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sphinx/setup_command.py b/sphinx/setup_command.py index 07cd520c30..7999bf591e 100644 --- a/sphinx/setup_command.py +++ b/sphinx/setup_command.py @@ -14,6 +14,7 @@ import sys import os +import types from StringIO import StringIO from distutils.cmd import Command @@ -98,6 +99,19 @@ class BuildDoc(Command): return root return None + # Overriding distutils' Command._ensure_stringlike which doesn't support + # unicode, causing finalize_options to fail if invoked again. Workaround + # for http://bugs.python.org/issue19570 + def _ensure_stringlike(self, option, what, default=None): + val = getattr(self, option) + if val is None: + setattr(self, option, default) + return default + elif not isinstance(val, types.StringTypes): + raise DistutilsOptionError("'%s' must be a %s (got `%s`)" + % (option, what, val)) + return val + def finalize_options(self): if self.source_dir is None: self.source_dir = self._guess_source_dir() From 644c87c9d56439f636669e64c40fe7c509b30e62 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 10:39:23 +0100 Subject: [PATCH 06/50] Add "struct" to C++ modifiers. --- sphinx/domains/cpp.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 377130b841..d44c9cd742 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -537,6 +537,7 @@ class DefinitionParser(object): 'mutable': None, 'const': None, 'typename': None, + 'struct': None, 'unsigned': set(('char', 'short', 'int', 'long')), 'signed': set(('char', 'short', 'int', 'long')), 'short': set(('int',)), From 8b986fe4db94b391b5b58d29f1e96ea2af5204cb Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 11:14:38 +0100 Subject: [PATCH 07/50] Closes #1266: include private modules if includeprivate is true. --- CHANGES | 2 +- sphinx/apidoc.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 3621be4bef..36b38e03cd 100644 --- a/CHANGES +++ b/CHANGES @@ -102,7 +102,7 @@ Bugs fixed * #848: Always take the newest code in incremental rebuilds with the :mod:`sphinx.ext.viewcode` extension. -* #979: Fix exclude handling in ``sphinx-apidoc``. +* #979, #1266: Fix exclude handling in ``sphinx-apidoc``. Documentation ------------- diff --git a/sphinx/apidoc.py b/sphinx/apidoc.py index 4430cdd000..7e95cdaedf 100644 --- a/sphinx/apidoc.py +++ b/sphinx/apidoc.py @@ -182,6 +182,7 @@ def recurse_tree(rootpath, excludes, opts): toplevels = [] followlinks = getattr(opts, 'followlinks', False) + includeprivate = getattr(opts, 'includeprivate', False) for root, subs, files in os.walk(rootpath, followlinks=followlinks): # document only Python module files (that aren't excluded) py_files = sorted(f for f in files @@ -197,7 +198,11 @@ def recurse_tree(rootpath, excludes, opts): continue # remove hidden ('.') and private ('_') directories, as well as # excluded dirs - subs[:] = sorted(sub for sub in subs if sub[0] not in ['.', '_'] + if includeprivate: + exclude_prefixes = ('.',) + else: + exclude_prefixes = ('.', '_') + subs[:] = sorted(sub for sub in subs if not sub.startswith(exclude_prefixes) and not is_excluded(path.join(root, sub), excludes)) if is_pkg: From 310b4fb806a15f97b5cb5de4717b9869bd47a8a0 Mon Sep 17 00:00:00 2001 From: Sascha Peilicke Date: Wed, 13 Nov 2013 15:33:25 +0100 Subject: [PATCH 08/50] BuildDoc shouldn't fail on Unicode paths. Sub-classes of sphinx.setup_command.BuildDoc may choose to call finalize_options in their run() method again for various reasons. However, currently this fails with py2.7 because of http://bugs.python.org/issue19570. Since it is unlikely that the upstream issue will be solved, a workaround is to re-implement distutils' Command._ensure_stringlike to support Unicode strings. --- sphinx/setup_command.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sphinx/setup_command.py b/sphinx/setup_command.py index 07cd520c30..7999bf591e 100644 --- a/sphinx/setup_command.py +++ b/sphinx/setup_command.py @@ -14,6 +14,7 @@ import sys import os +import types from StringIO import StringIO from distutils.cmd import Command @@ -98,6 +99,19 @@ class BuildDoc(Command): return root return None + # Overriding distutils' Command._ensure_stringlike which doesn't support + # unicode, causing finalize_options to fail if invoked again. Workaround + # for http://bugs.python.org/issue19570 + def _ensure_stringlike(self, option, what, default=None): + val = getattr(self, option) + if val is None: + setattr(self, option, default) + return default + elif not isinstance(val, types.StringTypes): + raise DistutilsOptionError("'%s' must be a %s (got `%s`)" + % (option, what, val)) + return val + def finalize_options(self): if self.source_dir is None: self.source_dir = self._guess_source_dir() From a8fc938d1b9fb1be7979da8b8236aba287fc082e Mon Sep 17 00:00:00 2001 From: Takayuki Shimizukawa Date: Sun, 15 Dec 2013 14:16:53 +0900 Subject: [PATCH 09/50] Drop python-2.5 and remove 2.4,2.5 support codes --- CHANGES | 6 ++ doc/install.rst | 6 +- doc/intro.rst | 2 +- setup.py | 12 +-- sphinx/__init__.py | 4 +- sphinx/builders/epub.py | 8 -- sphinx/builders/html.py | 12 +-- sphinx/builders/linkcheck.py | 3 +- sphinx/ext/autodoc.py | 4 +- sphinx/highlighting.py | 12 +-- sphinx/pycode/__init__.py | 2 +- sphinx/pycode/pgen2/parse.c | 89 ----------------- sphinx/quickstart.py | 2 +- sphinx/transforms.py | 1 - sphinx/util/inspect.py | 9 +- sphinx/util/jsonimpl.py | 18 +--- sphinx/util/osutil.py | 10 +- sphinx/util/pycompat.py | 184 ++--------------------------------- sphinx/versioning.py | 3 +- sphinx/writers/latex.py | 1 - tests/test_autodoc.py | 11 +-- tests/test_intl.py | 6 +- tests/test_versioning.py | 1 - tests/test_websupport.py | 7 +- tests/util.py | 7 +- tox.ini | 10 +- 26 files changed, 54 insertions(+), 376 deletions(-) diff --git a/CHANGES b/CHANGES index c15cd0b93d..3b6c88881f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,12 @@ Release 1.3 (in development) ============================ +Change support versions +----------------------- + +* Drop Python-2.5. (support code was completely removed) + + Release 1.2 (released Dec 10, 2013) =================================== diff --git a/doc/install.rst b/doc/install.rst index 9c25842087..828dc3b436 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -4,7 +4,7 @@ Installing Sphinx ================= Since Sphinx is written in the Python language, you need to install Python -(the required version is at least 2.5) and Sphinx. +(the required version is at least 2.6) and Sphinx. Sphinx packages are available on the `Python Package Index `_. @@ -79,8 +79,8 @@ sidebar and under "Quick Links", click "Windows Installer" to download. .. note:: - Currently, Python offers two major versions, 2.x and 3.x. Sphinx 1.2 can run - under Python 2.5 to 2.7 and 3.1 to 3.3, with the recommended version being + Currently, Python offers two major versions, 2.x and 3.x. Sphinx 1.3 can run + under Python 2.6, 2.7 and 3.1 to 3.3, with the recommended version being 2.7. This chapter assumes you have installed Python 2.7. Follow the Windows installer for Python. diff --git a/doc/intro.rst b/doc/intro.rst index 4d052c818f..f0593cee24 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -50,7 +50,7 @@ See the :ref:`pertinent section in the FAQ list `. Prerequisites ------------- -Sphinx needs at least **Python 2.5** or **Python 3.1** to run, as well as the +Sphinx needs at least **Python 2.6** or **Python 3.1** to run, as well as the docutils_ and Jinja2_ libraries. Sphinx should work with docutils version 0.7 or some (not broken) SVN trunk snapshot. If you like to have source code highlighting support, you must also install the Pygments_ library. diff --git a/setup.py b/setup.py index 553cf12d2f..543a319d2a 100644 --- a/setup.py +++ b/setup.py @@ -44,20 +44,20 @@ A development egg can be found `here `_. ''' +if sys.version_info < (2, 6): + print('ERROR: Sphinx requires at least Python 2.6 to run.') + sys.exit(1) + requires = ['Pygments>=1.2', 'docutils>=0.7'] if sys.version_info[:3] >= (3, 3, 0): requires[1] = 'docutils>=0.10' -if sys.version_info < (2, 6) or (3, 0) <= sys.version_info < (3, 3): +if (3, 0) <= sys.version_info < (3, 3): requires.append('Jinja2>=2.3,<2.7') -else: +else: # 2.6, 2.7, 3.3 or later requires.append('Jinja2>=2.3') -if sys.version_info < (2, 5): - print('ERROR: Sphinx requires at least Python 2.5 to run.') - sys.exit(1) - # tell distribute to use 2to3 with our own fixers extra = {} if sys.version_info >= (3, 0): diff --git a/sphinx/__init__.py b/sphinx/__init__.py index f93e57a118..bbd45b245b 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -42,8 +42,8 @@ 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): + 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/builders/epub.py b/sphinx/builders/epub.py index 1dfcc70452..35061b79cb 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 e00b91c368..b0cde2976d 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 @@ -1095,8 +1091,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 c567401c53..c3369b5af5 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 @@ -97,7 +96,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/ext/autodoc.py b/sphinx/ext/autodoc.py index 8d78feb21f..5ea64bf309 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 @@ -1128,7 +1128,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): diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py index df422321b5..7a3927d31c 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 @@ -154,7 +144,7 @@ class PygmentsBridge(object): 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 64999df88c..d69fe0ca49 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -16,7 +16,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 e09f50589c..96fa6c8b2b 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 a4dca3333e..5733b34b5f 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: diff --git a/sphinx/transforms.py b/sphinx/transforms.py index 338d473921..35e9d297b4 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/inspect.py b/sphinx/util/inspect.py index 61061a9af6..c7556d056f 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 aa0ea8251b..de846b240c 100644 --- a/sphinx/util/jsonimpl.py +++ b/sphinx/util/jsonimpl.py @@ -10,27 +10,15 @@ """ import UserString - -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 +import json -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 87717771a3..e8655fe1f3 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -69,6 +69,10 @@ def ensuredir(path): raise +# TODO: This function can be removed because this function is same as os.walk +# of Python2.6, 2.7, 3.1, 3.2, 3.3. +# HOWEVER, this function is customized to check UnicodeError that 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 +159,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 +174,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 3d252c9171..68e6d8fbc1 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 @@ -45,6 +44,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 @@ -62,6 +62,7 @@ else: # safely encode a string for printing to the terminal def terminal_safe(s): return s.encode('ascii', 'backslashreplace') + from itertools import izip_longest as zip_longest # use Python 3 name def execfile_(filepath, _globals): @@ -74,8 +75,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,py31 accept only LF eol instead of CRLF + if sys.version_info[:2] in ((2, 6), (3, 1)): source = source.replace(b('\r\n'), b('\n')) # compile to a code object, handle syntax errors @@ -93,178 +94,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 a16751bb50..ccab41d4b7 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/latex.py b/sphinx/writers/latex.py index 80080752e6..5e2bf93fcf 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 diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 63c341f1b3..7e60c5fb34 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -737,12 +737,8 @@ def _funky_classmethod(name, b, c, d, docstring=None): some arguments.""" def template(cls, a, b, c, d=4, e=5, f=6): return a, b, c, d, e, f - if sys.version_info >= (2, 5): - from functools import partial - function = partial(template, b=b, c=c, d=d) - else: - def function(cls, a, e=5, f=6): - return template(a, b, c, d, e, f) + from functools import partial + function = partial(template, b=b, c=c, d=d) function.__name__ = name function.__doc__ = docstring return classmethod(function) @@ -774,10 +770,9 @@ class Class(Base): #: should be documented -- süß attr = 'bar' + @property def prop(self): """Property.""" - # stay 2.4 compatible (docstring!) - prop = property(prop, doc="Property.") docattr = 'baz' """should likewise be documented -- süß""" diff --git a/tests/test_intl.py b/tests/test_intl.py index c98220890a..c241c0aed9 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -16,8 +16,6 @@ from StringIO import StringIO from subprocess import Popen, PIPE from xml.etree import ElementTree -from sphinx.util.pycompat import relpath - from util import test_roots, path, with_app, SkipTest @@ -49,7 +47,7 @@ def setup_module(): for f in [f for f in files if f.endswith('.po')]: po = dirpath / f mo = root / 'xx' / 'LC_MESSAGES' / ( - relpath(po[:-3], root) + '.mo') + os.path.relpath(po[:-3], root) + '.mo') if not mo.parent.exists(): mo.parent.makedirs() try: @@ -75,7 +73,7 @@ def teardown_module(): def elem_gettexts(elem): def itertext(self): # this function copied from Python-2.7 'ElementTree.itertext'. - # for compatibility to Python-2.5, 2.6, 3.1 + # for compatibility to Python-2.6, 3.1 tag = self.tag if not isinstance(tag, basestring) and tag is not None: return diff --git a/tests/test_versioning.py b/tests/test_versioning.py index 6469a33ccc..5cff92ffd9 100644 --- a/tests/test_versioning.py +++ b/tests/test_versioning.py @@ -15,7 +15,6 @@ from docutils.parsers.rst.directives.html import MetaBody from sphinx import addnodes from sphinx.versioning import add_uids, merge_doctrees, get_ratio -from sphinx.util.pycompat import all from util import test_root, TestApp diff --git a/tests/test_websupport.py b/tests/test_websupport.py index 611a131acc..d950a36c38 100644 --- a/tests/test_websupport.py +++ b/tests/test_websupport.py @@ -11,12 +11,7 @@ import os from StringIO import StringIO - -try: - from functools import wraps -except ImportError: - # functools is new in 2.5 - wraps = lambda f: (lambda w: w) +from functools import wraps from sphinx.websupport import WebSupport from sphinx.websupport.errors import DocumentNotFoundError, \ diff --git a/tests/util.py b/tests/util.py index 4ba89030a7..a2f345bf8f 100644 --- a/tests/util.py +++ b/tests/util.py @@ -13,12 +13,7 @@ import tempfile import shutil import re from codecs import open - -try: - from functools import wraps -except ImportError: - # functools is new in 2.4 - wraps = lambda f: (lambda w: w) +from functools import wraps from sphinx import application from sphinx.theming import Theme diff --git a/tox.ini b/tox.ini index f1675e9017..b03cc9e0a2 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py25,py26,py27,py31,py32,py33,pypy,du11,du10,du09,du08,du07 +envlist=py26,py27,py31,py32,py33,pypy,du11,du10,du09,du08,du07 [testenv] deps= @@ -12,14 +12,6 @@ commands= {envpython} tests/run.py {posargs} sphinx-build -W -b html -d {envtmpdir}/doctrees doc {envtmpdir}/html -[testenv:py25] -deps= - simplejson==2.5.0 - {[testenv]deps} -setenv= - PIP_INSECURE = 1 - {[testenv]setenv} - [testenv:py33] deps= docutils>=0.10.0 From f7cb510ebd558f16cc3ac6102c0c4a6280ee06ed Mon Sep 17 00:00:00 2001 From: Takayuki Shimizukawa Date: Sun, 15 Dec 2013 16:04:23 +0900 Subject: [PATCH 10/50] Drop python-3.1 and remove support codes --- CHANGES | 2 +- doc/install.rst | 2 +- doc/intro.rst | 2 +- setup.py | 4 ++-- sphinx/highlighting.py | 6 ------ sphinx/util/osutil.py | 2 +- sphinx/util/pycompat.py | 4 ++-- tests/test_intl.py | 2 +- tox.ini | 2 +- 9 files changed, 10 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index 3b6c88881f..df139ef00d 100644 --- a/CHANGES +++ b/CHANGES @@ -4,7 +4,7 @@ Release 1.3 (in development) Change support versions ----------------------- -* Drop Python-2.5. (support code was completely removed) +* Drop Python-2.5, 3.1 (support code was completely removed) diff --git a/doc/install.rst b/doc/install.rst index 828dc3b436..bc969b0be6 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -80,7 +80,7 @@ sidebar and under "Quick Links", click "Windows Installer" to download. .. note:: Currently, Python offers two major versions, 2.x and 3.x. Sphinx 1.3 can run - under Python 2.6, 2.7 and 3.1 to 3.3, with the recommended version being + under Python 2.6, 2.7, 3.2, 3.3, with the recommended version being 2.7. This chapter assumes you have installed Python 2.7. Follow the Windows installer for Python. diff --git a/doc/intro.rst b/doc/intro.rst index f0593cee24..b2c25023e5 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -50,7 +50,7 @@ See the :ref:`pertinent section in the FAQ list `. Prerequisites ------------- -Sphinx needs at least **Python 2.6** or **Python 3.1** to run, as well as the +Sphinx needs at least **Python 2.6** or **Python 3.2** to run, as well as the docutils_ and Jinja2_ libraries. Sphinx should work with docutils version 0.7 or some (not broken) SVN trunk snapshot. If you like to have source code highlighting support, you must also install the Pygments_ library. diff --git a/setup.py b/setup.py index 543a319d2a..53a4c04b2a 100644 --- a/setup.py +++ b/setup.py @@ -44,8 +44,8 @@ A development egg can be found `here `_. ''' -if sys.version_info < (2, 6): - print('ERROR: Sphinx requires at least Python 2.6 to run.') +if sys.version_info < (2, 6) or (3, 0) <= sys.version_info < (3, 2): + print('ERROR: Sphinx requires at least Python 2.6 or 3.2 to run.') sys.exit(1) requires = ['Pygments>=1.2', 'docutils>=0.7'] diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py index 7a3927d31c..807a182618 100644 --- a/sphinx/highlighting.py +++ b/sphinx/highlighting.py @@ -133,12 +133,6 @@ 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 diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index e8655fe1f3..d2f8679ed1 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -70,7 +70,7 @@ def ensuredir(path): # TODO: This function can be removed because this function is same as os.walk -# of Python2.6, 2.7, 3.1, 3.2, 3.3. +# of Python2.6, 2.7, 3.2, 3.3. # HOWEVER, this function is customized to check UnicodeError that obstacle to # replace the function with the os.walk. def walk(top, topdown=True, followlinks=False): diff --git a/sphinx/util/pycompat.py b/sphinx/util/pycompat.py index 68e6d8fbc1..a0e0996591 100644 --- a/sphinx/util/pycompat.py +++ b/sphinx/util/pycompat.py @@ -75,8 +75,8 @@ def execfile_(filepath, _globals): finally: f.close() - # py26,py31 accept only LF eol instead of CRLF - if sys.version_info[:2] in ((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 diff --git a/tests/test_intl.py b/tests/test_intl.py index c241c0aed9..0119d677f6 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -73,7 +73,7 @@ def teardown_module(): def elem_gettexts(elem): def itertext(self): # this function copied from Python-2.7 'ElementTree.itertext'. - # for compatibility to Python-2.6, 3.1 + # for compatibility to Python-2.6 tag = self.tag if not isinstance(tag, basestring) and tag is not None: return diff --git a/tox.ini b/tox.ini index b03cc9e0a2..006819ef57 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py26,py27,py31,py32,py33,pypy,du11,du10,du09,du08,du07 +envlist=py26,py27,py32,py33,pypy,du11,du10,du09,du08,du07 [testenv] deps= From 91d92be8adae2e44e1e23e1c07b7088c35261abf Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 11 Jan 2014 19:36:05 +0100 Subject: [PATCH 11/50] Add "make mode" to sphinx-build, invoked by the -M flag. This is intended to do almost all of what the quickstart-generated Makefile and make.bat did, but within Sphinx. The advantages are: * no duplication between Unix and Windows files * updates and fixes are propagated (the generated makefiles never update) * more Python code, less shell code! --- doc/Makefile | 164 +++-------------------------------- sphinx-build.py | 7 +- sphinx/__init__.py | 6 ++ sphinx/cmdline.py | 36 +++++--- sphinx/make_mode.py | 202 +++++++++++++++++++++++++++++++++++++++++++ sphinx/quickstart.py | 66 ++++++++++++++ 6 files changed, 315 insertions(+), 166 deletions(-) create mode 100644 sphinx/make_mode.py diff --git a/doc/Makefile b/doc/Makefile index 831c12c52a..d799c3658f 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -4,157 +4,21 @@ # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = python ../sphinx-build.py -PAPER = +SPHINXPROJ = sphinx +BUILDDIR = _build -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) \ - $(SPHINXOPTS) $(O) . -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(O) . - -.PHONY: help clean html dirhtml singlehtml text man pickle json htmlhelp \ - qthelp devhelp epub latex latexpdf changes linkcheck doctest xml \ - pseudoxml +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error \ +The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx \ +installed, then set the SPHINXBUILD environment variable to point \ +to the full path of the '$(SPHINXBUILD)' executable. Alternatively you \ +can add the directory with the executable to your PATH. \ +If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files called index.html in directories" - @echo " singlehtml to make one big HTML file" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " pickle to make pickle files" - @echo " json to make json files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make Qt help files and project" - @echo " devhelp to make Devhelp files and project" - @echo " epub to make an epub file" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run pdflatex" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview over all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" + @$(SPHINXBUILD) -M help "$(BUILDDIR)" $(SPHINXOPTS) $(O) -clean: - rm -rf _build/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html - @echo - @echo "Build finished. The HTML pages are in _build/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml - @echo - @echo "Build finished. The HTML pages are in _build/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) _build/singlehtml - @echo - @echo "Build finished. The HTML page is in _build/singlehtml." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) _build/text - @echo - @echo "Build finished." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) _build/man - @echo - @echo "Build finished." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle - @echo - @echo "Build finished." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json - @echo - @echo "Build finished." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in _build/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp - @echo - @echo "Build finished; now you can run qcollectiongenerator with the" \ - ".qhcp project file in build/qthelp." - @echo "# qcollectiongenerator _build/qthelp/Sphinx.qhcp" - @echo "To view the help collection:" - @echo "# assistant -collectionFile _build/qthelp/Sphinx.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) _build/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/sphinx" - @echo "# ln -s _build/devhelp $$HOME/.local/share/devhelp/sphinx" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) _build/epub - @echo - @echo "Build finished. The epub file is in _build/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex - @echo - @echo "Build finished; the LaTeX files are in _build/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex - @echo "Running LaTeX files through pdflatex..." - make -C _build/latex all-pdf - @echo "pdflatex finished; the PDF files are in _build/latex." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) _build/locale - @echo - @echo "Build finished. The message catalogs are in _build/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes - @echo - @echo "The overview file is in _build/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in _build/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) _build/texinfo - @echo - @echo "Build finished. The Texinfo files are in _build/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) _build/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C _build/texinfo info - @echo "makeinfo finished; the Info files are in _build/texinfo." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) _build/xml - @echo - @echo "Build finished. The XML files are in _build/XML." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) _build/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in _build/pseudoxml." +%: + @$(SPHINXBUILD) -M $@ "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/sphinx-build.py b/sphinx-build.py index 6737d07292..54bdb5293a 100755 --- a/sphinx-build.py +++ b/sphinx-build.py @@ -11,5 +11,8 @@ import sys if __name__ == '__main__': - from sphinx import main - sys.exit(main(sys.argv)) + from sphinx import main, make_main + if sys.argv[1:2] == ['-M']: + sys.exit(make_main(sys.argv)) + else: + sys.exit(main(sys.argv)) diff --git a/sphinx/__init__.py b/sphinx/__init__.py index d41d4424a1..8a1b97f702 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -80,5 +80,11 @@ def main(argv=sys.argv): return cmdline.main(argv) +def make_main(argv=sys.argv): + """Sphinx build "make mode" entry.""" + from sphinx import make_mode + return make_mode.run_make_mode(argv[2:]) + + if __name__ == '__main__': sys.exit(main(sys.argv)) diff --git a/sphinx/cmdline.py b/sphinx/cmdline.py index e5ad3ab5b4..f4a09e1610 100644 --- a/sphinx/cmdline.py +++ b/sphinx/cmdline.py @@ -42,6 +42,7 @@ General options -d path for the cached environment and doctree files (default: outdir/.doctrees) -j build in parallel with N processes where possible +-M "make" mode -- used by Makefile, like "sphinx-build -M html" Build configuration options ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -82,19 +83,28 @@ def main(argv): # Windows' poor cmd box doesn't understand ANSI sequences nocolor() + # parse options try: opts, args = getopt.getopt(argv[1:], 'ab:t:d:c:CD:A:nNEqQWw:PThvj:', ['help', 'version']) - allopts = set(opt[0] for opt in opts) - if '-h' in allopts or '--help' in allopts: - usage(argv) - print >>sys.stderr - print >>sys.stderr, 'For more information, see '\ - '.' - return 0 - if '--version' in allopts: - print 'Sphinx (sphinx-build) %s' % __version__ - return 0 + except getopt.error, err: + usage(argv, 'Error: %s' % err) + return 1 + + # handle basic options + allopts = set(opt[0] for opt in opts) + # help and version options + if '-h' in allopts or '--help' in allopts: + usage(argv) + print >>sys.stderr + print >>sys.stderr, 'For more information, see .' + return 0 + if '--version' in allopts: + print 'Sphinx (sphinx-build) %s' % __version__ + return 0 + + # get paths (first and second positional argument) + try: srcdir = confdir = abspath(args[0]) if not path.isdir(srcdir): print >>sys.stderr, 'Error: Cannot find source directory `%s\'.' % ( @@ -103,12 +113,9 @@ def main(argv): if not path.isfile(path.join(srcdir, 'conf.py')) and \ '-c' not in allopts and '-C' not in allopts: print >>sys.stderr, ('Error: Source directory doesn\'t ' - 'contain conf.py file.') + 'contain a conf.py file.') return 1 outdir = abspath(args[1]) - except getopt.error, err: - usage(argv, 'Error: %s' % err) - return 1 except IndexError: usage(argv, 'Error: Insufficient arguments.') return 1 @@ -118,6 +125,7 @@ def main(argv): 'encoding (%r).' % fs_encoding) return 1 + # handle remaining filename arguments filenames = args[2:] err = 0 for filename in filenames: diff --git a/sphinx/make_mode.py b/sphinx/make_mode.py new file mode 100644 index 0000000000..5ef065a5da --- /dev/null +++ b/sphinx/make_mode.py @@ -0,0 +1,202 @@ +# -*- coding: utf-8 -*- +""" + sphinx.make_mode + ~~~~~~~~~~~~~~~~ + + sphinx-build -M command-line handling. + + This replaces the old, platform-dependent and once-generated content + of Makefile / make.bat. + + This is in its own module so that importing it is fast. It should not + import the main Sphinx modules (like sphinx.applications, sphinx.builders). + + :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import os +import sys +import shutil +from os import path +from subprocess import call + +import sphinx +from sphinx.util.console import bold, blue + +proj_name = os.getenv('SPHINXPROJ', '') + +def build_clean(builddir, opts): + if not path.exists(builddir): + return + elif not path.isdir(builddir): + print "Error: %r is not a directory!" % builddir + return 1 + print "removing everything under %r..." % builddir + for item in os.listdir(builddir): + shutil.rmtree(path.join(builddir, item)) + +BUILDERS = [ + ("", "html", "to make standalone HTML files"), + ("", "dirhtml", "to make HTML files named index.html in directories"), + ("", "singlehtml","to make a single large HTML file"), + ("", "pickle", "to make pickle files"), + ("", "json", "to make JSON files"), + ("", "htmlhelp", "to make HTML files and a HTML help project"), + ("", "qthelp", "to make HTML files and a qthelp project"), + ("", "devhelp", "to make HTML files and a Devhelp project"), + ("", "epub", "to make an epub"), + ("", "latex", "to make LaTeX files, you can set PAPER=a4 or PAPER=letter"), + ("posix", "latexpdf", "to make LaTeX files and run them through pdflatex"), + ("posix", "latexpdfja","to make LaTeX files and run them through platex/dvipdfmx"), + ("", "text", "to make text files"), + ("", "man", "to make manual pages"), + ("", "texinfo", "to make Texinfo files"), + ("posix", "info", "to make Texinfo files and run them through makeinfo"), + ("", "gettext", "to make PO message catalogs"), + ("", "changes", "to make an overview of all changed/added/deprecated items"), + ("", "xml", "to make Docutils-native XML files"), + ("", "pseudoxml", "to make pseudoxml-XML files for display purposes"), + ("", "linkcheck", "to check all external links for integrity"), + ("", "doctest", "to run all doctests embedded in the documentation (if enabled)"), +] + +def build_help(builddir, opts): + print bold("Sphinx v%s" % sphinx.__version__) + print "Please use `make %s' where %s is one of" % ((blue('target'),)*2) + for osname, bname, description in BUILDERS: + if not osname or os.name == osname: + print ' %s %s' % (blue(bname.ljust(10)), description) + + +def build_html(builddir, opts): + if run_generic_build('html', builddir, opts) > 0: + return 1 + print + print 'Build finished. The HTML pages are in %s.' % path.join(builddir, 'html') + +def build_dirhtml(builddir, opts): + if run_generic_build('dirhtml', builddir, opts) > 0: + return 1 + print + print 'Build finished. The HTML pages are in %s.' % path.join(builddir, 'dirhtml') + +def build_singlehtml(builddir, opts): + if run_generic_build('singlehtml', builddir, opts) > 0: + return 1 + print + print 'Build finished. The HTML page is in %s.' % path.join(builddir, 'singlehtml') + +def build_pickle(builddir, opts): + if run_generic_build('pickle', builddir, opts) > 0: + return 1 + print + print 'Build finished; now you can process the pickle files.' + +def build_json(builddir, opts): + if run_generic_build('json', builddir, opts) > 0: + return 1 + print + print 'Build finished; now you can process the JSON files.' + +def build_htmlhelp(builddir, opts): + if run_generic_build('htmlhelp', builddir, opts) > 0: + return 1 + print + print ('Build finished; now you can run HTML Help Workshop with the ' + '.hhp project file in %s.') % path.join(builddir, 'htmlhelp') + +def build_qthelp(builddir, opts): + if run_generic_build('qthelp', builddir, opts) > 0: + return 1 + print + print ('Build finished; now you can run "qcollectiongenerator" with the ' + '.qhcp project file in %s, like this:') % path.join(builddir, 'qthelp') + print '$ qcollectiongenerator %s.qhcp' % path.join(builddir, 'qthelp', proj_name) + print 'To view the help file:' + print '$ assistant -collectionFile %s.qhc' % path.join(builddir, 'qthelp', proj_name) + +def build_devhelp(builddir, opts): + if run_generic_build('devhelp', builddir, opts) > 0: + return 1 + print + print "Build finished." + print "To view the help file:" + print "$ mkdir -p $HOME/.local/share/devhelp/" + proj_name + print "$ ln -s %s $HOME/.local/share/devhelp/%s" % \ + (path.join(builddir, 'devhelp'), proj_name) + print "$ devhelp" + +def build_epub(builddir, opts): + if run_generic_build('epub', builddir, opts) > 0: + return 1 + print + print 'Build finished. The ePub file is in %s.' % path.join(builddir, 'epub') + + +# latex +# latexpdf +# latexpdfja + +def build_text(builddir, opts): + if run_generic_build('text', builddir, opts) > 0: + return 1 + print + print 'Build finished. The text files are in %s.' % path.join(builddir, 'text') + +# texinfo +# info + +def build_gettext(builddir, opts): + dtdir = path.join(builddir, 'gettext', '.doctrees') + if run_generic_build('gettext', builddir, opts, doctreedir=dtdir) > 0: + return 1 + print + print 'Build finished. The message catalogs are in %s.' % path.join(builddir, 'gettext') + +def build_changes(builddir, opts): + if run_generic_build('changes', builddir, opts) > 0: + return 1 + print + print 'Build finished. The overview file is in %s.' % path.join(builddir, 'changes') + +def build_linkcheck(builddir, opts): + res = run_generic_build('linkcheck', builddir, opts) + print + print ('Link check complete; look for any errors in the above output ' + 'or in %s.') % path.join(builddir, 'linkcheck', 'output.txt') + return res + +def build_xml(builddir, opts): + if run_generic_build('xml', builddir, opts) > 0: + return 1 + print + print 'Build finished. The XML files are in %s.' % path.join(builddir, 'xml') + +def build_pseudoxml(builddir, opts): + if run_generic_build('pseudoxml', builddir, opts) > 0: + return 1 + print + print 'Build finished. The pseudo-XML files are in %s.' % path.join(builddir, 'pseudoxml') + + +def run_generic_build(builder, builddir, opts, doctreedir=None): + # compatibility with old Makefile + papersize = os.getenv('PAPER', '') + if papersize in ('a4', 'letter'): + opts.extend(['-D', 'latex_paper_size=' + papersize]) + if doctreedir is None: + doctreedir = path.join(builddir, 'doctrees') + return call([sys.executable, sys.argv[0], '-b', builder, + '-d', doctreedir, '.', path.join(builddir, builder)] + opts) + + +def run_make_mode(args): + if len(args) < 2: + print >>sys.stderr, ('Error: at least two arguments (builder, build ' + 'dir) are required.') + return 1 + run_method = 'build_' + args[0] + if run_method in globals(): + return globals()[run_method](args[1], args[2:]) + return run_generic_build(args[0], args[1], args[2:]) diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index f77e2f22b2..7ce3ad8489 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -837,6 +837,72 @@ if "%%1" == "pseudoxml" ( :end ''' +# This will become the Makefile template for Sphinx 1.5. +MAKEFILE_NEW = u'''\ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = %(project_fn)s +BUILDDIR = %(rbuilddir)s + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error \ +The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx \ +installed, then set the SPHINXBUILD environment variable to point \ +to the full path of the '$(SPHINXBUILD)' executable. Alternatively you \ +can add the directory with the executable to your PATH. \ +If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Has to be explicit, otherwise we don't get "make" without targets right. +help: +\t@$(SPHINXBUILD) -M help "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +# Catch-all target using the new "make mode" option. +%: +\t@$(SPHINXBUILD) -M $@ "$(BUILDDIR)" $(SPHINXOPTS) $(O) +''' + +# This will become the make.bat template for Sphinx 1.5. +BATCHFILE_NEW = u'''\ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%%SPHINXBUILD%%" == "" ( +\tset SPHINXBUILD=sphinx-build +) +set BUILDDIR=%(rbuilddir)s +set SPHINXPROJ=%(project_fn)s + +if "%%1" == "" goto help + +%%SPHINXBUILD%% 2> nul +if errorlevel 9009 ( +\techo. +\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx +\techo.installed, then set the SPHINXBUILD environment variable to point +\techo.to the full path of the 'sphinx-build' executable. Alternatively you +\techo.may add the Sphinx directory to PATH. +\techo. +\techo.If you don't have Sphinx installed, grab it from +\techo.http://sphinx-doc.org/ +\texit /b 1 +) + +%%SPHINXBUILD%% -M %%1 %%BUILDDIR%% %%SPHINXOPTS%% +goto end + +:help +%%SPHINXBUILD%% -M help %%BUILDDIR%% %%SPHINXOPTS%% + +:end +''' + def mkdir_p(dir): if path.isdir(dir): From b6005b22ef9c2db78804f0b11816be48711bea0a Mon Sep 17 00:00:00 2001 From: Takayuki Shimizukawa Date: Sat, 18 Jan 2014 16:30:54 +0900 Subject: [PATCH 12/50] Fix: The application now check extra Python versions (3.0, 3.1) to stop invoking. And fix a trivial comment. --- sphinx/__init__.py | 3 ++- sphinx/util/osutil.py | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sphinx/__init__.py b/sphinx/__init__.py index bbd45b245b..2551526f9b 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -42,7 +42,8 @@ if '+' in __version__ or 'pre' in __version__: def main(argv=sys.argv): """Sphinx build "main" command-line entry.""" - if sys.version_info[:3] < (2, 6, 0): + 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: diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index d2f8679ed1..a5c461a6fe 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -69,10 +69,9 @@ def ensuredir(path): raise -# TODO: This function can be removed because this function is same as os.walk -# of Python2.6, 2.7, 3.2, 3.3. -# HOWEVER, this function is customized to check UnicodeError that obstacle to -# replace the function with the os.walk. +# 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. From ec06a5d7e0a55f58be414ad041b58388d946f265 Mon Sep 17 00:00:00 2001 From: Takayuki Shimizukawa Date: Sat, 18 Jan 2014 22:57:02 +0900 Subject: [PATCH 13/50] add missing 'feature added' for non-ASCII filename handling. --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 3c0ab53c53..443ec77463 100644 --- a/CHANGES +++ b/CHANGES @@ -350,6 +350,8 @@ Features added reference documentation in doc directory provides a ``sphinx.pot`` file with message strings from ``doc/_templates/*.html`` when using ``make gettext``. + - PR#61,#703: Add support non-ASCII filename handling. + * Other builders: - Added the Docutils-native XML and pseudo-XML builders. See From f9758086b02089c28d5f2f8395f0279fe99bf778 Mon Sep 17 00:00:00 2001 From: Takayuki Shimizukawa Date: Sun, 19 Jan 2014 00:23:17 +0900 Subject: [PATCH 14/50] grammar fix --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 443ec77463..79de153190 100644 --- a/CHANGES +++ b/CHANGES @@ -350,7 +350,7 @@ Features added reference documentation in doc directory provides a ``sphinx.pot`` file with message strings from ``doc/_templates/*.html`` when using ``make gettext``. - - PR#61,#703: Add support non-ASCII filename handling. + - PR#61,#703: Add support for non-ASCII filename handling. * Other builders: From a00a096069f44bbb131fe22414fb3053f3ef9fd6 Mon Sep 17 00:00:00 2001 From: Rob Ruana Date: Sat, 18 Jan 2014 13:56:23 -0500 Subject: [PATCH 15/50] Merges napoleon extension into mainline sphinx --- AUTHORS | 1 + doc/conf.py | 4 +- doc/ext/example_google.py | 223 ++++++++++ doc/ext/example_google.rst | 15 + doc/ext/example_numpy.py | 272 ++++++++++++ doc/ext/example_numpy.rst | 15 + doc/ext/napoleon.rst | 365 ++++++++++++++++ doc/extensions.rst | 1 + sphinx/ext/napoleon/__init__.py | 371 ++++++++++++++++ sphinx/ext/napoleon/docstring.py | 714 +++++++++++++++++++++++++++++++ sphinx/ext/napoleon/iterators.py | 244 +++++++++++ tests/root/conf.py | 3 +- tests/test_napoleon.py | 190 ++++++++ tests/test_napoleon_docstring.py | 259 +++++++++++ tests/test_napoleon_iterators.py | 346 +++++++++++++++ tox.ini | 1 + 16 files changed, 3022 insertions(+), 2 deletions(-) create mode 100644 doc/ext/example_google.py create mode 100644 doc/ext/example_google.rst create mode 100644 doc/ext/example_numpy.py create mode 100644 doc/ext/example_numpy.rst create mode 100644 doc/ext/napoleon.rst create mode 100644 sphinx/ext/napoleon/__init__.py create mode 100644 sphinx/ext/napoleon/docstring.py create mode 100644 sphinx/ext/napoleon/iterators.py create mode 100644 tests/test_napoleon.py create mode 100644 tests/test_napoleon_docstring.py create mode 100644 tests/test_napoleon_iterators.py diff --git a/AUTHORS b/AUTHORS index 2a9dbbac91..24b9be74d6 100644 --- a/AUTHORS +++ b/AUTHORS @@ -32,6 +32,7 @@ Other contributors, listed alphabetically, are: * Christopher Perkins -- autosummary integration * Benjamin Peterson -- unittests * T. Powers -- HTML output improvements +* Rob Ruana -- napoleon extension * Stefan Seefeld -- toctree improvements * Shibukawa Yoshiki -- pluggable search API and Japanese search * Antonio Valentino -- qthelp builder diff --git a/doc/conf.py b/doc/conf.py index 08149fba54..b6515a38de 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -5,8 +5,10 @@ import re import sphinx + extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', - 'sphinx.ext.autosummary', 'sphinx.ext.extlinks'] + 'sphinx.ext.autosummary', 'sphinx.ext.extlinks', + 'sphinx.ext.napoleon'] master_doc = 'contents' templates_path = ['_templates'] diff --git a/doc/ext/example_google.py b/doc/ext/example_google.py new file mode 100644 index 0000000000..c94dcdf1d6 --- /dev/null +++ b/doc/ext/example_google.py @@ -0,0 +1,223 @@ +# -*- coding: utf-8 -*- +"""Example Google style docstrings. + +This module demonstrates documentation as specified by the `Google Python +Style Guide`_. Docstrings may extend over multiple lines. Sections are created +with a section header and a colon followed by a block of indented text. + +Example: + Examples can be given using either the ``Example`` or ``Examples`` + sections. Sections support any reStructuredText formatting, including + literal blocks:: + + $ python example_google.py + +Section breaks are created by simply resuming unindented text. Section breaks +are also implicitly created anytime a new section starts. + +Attributes: + module_level_variable (int): Module level variables may be documented in + either the ``Attributes`` section of the module docstring, or in an + inline docstring immediately following the variable. + + Either form is acceptable, but the two should not be mixed. Choose + one convention to document module level variables and be consistent + with it. + +.. _Google Python Style Guide: + http://google-styleguide.googlecode.com/svn/trunk/pyguide.html + +""" + +module_level_variable = 12345 + + +def module_level_function(param1, param2=None, *args, **kwargs): + """This is an example of a module level function. + + Function parameters should be documented in the ``Args`` section. The name + of each parameter is required. The type and description of each parameter + is optional, but should be included if not obvious. + + If the parameter itself is optional, it should be noted by adding + ", optional" to the type. If \*args or \*\*kwargs are accepted, they + should be listed as \*args and \*\*kwargs. + + The format for a parameter is:: + + name (type): description + The description may span multiple lines. Following + lines should be indented. + + Multiple paragraphs are supported in parameter + descriptions. + + Args: + param1 (int): The first parameter. + param2 (str, optional): The second parameter. Defaults to None. + Second line of description should be indented. + *args: Variable length argument list. + **kwargs: Arbitrary keyword arguments. + + Returns: + bool: True if successful, False otherwise. + + The return type is optional and may be specified at the beginning of + the ``Returns`` section followed by a colon. + + The ``Returns`` section may span multiple lines and paragraphs. + Following lines should be indented to match the first line. + + The ``Returns`` section supports any reStructuredText formatting, + including literal blocks:: + + { + 'param1': param1, + 'param2': param2 + } + + Raises: + AttributeError: The ``Raises`` section is a list of all exceptions + that are relevant to the interface. + ValueError: If `param2` is equal to `param1`. + + """ + if param1 == param2: + raise ValueError('param1 may not be equal to param2') + return True + + +def example_generator(n): + """Generators have a ``Yields`` section instead of a ``Returns`` section. + + Args: + n (int): The upper limit of the range to generate, from 0 to `n` - 1 + + Yields: + int: The next number in the range of 0 to `n` - 1 + + Examples: + Examples should be written in doctest format, and should illustrate how + to use the function. + + >>> print [i for i in example_generator(4)] + [0, 1, 2, 3] + + """ + for i in range(n): + yield i + + +class ExampleError(Exception): + """Exceptions are documented in the same way as classes. + + The __init__ method may be documented in either the class level + docstring, or as a docstring on the __init__ method itself. + + Either form is acceptable, but the two should not be mixed. Choose one + convention to document the __init__ method and be consistent with it. + + Note: + Do not include the `self` parameter in the ``Args`` section. + + Args: + msg (str): Human readable string describing the exception. + code (int, optional): Error code, defaults to 2. + + Attributes: + msg (str): Human readable string describing the exception. + code (int): Exception error code. + + """ + def __init__(self, msg, code=2): + self.msg = msg + self.code = code + + +class ExampleClass(object): + """The summary line for a class docstring should fit on one line. + + If the class has public attributes, they should be documented here + in an ``Attributes`` section and follow the same formatting as a + function's ``Args`` section. + + Attributes: + attr1 (str): Description of `attr1`. + attr2 (list of str): Description of `attr2`. + attr3 (int): Description of `attr3`. + + """ + def __init__(self, param1, param2, param3=0): + """Example of docstring on the __init__ method. + + The __init__ method may be documented in either the class level + docstring, or as a docstring on the __init__ method itself. + + Either form is acceptable, but the two should not be mixed. Choose one + convention to document the __init__ method and be consistent with it. + + Note: + Do not include the `self` parameter in the ``Args`` section. + + Args: + param1 (str): Description of `param1`. + param2 (list of str): Description of `param2`. Multiple + lines are supported. + param3 (int, optional): Description of `param3`, defaults to 0. + + """ + self.attr1 = param1 + self.attr2 = param2 + self.attr3 = param3 + + def example_method(self, param1, param2): + """Class methods are similar to regular functions. + + Note: + Do not include the `self` parameter in the ``Args`` section. + + Args: + param1: The first parameter. + param2: The second parameter. + + Returns: + True if successful, False otherwise. + + """ + return True + + def __special__(self): + """By default special members with docstrings are included. + + Special members are any methods or attributes that start with and + end with a double underscore. Any special member with a docstring + will be included in the output. + + This behavior can be disabled by changing the following setting in + Sphinx's conf.py:: + + napoleon_include_special_with_doc = False + + """ + pass + + def __special_without_docstring__(self): + pass + + def _private(self): + """By default private members are not included. + + Private members are any methods or attributes that start with an + underscore and are *not* special. By default they are not included + in the output. + + This behavior can be changed such that private members *are* included + by changing the following setting in Sphinx's conf.py:: + + napoleon_include_private_with_doc = True + + """ + pass + + def _private_without_docstring(self): + pass diff --git a/doc/ext/example_google.rst b/doc/ext/example_google.rst new file mode 100644 index 0000000000..06508082c1 --- /dev/null +++ b/doc/ext/example_google.rst @@ -0,0 +1,15 @@ +:orphan: + +.. _example_google: + +Example Google Style Python Docstrings +====================================== + +.. seealso:: + + :ref:`example_numpy` + +Download: :download:`example_google.py ` + +.. literalinclude:: example_google.py + :language: python diff --git a/doc/ext/example_numpy.py b/doc/ext/example_numpy.py new file mode 100644 index 0000000000..df1d20e6d7 --- /dev/null +++ b/doc/ext/example_numpy.py @@ -0,0 +1,272 @@ +# -*- coding: utf-8 -*- +"""Example NumPy style docstrings. + +This module demonstrates documentation as specified by the `NumPy +Documentation HOWTO`_. Docstrings may extend over multiple lines. Sections +are created with a section header followed by an underline of equal length. + +Example +------- +Examples can be given using either the ``Example`` or ``Examples`` +sections. Sections support any reStructuredText formatting, including +literal blocks:: + + $ python example_numpy.py + + +Section breaks are created with two blank lines. Section breaks are also +implicitly created anytime a new section starts. Section bodies *may* be +indented: + +Notes +----- + This is an example of an indented section. It's like any other section, + but the body is indented to help it stand out from surrounding text. + +If a section is indented, then a section break is created simply by +resuming unindented text. + +Attributes +---------- +module_level_variable : int + Module level variables may be documented in either the ``Attributes`` + section of the module docstring, or in an inline docstring immediately + following the variable. + + Either form is acceptable, but the two should not be mixed. Choose + one convention to document module level variables and be consistent + with it. + +.. _NumPy Documentation HOWTO: + https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt + +""" + +module_level_variable = 12345 + + +def module_level_function(param1, param2=None, *args, **kwargs): + """This is an example of a module level function. + + Function parameters should be documented in the ``Parameters`` section. + The name of each parameter is required. The type and description of each + parameter is optional, but should be included if not obvious. + + If the parameter itself is optional, it should be noted by adding + ", optional" to the type. If \*args or \*\*kwargs are accepted, they + should be listed as \*args and \*\*kwargs. + + The format for a parameter is:: + + name : type + description + + The description may span multiple lines. Following lines + should be indented to match the first line of the description. + + Multiple paragraphs are supported in parameter + descriptions. + + Parameters + ---------- + param1 : int + The first parameter. + param2 : str, optional + The second parameter, defaults to None. + *args + Variable length argument list. + **kwargs + Arbitrary keyword arguments. + + Returns + ------- + bool + True if successful, False otherwise. + + The return type is not optional. The ``Returns`` section may span + multiple lines and paragraphs. Following lines should be indented to + match the first line of the description. + + The ``Returns`` section supports any reStructuredText formatting, + including literal blocks:: + + { + 'param1': param1, + 'param2': param2 + } + + Raises + ------ + AttributeError + The ``Raises`` section is a list of all exceptions + that are relevant to the interface. + ValueError + If `param2` is equal to `param1`. + + """ + if param1 == param2: + raise ValueError('param1 may not be equal to param2') + return True + + +def example_generator(n): + """Generators have a ``Yields`` section instead of a ``Returns`` section. + + Parameters + ---------- + n : int + The upper limit of the range to generate, from 0 to `n` - 1 + + Yields + ------ + int + The next number in the range of 0 to `n` - 1 + + Examples + -------- + Examples should be written in doctest format, and should illustrate how + to use the function. + + >>> print [i for i in example_generator(4)] + [0, 1, 2, 3] + + """ + for i in range(n): + yield i + + +class ExampleError(Exception): + """Exceptions are documented in the same way as classes. + + The __init__ method may be documented in either the class level + docstring, or as a docstring on the __init__ method itself. + + Either form is acceptable, but the two should not be mixed. Choose one + convention to document the __init__ method and be consistent with it. + + Note + ---- + Do not include the `self` parameter in the ``Parameters`` section. + + Parameters + ---------- + msg : str + Human readable string describing the exception. + code : int, optional + Error code, defaults to 2. + + Attributes + ---------- + msg : str + Human readable string describing the exception. + code : int + Exception error code. + + """ + def __init__(self, msg, code=2): + self.msg = msg + self.code = code + + +class ExampleClass(object): + """The summary line for a class docstring should fit on one line. + + If the class has public attributes, they should be documented here + in an ``Attributes`` section and follow the same formatting as a + function's ``Parameters`` section. + + Attributes + ---------- + attr1 : str + Description of `attr1`. + attr2 : list of str + Description of `attr2`. + attr3 : int + Description of `attr3`. + + """ + def __init__(self, param1, param2, param3=0): + """Example of docstring on the __init__ method. + + The __init__ method may be documented in either the class level + docstring, or as a docstring on the __init__ method itself. + + Either form is acceptable, but the two should not be mixed. Choose one + convention to document the __init__ method and be consistent with it. + + Note + ---- + Do not include the `self` parameter in the ``Parameters`` section. + + Parameters + ---------- + param1 : str + Description of `param1`. + param2 : list of str + Description of `param2`. Multiple + lines are supported. + param3 : int, optional + Description of `param3`, defaults to 0. + + """ + self.attr1 = param1 + self.attr2 = param2 + self.attr3 = param3 + + def example_method(self, param1, param2): + """Class methods are similar to regular functions. + + Note + ---- + Do not include the `self` parameter in the ``Parameters`` section. + + Parameters + ---------- + param1 + The first parameter. + param2 + The second parameter. + + Returns + ------- + bool + True if successful, False otherwise. + + """ + return True + + def __special__(self): + """By default special members with docstrings are included. + + Special members are any methods or attributes that start with and + end with a double underscore. Any special member with a docstring + will be included in the output. + + This behavior can be disabled by changing the following setting in + Sphinx's conf.py:: + + napoleon_include_special_with_doc = False + + """ + pass + + def __special_without_docstring__(self): + pass + + def _private(self): + """By default private members are not included. + + Private members are any methods or attributes that start with an + underscore and are *not* special. By default they are not included + in the output. + + This behavior can be changed such that private members *are* included + by changing the following setting in Sphinx's conf.py:: + + napoleon_include_private_with_doc = True + + """ + pass + + def _private_without_docstring(self): + pass diff --git a/doc/ext/example_numpy.rst b/doc/ext/example_numpy.rst new file mode 100644 index 0000000000..a3b41613ec --- /dev/null +++ b/doc/ext/example_numpy.rst @@ -0,0 +1,15 @@ +:orphan: + +.. _example_numpy: + +Example NumPy Style Python Docstrings +====================================== + +.. seealso:: + + :ref:`example_google` + +Download: :download:`example_numpy.py ` + +.. literalinclude:: example_numpy.py + :language: python diff --git a/doc/ext/napoleon.rst b/doc/ext/napoleon.rst new file mode 100644 index 0000000000..fde23c7b58 --- /dev/null +++ b/doc/ext/napoleon.rst @@ -0,0 +1,365 @@ +:mod:`sphinx.ext.napoleon` -- Support for NumPy and Google style docstrings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. module:: sphinx.ext.napoleon + :synopsis: Support for NumPy and Google style docstrings + +.. moduleauthor:: Rob Ruana + +.. versionadded:: 1.3 + +Napoleon - *Marching toward legible docstrings* +=============================================== + +Are you tired of writing docstrings that look like this:: + + :param path: The path of the file to wrap + :type path: str + :param field_storage: The :class:`FileStorage` instance to wrap + :type field_storage: FileStorage + :param temporary: Whether or not to delete the file when the File + instance is destructed + :type temporary: bool + :returns: A buffered writable file descriptor + :rtype: BufferedFileStorage + +`ReStructuredText`_ is great, but it creates visually dense, hard to read +`docstrings`_. Compare the jumble above to the same thing rewritten +according to the `Google Python Style Guide`_:: + + Args: + path (str): The path of the file to wrap + field_storage (FileStorage): The :class:`FileStorage` instance to wrap + temporary (bool): Whether or not to delete the file when the File + instance is destructed + + Returns: + BufferedFileStorage: A buffered writable file descriptor + +Much more legible, no? + +Napoleon is a `Sphinx extension`_ that allows you to write readable API +documentation in your source code. Napoleon understands both `NumPy`_ and +`Google`_ style docstrings - the style recommended by `Khan Academy`_. + +.. _ReStructuredText: http://docutils.sourceforge.net/rst.html +.. _docstrings: http://www.python.org/dev/peps/pep-0287/ +.. _Google Python Style Guide: + http://google-styleguide.googlecode.com/svn/trunk/pyguide.html +.. _Sphinx extension: http://sphinx-doc.org/extensions.html +.. _Google: + http://google-styleguide.googlecode.com/svn/trunk/pyguide.html#Comments +.. _NumPy: + https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt +.. _Khan Academy: + https://sites.google.com/a/khanacademy.org/forge/for-developers/styleguide/python#TOC-Docstrings + +Getting Started +--------------- + +1. After `setting up Sphinx`_ to build your docs, enable napoleon in the + Sphinx `conf.py` file:: + + # conf.py + + # Add autodoc and napoleon to the extensions list + extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon'] + +2. Use `sphinx-apidoc` to build your API documentation:: + + $ sphinx-apidoc -f -o docs/source projectdir + +.. _setting up Sphinx: http://sphinx-doc.org/tutorial.html + +Docstrings +---------- + +Napoleon interprets every docstring that `Sphinx autodoc`_ can find, +including docstrings on: ``modules``, ``classes``, ``attributes``, +``methods``, ``functions``, and ``variables``. Inside each docstring, +specially formatted `Sections`_ are parsed and converted to +reStructuredText. + +All standard reStructuredText formatting still works as expected. + +.. _Sphinx autodoc: http://sphinx-doc.org/ext/autodoc.html + + +.. _Sections: + +Docstring Sections +------------------ + +All of the following section headers are supported: + + * ``Args`` *(alias of Parameters)* + * ``Arguments`` *(alias of Parameters)* + * ``Attributes`` + * ``Example`` + * ``Examples`` + * ``Keyword Args`` *(alias of Keyword Arguments)* + * ``Keyword Arguments`` + * ``Methods`` + * ``Note`` + * ``Notes`` + * ``Other Parameters`` + * ``Parameters`` + * ``Return`` *(alias of Returns)* + * ``Returns`` + * ``Raises`` + * ``References`` + * ``See Also`` + * ``Warning`` + * ``Warnings`` *(alias of Warning)* + * ``Warns`` + * ``Yields`` + +Google vs NumPy +--------------- + +Napoleon supports two styles of docstrings: `Google`_ and `NumPy`_. The +main difference between the two styles is that Google uses indention to +separate sections, whereas NumPy uses underlines. + +Google style:: + + def func(arg1, arg2): + """Summary line. + + Extended description of function. + + Args: + arg1 (int): Description of arg1 + arg2 (str): Description of arg2 + + Returns: + bool: Description of return value + + """ + return True + +NumPy style:: + + def func(arg1, arg2): + """Summary line. + + Extended description of function. + + Parameters + ---------- + arg1 : int + Description of arg1 + arg2 : str + Description of arg2 + + Returns + ------- + bool + Description of return value + + """ + return True + +NumPy style tends to require more vertical space, whereas Google style +tends to use more horizontal space. Google style tends to be easier to +read for short and simple docstrings, whereas NumPy style tends be easier +to read for long and in-depth docstrings. + +The `Khan Academy`_ recommends using Google style. + +The choice between styles is largely aesthetic, but the two styles should +not be mixed. Choose one style for your project and be consistent with it. + +.. seealso:: + + For complete examples: + + * :ref:`example_google` + * :ref:`example_numpy` + + +Configuration +============= + +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 + + + +**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. + + .. seealso:: + + :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. + + .. seealso:: + + :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 diff --git a/doc/extensions.rst b/doc/extensions.rst index 334e50392b..eaa7376d13 100644 --- a/doc/extensions.rst +++ b/doc/extensions.rst @@ -54,6 +54,7 @@ These extensions are built in and can be activated by respective entries in the ext/viewcode ext/linkcode ext/oldcmarkup + ext/napoleon Third-party extensions diff --git a/sphinx/ext/napoleon/__init__.py b/sphinx/ext/napoleon/__init__.py new file mode 100644 index 0000000000..168ac57415 --- /dev/null +++ b/sphinx/ext/napoleon/__init__.py @@ -0,0 +1,371 @@ +# -*- 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 + + + + **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. + + .. seealso:: :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. + + .. seealso:: :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 0000000000..d6c73db86d --- /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. + + Extended description. + + :param arg1: Description of `arg1` + :type arg1: int + :param arg2: Description of `arg2` + :type arg2: str + + :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. + + Extended description. + + :param arg1: Description of `arg1` + :type arg1: int + :param arg2: Description of `arg2` + :type arg2: str + + :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 0000000000..2f1904dab1 --- /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 `_ 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/tests/root/conf.py b/tests/root/conf.py index 8025ba33cb..9b881760a8 100644 --- a/tests/root/conf.py +++ b/tests/root/conf.py @@ -8,7 +8,8 @@ sys.path.append(os.path.abspath('..')) extensions = ['sphinx.ext.autodoc', 'sphinx.ext.jsmath', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinx.ext.extlinks', - 'sphinx.ext.viewcode', 'sphinx.ext.oldcmarkup', 'ext'] + 'sphinx.ext.viewcode', 'sphinx.ext.oldcmarkup', + 'sphinx.ext.napoleon', 'ext'] jsmath_path = 'dummy.js' diff --git a/tests/test_napoleon.py b/tests/test_napoleon.py new file mode 100644 index 0000000000..d8c7196094 --- /dev/null +++ b/tests/test_napoleon.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- +""" + test_napoleon + ~~~~~~~~~~~~~ + + Tests for :mod:`sphinx.ext.napoleon.__init__` module. + + + :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from mock import Mock +from sphinx.application import Sphinx +from sphinx.ext.napoleon import (_process_docstring, _skip_member, Config, + setup) +from unittest import TestCase + + +def _private_doc(): + """module._private_doc.DOCSTRING""" + pass + + +def _private_undoc(): + pass + + +def __special_doc__(): + """module.__special_doc__.DOCSTRING""" + pass + + +def __special_undoc__(): + pass + + +class SampleClass(object): + def _private_doc(self): + """SampleClass._private_doc.DOCSTRING""" + pass + + def _private_undoc(self): + pass + + def __special_doc__(self): + """SampleClass.__special_doc__.DOCSTRING""" + pass + + def __special_undoc__(self): + pass + + +class SampleError(Exception): + def _private_doc(self): + """SampleError._private_doc.DOCSTRING""" + pass + + def _private_undoc(self): + pass + + def __special_doc__(self): + """SampleError.__special_doc__.DOCSTRING""" + pass + + def __special_undoc__(self): + pass + + +class ProcessDocstringTest(TestCase): + def test_modify_in_place(self): + lines = ['Summary line.', + '', + 'Args:', + ' arg1: arg1 description'] + app = Mock() + app.config = Config() + _process_docstring(app, 'class', 'SampleClass', SampleClass, Mock(), + lines) + + expected = ['Summary line.', + '', + ':Parameters: **arg1** --', + ' arg1 description'] + self.assertEqual(expected, lines) + + +class SetupTest(TestCase): + def test_unknown_app_type(self): + setup(object()) + + def test_add_config_values(self): + app = Mock(Sphinx) + setup(app) + for name, (default, rebuild) in Config._config_values.items(): + has_config = False + for method_name, args, kwargs in app.method_calls: + if(method_name == 'add_config_value' and + args[0] == name): + has_config = True + if not has_config: + self.fail('Config value was not added to app %s' % name) + + has_process_docstring = False + has_skip_member = False + for method_name, args, kwargs in app.method_calls: + if method_name == 'connect': + if(args[0] == 'autodoc-process-docstring' and + args[1] == _process_docstring): + has_process_docstring = True + elif(args[0] == 'autodoc-skip-member' and + args[1] == _skip_member): + has_skip_member = True + if not has_process_docstring: + self.fail('autodoc-process-docstring never connected') + if not has_skip_member: + self.fail('autodoc-skip-member never connected') + + +class SkipMemberTest(TestCase): + def assertSkip(self, what, member, obj, expect_skip, config_name): + skip = 'default skip' + app = Mock() + app.config = Config() + setattr(app.config, config_name, True) + if expect_skip: + self.assertEqual(skip, _skip_member(app, what, member, obj, skip, + Mock())) + else: + self.assertFalse(_skip_member(app, what, member, obj, skip, + Mock())) + setattr(app.config, config_name, False) + self.assertEqual(skip, _skip_member(app, what, member, obj, skip, + Mock())) + + def test_class_private_doc(self): + self.assertSkip('class', '_private_doc', + SampleClass._private_doc, False, + 'napoleon_include_private_with_doc') + + def test_class_private_undoc(self): + self.assertSkip('class', '_private_undoc', + SampleClass._private_undoc, True, + 'napoleon_include_private_with_doc') + + def test_class_special_doc(self): + self.assertSkip('class', '__special_doc__', + SampleClass.__special_doc__, False, + 'napoleon_include_special_with_doc') + + def test_class_special_undoc(self): + self.assertSkip('class', '__special_undoc__', + SampleClass.__special_undoc__, True, + 'napoleon_include_special_with_doc') + + def test_exception_private_doc(self): + self.assertSkip('exception', '_private_doc', + SampleError._private_doc, False, + 'napoleon_include_private_with_doc') + + def test_exception_private_undoc(self): + self.assertSkip('exception', '_private_undoc', + SampleError._private_undoc, True, + 'napoleon_include_private_with_doc') + + def test_exception_special_doc(self): + self.assertSkip('exception', '__special_doc__', + SampleError.__special_doc__, False, + 'napoleon_include_special_with_doc') + + def test_exception_special_undoc(self): + self.assertSkip('exception', '__special_undoc__', + SampleError.__special_undoc__, True, + 'napoleon_include_special_with_doc') + + def test_module_private_doc(self): + self.assertSkip('module', '_private_doc', _private_doc, False, + 'napoleon_include_private_with_doc') + + def test_module_private_undoc(self): + self.assertSkip('module', '_private_undoc', _private_undoc, True, + 'napoleon_include_private_with_doc') + + def test_module_special_doc(self): + self.assertSkip('module', '__special_doc__', __special_doc__, False, + 'napoleon_include_special_with_doc') + + def test_module_special_undoc(self): + self.assertSkip('module', '__special_undoc__', __special_undoc__, True, + 'napoleon_include_special_with_doc') diff --git a/tests/test_napoleon_docstring.py b/tests/test_napoleon_docstring.py new file mode 100644 index 0000000000..aa52aebe79 --- /dev/null +++ b/tests/test_napoleon_docstring.py @@ -0,0 +1,259 @@ +# -*- coding: utf-8 -*- +""" + test_napoleon_docstring + ~~~~~~~~~~~~~~~~~~~~~~~ + + Tests for :mod:`sphinx.ext.napoleon.docstring` module. + + + :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import textwrap +from sphinx.ext.napoleon.docstring import GoogleDocstring, NumpyDocstring +from unittest import TestCase + + +class BaseDocstringTest(TestCase): + pass + + +class GoogleDocstringTest(BaseDocstringTest): + docstrings = [( + """Single line summary""", + """Single line summary""" + ), ( + """ + Single line summary + + Extended description + + """, + """ + Single line summary + + Extended description + """ + ), ( + """ + Single line summary + + Args: + arg1(str):Extended + description of arg1 + """, + """ + Single line summary + + :Parameters: **arg1** (*str*) -- + Extended + description of arg1""" + ), ( + """ + Single line summary + + Args: + arg1(str):Extended + description of arg1 + arg2 ( int ) : Extended + description of arg2 + + Keyword Args: + kwarg1(str):Extended + description of kwarg1 + kwarg2 ( int ) : Extended + description of kwarg2""", + """ + Single line summary + + :Parameters: * **arg1** (*str*) -- + Extended + description of arg1 + * **arg2** (*int*) -- + Extended + description of arg2 + + :Keyword Arguments: * **kwarg1** (*str*) -- + Extended + description of kwarg1 + * **kwarg2** (*int*) -- + Extended + description of kwarg2""" + ), ( + """ + Single line summary + + Arguments: + arg1(str):Extended + description of arg1 + arg2 ( int ) : Extended + description of arg2 + + Keyword Arguments: + kwarg1(str):Extended + description of kwarg1 + kwarg2 ( int ) : Extended + description of kwarg2""", + """ + Single line summary + + :Parameters: * **arg1** (*str*) -- + Extended + description of arg1 + * **arg2** (*int*) -- + Extended + description of arg2 + + :Keyword Arguments: * **kwarg1** (*str*) -- + Extended + description of kwarg1 + * **kwarg2** (*int*) -- + Extended + description of kwarg2""" + ), ( + """ + Single line summary + + Return: + str:Extended + description of return value + """, + """ + Single line summary + + :returns: *str* -- + Extended + description of return value""" + ), ( + """ + Single line summary + + Returns: + str:Extended + description of return value + """, + """ + Single line summary + + :returns: *str* -- + Extended + description of return value""" + )] + + def test_docstrings(self): + for docstring, expected in self.docstrings: + actual = str(GoogleDocstring(textwrap.dedent(docstring))) + expected = textwrap.dedent(expected) + self.assertEqual(expected, actual) + + +class NumpyDocstringTest(BaseDocstringTest): + docstrings = [( + """Single line summary""", + """Single line summary""" + ), ( + """ + Single line summary + + Extended description + + """, + """ + Single line summary + + Extended description + """ + ), ( + """ + Single line summary + + Parameters + ---------- + arg1:str + Extended + description of arg1 + """, + """ + Single line summary + + :Parameters: **arg1** (*str*) -- + Extended + description of arg1""" + ), ( + """ + Single line summary + + Parameters + ---------- + arg1:str + Extended + description of arg1 + arg2 : int + Extended + description of arg2 + + Keyword Arguments + ----------------- + kwarg1:str + Extended + description of kwarg1 + kwarg2 : int + Extended + description of kwarg2 + """, + """ + Single line summary + + :Parameters: * **arg1** (*str*) -- + Extended + description of arg1 + * **arg2** (*int*) -- + Extended + description of arg2 + + :Keyword Arguments: * **kwarg1** (*str*) -- + Extended + description of kwarg1 + * **kwarg2** (*int*) -- + Extended + description of kwarg2""" + ), ( + """ + Single line summary + + Return + ------ + str + Extended + description of return value + """, + """ + Single line summary + + :returns: *str* -- + Extended + description of return value""" + ), ( + """ + Single line summary + + Returns + ------- + str + Extended + description of return value + """, + """ + Single line summary + + :returns: *str* -- + Extended + description of return value""" + )] + + def test_docstrings(self): + for docstring, expected in self.docstrings: + actual = str(NumpyDocstring(textwrap.dedent(docstring))) + expected = textwrap.dedent(expected) + self.assertEqual(expected, actual) diff --git a/tests/test_napoleon_iterators.py b/tests/test_napoleon_iterators.py new file mode 100644 index 0000000000..db0be32c8f --- /dev/null +++ b/tests/test_napoleon_iterators.py @@ -0,0 +1,346 @@ +# -*- coding: utf-8 -*- +""" + test_napoleon_iterators + ~~~~~~~~~~~~~~~~~~~~~~~ + + Tests for :mod:`sphinx.ext.napoleon.iterators` module. + + + :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from sphinx.ext.napoleon.iterators import peek_iter, modify_iter +from unittest import TestCase + + +class BaseIteratorsTest(TestCase): + def assertEqualTwice(self, expected, func, *args): + self.assertEqual(expected, func(*args)) + self.assertEqual(expected, func(*args)) + + def assertFalseTwice(self, func, *args): + self.assertFalse(func(*args)) + self.assertFalse(func(*args)) + + def assertNext(self, it, expected, is_last): + self.assertTrueTwice(it.has_next) + self.assertEqualTwice(expected, it.peek) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice(expected, it.peek) + self.assertTrueTwice(it.has_next) + self.assertEqual(expected, it.next()) + if is_last: + self.assertFalseTwice(it.has_next) + self.assertRaisesTwice(StopIteration, it.next) + else: + self.assertTrueTwice(it.has_next) + + def assertRaisesTwice(self, exc, func, *args): + self.assertRaises(exc, func, *args) + self.assertRaises(exc, func, *args) + + def assertTrueTwice(self, func, *args): + self.assertTrue(func(*args)) + self.assertTrue(func(*args)) + + +class PeekIterTest(BaseIteratorsTest): + def test_init_with_sentinel(self): + a = iter(['1', '2', 'DONE']) + sentinel = 'DONE' + self.assertRaises(TypeError, peek_iter, a, sentinel) + + def get_next(): + return next(a) + it = peek_iter(get_next, sentinel) + self.assertEqual(it.sentinel, sentinel) + self.assertNext(it, '1', is_last=False) + self.assertNext(it, '2', is_last=True) + + def test_iter(self): + a = ['1', '2', '3'] + it = peek_iter(a) + self.assertTrue(it is it.__iter__()) + + a = [] + b = [i for i in peek_iter(a)] + self.assertEqual([], b) + + a = ['1'] + b = [i for i in peek_iter(a)] + self.assertEqual(['1'], b) + + a = ['1', '2'] + b = [i for i in peek_iter(a)] + self.assertEqual(['1', '2'], b) + + a = ['1', '2', '3'] + b = [i for i in peek_iter(a)] + self.assertEqual(['1', '2', '3'], b) + + def test_next_with_multi(self): + a = [] + it = peek_iter(a) + self.assertFalseTwice(it.has_next) + self.assertRaisesTwice(StopIteration, it.next, 2) + + a = ['1'] + it = peek_iter(a) + self.assertTrueTwice(it.has_next) + self.assertRaisesTwice(StopIteration, it.next, 2) + self.assertTrueTwice(it.has_next) + + a = ['1', '2'] + it = peek_iter(a) + self.assertTrueTwice(it.has_next) + self.assertEqual(['1', '2'], it.next(2)) + self.assertFalseTwice(it.has_next) + + a = ['1', '2', '3'] + it = peek_iter(a) + self.assertTrueTwice(it.has_next) + self.assertEqual(['1', '2'], it.next(2)) + self.assertTrueTwice(it.has_next) + self.assertRaisesTwice(StopIteration, it.next, 2) + self.assertTrueTwice(it.has_next) + + a = ['1', '2', '3', '4'] + it = peek_iter(a) + self.assertTrueTwice(it.has_next) + self.assertEqual(['1', '2'], it.next(2)) + self.assertTrueTwice(it.has_next) + self.assertEqual(['3', '4'], it.next(2)) + self.assertFalseTwice(it.has_next) + self.assertRaisesTwice(StopIteration, it.next, 2) + self.assertFalseTwice(it.has_next) + + def test_next_with_none(self): + a = [] + it = peek_iter(a) + self.assertFalseTwice(it.has_next) + self.assertRaisesTwice(StopIteration, it.next) + self.assertFalseTwice(it.has_next) + + a = ['1'] + it = peek_iter(a) + self.assertEqual('1', it.__next__()) + + a = ['1'] + it = peek_iter(a) + self.assertNext(it, '1', is_last=True) + + a = ['1', '2'] + it = peek_iter(a) + self.assertNext(it, '1', is_last=False) + self.assertNext(it, '2', is_last=True) + + a = ['1', '2', '3'] + it = peek_iter(a) + self.assertNext(it, '1', is_last=False) + self.assertNext(it, '2', is_last=False) + self.assertNext(it, '3', is_last=True) + + def test_next_with_one(self): + a = [] + it = peek_iter(a) + self.assertFalseTwice(it.has_next) + self.assertRaisesTwice(StopIteration, it.next, 1) + + a = ['1'] + it = peek_iter(a) + self.assertTrueTwice(it.has_next) + self.assertEqual(['1'], it.next(1)) + self.assertFalseTwice(it.has_next) + self.assertRaisesTwice(StopIteration, it.next, 1) + + a = ['1', '2'] + it = peek_iter(a) + self.assertTrueTwice(it.has_next) + self.assertEqual(['1'], it.next(1)) + self.assertTrueTwice(it.has_next) + self.assertEqual(['2'], it.next(1)) + self.assertFalseTwice(it.has_next) + self.assertRaisesTwice(StopIteration, it.next, 1) + + def test_next_with_zero(self): + a = [] + it = peek_iter(a) + self.assertFalseTwice(it.has_next) + self.assertRaisesTwice(StopIteration, it.next, 0) + + a = ['1'] + it = peek_iter(a) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice([], it.next, 0) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice([], it.next, 0) + + a = ['1', '2'] + it = peek_iter(a) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice([], it.next, 0) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice([], it.next, 0) + + def test_peek_with_multi(self): + a = [] + it = peek_iter(a) + self.assertFalseTwice(it.has_next) + self.assertEqualTwice([it.sentinel, it.sentinel], it.peek, 2) + self.assertFalseTwice(it.has_next) + + a = ['1'] + it = peek_iter(a) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice(['1', it.sentinel], it.peek, 2) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice(['1', it.sentinel, it.sentinel], it.peek, 3) + self.assertTrueTwice(it.has_next) + + a = ['1', '2'] + it = peek_iter(a) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice(['1', '2'], it.peek, 2) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice(['1', '2', it.sentinel], it.peek, 3) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice(['1', '2', it.sentinel, it.sentinel], it.peek, 4) + self.assertTrueTwice(it.has_next) + + a = ['1', '2', '3'] + it = peek_iter(a) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice(['1', '2'], it.peek, 2) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice(['1', '2', '3'], it.peek, 3) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice(['1', '2', '3', it.sentinel], it.peek, 4) + self.assertTrueTwice(it.has_next) + self.assertEqual('1', it.next()) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice(['2', '3'], it.peek, 2) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice(['2', '3', it.sentinel], it.peek, 3) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice(['2', '3', it.sentinel, it.sentinel], it.peek, 4) + self.assertTrueTwice(it.has_next) + + def test_peek_with_none(self): + a = [] + it = peek_iter(a) + self.assertFalseTwice(it.has_next) + self.assertEqualTwice(it.sentinel, it.peek) + self.assertFalseTwice(it.has_next) + + a = ['1'] + it = peek_iter(a) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice('1', it.peek) + self.assertEqual('1', it.next()) + self.assertFalseTwice(it.has_next) + self.assertEqualTwice(it.sentinel, it.peek) + self.assertFalseTwice(it.has_next) + + a = ['1', '2'] + it = peek_iter(a) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice('1', it.peek) + self.assertEqual('1', it.next()) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice('2', it.peek) + self.assertEqual('2', it.next()) + self.assertFalseTwice(it.has_next) + self.assertEqualTwice(it.sentinel, it.peek) + self.assertFalseTwice(it.has_next) + + def test_peek_with_one(self): + a = [] + it = peek_iter(a) + self.assertFalseTwice(it.has_next) + self.assertEqualTwice([it.sentinel], it.peek, 1) + self.assertFalseTwice(it.has_next) + + a = ['1'] + it = peek_iter(a) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice(['1'], it.peek, 1) + self.assertEqual('1', it.next()) + self.assertFalseTwice(it.has_next) + self.assertEqualTwice([it.sentinel], it.peek, 1) + self.assertFalseTwice(it.has_next) + + a = ['1', '2'] + it = peek_iter(a) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice(['1'], it.peek, 1) + self.assertEqual('1', it.next()) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice(['2'], it.peek, 1) + self.assertEqual('2', it.next()) + self.assertFalseTwice(it.has_next) + self.assertEqualTwice([it.sentinel], it.peek, 1) + self.assertFalseTwice(it.has_next) + + def test_peek_with_zero(self): + a = [] + it = peek_iter(a) + self.assertFalseTwice(it.has_next) + self.assertEqualTwice([], it.peek, 0) + + a = ['1'] + it = peek_iter(a) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice([], it.peek, 0) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice([], it.peek, 0) + + a = ['1', '2'] + it = peek_iter(a) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice([], it.peek, 0) + self.assertTrueTwice(it.has_next) + self.assertEqualTwice([], it.peek, 0) + + +class ModifyIterTest(BaseIteratorsTest): + def test_init_with_sentinel_args(self): + a = iter(['1', '2', '3', 'DONE']) + sentinel = 'DONE' + + def get_next(): + return next(a) + it = modify_iter(get_next, sentinel, int) + expected = [1, 2, 3] + self.assertEqual(expected, [i for i in it]) + + def test_init_with_sentinel_kwargs(self): + a = iter([1, 2, 3, 4]) + sentinel = 4 + + def get_next(): + return next(a) + it = modify_iter(get_next, sentinel, modifier=str) + expected = ['1', '2', '3'] + self.assertEqual(expected, [i for i in it]) + + def test_modifier_default(self): + a = ['', ' ', ' a ', 'b ', ' c', ' ', ''] + it = modify_iter(a) + expected = ['', ' ', ' a ', 'b ', ' c', ' ', ''] + self.assertEqual(expected, [i for i in it]) + + def test_modifier_not_callable(self): + self.assertRaises(TypeError, modify_iter, [1], modifier='not_callable') + + def test_modifier_rstrip(self): + a = ['', ' ', ' a ', 'b ', ' c', ' ', ''] + it = modify_iter(a, modifier=lambda s: s.rstrip()) + expected = ['', '', ' a', 'b', ' c', '', ''] + self.assertEqual(expected, [i for i in it]) + + def test_modifier_rstrip_unicode(self): + a = [u'', u' ', u' a ', u'b ', u' c', u' ', u''] + it = modify_iter(a, modifier=lambda s: s.rstrip()) + expected = [u'', u'', u' a', u'b', u' c', u'', u''] + self.assertEqual(expected, [i for i in it]) diff --git a/tox.ini b/tox.ini index f1675e9017..dfa1f923c5 100644 --- a/tox.ini +++ b/tox.ini @@ -3,6 +3,7 @@ envlist=py25,py26,py27,py31,py32,py33,pypy,du11,du10,du09,du08,du07 [testenv] deps= + mock nose sqlalchemy whoosh From fa9695dbe0b090eb9907647b7a8d1c20210efa5b Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 07:32:10 +0100 Subject: [PATCH 16/50] changelog: bette note about literal_strong --- CHANGES | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 33cc66fd4d..e387ffd53f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,16 +1,17 @@ Release 1.3 (in development) ============================ -Change support versions ------------------------ - -* Drop Python-2.5, 3.1 (support code was completely removed) - Incompatible changes -------------------- +* Dropped support for Python 2.5 and 3.1. + * Removed the ``sphinx.ext.oldcmarkup`` extension. +* A new node, ``sphinx.addnodes.literal_strong``, has been added, for text that + should appear literally (i.e. no smart quotes) in strong font. Custom writers + will have to be adapted to handle this node. + New features ------------ From e5c1a81ed8f35fd66582b0d9e6a6c338c9e8784c Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 09:54:24 +0100 Subject: [PATCH 17/50] Closes #848: Always take the newest code in incremental rebuilds with the :mod:`sphinx.ext.viewcode` extension. --- CHANGES | 3 +++ sphinx/ext/viewcode.py | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index 79de153190..7600958f7d 100644 --- a/CHANGES +++ b/CHANGES @@ -99,6 +99,9 @@ Bugs fixed * #1285: Avoid name clashes between C domain objects and section titles. +* #848: Always take the newest code in incremental rebuilds with the + :mod:`sphinx.ext.viewcode` extension. + Documentation ------------- diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py index 962b543bc5..36fb47d27e 100644 --- a/sphinx/ext/viewcode.py +++ b/sphinx/ext/viewcode.py @@ -24,17 +24,17 @@ def doctree_read(app, doctree): def has_tag(modname, fullname, docname): entry = env._viewcode_modules.get(modname, None) - if entry is None: - try: - analyzer = ModuleAnalyzer.for_module(modname) - except Exception: - env._viewcode_modules[modname] = False - return + try: + analyzer = ModuleAnalyzer.for_module(modname) + except Exception: + env._viewcode_modules[modname] = False + return + if not isinstance(analyzer.code, unicode): + code = analyzer.code.decode(analyzer.encoding) + else: + code = analyzer.code + if entry is None or entry[0] != code: analyzer.find_tags() - if not isinstance(analyzer.code, unicode): - code = analyzer.code.decode(analyzer.encoding) - else: - code = analyzer.code entry = code, analyzer.tags, {} env._viewcode_modules[modname] = entry elif entry is False: @@ -142,7 +142,7 @@ def collect_pages(app): if not modnames: return - app.builder.info(' _modules/index') + app.builder.info(' _modules/index', nonl=True) html = ['\n'] # the stack logic is needed for using nested lists for submodules stack = [''] From db1b0d1d76b47a1a0d7b8c9a60eae83c7784730a Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 10:13:43 +0100 Subject: [PATCH 18/50] Closes #979: Fix exclude handling in ``sphinx-apidoc``. --- CHANGES | 2 ++ sphinx/apidoc.py | 52 ++++++++++++++++++------------------------------ 2 files changed, 21 insertions(+), 33 deletions(-) diff --git a/CHANGES b/CHANGES index 7600958f7d..3621be4bef 100644 --- a/CHANGES +++ b/CHANGES @@ -102,6 +102,8 @@ Bugs fixed * #848: Always take the newest code in incremental rebuilds with the :mod:`sphinx.ext.viewcode` extension. +* #979: Fix exclude handling in ``sphinx-apidoc``. + Documentation ------------- diff --git a/sphinx/apidoc.py b/sphinx/apidoc.py index c30546623f..4430cdd000 100644 --- a/sphinx/apidoc.py +++ b/sphinx/apidoc.py @@ -65,7 +65,7 @@ def write_file(name, text, opts): def format_heading(level, text): """Create a heading of [1, 2 or 3 supported].""" - underlining = ['=', '-', '~', ][level-1] * len(text) + underlining = ['=', '-', '~', ][level - 1] * len(text) return '%s\n%s\n\n' % (text, underlining) @@ -173,9 +173,6 @@ def recurse_tree(rootpath, excludes, opts): Look for every file in the directory tree and create the corresponding ReST files. """ - # use absolute path for root, as relative paths like '../../foo' cause - # 'if "/." in root ...' to filter out *all* modules otherwise - rootpath = path.normpath(path.abspath(rootpath)) # check if the base directory is a package and get its name if INITPY in os.listdir(rootpath): root_package = rootpath.split(path.sep)[-1] @@ -186,12 +183,10 @@ def recurse_tree(rootpath, excludes, opts): toplevels = [] followlinks = getattr(opts, 'followlinks', False) for root, subs, files in os.walk(rootpath, followlinks=followlinks): - if is_excluded(root, excludes): - del subs[:] - continue - # document only Python module files + # document only Python module files (that aren't excluded) py_files = sorted(f for f in files - if path.splitext(f)[1] in PY_SUFFIXES) + if path.splitext(f)[1] in PY_SUFFIXES and + not is_excluded(path.join(root, f), excludes)) is_pkg = INITPY in py_files if is_pkg: py_files.remove(INITPY) @@ -200,8 +195,10 @@ def recurse_tree(rootpath, excludes, opts): # only accept non-package at toplevel del subs[:] continue - # remove hidden ('.') and private ('_') directories - subs[:] = sorted(sub for sub in subs if sub[0] not in ['.', '_']) + # remove hidden ('.') and private ('_') directories, as well as + # excluded dirs + subs[:] = sorted(sub for sub in subs if sub[0] not in ['.', '_'] + and not is_excluded(path.join(root, sub), excludes)) if is_pkg: # we are in a package with something to document @@ -225,47 +222,35 @@ def recurse_tree(rootpath, excludes, opts): def normalize_excludes(rootpath, excludes): - """ - Normalize the excluded directory list: - * must be either an absolute path or start with rootpath, - * otherwise it is joined with rootpath - * with trailing slash - """ - f_excludes = [] - for exclude in excludes: - if not path.isabs(exclude) and not exclude.startswith(rootpath): - exclude = path.join(rootpath, exclude) - f_excludes.append(path.normpath(exclude) + path.sep) - return f_excludes + """Normalize the excluded directory list.""" + return [path.normpath(path.abspath(exclude)) for exclude in excludes] def is_excluded(root, excludes): - """ - Check if the directory is in the exclude list. + """Check if the directory is in the exclude list. Note: by having trailing slashes, we avoid common prefix issues, like e.g. an exlude "foo" also accidentally excluding "foobar". """ - sep = path.sep - if not root.endswith(sep): - root += sep + root = path.normpath(root) for exclude in excludes: - if root.startswith(exclude): + if root == exclude: return True return False def main(argv=sys.argv): - """ - Parse and check the command line arguments. - """ + """Parse and check the command line arguments.""" parser = optparse.OptionParser( usage="""\ -usage: %prog [options] -o [exclude_paths, ...] +usage: %prog [options] -o [exclude_path, ...] Look recursively in for Python modules and packages and create one reST file with automodule directives per package in the . +The s can be files and/or directories that will be excluded +from generation. + Note: By default this script will not overwrite already created files.""") parser.add_option('-o', '--output-dir', action='store', dest='destdir', @@ -327,6 +312,7 @@ Note: By default this script will not overwrite already created files.""") if not path.isdir(opts.destdir): if not opts.dryrun: os.makedirs(opts.destdir) + rootpath = path.normpath(path.abspath(rootpath)) excludes = normalize_excludes(rootpath, excludes) modules = recurse_tree(rootpath, excludes, opts) if opts.full: From b7a639a22bbd74149a12a40e7a0ccedf2d274400 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 10:29:06 +0100 Subject: [PATCH 19/50] In search word highlighting, dont insist on having div.body. --- sphinx/themes/basic/static/doctools.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sphinx/themes/basic/static/doctools.js b/sphinx/themes/basic/static/doctools.js index 8614442ebe..2036e5f5f8 100644 --- a/sphinx/themes/basic/static/doctools.js +++ b/sphinx/themes/basic/static/doctools.js @@ -168,6 +168,9 @@ var Documentation = { var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; if (terms.length) { var body = $('div.body'); + if (!body.length) { + body = $('body'); + } window.setTimeout(function() { $.each(terms, function() { body.highlightText(this.toLowerCase(), 'highlighted'); From 5023da8ac31534f6bf3104fd3e5cf63a7a28df6c Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 10:34:52 +0100 Subject: [PATCH 20/50] mock: little changes, changelog and versionadded --- CHANGES | 3 +++ doc/ext/autodoc.rst | 4 ++++ sphinx/ext/autodoc.py | 21 +++++++++++---------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index 2d5c6981fe..f3f3b1c125 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,9 @@ New features * PR#202: Allow "." and "~" prefixed references in ``:param:`` doc fields for Python. +* PR#184: Add :confval:`autodoc_mock_imports`, allowing to mock imports of + external modules that need not be present when autodocumenting. + * #925: Allow list-typed config values to be provided on the command line, like ``-D key=val1,val2``. diff --git a/doc/ext/autodoc.rst b/doc/ext/autodoc.rst index c37328f543..94b054233f 100644 --- a/doc/ext/autodoc.rst +++ b/doc/ext/autodoc.rst @@ -199,6 +199,8 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, import errors to halt the building process when some external dependencies are not importable at build time. + .. versionadded:: 1.3 + .. rst:directive:: autofunction autodata @@ -345,6 +347,8 @@ There are also new config values that you can set: some external dependencies are not met at build time and break the building process. + .. versionadded:: 1.3 + Docstring preprocessing ----------------------- diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index 6c13e24321..86837ff8b7 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -71,6 +71,7 @@ class Options(dict): class _MockModule(object): + """Used by autodoc_mock_imports.""" def __init__(self, *args, **kwargs): pass @@ -89,6 +90,14 @@ class _MockModule(object): 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() @@ -353,7 +362,8 @@ class Documenter(object): try: dbg('[autodoc] import %s', self.modname) for modname in self.env.config.autodoc_mock_imports: - self._mock_import(modname) + dbg('[autodoc] adding a mock module %s!', self.modname) + mock_import(modname) __import__(self.modname) parent = None obj = self.module = sys.modules[self.modname] @@ -383,15 +393,6 @@ class Documenter(object): self.env.note_reread() return False - def _mock_import(self, modname): - if '.' in modname: - pkg, _n, mods = modname.rpartition('.') - self._mock_import(pkg) - mod = _MockModule() - sys.modules[modname] = mod - return mod - - def get_real_modname(self): """Get the real module name of an object to document. From 2458e5731afbc1eda73548b8b0945fa38c73f517 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 11:04:12 +0100 Subject: [PATCH 21/50] Add :filename: also for literalinclude. Changelog entry. --- CHANGES | 4 ++++ doc/markup/code.rst | 19 +++++++++++++++++++ sphinx/directives/code.py | 6 ++++++ 3 files changed, 29 insertions(+) diff --git a/CHANGES b/CHANGES index f3f3b1c125..42d544b020 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,10 @@ New features * #668: Allow line numbering of ``code-block`` and ``literalinclude`` directives to start at an arbitrary line number, with a new ``lineno-start`` option. +* PR#172: The :rst:dir:`code-block` and :rst:dir:`literalinclude` directives now + can have a ``filename`` option that shows a filename before the code in the + output. + * Prompt for the document language in sphinx-quickstart. Bugs fixed diff --git a/doc/markup/code.rst b/doc/markup/code.rst index 6e707e007b..e9f8f1da43 100644 --- a/doc/markup/code.rst +++ b/doc/markup/code.rst @@ -188,6 +188,25 @@ Includes The ``prepend`` and ``append`` options, as well as ``tab-width``. +Showing a file name +^^^^^^^^^^^^^^^^^^^ + +.. versionadded:: 1.3 + +A ``filename`` option can be given to show that name before the code block. For +example:: + + .. code-block:: python + :filename: this.py + + print 'Explicit is better than implicit.' + + +:rst:dir:`literalinclude` also supports the ``filename`` option, with the +additional feature that if you leave the value empty, the shown filename will be +exactly the one given as an argument. + + .. rubric:: Footnotes .. [1] There is a standard ``.. include`` directive, but it raises errors if the diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py index bfe6c48e9e..6900ea6b6e 100644 --- a/sphinx/directives/code.py +++ b/sphinx/directives/code.py @@ -115,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): @@ -216,6 +217,11 @@ class LiteralInclude(Directive): retnode['language'] = self.options['language'] 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: extra_args['hl_lines'] = hl_lines From 3f7ecbde9678c7b52ef15d133c8b291468fe38f2 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 11:36:11 +0100 Subject: [PATCH 22/50] Closes #1302: Fix regression in :mod:`sphinx.ext.inheritance_diagram` when documenting classes that can't be pickled. --- CHANGES | 3 +++ sphinx/ext/graphviz.py | 3 +++ sphinx/ext/inheritance_diagram.py | 26 ++++++++++++++++---------- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index 36b38e03cd..13fc1bc4f3 100644 --- a/CHANGES +++ b/CHANGES @@ -104,6 +104,9 @@ Bugs fixed * #979, #1266: Fix exclude handling in ``sphinx-apidoc``. +* #1302: Fix regression in :mod:`sphinx.ext.inheritance_diagram` when + documenting classes that can't be pickled. + Documentation ------------- diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py index 800e9ca80e..32bb96d337 100644 --- a/sphinx/ext/graphviz.py +++ b/sphinx/ext/graphviz.py @@ -178,6 +178,9 @@ def render_dot(self, code, options, format, prefix='graphviz'): if p.returncode != 0: raise GraphvizError('dot exited with error:\n[stderr]\n%s\n' '[stdout]\n%s' % (stderr, stdout)) + if not path.isfile(outfn): + raise GraphvizError('dot did not produce an output file:\n[stderr]\n%s\n' + '[stdout]\n%s' % (stderr, stdout)) return relfn, outfn diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py index 2d1d6e30c3..fd973544d6 100644 --- a/sphinx/ext/inheritance_diagram.py +++ b/sphinx/ext/inheritance_diagram.py @@ -154,8 +154,18 @@ class InheritanceGraph(object): nodename = self.class_name(cls, parts) fullname = self.class_name(cls, 0) + # Use first line of docstring as tooltip, if available + tooltip = None + try: + if cls.__doc__: + doc = cls.__doc__.strip().split("\n")[0] + if doc: + tooltip = '"%s"' % doc.replace('"', '\\"') + except Exception: # might raise AttributeError for strange classes + pass + baselist = [] - all_classes[cls] = (nodename, fullname, baselist) + all_classes[cls] = (nodename, fullname, baselist, tooltip) for base in cls.__bases__: if not show_builtins and base in builtins: continue @@ -168,7 +178,7 @@ class InheritanceGraph(object): for cls in classes: recurse(cls) - return all_classes + return all_classes.values() def class_name(self, cls, parts=0): """Given a class object, return a fully-qualified name. @@ -188,7 +198,7 @@ class InheritanceGraph(object): def get_all_class_names(self): """Get all of the class names involved in the graph.""" - return [fullname for (_, fullname, _) in self.class_info.values()] + return [fullname for (_, fullname, _, _) in self.class_info] # These are the default attrs for graphviz default_graph_attrs = { @@ -241,17 +251,13 @@ class InheritanceGraph(object): res.append('digraph %s {\n' % name) res.append(self._format_graph_attrs(g_attrs)) - for cls, (name, fullname, bases) in sorted(self.class_info.items()): + for name, fullname, bases, tooltip in sorted(self.class_info): # Write the node this_node_attrs = n_attrs.copy() if fullname in urls: this_node_attrs['URL'] = '"%s"' % urls[fullname] - # Use first line of docstring as tooltip, if available - if cls.__doc__: - doc = cls.__doc__.strip().split("\n")[0] - if doc: - doc = doc.replace('"', '\\"') - this_node_attrs['tooltip'] = '"%s"' % doc + if tooltip: + this_node_attrs['tooltip'] = tooltip res.append(' "%s" [%s];\n' % (name, self._format_node_attrs(this_node_attrs))) From 97b3ac0187bd8941c7c9db94fd68a20fed7774f4 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 11:38:41 +0100 Subject: [PATCH 23/50] Closes #1316: Remove hard-coded ``font-face`` resources from epub theme. --- CHANGES | 2 ++ sphinx/themes/epub/static/epub.css | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 13fc1bc4f3..ee550517ab 100644 --- a/CHANGES +++ b/CHANGES @@ -107,6 +107,8 @@ Bugs fixed * #1302: Fix regression in :mod:`sphinx.ext.inheritance_diagram` when documenting classes that can't be pickled. +* #1316: Remove hard-coded ``font-face`` resources from epub theme. + Documentation ------------- diff --git a/sphinx/themes/epub/static/epub.css b/sphinx/themes/epub/static/epub.css index 3f4664f6da..6c8ff5e77e 100644 --- a/sphinx/themes/epub/static/epub.css +++ b/sphinx/themes/epub/static/epub.css @@ -339,7 +339,7 @@ dl.glossary dt { /* -- code displays --------------------------------------------------------- */ pre { - font-family: "LiberationNarrow", monospace; + font-family: monospace; overflow: auto; overflow-y: hidden; } @@ -360,7 +360,7 @@ table.highlighttable td { } tt { - font-family: "LiberationNarrow", monospace; + font-family: monospace; } tt.descname { @@ -432,6 +432,7 @@ table .link-target { /* -- font-face ------------------------------------------------------------- */ +/* @font-face { font-family: "LiberationNarrow"; font-style: normal; @@ -460,4 +461,4 @@ table .link-target { src: url("res:///Data/fonts/LiberationNarrow-BoldItalic.otf") format("opentype"); } - +*/ \ No newline at end of file From 1762a22bd850ab65ecd909ee4afd63340d19dbbc Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 11:42:44 +0100 Subject: [PATCH 24/50] Closes #1329: Fix traceback with empty translation msgstr in .po files. --- CHANGES | 2 ++ sphinx/transforms.py | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index ee550517ab..3fce72ab74 100644 --- a/CHANGES +++ b/CHANGES @@ -109,6 +109,8 @@ Bugs fixed * #1316: Remove hard-coded ``font-face`` resources from epub theme. +* #1329: Fix traceback with empty translation msgstr in .po files. + Documentation ------------- diff --git a/sphinx/transforms.py b/sphinx/transforms.py index 338d473921..14e2c97fdb 100644 --- a/sphinx/transforms.py +++ b/sphinx/transforms.py @@ -196,7 +196,10 @@ class Locale(Transform): patch = new_document(source, settings) CustomLocaleReporter(node.source, node.line).set_reporter(patch) parser.parse(msgstr, patch) - patch = patch[0] + try: + patch = patch[0] + except IndexError: # empty node + pass # XXX doctest and other block markup if not isinstance(patch, nodes.paragraph): continue # skip for now @@ -299,7 +302,10 @@ class Locale(Transform): patch = new_document(source, settings) CustomLocaleReporter(node.source, node.line).set_reporter(patch) parser.parse(msgstr, patch) - patch = patch[0] + try: + patch = patch[0] + except IndexError: # empty node + pass # XXX doctest and other block markup if not isinstance(patch, nodes.paragraph): continue # skip for now From fbd577234297ef26eb57e35663100083ad0f70ed Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 15:39:31 +0100 Subject: [PATCH 25/50] Closes #1300: Fix references not working in translated documents in some instances. --- CHANGES | 2 ++ sphinx/transforms.py | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 3fce72ab74..3862281c1a 100644 --- a/CHANGES +++ b/CHANGES @@ -111,6 +111,8 @@ Bugs fixed * #1329: Fix traceback with empty translation msgstr in .po files. +* #1300: Fix references not working in translated documents in some instances. + Documentation ------------- diff --git a/sphinx/transforms.py b/sphinx/transforms.py index 14e2c97fdb..53bbadb0e0 100644 --- a/sphinx/transforms.py +++ b/sphinx/transforms.py @@ -242,8 +242,7 @@ class Locale(Transform): self.document.ids.pop(_id, None) # re-entry with new named section node. - self.document.note_implicit_target( - section_node, section_node) + self.document.note_implicit_target(section_node) # replace target's refname to new target name def is_named_target(node): From b74759c67d06e1b42bb76d7ec587dd6593df86b5 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 15:49:51 +0100 Subject: [PATCH 26/50] make mode: add missing build methods. --- sphinx/make_mode.py | 48 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/sphinx/make_mode.py b/sphinx/make_mode.py index 5ef065a5da..c8a2eae5f1 100644 --- a/sphinx/make_mode.py +++ b/sphinx/make_mode.py @@ -59,6 +59,7 @@ BUILDERS = [ ("", "pseudoxml", "to make pseudoxml-XML files for display purposes"), ("", "linkcheck", "to check all external links for integrity"), ("", "doctest", "to run all doctests embedded in the documentation (if enabled)"), + ("", "coverage", "to run coverage check of the documentation (if enabled)"), ] def build_help(builddir, opts): @@ -133,10 +134,23 @@ def build_epub(builddir, opts): print print 'Build finished. The ePub file is in %s.' % path.join(builddir, 'epub') +def build_latex(builddir, opts): + if run_generic_build('latex', builddir, opts) > 0: + return 1 + print "Build finished; the LaTeX files are in %s." % path.join(builddir, 'latex') + if os.name == 'posix': + print "Run `make' in that directory to run these through (pdf)latex" + print "(use `make latexpdf' here to do that automatically)." -# latex -# latexpdf -# latexpdfja +def build_latexpdf(builddir, opts): + if run_generic_build('latex', builddir, opts) > 0: + return 1 + os.system('make -C %s all-pdf' % path.join(builddir, 'latex')) + +def build_latexpdfja(builddir, opts): + if run_generic_build('latex', builddir, opts) > 0: + return 1 + os.system('make -C %s all-pdf-ja' % path.join(builddir, 'latex')) def build_text(builddir, opts): if run_generic_build('text', builddir, opts) > 0: @@ -144,8 +158,18 @@ def build_text(builddir, opts): print print 'Build finished. The text files are in %s.' % path.join(builddir, 'text') -# texinfo -# info +def build_texinfo(builddir, opts): + if run_generic_build('texinfo', builddir, opts) > 0: + return 1 + print "Build finished; the Texinfo files are in %s." % path.join(builddir, 'texinfo') + if os.name == 'posix': + print "Run `make' in that directory to run these through makeinfo" + print "(use `make info' here to do that automatically)." + +def build_info(builddir, opts): + if run_generic_build('texinfo', builddir, opts) > 0: + return 1 + os.system('make -C %s info' % path.join(builddir, 'texinfo')) def build_gettext(builddir, opts): dtdir = path.join(builddir, 'gettext', '.doctrees') @@ -167,6 +191,20 @@ def build_linkcheck(builddir, opts): 'or in %s.') % path.join(builddir, 'linkcheck', 'output.txt') return res +def build_doctest(builddir, opts): + res = run_generic_build('doctest', builddir, opts) + print ("Testing of doctests in the sources finished, look at the " + "results in %s." % path.join(builddir, 'doctest', 'output.txt')) + return res + +def build_coverage(builddir, opts): + if run_generic_build('coverage', builddir, opts) > 0: + print "Has the coverage extension been enabled?" + return 1 + print + print ("Testing of coverage in the sources finished, look at the " + "results in %s." % path.join(builddir, 'coverage')) + def build_xml(builddir, opts): if run_generic_build('xml', builddir, opts) > 0: return 1 From 1653c5d242e06453bcf8adbc136cd30614935acb Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 16:23:23 +0100 Subject: [PATCH 27/50] Make mode: refactor using a class and add source dir to the makefile --- doc/Makefile | 18 +-- sphinx/make_mode.py | 322 ++++++++++++++++++++++--------------------- sphinx/quickstart.py | 14 +- 3 files changed, 182 insertions(+), 172 deletions(-) diff --git a/doc/Makefile b/doc/Makefile index d799c3658f..8bc59724e3 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -5,20 +5,14 @@ SPHINXOPTS = SPHINXBUILD = python ../sphinx-build.py SPHINXPROJ = sphinx +SOURCEDIR = . BUILDDIR = _build -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error \ -The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx \ -installed, then set the SPHINXBUILD environment variable to point \ -to the full path of the '$(SPHINXBUILD)' executable. Alternatively you \ -can add the directory with the executable to your PATH. \ -If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - +# Has to be explicit, otherwise we don't get "make" without targets right. help: - @$(SPHINXBUILD) -M help "$(BUILDDIR)" $(SPHINXOPTS) $(O) + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: - @$(SPHINXBUILD) -M $@ "$(BUILDDIR)" $(SPHINXOPTS) $(O) + $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/sphinx/make_mode.py b/sphinx/make_mode.py index c8a2eae5f1..24de6b2864 100644 --- a/sphinx/make_mode.py +++ b/sphinx/make_mode.py @@ -26,15 +26,6 @@ from sphinx.util.console import bold, blue proj_name = os.getenv('SPHINXPROJ', '') -def build_clean(builddir, opts): - if not path.exists(builddir): - return - elif not path.isdir(builddir): - print "Error: %r is not a directory!" % builddir - return 1 - print "removing everything under %r..." % builddir - for item in os.listdir(builddir): - shutil.rmtree(path.join(builddir, item)) BUILDERS = [ ("", "html", "to make standalone HTML files"), @@ -62,179 +53,200 @@ BUILDERS = [ ("", "coverage", "to run coverage check of the documentation (if enabled)"), ] -def build_help(builddir, opts): - print bold("Sphinx v%s" % sphinx.__version__) - print "Please use `make %s' where %s is one of" % ((blue('target'),)*2) - for osname, bname, description in BUILDERS: - if not osname or os.name == osname: - print ' %s %s' % (blue(bname.ljust(10)), description) +class Make(object): -def build_html(builddir, opts): - if run_generic_build('html', builddir, opts) > 0: - return 1 - print - print 'Build finished. The HTML pages are in %s.' % path.join(builddir, 'html') + def __init__(self, srcdir, builddir, opts): + self.srcdir = srcdir + self.builddir = builddir + self.opts = opts -def build_dirhtml(builddir, opts): - if run_generic_build('dirhtml', builddir, opts) > 0: - return 1 - print - print 'Build finished. The HTML pages are in %s.' % path.join(builddir, 'dirhtml') + def builddir_join(self, *comps): + return path.join(self.builddir, *comps) -def build_singlehtml(builddir, opts): - if run_generic_build('singlehtml', builddir, opts) > 0: - return 1 - print - print 'Build finished. The HTML page is in %s.' % path.join(builddir, 'singlehtml') + def build_clean(self): + if not path.exists(self.builddir): + return + elif not path.isdir(self.builddir): + print "Error: %r is not a directory!" % self.builddir + return 1 + print "Removing everything under %r..." % self.builddir + for item in os.listdir(self.builddir): + shutil.rmtree(self.builddir_join(item)) -def build_pickle(builddir, opts): - if run_generic_build('pickle', builddir, opts) > 0: - return 1 - print - print 'Build finished; now you can process the pickle files.' + def build_help(self): + print bold("Sphinx v%s" % sphinx.__version__) + print "Please use `make %s' where %s is one of" % ((blue('target'),)*2) + for osname, bname, description in BUILDERS: + if not osname or os.name == osname: + print ' %s %s' % (blue(bname.ljust(10)), description) -def build_json(builddir, opts): - if run_generic_build('json', builddir, opts) > 0: - return 1 - print - print 'Build finished; now you can process the JSON files.' + def build_html(self): + if self.run_generic_build('html') > 0: + return 1 + print + print 'Build finished. The HTML pages are in %s.' % self.builddir_join('html') -def build_htmlhelp(builddir, opts): - if run_generic_build('htmlhelp', builddir, opts) > 0: - return 1 - print - print ('Build finished; now you can run HTML Help Workshop with the ' - '.hhp project file in %s.') % path.join(builddir, 'htmlhelp') + def build_dirhtml(self): + if self.run_generic_build('dirhtml') > 0: + return 1 + print + print 'Build finished. The HTML pages are in %s.' % self.builddir_join('dirhtml') -def build_qthelp(builddir, opts): - if run_generic_build('qthelp', builddir, opts) > 0: - return 1 - print - print ('Build finished; now you can run "qcollectiongenerator" with the ' - '.qhcp project file in %s, like this:') % path.join(builddir, 'qthelp') - print '$ qcollectiongenerator %s.qhcp' % path.join(builddir, 'qthelp', proj_name) - print 'To view the help file:' - print '$ assistant -collectionFile %s.qhc' % path.join(builddir, 'qthelp', proj_name) + def build_singlehtml(self): + if self.run_generic_build('singlehtml') > 0: + return 1 + print + print 'Build finished. The HTML page is in %s.' % self.builddir_join('singlehtml') -def build_devhelp(builddir, opts): - if run_generic_build('devhelp', builddir, opts) > 0: - return 1 - print - print "Build finished." - print "To view the help file:" - print "$ mkdir -p $HOME/.local/share/devhelp/" + proj_name - print "$ ln -s %s $HOME/.local/share/devhelp/%s" % \ - (path.join(builddir, 'devhelp'), proj_name) - print "$ devhelp" + def build_pickle(self): + if self.run_generic_build('pickle') > 0: + return 1 + print + print 'Build finished; now you can process the pickle files.' -def build_epub(builddir, opts): - if run_generic_build('epub', builddir, opts) > 0: - return 1 - print - print 'Build finished. The ePub file is in %s.' % path.join(builddir, 'epub') + def build_json(self): + if self.run_generic_build('json') > 0: + return 1 + print + print 'Build finished; now you can process the JSON files.' -def build_latex(builddir, opts): - if run_generic_build('latex', builddir, opts) > 0: - return 1 - print "Build finished; the LaTeX files are in %s." % path.join(builddir, 'latex') - if os.name == 'posix': - print "Run `make' in that directory to run these through (pdf)latex" - print "(use `make latexpdf' here to do that automatically)." + def build_htmlhelp(self): + if self.run_generic_build('htmlhelp') > 0: + return 1 + print + print ('Build finished; now you can run HTML Help Workshop with the ' + '.hhp project file in %s.') % self.builddir_join('htmlhelp') -def build_latexpdf(builddir, opts): - if run_generic_build('latex', builddir, opts) > 0: - return 1 - os.system('make -C %s all-pdf' % path.join(builddir, 'latex')) + def build_qthelp(self): + if self.run_generic_build('qthelp') > 0: + return 1 + print + print ('Build finished; now you can run "qcollectiongenerator" with the ' + '.qhcp project file in %s, like this:') % self.builddir_join('qthelp') + print '$ qcollectiongenerator %s.qhcp' % self.builddir_join('qthelp', proj_name) + print 'To view the help file:' + print '$ assistant -collectionFile %s.qhc' % self.builddir_join('qthelp', proj_name) -def build_latexpdfja(builddir, opts): - if run_generic_build('latex', builddir, opts) > 0: - return 1 - os.system('make -C %s all-pdf-ja' % path.join(builddir, 'latex')) + def build_devhelp(self): + if self.run_generic_build('devhelp') > 0: + return 1 + print + print "Build finished." + print "To view the help file:" + print "$ mkdir -p $HOME/.local/share/devhelp/" + proj_name + print "$ ln -s %s $HOME/.local/share/devhelp/%s" % \ + (self.builddir_join('devhelp'), proj_name) + print "$ devhelp" -def build_text(builddir, opts): - if run_generic_build('text', builddir, opts) > 0: - return 1 - print - print 'Build finished. The text files are in %s.' % path.join(builddir, 'text') + def build_epub(self): + if self.run_generic_build('epub') > 0: + return 1 + print + print 'Build finished. The ePub file is in %s.' % self.builddir_join('epub') -def build_texinfo(builddir, opts): - if run_generic_build('texinfo', builddir, opts) > 0: - return 1 - print "Build finished; the Texinfo files are in %s." % path.join(builddir, 'texinfo') - if os.name == 'posix': - print "Run `make' in that directory to run these through makeinfo" - print "(use `make info' here to do that automatically)." + def build_latex(self): + if self.run_generic_build('latex') > 0: + return 1 + print "Build finished; the LaTeX files are in %s." % self.builddir_join('latex') + if os.name == 'posix': + print "Run `make' in that directory to run these through (pdf)latex" + print "(use `make latexpdf' here to do that automatically)." -def build_info(builddir, opts): - if run_generic_build('texinfo', builddir, opts) > 0: - return 1 - os.system('make -C %s info' % path.join(builddir, 'texinfo')) + def build_latexpdf(self): + if self.run_generic_build('latex') > 0: + return 1 + os.system('make -C %s all-pdf' % self.builddir_join('latex')) -def build_gettext(builddir, opts): - dtdir = path.join(builddir, 'gettext', '.doctrees') - if run_generic_build('gettext', builddir, opts, doctreedir=dtdir) > 0: - return 1 - print - print 'Build finished. The message catalogs are in %s.' % path.join(builddir, 'gettext') + def build_latexpdfja(self): + if self.run_generic_build('latex') > 0: + return 1 + os.system('make -C %s all-pdf-ja' % self.builddir_join('latex')) -def build_changes(builddir, opts): - if run_generic_build('changes', builddir, opts) > 0: - return 1 - print - print 'Build finished. The overview file is in %s.' % path.join(builddir, 'changes') + def build_text(self): + if self.run_generic_build('text') > 0: + return 1 + print + print 'Build finished. The text files are in %s.' % self.builddir_join('text') -def build_linkcheck(builddir, opts): - res = run_generic_build('linkcheck', builddir, opts) - print - print ('Link check complete; look for any errors in the above output ' - 'or in %s.') % path.join(builddir, 'linkcheck', 'output.txt') - return res + def build_texinfo(self): + if self.run_generic_build('texinfo') > 0: + return 1 + print "Build finished; the Texinfo files are in %s." % self.builddir_join('texinfo') + if os.name == 'posix': + print "Run `make' in that directory to run these through makeinfo" + print "(use `make info' here to do that automatically)." -def build_doctest(builddir, opts): - res = run_generic_build('doctest', builddir, opts) - print ("Testing of doctests in the sources finished, look at the " - "results in %s." % path.join(builddir, 'doctest', 'output.txt')) - return res + def build_info(self): + if self.run_generic_build('texinfo') > 0: + return 1 + os.system('make -C %s info' % self.builddir_join('texinfo')) -def build_coverage(builddir, opts): - if run_generic_build('coverage', builddir, opts) > 0: - print "Has the coverage extension been enabled?" - return 1 - print - print ("Testing of coverage in the sources finished, look at the " - "results in %s." % path.join(builddir, 'coverage')) + def build_gettext(self): + dtdir = self.builddir_join('gettext', '.doctrees') + if self.run_generic_build('gettext', doctreedir=dtdir) > 0: + return 1 + print + print 'Build finished. The message catalogs are in %s.' % self.builddir_join('gettext') -def build_xml(builddir, opts): - if run_generic_build('xml', builddir, opts) > 0: - return 1 - print - print 'Build finished. The XML files are in %s.' % path.join(builddir, 'xml') + def build_changes(self): + if self.run_generic_build('changes') > 0: + return 1 + print + print 'Build finished. The overview file is in %s.' % self.builddir_join('changes') -def build_pseudoxml(builddir, opts): - if run_generic_build('pseudoxml', builddir, opts) > 0: - return 1 - print - print 'Build finished. The pseudo-XML files are in %s.' % path.join(builddir, 'pseudoxml') + def build_linkcheck(self): + res = self.run_generic_build('linkcheck') + print + print ('Link check complete; look for any errors in the above output ' + 'or in %s.') % self.builddir_join('linkcheck', 'output.txt') + return res + def build_doctest(self): + res = self.run_generic_build('doctest') + print ("Testing of doctests in the sources finished, look at the " + "results in %s." % self.builddir_join('doctest', 'output.txt')) + return res -def run_generic_build(builder, builddir, opts, doctreedir=None): - # compatibility with old Makefile - papersize = os.getenv('PAPER', '') - if papersize in ('a4', 'letter'): - opts.extend(['-D', 'latex_paper_size=' + papersize]) - if doctreedir is None: - doctreedir = path.join(builddir, 'doctrees') - return call([sys.executable, sys.argv[0], '-b', builder, - '-d', doctreedir, '.', path.join(builddir, builder)] + opts) + def build_coverage(self): + if self.run_generic_build('coverage') > 0: + print "Has the coverage extension been enabled?" + return 1 + print + print ("Testing of coverage in the sources finished, look at the " + "results in %s." % self.builddir_join('coverage')) + + def build_xml(self): + if self.run_generic_build('xml') > 0: + return 1 + print + print 'Build finished. The XML files are in %s.' % self.builddir_join('xml') + + def build_pseudoxml(self): + if self.run_generic_build('pseudoxml') > 0: + return 1 + print + print 'Build finished. The pseudo-XML files are in %s.' % self.builddir_join('pseudoxml') + + def run_generic_build(self, builder, doctreedir=None): + # compatibility with old Makefile + papersize = os.getenv('PAPER', '') + opts = self.opts + if papersize in ('a4', 'letter'): + opts.extend(['-D', 'latex_paper_size=' + papersize]) + if doctreedir is None: + doctreedir = self.builddir_join('doctrees') + return call([sys.executable, sys.argv[0], '-b', builder, + '-d', doctreedir, self.srcdir, self.builddir_join(builder)] + opts) def run_make_mode(args): - if len(args) < 2: - print >>sys.stderr, ('Error: at least two arguments (builder, build ' - 'dir) are required.') + if len(args) < 3: + print >>sys.stderr, ('Error: at least 3 arguments (builder, source ' + 'dir, build dir) are required.') return 1 + make = Make(args[1], args[2], args[3:]) run_method = 'build_' + args[0] - if run_method in globals(): - return globals()[run_method](args[1], args[2:]) - return run_generic_build(args[0], args[1], args[2:]) + if hasattr(make, run_method): + return getattr(make, run_method)() + return make.run_generic_build(args[0]) diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index 7ce3ad8489..d9449f749f 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -839,16 +839,17 @@ if "%%1" == "pseudoxml" ( # This will become the Makefile template for Sphinx 1.5. MAKEFILE_NEW = u'''\ -# Makefile for Sphinx documentation +# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = %(project_fn)s +SOURCEDIR = %(rsrcdir)s BUILDDIR = %(rbuilddir)s -# User-friendly check for sphinx-build +# User-friendly check for sphinx-build. ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error \ The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx \ @@ -860,11 +861,14 @@ endif # Has to be explicit, otherwise we don't get "make" without targets right. help: -\t@$(SPHINXBUILD) -M help "$(BUILDDIR)" $(SPHINXOPTS) $(O) +\t@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -# Catch-all target using the new "make mode" option. +# You can add custom targets here. + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: -\t@$(SPHINXBUILD) -M $@ "$(BUILDDIR)" $(SPHINXOPTS) $(O) +\t@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) ''' # This will become the make.bat template for Sphinx 1.5. From bc614bcd9356ed25e959b2f4fdf19551f0727c08 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 17:33:55 +0100 Subject: [PATCH 28/50] Closes #1283: Fix a bug in the detection of changed files that would try to access doctrees of deleted documents. --- CHANGES | 3 +++ sphinx/builders/__init__.py | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/CHANGES b/CHANGES index 3862281c1a..0ff050036c 100644 --- a/CHANGES +++ b/CHANGES @@ -113,6 +113,9 @@ Bugs fixed * #1300: Fix references not working in translated documents in some instances. +* #1283: Fix a bug in the detection of changed files that would try to access + doctrees of deleted documents. + Documentation ------------- diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index c153d12108..f1280a0b2f 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -265,6 +265,12 @@ class Builder(object): self.info(bold('no targets are out of date.')) return + # filter "docnames" (list of outdated files) by the updated + # found_docs of the environment; this will remove docs that + # have since been removed + if docnames != ['__all__']: + docnames = set(docnames) & self.env.found_docs + # another indirection to support builders that don't build # files individually self.write(docnames, list(updated_docnames), method) @@ -289,6 +295,7 @@ class Builder(object): docnames = set(build_docnames) | set(updated_docnames) else: docnames = set(build_docnames) + self.app.debug('docnames to write: %s', ', '.join(sorted(docnames))) # add all toctree-containing files that may have changed for docname in list(docnames): From 8c2fabe117227ea3f1c233d1196a3c87dc947b1c Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 17:46:06 +0100 Subject: [PATCH 29/50] The deprecated config values ``exclude_trees``, ``exclude_dirnames`` and ``unused_docs`` have been removed. --- CHANGES | 3 +++ doc/config.rst | 32 -------------------------------- sphinx/builders/html.py | 5 +---- sphinx/config.py | 4 ---- sphinx/environment.py | 3 --- tests/root/conf.py | 1 - 6 files changed, 4 insertions(+), 44 deletions(-) diff --git a/CHANGES b/CHANGES index 4451a11652..402487b2f8 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,9 @@ Incompatible changes * Removed the ``sphinx.ext.oldcmarkup`` extension. +* The deprecated config values ``exclude_trees``, ``exclude_dirnames`` and + ``unused_docs`` have been removed. + * A new node, ``sphinx.addnodes.literal_strong``, has been added, for text that should appear literally (i.e. no smart quotes) in strong font. Custom writers will have to be adapted to handle this node. diff --git a/doc/config.rst b/doc/config.rst index e4d9feafed..aa34ad4051 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -114,38 +114,6 @@ General configuration .. versionadded:: 1.0 -.. confval:: unused_docs - - A list of document names that are present, but not currently included in the - toctree. Use this setting to suppress the warning that is normally emitted - in that case. - - .. deprecated:: 1.0 - Use :confval:`exclude_patterns` or :ref:`metadata` instead. - -.. confval:: exclude_trees - - A list of directory paths, relative to the source directory, that are to be - recursively excluded from the search for source files, that is, their - subdirectories won't be searched too. The default is ``[]``. - - .. versionadded:: 0.4 - - .. deprecated:: 1.0 - Use :confval:`exclude_patterns` instead. - -.. confval:: exclude_dirnames - - A list of directory names that are to be excluded from any recursive - operation Sphinx performs (e.g. searching for source files or copying static - files). This is useful, for example, to exclude version-control-specific - directories like ``'CVS'``. The default is ``[]``. - - .. versionadded:: 0.5 - - .. deprecated:: 1.0 - Use :confval:`exclude_patterns` instead. - .. confval:: templates_path A list of paths that contain extra templates (or templates that overwrite diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index b6ebf92673..d715607e46 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -579,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) diff --git a/sphinx/config.py b/sphinx/config.py index df74e914fe..ace5f2a61a 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'), diff --git a/sphinx/environment.py b/sphinx/environment.py index a319ef3c1d..09f76bf22e 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -333,9 +333,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/tests/root/conf.py b/tests/root/conf.py index cf88c2bb18..f0d40148b4 100644 --- a/tests/root/conf.py +++ b/tests/root/conf.py @@ -23,7 +23,6 @@ copyright = '2010, Georg Brandl & Team' version = '0.6' release = '0.6alpha1' today_fmt = '%B %d, %Y' -# unused_docs = [] exclude_patterns = ['_build', '**/excluded.*'] keep_warnings = True pygments_style = 'sphinx' From 9f5cb0eca988a9c6b9371e871336ee23f59a9d46 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 17:49:28 +0100 Subject: [PATCH 30/50] docnames can also be None... --- sphinx/builders/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index f1280a0b2f..17c9f51d71 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -268,7 +268,7 @@ class Builder(object): # filter "docnames" (list of outdated files) by the updated # found_docs of the environment; this will remove docs that # have since been removed - if docnames != ['__all__']: + if docnames and docnames != ['__all__']: docnames = set(docnames) & self.env.found_docs # another indirection to support builders that don't build From 48aeed6fd80a2ebdc8136b4d0ccc9e60dfc9e5ef Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 17:49:57 +0100 Subject: [PATCH 31/50] add back missing @ --- doc/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Makefile b/doc/Makefile index 8bc59724e3..5590964932 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -15,4 +15,4 @@ help: # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: - $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) From 5dcf03fb2ddcbf67579b1576ab20d6ee665d9d26 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 18:10:19 +0100 Subject: [PATCH 32/50] Closes #1330: Fix :confval:`exclude_patterns` behavior with subdirectories in the :confval:`html_static_path`. --- CHANGES | 3 +++ sphinx/util/__init__.py | 23 +++++++++++------------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/CHANGES b/CHANGES index 0ff050036c..78d6b446a6 100644 --- a/CHANGES +++ b/CHANGES @@ -116,6 +116,9 @@ Bugs fixed * #1283: Fix a bug in the detection of changed files that would try to access doctrees of deleted documents. +* #1330: Fix :confval:`exclude_patterns` behavior with subdirectories in the + :confval:`html_static_path`. + Documentation ------------- diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 5cbbb61b69..a20fc19be8 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -158,18 +158,17 @@ def copy_static_entry(source, targetdir, builder, context={}, else: copyfile(source, target) elif path.isdir(source): - if level == 0: - for entry in os.listdir(source): - if entry.startswith('.'): - continue - copy_static_entry(path.join(source, entry), targetdir, - builder, context, level=1, - exclude_matchers=exclude_matchers) - else: - target = path.join(targetdir, path.basename(source)) - if path.exists(target): - shutil.rmtree(target) - shutil.copytree(source, target) + if not path.isdir(targetdir): + os.mkdir(targetdir) + for entry in os.listdir(source): + if entry.startswith('.'): + continue + newtarget = targetdir + if path.isdir(path.join(source, entry)): + newtarget = path.join(targetdir, entry) + copy_static_entry(path.join(source, entry), newtarget, + builder, context, level=level+1, + exclude_matchers=exclude_matchers) _DEBUG_HEADER = '''\ From ce492862409700399c5dee8f01715ca18f0566c1 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 18:12:52 +0100 Subject: [PATCH 33/50] Closes #1071: add mention of "orphan" to toctree docs --- doc/markup/toctree.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/markup/toctree.rst b/doc/markup/toctree.rst index 9316e69a7e..1d5d667ae7 100644 --- a/doc/markup/toctree.rst +++ b/doc/markup/toctree.rst @@ -141,8 +141,12 @@ tables of contents. The ``toctree`` directive is the central element. In the end, all documents in the :term:`source directory` (or subdirectories) must occur in some ``toctree`` directive; Sphinx will emit a warning if it finds a file that is not included, because that means that this file will not - be reachable through standard navigation. Use :confval:`exclude_patterns` to - explicitly exclude documents or directories from building. + be reachable through standard navigation. + + Use :confval:`exclude_patterns` to explicitly exclude documents or + directories from building completely. Use :ref:`the "orphan" metadata + ` to let a document be built, but notify Sphinx that it is not + reachable via a toctree. The "master document" (selected by :confval:`master_doc`) is the "root" of the TOC tree hierarchy. It can be used as the documentation's main page, or From 7a74fe81b18f18d38cf3820fe3154a3279219ffc Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 18:21:23 +0100 Subject: [PATCH 34/50] Closes #1323: Fix emitting empty ``
    `` tags in the HTML writer, which is not valid HTML. --- CHANGES | 3 +++ sphinx/writers/html.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGES b/CHANGES index 78d6b446a6..950c7a4b93 100644 --- a/CHANGES +++ b/CHANGES @@ -119,6 +119,9 @@ Bugs fixed * #1330: Fix :confval:`exclude_patterns` behavior with subdirectories in the :confval:`html_static_path`. +* #1323: Fix emitting empty ``
      `` tags in the HTML writer, which is not + valid HTML. + Documentation ------------- diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py index 41663a0cc1..2d68564f5f 100644 --- a/sphinx/writers/html.py +++ b/sphinx/writers/html.py @@ -240,6 +240,12 @@ class HTMLTranslator(BaseTranslator): self.body.append('.'.join(map(str, numbers)) + self.secnumber_suffix) + # overwritten to avoid emitting empty
        + def visit_bullet_list(self, node): + if len(node) == 1 and node[0].tagname == 'toctree': + raise nodes.SkipNode + BaseTranslator.visit_bullet_list(self, node) + # overwritten def visit_title(self, node): BaseTranslator.visit_title(self, node) From 63b4f3bfae99a2a8609521e667be298dfe6bf118 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 18:40:00 +0100 Subject: [PATCH 35/50] Closes #1147: Don't emit a sidebar search box in the "singlehtml" builder. --- CHANGES | 2 ++ sphinx/themes/basic/searchbox.html | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 950c7a4b93..6035c50bf3 100644 --- a/CHANGES +++ b/CHANGES @@ -122,6 +122,8 @@ Bugs fixed * #1323: Fix emitting empty ``
          `` tags in the HTML writer, which is not valid HTML. +* #1147: Don't emit a sidebar search box in the "singlehtml" builder. + Documentation ------------- diff --git a/sphinx/themes/basic/searchbox.html b/sphinx/themes/basic/searchbox.html index 609aac83b9..1495701a25 100644 --- a/sphinx/themes/basic/searchbox.html +++ b/sphinx/themes/basic/searchbox.html @@ -7,7 +7,7 @@ :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} -{%- if pagename != "search" %} +{%- if pagename != "search" and builder != "singlehtml" %}