diff --git a/doc/ext/apidoc.rst b/doc/ext/apidoc.rst new file mode 100644 index 00000000000..1042f838f0d --- /dev/null +++ b/doc/ext/apidoc.rst @@ -0,0 +1,51 @@ +.. highlight:: rest + +:mod:`sphinx.ext.apidoc` -- Generate autodoc stub pages +======================================================= + +.. module:: sphinx.ext.apidoc + :synopsis: Generate autodoc stub pages + +.. versionadded:: 1.8 + + The functionality of this extension was previously available as part of the + :program:`sphinx-apidoc` tool. This ability to run this automatically by way + of a Sphinx extension was added in 1.8. + +This extension generates function, method and attribute stub documentation +pages, similar to that generated by API doc tools like Doxygen or Javadoc. + +.. warning:: + + The apidoc extension generates source files that use + :mod:`sphinx.ext.autodoc` to document all found modules. If any modules + have side effects on import, these will be executed when building + documentation. + + If you document scripts (as opposed to library modules), make sure their + main routine is protected by a ``if __name__ == '__main__'`` condition. + +Configuration +------------- + +The apidoc extension uses the following configuration values: + +.. confval:: apidoc_module_dir + + The path to the module to document. This must be a path to a Python package. + This path can be a path relative to the documentation source directory or an + absolute path. + +.. confval:: apidoc_output_dir + + The output directory. If it does not exist, it is created. This path is + relative to the documentation source directory. + + Defaults to ``api``. + +.. confval:: apidoc_excluded_modules + + An optional list of modules to exclude. These should be paths relative to + ``api_module_dir``. fnmatch-style wildcarding is supported. + + Defaults to ``[]``. diff --git a/doc/ext/builtins.rst b/doc/ext/builtins.rst index 6972a59574c..350a5d6fc9a 100644 --- a/doc/ext/builtins.rst +++ b/doc/ext/builtins.rst @@ -6,6 +6,7 @@ These extensions are built in and can be activated by respective entries in the .. toctree:: + apidoc autodoc autosectionlabel autosummary diff --git a/sphinx/ext/apidoc.py b/sphinx/ext/apidoc.py index ad024ea5437..8d0e38b80bf 100644 --- a/sphinx/ext/apidoc.py +++ b/sphinx/ext/apidoc.py @@ -31,12 +31,15 @@ from sphinx import __display_version__, package_dir from sphinx.cmd.quickstart import EXTENSIONS from sphinx.locale import __ -from sphinx.util import rst +from sphinx.util import logging, rst from sphinx.util.osutil import FileAvoidWrite, ensuredir, walk if False: # For type annotation - from typing import Any, List, Tuple # NOQA + from typing import Any, Dict, List, Tuple # NOQA + from sphinx.application import Sphinx # NOQA + +logger = logging.getLogger(__name__) # automodule options if 'SPHINX_APIDOC_OPTIONS' in os.environ: @@ -459,6 +462,45 @@ def main(argv=sys.argv[1:]): return 0 -# So program can be started with "python -m sphinx.apidoc ..." +def builder_inited(app): + # type: (Sphinx) -> None + module_dir = app.config.apidoc_module_dir + output_dir = path.join(app.srcdir, app.config.apidoc_output_dir) + excludes = app.config.apidoc_excluded_modules + + if not module_dir: + logger.warning("No 'apidoc_module_dir' specified; skipping API doc " + "generation") + return + + # if the path is relative, make it relative to the 'conf.py' directory + if not path.isabs(module_dir): + module_dir = path.abspath(path.join(app.srcdir, module_dir)) + + excludes = [path.abspath(path.join(module_dir, exc)) for exc in excludes] + + # refactor this module so that we can call 'recurse_tree' like a sane + # person - at present there is way too much passing around of the + # 'optparse.Value' instance returned by 'optparse.parse_args' + cmd = ['--force', '-o', output_dir, module_dir] + if excludes: + cmd += excludes + + main(cmd) + + +def setup(app): + # type: (Sphinx) -> Dict[unicode, Any] + app.setup_extension('sphinx.ext.autodoc') # We need autodoc to function + + app.connect('builder-inited', builder_inited) + app.add_config_value('apidoc_module_dir', None, 'env', [str]) + app.add_config_value('apidoc_output_dir', 'api', 'env', [str]) + app.add_config_value('apidoc_excluded_modules', [], 'env', + [[str]]) + + return {'version': __display_version__, 'parallel_read_safe': True} + + if __name__ == "__main__": main() diff --git a/tests/roots/test-ext-apidoc/apidoc_dummy_module.py b/tests/roots/test-ext-apidoc/apidoc_dummy_module.py new file mode 100644 index 00000000000..aa4aaa9f243 --- /dev/null +++ b/tests/roots/test-ext-apidoc/apidoc_dummy_module.py @@ -0,0 +1,9 @@ +from os import * + + +class Foo: + def __init__(self): + pass + + def bar(self): + pass diff --git a/tests/roots/test-ext-apidoc/conf.py b/tests/roots/test-ext-apidoc/conf.py new file mode 100644 index 00000000000..78eedd18764 --- /dev/null +++ b/tests/roots/test-ext-apidoc/conf.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- + +import os +import sys + +sys.path.insert(0, os.path.abspath('./')) + +extensions = ['sphinx.ext.apidoc'] +master_doc = 'index' + +apidoc_module_dir = '.' +apidoc_excluded_modules = ['conf.py'] diff --git a/tests/roots/test-ext-apidoc/index.rst b/tests/roots/test-ext-apidoc/index.rst new file mode 100644 index 00000000000..5aa85e12994 --- /dev/null +++ b/tests/roots/test-ext-apidoc/index.rst @@ -0,0 +1,6 @@ +apidoc +====== + +.. toctree:: + + api/modules diff --git a/tests/test_ext_apidoc.py b/tests/test_ext_apidoc.py index d3d61d1e069..74c64ceacab 100644 --- a/tests/test_ext_apidoc.py +++ b/tests/test_ext_apidoc.py @@ -384,8 +384,6 @@ def extract_toc(path): coderoot='test-apidoc-subpackage-in-toc', options=['--separate'] ) - - def test_subpackage_in_toc(make_app, apidoc): """Make sure that empty subpackages with non-empty subpackages in them are not skipped (issue #4520) @@ -404,3 +402,12 @@ def test_subpackage_in_toc(make_app, apidoc): assert 'parent.child.foo' in parent_child assert (outdir / 'parent.child.foo.rst').isfile() + + +@pytest.mark.sphinx('html', testroot='ext-apidoc') +def test_apidoc_extension(app, status, warning): + app.builder.build_all() + assert (app.outdir / 'api').isdir() + assert (app.outdir / 'api' / 'modules.html').exists() + assert (app.outdir / 'api' / 'apidoc_dummy_module.html').exists() + assert not (app.outdir / 'api' / 'conf.html').exists()