mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Fix #8727: apidoc: namespace module file is not generated if no submodules
sphinx-apidoc should generate a namespace module file when `--implicit-namespace` option given. This fixes the case the namespace module has subpackages, but no submodules.
This commit is contained in:
parent
a71028bf9e
commit
650f8ea237
1
CHANGES
1
CHANGES
@ -51,6 +51,7 @@ Features added
|
|||||||
Bugs fixed
|
Bugs fixed
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
* #8727: apidoc: namespace module file is not generated if no submodules there
|
||||||
* #741: autodoc: inherited-members doesn't work for instance attributes on super
|
* #741: autodoc: inherited-members doesn't work for instance attributes on super
|
||||||
class
|
class
|
||||||
* #8592: autodoc: ``:meta public:`` does not effect to variables
|
* #8592: autodoc: ``:meta public:`` does not effect to variables
|
||||||
|
@ -24,7 +24,7 @@ from copy import copy
|
|||||||
from fnmatch import fnmatch
|
from fnmatch import fnmatch
|
||||||
from importlib.machinery import EXTENSION_SUFFIXES
|
from importlib.machinery import EXTENSION_SUFFIXES
|
||||||
from os import path
|
from os import path
|
||||||
from typing import Any, List, Tuple
|
from typing import Any, Generator, List, Tuple
|
||||||
|
|
||||||
import sphinx.locale
|
import sphinx.locale
|
||||||
from sphinx import __display_version__, package_dir
|
from sphinx import __display_version__, package_dir
|
||||||
@ -264,14 +264,46 @@ def is_skipped_module(filename: str, opts: Any, excludes: List[str]) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def walk(rootpath: str, excludes: List[str], opts: Any
|
||||||
|
) -> Generator[Tuple[str, List[str], List[str]], None, None]:
|
||||||
|
"""Walk through the directory and list files and subdirectories up."""
|
||||||
|
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)
|
||||||
|
files = sorted(f for f in files
|
||||||
|
if f.endswith(PY_SUFFIXES) and
|
||||||
|
not is_excluded(path.join(root, f), excludes))
|
||||||
|
|
||||||
|
# remove hidden ('.') and private ('_') directories, as well as
|
||||||
|
# excluded dirs
|
||||||
|
if includeprivate:
|
||||||
|
exclude_prefixes = ('.',) # type: Tuple[str, ...]
|
||||||
|
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))
|
||||||
|
|
||||||
|
yield root, subs, files
|
||||||
|
|
||||||
|
|
||||||
|
def has_child_module(rootpath: str, excludes: List[str], opts: Any) -> bool:
|
||||||
|
"""Check the given directory contains child modules at least one."""
|
||||||
|
for root, subs, files in walk(rootpath, excludes, opts):
|
||||||
|
if files:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def recurse_tree(rootpath: str, excludes: List[str], opts: Any,
|
def recurse_tree(rootpath: str, excludes: List[str], opts: Any,
|
||||||
user_template_dir: str = None) -> List[str]:
|
user_template_dir: str = None) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Look for every file in the directory tree and create the corresponding
|
Look for every file in the directory tree and create the corresponding
|
||||||
ReST files.
|
ReST files.
|
||||||
"""
|
"""
|
||||||
followlinks = getattr(opts, 'followlinks', False)
|
|
||||||
includeprivate = getattr(opts, 'includeprivate', False)
|
|
||||||
implicit_namespaces = getattr(opts, 'implicit_namespaces', False)
|
implicit_namespaces = getattr(opts, 'implicit_namespaces', False)
|
||||||
|
|
||||||
# check if the base directory is a package and get its name
|
# check if the base directory is a package and get its name
|
||||||
@ -282,48 +314,36 @@ def recurse_tree(rootpath: str, excludes: List[str], opts: Any,
|
|||||||
root_package = None
|
root_package = None
|
||||||
|
|
||||||
toplevels = []
|
toplevels = []
|
||||||
for root, subs, files in os.walk(rootpath, followlinks=followlinks):
|
for root, subs, files in walk(rootpath, excludes, opts):
|
||||||
# document only Python module files (that aren't excluded)
|
is_pkg = is_packagedir(None, files)
|
||||||
py_files = sorted(f for f in files
|
|
||||||
if f.endswith(PY_SUFFIXES) and
|
|
||||||
not is_excluded(path.join(root, f), excludes))
|
|
||||||
is_pkg = is_packagedir(None, py_files)
|
|
||||||
is_namespace = not is_pkg and implicit_namespaces
|
is_namespace = not is_pkg and implicit_namespaces
|
||||||
if is_pkg:
|
if is_pkg:
|
||||||
for f in py_files[:]:
|
for f in files[:]:
|
||||||
if is_initpy(f):
|
if is_initpy(f):
|
||||||
py_files.remove(f)
|
files.remove(f)
|
||||||
py_files.insert(0, f)
|
files.insert(0, f)
|
||||||
elif root != rootpath:
|
elif root != rootpath:
|
||||||
# only accept non-package at toplevel unless using implicit namespaces
|
# only accept non-package at toplevel unless using implicit namespaces
|
||||||
if not implicit_namespaces:
|
if not implicit_namespaces:
|
||||||
del subs[:]
|
del subs[:]
|
||||||
continue
|
continue
|
||||||
# remove hidden ('.') and private ('_') directories, as well as
|
|
||||||
# excluded dirs
|
|
||||||
if includeprivate:
|
|
||||||
exclude_prefixes = ('.',) # type: Tuple[str, ...]
|
|
||||||
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 or is_namespace:
|
if is_pkg or is_namespace:
|
||||||
# we are in a package with something to document
|
# we are in a package with something to document
|
||||||
if subs or len(py_files) > 1 or not is_skipped_package(root, opts):
|
if subs or len(files) > 1 or not is_skipped_package(root, opts):
|
||||||
subpackage = root[len(rootpath):].lstrip(path.sep).\
|
subpackage = root[len(rootpath):].lstrip(path.sep).\
|
||||||
replace(path.sep, '.')
|
replace(path.sep, '.')
|
||||||
# if this is not a namespace or
|
# if this is not a namespace or
|
||||||
# a namespace and there is something there to document
|
# a namespace and there is something there to document
|
||||||
if not is_namespace or len(py_files) > 0:
|
if not is_namespace or has_child_module(root, excludes, opts):
|
||||||
create_package_file(root, root_package, subpackage,
|
create_package_file(root, root_package, subpackage,
|
||||||
py_files, opts, subs, is_namespace, excludes,
|
files, opts, subs, is_namespace, excludes,
|
||||||
user_template_dir)
|
user_template_dir)
|
||||||
toplevels.append(module_join(root_package, subpackage))
|
toplevels.append(module_join(root_package, subpackage))
|
||||||
else:
|
else:
|
||||||
# if we are at the root level, we don't require it to be a package
|
# if we are at the root level, we don't require it to be a package
|
||||||
assert root == rootpath and root_package is None
|
assert root == rootpath and root_package is None
|
||||||
for py_file in py_files:
|
for py_file in files:
|
||||||
if not is_skipped_module(path.join(rootpath, py_file), opts, excludes):
|
if not is_skipped_module(path.join(rootpath, py_file), opts, excludes):
|
||||||
module = py_file.split('.')[0]
|
module = py_file.split('.')[0]
|
||||||
create_module_file(root_package, module, opts, user_template_dir)
|
create_module_file(root_package, module, opts, user_template_dir)
|
||||||
|
@ -216,6 +216,8 @@ def test_trailing_underscore(make_app, apidoc):
|
|||||||
def test_excludes(apidoc):
|
def test_excludes(apidoc):
|
||||||
outdir = apidoc.outdir
|
outdir = apidoc.outdir
|
||||||
assert (outdir / 'conf.py').isfile()
|
assert (outdir / 'conf.py').isfile()
|
||||||
|
assert (outdir / 'a.rst').isfile()
|
||||||
|
assert (outdir / 'a.b.rst').isfile()
|
||||||
assert (outdir / 'a.b.c.rst').isfile() # generated because not empty
|
assert (outdir / 'a.b.c.rst').isfile() # generated because not empty
|
||||||
assert not (outdir / 'a.b.e.rst').isfile() # skipped because of empty after excludes
|
assert not (outdir / 'a.b.e.rst').isfile() # skipped because of empty after excludes
|
||||||
assert (outdir / 'a.b.x.rst').isfile()
|
assert (outdir / 'a.b.x.rst').isfile()
|
||||||
@ -231,6 +233,8 @@ def test_excludes_subpackage_should_be_skipped(apidoc):
|
|||||||
"""Subpackage exclusion should work."""
|
"""Subpackage exclusion should work."""
|
||||||
outdir = apidoc.outdir
|
outdir = apidoc.outdir
|
||||||
assert (outdir / 'conf.py').isfile()
|
assert (outdir / 'conf.py').isfile()
|
||||||
|
assert (outdir / 'a.rst').isfile()
|
||||||
|
assert (outdir / 'a.b.rst').isfile()
|
||||||
assert (outdir / 'a.b.c.rst').isfile() # generated because not empty
|
assert (outdir / 'a.b.c.rst').isfile() # generated because not empty
|
||||||
assert not (outdir / 'a.b.e.f.rst').isfile() # skipped because 'b/e' subpackage is skipped
|
assert not (outdir / 'a.b.e.f.rst').isfile() # skipped because 'b/e' subpackage is skipped
|
||||||
|
|
||||||
@ -244,6 +248,8 @@ def test_excludes_module_should_be_skipped(apidoc):
|
|||||||
"""Module exclusion should work."""
|
"""Module exclusion should work."""
|
||||||
outdir = apidoc.outdir
|
outdir = apidoc.outdir
|
||||||
assert (outdir / 'conf.py').isfile()
|
assert (outdir / 'conf.py').isfile()
|
||||||
|
assert (outdir / 'a.rst').isfile()
|
||||||
|
assert (outdir / 'a.b.rst').isfile()
|
||||||
assert (outdir / 'a.b.c.rst').isfile() # generated because not empty
|
assert (outdir / 'a.b.c.rst').isfile() # generated because not empty
|
||||||
assert not (outdir / 'a.b.e.f.rst').isfile() # skipped because of empty after excludes
|
assert not (outdir / 'a.b.e.f.rst').isfile() # skipped because of empty after excludes
|
||||||
|
|
||||||
@ -257,6 +263,8 @@ def test_excludes_module_should_not_be_skipped(apidoc):
|
|||||||
"""Module should be included if no excludes are used."""
|
"""Module should be included if no excludes are used."""
|
||||||
outdir = apidoc.outdir
|
outdir = apidoc.outdir
|
||||||
assert (outdir / 'conf.py').isfile()
|
assert (outdir / 'conf.py').isfile()
|
||||||
|
assert (outdir / 'a.rst').isfile()
|
||||||
|
assert (outdir / 'a.b.rst').isfile()
|
||||||
assert (outdir / 'a.b.c.rst').isfile() # generated because not empty
|
assert (outdir / 'a.b.c.rst').isfile() # generated because not empty
|
||||||
assert (outdir / 'a.b.e.f.rst').isfile() # skipped because of empty after excludes
|
assert (outdir / 'a.b.e.f.rst').isfile() # skipped because of empty after excludes
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user