From e05e29f726be51e77f54a06b03faca6c814beff6 Mon Sep 17 00:00:00 2001 From: Markus Zapke-Gr?ndemann Date: Thu, 19 Sep 2013 21:48:54 +0200 Subject: 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 70ebacb4..ae41594e 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 -- cgit v1.2.1 From dccd43b51001bf227a656411837c4c58895c4346 Mon Sep 17 00:00:00 2001 From: M Nasimul Haque Date: Thu, 26 Sep 2013 00:56:16 +0100 Subject: 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 4d43e5ff..8364721e 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 3fb81178..e22ce16d 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 5db77108..c1f44477 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 4108125b..c3655dfc 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 3c492034..8808ee12 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 c4e94908..4e7bacc3 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 af498257..021240d7 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 6c6bd5f5..95114473 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 c6747f85..5508d210 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 ce92edfc..df4d5d60 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, -- cgit v1.2.1 From 10e786edac3809660e9129fe53f5e7e05cc9429e Mon Sep 17 00:00:00 2001 From: guibog Date: Wed, 23 Oct 2013 16:36:45 +0800 Subject: 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 c92fe0c4..c37328f5 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 348c072c..a556edc8 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 d4b3404c..aa0dffba 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 00000000..7a717345 --- /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 8025ba33..215cebf9 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') -- cgit v1.2.1 From 0715decc62404efeb34c4fa12b9098b247b316d9 Mon Sep 17 00:00:00 2001 From: tom prince Date: Tue, 29 Oct 2013 16:40:43 -0600 Subject: 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 6b87ad88..5e6030c0 100644 --- a/sphinx/texinputs/Makefile +++ b/sphinx/texinputs/Makefile @@ -9,6 +9,10 @@ ARCHIVEPRREFIX = # Additional LaTeX options LATEXOPTS = +LATEX = latex +PDFLATEX = pdflatex +MAKEINDEX = makeindex + all: $(ALLPDF) all-pdf: $(ALLPDF) all-dvi: $(ALLDVI) @@ -43,20 +47,20 @@ bz2: tar # The number of LaTeX runs is quite conservative, but I don't expect it # to get run often, so the little extra time won't hurt. %.dvi: %.tex - latex $(LATEXOPTS) '$<' - latex $(LATEXOPTS) '$<' - latex $(LATEXOPTS) '$<' - -makeindex -s python.ist '$(basename $<).idx' - latex $(LATEXOPTS) '$<' - latex $(LATEXOPTS) '$<' + $(LATEX) $(LATEXOPTS) '$<' + $(LATEX) $(LATEXOPTS) '$<' + $(LATEX) $(LATEXOPTS) '$<' + -$(MAKEINDEX) -s python.ist '$(basename $<).idx' + $(LATEX) $(LATEXOPTS) '$<' + $(LATEX) $(LATEXOPTS) '$<' %.pdf: %.tex - pdflatex $(LATEXOPTS) '$<' - pdflatex $(LATEXOPTS) '$<' - pdflatex $(LATEXOPTS) '$<' - -makeindex -s python.ist '$(basename $<).idx' - pdflatex $(LATEXOPTS) '$<' - pdflatex $(LATEXOPTS) '$<' + $(PDFLATEX) $(LATEXOPTS) '$<' + $(PDFLATEX) $(LATEXOPTS) '$<' + $(PDFLATEX) $(LATEXOPTS) '$<' + -$(MAKEINDEX) -s python.ist '$(basename $<).idx' + $(PDFLATEX) $(LATEXOPTS) '$<' + $(PDFLATEX) $(LATEXOPTS) '$<' clean: rm -f *.dvi *.log *.ind *.aux *.toc *.syn *.idx *.out *.ilg *.pla -- cgit v1.2.1 From d413fd5731de449c1f69a6c8f4898fd7645fd494 Mon Sep 17 00:00:00 2001 From: Sascha Peilicke Date: Wed, 13 Nov 2013 15:33:25 +0100 Subject: 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 07cd520c..7999bf59 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() -- cgit v1.2.1 From e55efec6d6058d130eac9c4cdb92ddd64335d4c2 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 10:39:23 +0100 Subject: 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 377130b8..d44c9cd7 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',)), -- cgit v1.2.1 From 12c2447b0972fe8bc23ba8132ad5bf13bea5baf7 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 11:14:38 +0100 Subject: 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 3621be4b..36b38e03 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 4430cdd0..7e95cdae 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: -- cgit v1.2.1 From b06dd5080fc5abd71dc3a427a2bc482cfd7a5733 Mon Sep 17 00:00:00 2001 From: Sascha Peilicke Date: Wed, 13 Nov 2013 15:33:25 +0100 Subject: 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 07cd520c..7999bf59 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() -- cgit v1.2.1 From 3a19adb976393dc4066def880d0db11e2f7378d7 Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Sun, 15 Dec 2013 14:16:53 +0900 Subject: 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 | 20 +---- 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, 55 insertions(+), 377 deletions(-) diff --git a/CHANGES b/CHANGES index c15cd0b9..3b6c8888 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 9c258420..828dc3b4 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 4d052c81..f0593cee 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 553cf12d..543a319d 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 f93e57a1..bbd45b24 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 1dfcc704..35061b79 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -12,7 +12,6 @@ import os import re -import sys import time import codecs import zipfile @@ -750,12 +749,5 @@ class EpubBuilder(StandaloneHTMLBuilder): zipfile.ZIP_STORED) for file in projectfiles: fp = path.join(outdir, file) - if sys.version_info < (2, 6): - # When zipile.ZipFile.write call with unicode filename, ZipFile - # encode filename to 'utf-8' (only after Python-2.6). - if isinstance(file, unicode): - # OEBPS Container Format (OCF) 2.0.1 specification require - # "File Names MUST be UTF-8 encoded". - file = file.encode('utf-8') epub.write(fp, file, zipfile.ZIP_DEFLATED) epub.close() diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index e00b91c3..b0cde297 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 c567401c..c3369b5a 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 8d78feb2..5ea64bf3 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 df422321..7a3927d3 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 64999df8..d69fe0ca 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 e09f5058..96fa6c8b 100644 --- a/sphinx/pycode/pgen2/parse.c +++ b/sphinx/pycode/pgen2/parse.c @@ -353,95 +353,6 @@ static void __Pyx_RaiseArgtupleInvalid(const char* func_name, int exact, static int __Pyx_ParseOptionalKeywords(PyObject *kwds, PyObject **argnames[], PyObject *kwds2, PyObject *values[], Py_ssize_t num_pos_args, const char* function_name); /*proto*/ -#if PY_VERSION_HEX < 0x02050000 -#ifndef PyAnySet_CheckExact - -#define PyAnySet_CheckExact(ob) \ - ((ob)->ob_type == &PySet_Type || \ - (ob)->ob_type == &PyFrozenSet_Type) - -#define PySet_New(iterable) \ - PyObject_CallFunctionObjArgs((PyObject *)&PySet_Type, (iterable), NULL) - -#define Pyx_PyFrozenSet_New(iterable) \ - PyObject_CallFunctionObjArgs((PyObject *)&PyFrozenSet_Type, (iterable), NULL) - -#define PySet_Size(anyset) \ - PyObject_Size((anyset)) - -#define PySet_Contains(anyset, key) \ - PySequence_Contains((anyset), (key)) - -#define PySet_Pop(set) \ - PyObject_CallMethod(set, (char *)"pop", NULL) - -static INLINE int PySet_Clear(PyObject *set) { - PyObject *ret = PyObject_CallMethod(set, (char *)"clear", NULL); - if (!ret) return -1; - Py_DECREF(ret); return 0; -} - -static INLINE int PySet_Discard(PyObject *set, PyObject *key) { - PyObject *ret = PyObject_CallMethod(set, (char *)"discard", (char *)"O", key); - if (!ret) return -1; - Py_DECREF(ret); return 0; -} - -static INLINE int PySet_Add(PyObject *set, PyObject *key) { - PyObject *ret = PyObject_CallMethod(set, (char *)"add", (char *)"O", key); - if (!ret) return -1; - Py_DECREF(ret); return 0; -} - -#endif /* PyAnySet_CheckExact (<= Py2.4) */ - -#if PY_VERSION_HEX < 0x02040000 -#ifndef Py_SETOBJECT_H -#define Py_SETOBJECT_H - -static PyTypeObject *__Pyx_PySet_Type = NULL; -static PyTypeObject *__Pyx_PyFrozenSet_Type = NULL; - -#define PySet_Type (*__Pyx_PySet_Type) -#define PyFrozenSet_Type (*__Pyx_PyFrozenSet_Type) - -#define PyAnySet_Check(ob) \ - (PyAnySet_CheckExact(ob) || \ - PyType_IsSubtype((ob)->ob_type, &PySet_Type) || \ - PyType_IsSubtype((ob)->ob_type, &PyFrozenSet_Type)) - -#define PyFrozenSet_CheckExact(ob) ((ob)->ob_type == &PyFrozenSet_Type) - -static int __Pyx_Py23SetsImport(void) { - PyObject *sets=0, *Set=0, *ImmutableSet=0; - - sets = PyImport_ImportModule((char *)"sets"); - if (!sets) goto bad; - Set = PyObject_GetAttrString(sets, (char *)"Set"); - if (!Set) goto bad; - ImmutableSet = PyObject_GetAttrString(sets, (char *)"ImmutableSet"); - if (!ImmutableSet) goto bad; - Py_DECREF(sets); - - __Pyx_PySet_Type = (PyTypeObject*) Set; - __Pyx_PyFrozenSet_Type = (PyTypeObject*) ImmutableSet; - - return 0; - - bad: - Py_XDECREF(sets); - Py_XDECREF(Set); - Py_XDECREF(ImmutableSet); - return -1; -} - -#else -static int __Pyx_Py23SetsImport(void) { return 0; } -#endif /* !Py_SETOBJECT_H */ -#endif /* < Py2.4 */ -#endif /* < Py2.5 */ - - static INLINE PyObject *__Pyx_GetItemInt_Generic(PyObject *o, PyObject* j) { PyObject *r; if (!j) return NULL; diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index a4dca333..5733b34b 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 338d4739..35e9d297 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 61061a9a..c7556d05 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -55,7 +55,7 @@ if sys.version_info >= (3, 0): raise TypeError('%r is not a Python function' % func) return inspect.getfullargspec(func) -elif sys.version_info >= (2, 5): +else: # 2.6, 2.7 from functools import partial def getargspec(func): """Like inspect.getargspec but supports functools.partial as well.""" @@ -86,12 +86,7 @@ elif sys.version_info >= (2, 5): del func_defaults[i] except IndexError: pass - if sys.version_info >= (2, 6): - return inspect.ArgSpec(args, varargs, varkw, func_defaults) - else: - return (args, varargs, varkw, func_defaults) -else: - getargspec = inspect.getargspec + return inspect.ArgSpec(args, varargs, varkw, func_defaults) def isdescriptor(x): diff --git a/sphinx/util/jsonimpl.py b/sphinx/util/jsonimpl.py index aa0ea825..de846b24 100644 --- a/sphinx/util/jsonimpl.py +++ b/sphinx/util/jsonimpl.py @@ -10,27 +10,15 @@ """ import UserString +import json -try: - import json - # json-py's json module has no JSONEncoder; this will raise AttributeError - # if json-py is imported instead of the built-in json module - JSONEncoder = json.JSONEncoder -except (ImportError, AttributeError): - try: - import simplejson as json - JSONEncoder = json.JSONEncoder - except ImportError: - json = None - JSONEncoder = object - - -class SphinxJSONEncoder(JSONEncoder): + +class SphinxJSONEncoder(json.JSONEncoder): """JSONEncoder subclass that forces translation proxies.""" def default(self, obj): if isinstance(obj, UserString.UserString): return unicode(obj) - return JSONEncoder.default(self, obj) + return json.JSONEncoder.default(self, obj) def dump(obj, fp, *args, **kwds): diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index 87717771..e8655fe1 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 3d252c91..68e6d8fb 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 a16751bb..ccab41d4 100644 --- a/sphinx/versioning.py +++ b/sphinx/versioning.py @@ -11,8 +11,9 @@ """ from uuid import uuid4 from operator import itemgetter +from itertools import product -from sphinx.util.pycompat import product, zip_longest, all +from sphinx.util.pycompat import zip_longest # anything below that ratio is considered equal/changed diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 80080752..5e2bf93f 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 63c341f1..7e60c5fb 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 c9822089..c241c0ae 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 6469a33c..5cff92ff 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 611a131a..d950a36c 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 4ba89030..a2f345bf 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 f1675e90..b03cc9e0 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 -- cgit v1.2.1 From 8b375619a7a91d26f07f1ba944757700ae82827f Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Sun, 15 Dec 2013 16:04:23 +0900 Subject: 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 3b6c8888..df139ef0 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 828dc3b4..bc969b0b 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 f0593cee..b2c25023 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 543a319d..53a4c04b 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 7a3927d3..807a1826 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 e8655fe1..d2f8679e 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 68e6d8fb..a0e09965 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 c241c0ae..0119d677 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 b03cc9e0..006819ef 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= -- cgit v1.2.1 From e3f0fcef07d5fdd1bf4e6201e1c03e8c7a75adb7 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 11 Jan 2014 19:36:05 +0100 Subject: 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 | 166 ++++-------------------------------------- sphinx-build.py | 7 +- sphinx/__init__.py | 6 ++ sphinx/cmdline.py | 36 +++++---- sphinx/make_mode.py | 202 +++++++++++++++++++++++++++++++++++++++++++++++++++ sphinx/quickstart.py | 66 +++++++++++++++++ 6 files changed, 316 insertions(+), 167 deletions(-) create mode 100644 sphinx/make_mode.py diff --git a/doc/Makefile b/doc/Makefile index 831c12c5..d799c365 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 = - -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 +SPHINXPROJ = sphinx +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 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" - -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." + @$(SPHINXBUILD) -M help "$(BUILDDIR)" $(SPHINXOPTS) $(O) -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 6737d072..54bdb529 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 d41d4424..8a1b97f7 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 e5ad3ab5..f4a09e16 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 00000000..5ef065a5 --- /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 f77e2f22..7ce3ad84 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): -- cgit v1.2.1 From 80a0712b8190ca3d55531a9d1fe3205e364437d1 Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Sat, 18 Jan 2014 16:30:54 +0900 Subject: 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 bbd45b24..2551526f 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 d2f8679e..a5c461a6 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. -- cgit v1.2.1 From 0b92e30e92f3901b1af3c99cd0f904803a999a30 Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Sat, 18 Jan 2014 22:57:02 +0900 Subject: add missing 'feature added' for non-ASCII filename handling. --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 3c0ab53c..443ec774 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 -- cgit v1.2.1 From 27199d51c3c4af6ccba6e102b910f82e4519e417 Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Sun, 19 Jan 2014 00:23:17 +0900 Subject: grammar fix --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 443ec774..79de1531 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: -- cgit v1.2.1 From d6498e009d54c1be6ae2d68e4c86c651dc108ee1 Mon Sep 17 00:00:00 2001 From: Rob Ruana Date: Sat, 18 Jan 2014 13:56:23 -0500 Subject: 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 2a9dbbac..24b9be74 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 08149fba..b6515a38 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 00000000..c94dcdf1 --- /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 00000000..06508082 --- /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 00000000..df1d20e6 --- /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 00000000..a3b41613 --- /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 00000000..fde23c7b --- /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 334e5039..eaa7376d 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 00000000..168ac574 --- /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 00000000..d6c73db8 --- /dev/null +++ b/sphinx/ext/napoleon/docstring.py @@ -0,0 +1,714 @@ +# -*- coding: utf-8 -*- +""" + sphinx.ext.napoleon.docstring + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + + Classes for docstring parsing and formatting. + + + :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re +import sys +from sphinx.ext.napoleon.iterators import modify_iter + + +if sys.version_info[0] >= 3: + basestring = str + xrange = range + + +_directive_regex = re.compile(r'\.\. \S+::') +_field_parens_regex = re.compile(r'\s*(\w+)\s*\(\s*(.+?)\s*\)') + + +class GoogleDocstring(object): + """Parse Google style docstrings. + + Convert Google style docstrings to reStructuredText. + + Parameters + ---------- + docstring : str or list of str + The docstring to parse, given either as a string or split into + individual lines. + config : sphinx.ext.napoleon.Config or sphinx.config.Config, optional + The configuration settings to use. If not given, defaults to the + config object on `app`; or if `app` is not given defaults to the + a new `sphinx.ext.napoleon.Config` object. + + See Also + -------- + :class:`sphinx.ext.napoleon.Config` + + Other Parameters + ---------------- + app : sphinx.application.Sphinx, optional + Application object representing the Sphinx process. + what : str, optional + A string specifying the type of the object to which the docstring + belongs. Valid values: "module", "class", "exception", "function", + "method", "attribute". + name : str, optional + The fully qualified name of the object. + obj : module, class, exception, function, method, or attribute + The object to which the docstring belongs. + options : sphinx.ext.autodoc.Options, optional + The options given to the directive: an object with attributes + inherited_members, undoc_members, show_inheritance and noindex that + are True if the flag option of same name was given to the auto + directive. + + Example + ------- + >>> from sphinx.ext.napoleon import Config + >>> config = Config(napoleon_use_param=True, napoleon_use_rtype=True) + >>> docstring = '''One line summary. + ... + ... Extended description. + ... + ... Args: + ... arg1(int): Description of `arg1` + ... arg2(str): Description of `arg2` + ... Returns: + ... str: Description of return value. + ... ''' + >>> print(GoogleDocstring(docstring, config)) + One line summary. + + 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 00000000..2f1904da --- /dev/null +++ b/sphinx/ext/napoleon/iterators.py @@ -0,0 +1,244 @@ +# -*- coding: utf-8 -*- +""" + sphinx.ext.napoleon.iterators + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + + A collection of helpful iterators. + + + :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import collections +import sys + + +if sys.version_info[0] >= 3: + callable = lambda o: hasattr(o, '__call__') + + +class peek_iter(object): + """An iterator object that supports peeking ahead. + + Parameters + ---------- + o : iterable or callable + `o` is interpreted very differently depending on the presence of + `sentinel`. + + If `sentinel` is not given, then `o` must be a collection object + which supports either the iteration protocol or the sequence protocol. + + If `sentinel` is given, then `o` must be a callable object. + + sentinel : any value, optional + If given, the iterator will call `o` with no arguments for each + call to its `next` method; if the value returned is equal to + `sentinel`, :exc:`StopIteration` will be raised, otherwise the + value will be returned. + + See Also + -------- + `peek_iter` can operate as a drop in replacement for the built-in + `iter `_ 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 8025ba33..9b881760 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 00000000..d8c71960 --- /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 00000000..aa52aebe --- /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 00000000..db0be32c --- /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 f1675e90..dfa1f923 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 -- cgit v1.2.1 From 59b355edaa4f17aff910bf2371526d5ce46fb648 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 07:32:10 +0100 Subject: changelog: bette note about literal_strong --- CHANGES | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 33cc66fd..e387ffd5 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 ------------ -- cgit v1.2.1 From 600dedfc2f49e158d32c0acf20462b4df78a592f Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 09:54:24 +0100 Subject: 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 79de1531..7600958f 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 962b543b..36fb47d2 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 = [''] -- cgit v1.2.1 From e2159e541dcc9b594aeed4a624df329c2f354545 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 10:13:43 +0100 Subject: 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 7600958f..3621be4b 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 c3054662..4430cdd0 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: -- cgit v1.2.1 From 604ea875698727d3c1d83317130af0fff5bb5ab8 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 10:29:06 +0100 Subject: 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 8614442e..2036e5f5 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'); -- cgit v1.2.1 From 2a033ce3c7397b85cbb12c68046167f1c9c13364 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 10:34:52 +0100 Subject: 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 2d5c6981..f3f3b1c1 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 c37328f5..94b05423 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 6c13e243..86837ff8 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. -- cgit v1.2.1 From f207d092533dcb75024fe9a3e036a72105f27856 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 11:04:12 +0100 Subject: 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 f3f3b1c1..42d544b0 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 6e707e00..e9f8f1da 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 bfe6c48e..6900ea6b 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 -- cgit v1.2.1 From 4d8e32c234bbd1da45f39b94741aef476b377f02 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 11:36:11 +0100 Subject: 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 36b38e03..13fc1bc4 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 800e9ca8..32bb96d3 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 2d1d6e30..fd973544 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))) -- cgit v1.2.1 From 029d8bec8503b5f1da91d86ca13a174d5753a4b9 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 11:38:41 +0100 Subject: 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 13fc1bc4..ee550517 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 3f4664f6..6c8ff5e7 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 -- cgit v1.2.1 From 2ad1a687bffbc9075ea4df56e638475e748d9af1 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 11:42:44 +0100 Subject: 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 ee550517..3fce72ab 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 338d4739..14e2c97f 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 -- cgit v1.2.1 From 99d7fd4d39fafb1b148ae646d21bfae49bab6606 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 15:39:31 +0100 Subject: 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 3fce72ab..3862281c 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 14e2c97f..53bbadb0 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): -- cgit v1.2.1 From e0e0ddee1cccafa1398c4dd17a410890973eb42b Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 15:49:51 +0100 Subject: 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 5ef065a5..c8a2eae5 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)." + +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')) -# latex -# latexpdf -# latexpdfja +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 -- cgit v1.2.1 From aaabdd9b8ba3757180e316098a478cf29d651aa7 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 16:23:23 +0100 Subject: Make mode: refactor using a class and add source dir to the makefile --- doc/Makefile | 18 +-- sphinx/make_mode.py | 370 ++++++++++++++++++++++++++------------------------- sphinx/quickstart.py | 14 +- 3 files changed, 206 insertions(+), 196 deletions(-) diff --git a/doc/Makefile b/doc/Makefile index d799c365..8bc59724 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 c8a2eae5..24de6b28 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) - - -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') - -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_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: - return 1 - print - print 'Build finished. The text files are in %s.' % path.join(builddir, 'text') - -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') - 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_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 - 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) +class Make(object): + + def __init__(self, srcdir, builddir, opts): + self.srcdir = srcdir + self.builddir = builddir + self.opts = opts + + def builddir_join(self, *comps): + return path.join(self.builddir, *comps) + + 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_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_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_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_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_pickle(self): + if self.run_generic_build('pickle') > 0: + return 1 + print + print 'Build finished; now you can process the pickle files.' + + 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_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_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_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_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_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_latexpdf(self): + if self.run_generic_build('latex') > 0: + return 1 + os.system('make -C %s all-pdf' % self.builddir_join('latex')) + + 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_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_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_info(self): + if self.run_generic_build('texinfo') > 0: + return 1 + os.system('make -C %s info' % self.builddir_join('texinfo')) + + 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_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_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 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 7ce3ad84..d9449f74 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) + +# You can add custom targets here. -# Catch-all target using the new "make mode" option. +# 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. -- cgit v1.2.1 From dbefc55b2fac1e0f531af7d5f4de51aeef65bcf2 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 17:33:55 +0100 Subject: 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 3862281c..0ff05003 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 c153d121..f1280a0b 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): -- cgit v1.2.1 From 2a8cf7c7781a4554adbcb4bae6bec2a40bfc156d Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 17:46:06 +0100 Subject: 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 4451a116..402487b2 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 e4d9feaf..aa34ad40 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 b6ebf926..d715607e 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 df74e914..ace5f2a6 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -51,10 +51,6 @@ class Config(object): source_suffix = ('.rst', 'env'), source_encoding = ('utf-8-sig', 'env'), exclude_patterns = ([], 'env'), - # the next three are all deprecated now - unused_docs = ([], 'env'), - exclude_trees = ([], 'env'), - exclude_dirnames = ([], 'env'), default_role = (None, 'env'), add_function_parentheses = (True, 'env'), add_module_names = (True, 'env'), diff --git a/sphinx/environment.py b/sphinx/environment.py index a319ef3c..09f76bf2 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 cf88c2bb..f0d40148 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' -- cgit v1.2.1 From 3565d4c9d198fe2e72135905beece0ad9421f036 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 17:49:28 +0100 Subject: 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 f1280a0b..17c9f51d 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 -- cgit v1.2.1 From 06be47ff5a50b8ae70d09b3062c463f09612d442 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 17:49:57 +0100 Subject: add back missing @ --- doc/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Makefile b/doc/Makefile index 8bc59724..55909649 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) -- cgit v1.2.1 From c24455d49eaf8a2ad9a15bb70cbc8f4fdae33c0a Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 18:10:19 +0100 Subject: 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 0ff05003..78d6b446 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 5cbbb61b..a20fc19b 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 = '''\ -- cgit v1.2.1 From ab13af39767e94ae78a0e2718d7935711f8034ac Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 18:12:52 +0100 Subject: 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 9316e69a..1d5d667a 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 -- cgit v1.2.1 From 16e15b75db027b7c3023c5c5c7b5d4eb241abc04 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 18:21:23 +0100 Subject: 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 78d6b446..950c7a4b 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 41663a0c..2d68564f 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) -- cgit v1.2.1 From db7406902d89352524f3ee43eb5a095e1a7bedc3 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 19 Jan 2014 18:40:00 +0100 Subject: 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 950c7a4b..6035c50b 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 609aac83..1495701a 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" %}