mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #3929 from stephenfin/move-sphinx-apidoc-to-ext
apidoc: Move apidoc to ext/apidoc
This commit is contained in:
commit
1ef0351ec1
2
setup.py
2
setup.py
@ -241,7 +241,7 @@ setup(
|
|||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
'sphinx-build = sphinx:main',
|
'sphinx-build = sphinx:main',
|
||||||
'sphinx-quickstart = sphinx.quickstart:main',
|
'sphinx-quickstart = sphinx.quickstart:main',
|
||||||
'sphinx-apidoc = sphinx.apidoc:main',
|
'sphinx-apidoc = sphinx.ext.apidoc:main',
|
||||||
'sphinx-autogen = sphinx.ext.autosummary.generate:main',
|
'sphinx-autogen = sphinx.ext.autosummary.generate:main',
|
||||||
],
|
],
|
||||||
'distutils.commands': [
|
'distutils.commands': [
|
||||||
|
@ -11,5 +11,5 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from sphinx.apidoc import main
|
from sphinx.ext.apidoc import main
|
||||||
sys.exit(main(sys.argv[1:]))
|
sys.exit(main(sys.argv[1:]))
|
||||||
|
437
sphinx/apidoc.py
437
sphinx/apidoc.py
@ -3,437 +3,32 @@
|
|||||||
sphinx.apidoc
|
sphinx.apidoc
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
Parses a directory tree looking for Python modules and packages and creates
|
This file has moved to :py:mod:`sphinx.ext.apidoc`.
|
||||||
ReST files appropriately to create code documentation with Sphinx. It also
|
|
||||||
creates a modules index (named modules.<suffix>).
|
|
||||||
|
|
||||||
This is derived from the "sphinx-autopackage" script, which is:
|
|
||||||
Copyright 2008 Société des arts technologiques (SAT),
|
|
||||||
http://www.sat.qc.ca/
|
|
||||||
|
|
||||||
:copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS.
|
:copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS.
|
||||||
:license: BSD, see LICENSE for details.
|
:license: BSD, see LICENSE for details.
|
||||||
"""
|
"""
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import os
|
import warnings
|
||||||
import sys
|
|
||||||
import optparse
|
|
||||||
from os import path
|
|
||||||
from six import binary_type
|
|
||||||
from fnmatch import fnmatch
|
|
||||||
|
|
||||||
from sphinx import __display_version__
|
from sphinx.deprecation import RemovedInSphinx20Warning
|
||||||
from sphinx.quickstart import EXTENSIONS
|
from sphinx.ext.apidoc import main as _main
|
||||||
from sphinx.util import rst
|
|
||||||
from sphinx.util.osutil import FileAvoidWrite, walk
|
|
||||||
|
|
||||||
if False:
|
|
||||||
# For type annotation
|
|
||||||
from typing import Any, List, Tuple # NOQA
|
|
||||||
|
|
||||||
# automodule options
|
|
||||||
if 'SPHINX_APIDOC_OPTIONS' in os.environ:
|
|
||||||
OPTIONS = os.environ['SPHINX_APIDOC_OPTIONS'].split(',')
|
|
||||||
else:
|
|
||||||
OPTIONS = [
|
|
||||||
'members',
|
|
||||||
'undoc-members',
|
|
||||||
# 'inherited-members', # disabled because there's a bug in sphinx
|
|
||||||
'show-inheritance',
|
|
||||||
]
|
|
||||||
|
|
||||||
INITPY = '__init__.py'
|
|
||||||
PY_SUFFIXES = set(['.py', '.pyx'])
|
|
||||||
|
|
||||||
|
|
||||||
def makename(package, module):
|
def main(*args, **kwargs):
|
||||||
# type: (unicode, unicode) -> unicode
|
warnings.warn(
|
||||||
"""Join package and module with a dot."""
|
'`sphinx.apidoc.main()` has moved to `sphinx.ext.apidoc.main()`.',
|
||||||
# Both package and module can be None/empty.
|
RemovedInSphinx20Warning,
|
||||||
if package:
|
stacklevel=2,
|
||||||
name = package
|
)
|
||||||
if module:
|
_main(*args, **kwargs)
|
||||||
name += '.' + module
|
|
||||||
else:
|
|
||||||
name = module
|
|
||||||
return name
|
|
||||||
|
|
||||||
|
|
||||||
def write_file(name, text, opts):
|
|
||||||
# type: (unicode, unicode, Any) -> None
|
|
||||||
"""Write the output file for module/package <name>."""
|
|
||||||
fname = path.join(opts.destdir, '%s.%s' % (name, opts.suffix))
|
|
||||||
if opts.dryrun:
|
|
||||||
print('Would create file %s.' % fname)
|
|
||||||
return
|
|
||||||
if not opts.force and path.isfile(fname):
|
|
||||||
print('File %s already exists, skipping.' % fname)
|
|
||||||
else:
|
|
||||||
print('Creating file %s.' % fname)
|
|
||||||
with FileAvoidWrite(fname) as f:
|
|
||||||
f.write(text)
|
|
||||||
|
|
||||||
|
|
||||||
def format_heading(level, text, escape=True):
|
|
||||||
# type: (int, unicode, bool) -> unicode
|
|
||||||
"""Create a heading of <level> [1, 2 or 3 supported]."""
|
|
||||||
if escape:
|
|
||||||
text = rst.escape(text)
|
|
||||||
underlining = ['=', '-', '~', ][level - 1] * len(text)
|
|
||||||
return '%s\n%s\n\n' % (text, underlining)
|
|
||||||
|
|
||||||
|
|
||||||
def format_directive(module, package=None):
|
|
||||||
# type: (unicode, unicode) -> unicode
|
|
||||||
"""Create the automodule directive and add the options."""
|
|
||||||
directive = '.. automodule:: %s\n' % makename(package, module)
|
|
||||||
for option in OPTIONS:
|
|
||||||
directive += ' :%s:\n' % option
|
|
||||||
return directive
|
|
||||||
|
|
||||||
|
|
||||||
def create_module_file(package, module, opts):
|
|
||||||
# type: (unicode, unicode, Any) -> None
|
|
||||||
"""Build the text of the file and write the file."""
|
|
||||||
if not opts.noheadings:
|
|
||||||
text = format_heading(1, '%s module' % module)
|
|
||||||
else:
|
|
||||||
text = ''
|
|
||||||
# text += format_heading(2, ':mod:`%s` Module' % module)
|
|
||||||
text += format_directive(module, package)
|
|
||||||
write_file(makename(package, module), text, opts)
|
|
||||||
|
|
||||||
|
|
||||||
def create_package_file(root, master_package, subroot, py_files, opts, subs, is_namespace):
|
|
||||||
# type: (unicode, unicode, unicode, List[unicode], Any, List[unicode], bool) -> None
|
|
||||||
"""Build the text of the file and write the file."""
|
|
||||||
text = format_heading(1, ('%s package' if not is_namespace else "%s namespace")
|
|
||||||
% makename(master_package, subroot))
|
|
||||||
|
|
||||||
if opts.modulefirst and not is_namespace:
|
|
||||||
text += format_directive(subroot, master_package)
|
|
||||||
text += '\n'
|
|
||||||
|
|
||||||
# build a list of directories that are szvpackages (contain an INITPY file)
|
|
||||||
subs = [sub for sub in subs if path.isfile(path.join(root, sub, INITPY))]
|
|
||||||
# if there are some package directories, add a TOC for theses subpackages
|
|
||||||
if subs:
|
|
||||||
text += format_heading(2, 'Subpackages')
|
|
||||||
text += '.. toctree::\n\n'
|
|
||||||
for sub in subs:
|
|
||||||
text += ' %s.%s\n' % (makename(master_package, subroot), sub)
|
|
||||||
text += '\n'
|
|
||||||
|
|
||||||
submods = [path.splitext(sub)[0] for sub in py_files
|
|
||||||
if not shall_skip(path.join(root, sub), opts) and
|
|
||||||
sub != INITPY]
|
|
||||||
if submods:
|
|
||||||
text += format_heading(2, 'Submodules')
|
|
||||||
if opts.separatemodules:
|
|
||||||
text += '.. toctree::\n\n'
|
|
||||||
for submod in submods:
|
|
||||||
modfile = makename(master_package, makename(subroot, submod))
|
|
||||||
text += ' %s\n' % modfile
|
|
||||||
|
|
||||||
# generate separate file for this module
|
|
||||||
if not opts.noheadings:
|
|
||||||
filetext = format_heading(1, '%s module' % modfile)
|
|
||||||
else:
|
|
||||||
filetext = ''
|
|
||||||
filetext += format_directive(makename(subroot, submod),
|
|
||||||
master_package)
|
|
||||||
write_file(modfile, filetext, opts)
|
|
||||||
else:
|
|
||||||
for submod in submods:
|
|
||||||
modfile = makename(master_package, makename(subroot, submod))
|
|
||||||
if not opts.noheadings:
|
|
||||||
text += format_heading(2, '%s module' % modfile)
|
|
||||||
text += format_directive(makename(subroot, submod),
|
|
||||||
master_package)
|
|
||||||
text += '\n'
|
|
||||||
text += '\n'
|
|
||||||
|
|
||||||
if not opts.modulefirst and not is_namespace:
|
|
||||||
text += format_heading(2, 'Module contents')
|
|
||||||
text += format_directive(subroot, master_package)
|
|
||||||
|
|
||||||
write_file(makename(master_package, subroot), text, opts)
|
|
||||||
|
|
||||||
|
|
||||||
def create_modules_toc_file(modules, opts, name='modules'):
|
|
||||||
# type: (List[unicode], Any, unicode) -> None
|
|
||||||
"""Create the module's index."""
|
|
||||||
text = format_heading(1, '%s' % opts.header, escape=False)
|
|
||||||
text += '.. toctree::\n'
|
|
||||||
text += ' :maxdepth: %s\n\n' % opts.maxdepth
|
|
||||||
|
|
||||||
modules.sort()
|
|
||||||
prev_module = '' # type: unicode
|
|
||||||
for module in modules:
|
|
||||||
# look if the module is a subpackage and, if yes, ignore it
|
|
||||||
if module.startswith(prev_module + '.'):
|
|
||||||
continue
|
|
||||||
prev_module = module
|
|
||||||
text += ' %s\n' % module
|
|
||||||
|
|
||||||
write_file(name, text, opts)
|
|
||||||
|
|
||||||
|
|
||||||
def shall_skip(module, opts):
|
|
||||||
# type: (unicode, Any) -> bool
|
|
||||||
"""Check if we want to skip this module."""
|
|
||||||
# skip if the file doesn't exist and not using implicit namespaces
|
|
||||||
if not opts.implicit_namespaces and not path.exists(module):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# skip it if there is nothing (or just \n or \r\n) in the file
|
|
||||||
if path.exists(module) and path.getsize(module) <= 2:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# skip if it has a "private" name and this is selected
|
|
||||||
filename = path.basename(module)
|
|
||||||
if filename != '__init__.py' and filename.startswith('_') and \
|
|
||||||
not opts.includeprivate:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def recurse_tree(rootpath, excludes, opts):
|
|
||||||
# type: (unicode, List[unicode], Any) -> List[unicode]
|
|
||||||
"""
|
|
||||||
Look for every file in the directory tree and create the corresponding
|
|
||||||
ReST files.
|
|
||||||
"""
|
|
||||||
followlinks = getattr(opts, 'followlinks', False)
|
|
||||||
includeprivate = getattr(opts, 'includeprivate', False)
|
|
||||||
implicit_namespaces = getattr(opts, 'implicit_namespaces', False)
|
|
||||||
|
|
||||||
# check if the base directory is a package and get its name
|
|
||||||
if INITPY in os.listdir(rootpath) or implicit_namespaces:
|
|
||||||
root_package = rootpath.split(path.sep)[-1]
|
|
||||||
else:
|
|
||||||
# otherwise, the base is a directory with packages
|
|
||||||
root_package = None
|
|
||||||
|
|
||||||
toplevels = []
|
|
||||||
for root, subs, files in walk(rootpath, followlinks=followlinks):
|
|
||||||
# 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 and
|
|
||||||
not is_excluded(path.join(root, f), excludes))
|
|
||||||
is_pkg = INITPY in py_files
|
|
||||||
is_namespace = INITPY not in py_files and implicit_namespaces
|
|
||||||
if is_pkg:
|
|
||||||
py_files.remove(INITPY)
|
|
||||||
py_files.insert(0, INITPY)
|
|
||||||
elif root != rootpath:
|
|
||||||
# only accept non-package at toplevel unless using implicit namespaces
|
|
||||||
if not implicit_namespaces:
|
|
||||||
del subs[:]
|
|
||||||
continue
|
|
||||||
# remove hidden ('.') and private ('_') directories, as well as
|
|
||||||
# excluded dirs
|
|
||||||
if includeprivate:
|
|
||||||
exclude_prefixes = ('.',) # type: Tuple[unicode, ...]
|
|
||||||
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:
|
|
||||||
# we are in a package with something to document
|
|
||||||
if subs or len(py_files) > 1 or not shall_skip(path.join(root, INITPY), opts):
|
|
||||||
subpackage = root[len(rootpath):].lstrip(path.sep).\
|
|
||||||
replace(path.sep, '.')
|
|
||||||
# if this is not a namespace or
|
|
||||||
# a namespace and there is something there to document
|
|
||||||
if not is_namespace or len(py_files) > 0:
|
|
||||||
create_package_file(root, root_package, subpackage,
|
|
||||||
py_files, opts, subs, is_namespace)
|
|
||||||
toplevels.append(makename(root_package, subpackage))
|
|
||||||
else:
|
|
||||||
# if we are at the root level, we don't require it to be a package
|
|
||||||
assert root == rootpath and root_package is None
|
|
||||||
for py_file in py_files:
|
|
||||||
if not shall_skip(path.join(rootpath, py_file), opts):
|
|
||||||
module = path.splitext(py_file)[0]
|
|
||||||
create_module_file(root_package, module, opts)
|
|
||||||
toplevels.append(module)
|
|
||||||
|
|
||||||
return toplevels
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_excludes(rootpath, excludes):
|
|
||||||
# type: (unicode, List[unicode]) -> List[unicode]
|
|
||||||
"""Normalize the excluded directory list."""
|
|
||||||
return [path.abspath(exclude) for exclude in excludes]
|
|
||||||
|
|
||||||
|
|
||||||
def is_excluded(root, excludes):
|
|
||||||
# type: (unicode, List[unicode]) -> bool
|
|
||||||
"""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".
|
|
||||||
"""
|
|
||||||
for exclude in excludes:
|
|
||||||
if fnmatch(root, exclude):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv=sys.argv[1:]):
|
|
||||||
# type: (List[str]) -> int
|
|
||||||
"""Parse and check the command line arguments."""
|
|
||||||
parser = optparse.OptionParser(
|
|
||||||
usage="""\
|
|
||||||
usage: %prog [options] -o <output_path> <module_path> [exclude_pattern, ...]
|
|
||||||
|
|
||||||
Look recursively in <module_path> for Python modules and packages and create
|
|
||||||
one reST file with automodule directives per package in the <output_path>.
|
|
||||||
|
|
||||||
The <exclude_pattern>s can be file and/or directory patterns 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',
|
|
||||||
help='Directory to place all output', default='')
|
|
||||||
parser.add_option('-d', '--maxdepth', action='store', dest='maxdepth',
|
|
||||||
help='Maximum depth of submodules to show in the TOC '
|
|
||||||
'(default: 4)', type='int', default=4)
|
|
||||||
parser.add_option('-f', '--force', action='store_true', dest='force',
|
|
||||||
help='Overwrite existing files')
|
|
||||||
parser.add_option('-l', '--follow-links', action='store_true',
|
|
||||||
dest='followlinks', default=False,
|
|
||||||
help='Follow symbolic links. Powerful when combined '
|
|
||||||
'with collective.recipe.omelette.')
|
|
||||||
parser.add_option('-n', '--dry-run', action='store_true', dest='dryrun',
|
|
||||||
help='Run the script without creating files')
|
|
||||||
parser.add_option('-e', '--separate', action='store_true',
|
|
||||||
dest='separatemodules',
|
|
||||||
help='Put documentation for each module on its own page')
|
|
||||||
parser.add_option('-P', '--private', action='store_true',
|
|
||||||
dest='includeprivate',
|
|
||||||
help='Include "_private" modules')
|
|
||||||
parser.add_option('-T', '--no-toc', action='store_true', dest='notoc',
|
|
||||||
help='Don\'t create a table of contents file')
|
|
||||||
parser.add_option('-E', '--no-headings', action='store_true',
|
|
||||||
dest='noheadings',
|
|
||||||
help='Don\'t create headings for the module/package '
|
|
||||||
'packages (e.g. when the docstrings already contain '
|
|
||||||
'them)')
|
|
||||||
parser.add_option('-M', '--module-first', action='store_true',
|
|
||||||
dest='modulefirst',
|
|
||||||
help='Put module documentation before submodule '
|
|
||||||
'documentation')
|
|
||||||
parser.add_option('--implicit-namespaces', action='store_true',
|
|
||||||
dest='implicit_namespaces',
|
|
||||||
help='Interpret module paths according to PEP-0420 '
|
|
||||||
'implicit namespaces specification')
|
|
||||||
parser.add_option('-s', '--suffix', action='store', dest='suffix',
|
|
||||||
help='file suffix (default: rst)', default='rst')
|
|
||||||
parser.add_option('-F', '--full', action='store_true', dest='full',
|
|
||||||
help='Generate a full project with sphinx-quickstart')
|
|
||||||
parser.add_option('-a', '--append-syspath', action='store_true',
|
|
||||||
dest='append_syspath',
|
|
||||||
help='Append module_path to sys.path, used when --full is given')
|
|
||||||
parser.add_option('-H', '--doc-project', action='store', dest='header',
|
|
||||||
help='Project name (default: root module name)')
|
|
||||||
parser.add_option('-A', '--doc-author', action='store', dest='author',
|
|
||||||
type='str',
|
|
||||||
help='Project author(s), used when --full is given')
|
|
||||||
parser.add_option('-V', '--doc-version', action='store', dest='version',
|
|
||||||
help='Project version, used when --full is given')
|
|
||||||
parser.add_option('-R', '--doc-release', action='store', dest='release',
|
|
||||||
help='Project release, used when --full is given, '
|
|
||||||
'defaults to --doc-version')
|
|
||||||
parser.add_option('--version', action='store_true', dest='show_version',
|
|
||||||
help='Show version information and exit')
|
|
||||||
group = parser.add_option_group('Extension options')
|
|
||||||
for ext in EXTENSIONS:
|
|
||||||
group.add_option('--ext-' + ext, action='store_true',
|
|
||||||
dest='ext_' + ext, default=False,
|
|
||||||
help='enable %s extension' % ext)
|
|
||||||
|
|
||||||
(opts, args) = parser.parse_args(argv)
|
|
||||||
|
|
||||||
if opts.show_version:
|
|
||||||
print('Sphinx (sphinx-apidoc) %s' % __display_version__)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if not args:
|
|
||||||
parser.error('A package path is required.')
|
|
||||||
|
|
||||||
rootpath, excludes = args[0], args[1:]
|
|
||||||
if not opts.destdir:
|
|
||||||
parser.error('An output directory is required.')
|
|
||||||
if opts.header is None:
|
|
||||||
opts.header = path.abspath(rootpath).split(path.sep)[-1]
|
|
||||||
if opts.suffix.startswith('.'):
|
|
||||||
opts.suffix = opts.suffix[1:]
|
|
||||||
if not path.isdir(rootpath):
|
|
||||||
print('%s is not a directory.' % rootpath, file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
if not path.isdir(opts.destdir):
|
|
||||||
if not opts.dryrun:
|
|
||||||
os.makedirs(opts.destdir)
|
|
||||||
rootpath = path.abspath(rootpath)
|
|
||||||
excludes = normalize_excludes(rootpath, excludes)
|
|
||||||
modules = recurse_tree(rootpath, excludes, opts)
|
|
||||||
if opts.full:
|
|
||||||
from sphinx import quickstart as qs
|
|
||||||
modules.sort()
|
|
||||||
prev_module = '' # type: unicode
|
|
||||||
text = ''
|
|
||||||
for module in modules:
|
|
||||||
if module.startswith(prev_module + '.'):
|
|
||||||
continue
|
|
||||||
prev_module = module
|
|
||||||
text += ' %s\n' % module
|
|
||||||
d = dict(
|
|
||||||
path = opts.destdir,
|
|
||||||
sep = False,
|
|
||||||
dot = '_',
|
|
||||||
project = opts.header,
|
|
||||||
author = opts.author or 'Author',
|
|
||||||
version = opts.version or '',
|
|
||||||
release = opts.release or opts.version or '',
|
|
||||||
suffix = '.' + opts.suffix,
|
|
||||||
master = 'index',
|
|
||||||
epub = True,
|
|
||||||
ext_autodoc = True,
|
|
||||||
ext_viewcode = True,
|
|
||||||
ext_todo = True,
|
|
||||||
makefile = True,
|
|
||||||
batchfile = True,
|
|
||||||
mastertocmaxdepth = opts.maxdepth,
|
|
||||||
mastertoctree = text,
|
|
||||||
language = 'en',
|
|
||||||
module_path = rootpath,
|
|
||||||
append_syspath = opts.append_syspath,
|
|
||||||
)
|
|
||||||
enabled_exts = {'ext_' + ext: getattr(opts, 'ext_' + ext)
|
|
||||||
for ext in EXTENSIONS if getattr(opts, 'ext_' + ext)}
|
|
||||||
d.update(enabled_exts)
|
|
||||||
|
|
||||||
if isinstance(opts.header, binary_type):
|
|
||||||
d['project'] = d['project'].decode('utf-8')
|
|
||||||
if isinstance(opts.author, binary_type):
|
|
||||||
d['author'] = d['author'].decode('utf-8')
|
|
||||||
if isinstance(opts.version, binary_type):
|
|
||||||
d['version'] = d['version'].decode('utf-8')
|
|
||||||
if isinstance(opts.release, binary_type):
|
|
||||||
d['release'] = d['release'].decode('utf-8')
|
|
||||||
|
|
||||||
if not opts.dryrun:
|
|
||||||
qs.generate(d, silent=True, overwrite=opts.force)
|
|
||||||
elif not opts.notoc:
|
|
||||||
create_modules_toc_file(modules, opts)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
# So program can be started with "python -m sphinx.apidoc ..."
|
# So program can be started with "python -m sphinx.apidoc ..."
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
warnings.warn(
|
||||||
|
'`sphinx.apidoc` has moved to `sphinx.ext.apidoc`.',
|
||||||
|
RemovedInSphinx20Warning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
main()
|
main()
|
||||||
|
440
sphinx/ext/apidoc.py
Normal file
440
sphinx/ext/apidoc.py
Normal file
@ -0,0 +1,440 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
sphinx.ext.apidoc
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Parses a directory tree looking for Python modules and packages and creates
|
||||||
|
ReST files appropriately to create code documentation with Sphinx. It also
|
||||||
|
creates a modules index (named modules.<suffix>).
|
||||||
|
|
||||||
|
This is derived from the "sphinx-autopackage" script, which is:
|
||||||
|
Copyright 2008 Société des arts technologiques (SAT),
|
||||||
|
http://www.sat.qc.ca/
|
||||||
|
|
||||||
|
:copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS.
|
||||||
|
:license: BSD, see LICENSE for details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import optparse
|
||||||
|
from os import path
|
||||||
|
from six import binary_type
|
||||||
|
from fnmatch import fnmatch
|
||||||
|
|
||||||
|
from sphinx import __display_version__
|
||||||
|
from sphinx.quickstart import EXTENSIONS
|
||||||
|
from sphinx.util import rst
|
||||||
|
from sphinx.util.osutil import FileAvoidWrite, walk
|
||||||
|
|
||||||
|
if False:
|
||||||
|
# For type annotation
|
||||||
|
from typing import Any, List, Tuple # NOQA
|
||||||
|
|
||||||
|
# automodule options
|
||||||
|
if 'SPHINX_APIDOC_OPTIONS' in os.environ:
|
||||||
|
OPTIONS = os.environ['SPHINX_APIDOC_OPTIONS'].split(',')
|
||||||
|
else:
|
||||||
|
OPTIONS = [
|
||||||
|
'members',
|
||||||
|
'undoc-members',
|
||||||
|
# 'inherited-members', # disabled because there's a bug in sphinx
|
||||||
|
'show-inheritance',
|
||||||
|
]
|
||||||
|
|
||||||
|
INITPY = '__init__.py'
|
||||||
|
PY_SUFFIXES = set(['.py', '.pyx'])
|
||||||
|
|
||||||
|
|
||||||
|
def makename(package, module):
|
||||||
|
# type: (unicode, unicode) -> unicode
|
||||||
|
"""Join package and module with a dot."""
|
||||||
|
# Both package and module can be None/empty.
|
||||||
|
if package:
|
||||||
|
name = package
|
||||||
|
if module:
|
||||||
|
name += '.' + module
|
||||||
|
else:
|
||||||
|
name = module
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
def write_file(name, text, opts):
|
||||||
|
# type: (unicode, unicode, Any) -> None
|
||||||
|
"""Write the output file for module/package <name>."""
|
||||||
|
fname = path.join(opts.destdir, '%s.%s' % (name, opts.suffix))
|
||||||
|
if opts.dryrun:
|
||||||
|
print('Would create file %s.' % fname)
|
||||||
|
return
|
||||||
|
if not opts.force and path.isfile(fname):
|
||||||
|
print('File %s already exists, skipping.' % fname)
|
||||||
|
else:
|
||||||
|
print('Creating file %s.' % fname)
|
||||||
|
with FileAvoidWrite(fname) as f:
|
||||||
|
f.write(text)
|
||||||
|
|
||||||
|
|
||||||
|
def format_heading(level, text, escape=True):
|
||||||
|
# type: (int, unicode, bool) -> unicode
|
||||||
|
"""Create a heading of <level> [1, 2 or 3 supported]."""
|
||||||
|
if escape:
|
||||||
|
text = rst.escape(text)
|
||||||
|
underlining = ['=', '-', '~', ][level - 1] * len(text)
|
||||||
|
return '%s\n%s\n\n' % (text, underlining)
|
||||||
|
|
||||||
|
|
||||||
|
def format_directive(module, package=None):
|
||||||
|
# type: (unicode, unicode) -> unicode
|
||||||
|
"""Create the automodule directive and add the options."""
|
||||||
|
directive = '.. automodule:: %s\n' % makename(package, module)
|
||||||
|
for option in OPTIONS:
|
||||||
|
directive += ' :%s:\n' % option
|
||||||
|
return directive
|
||||||
|
|
||||||
|
|
||||||
|
def create_module_file(package, module, opts):
|
||||||
|
# type: (unicode, unicode, Any) -> None
|
||||||
|
"""Build the text of the file and write the file."""
|
||||||
|
if not opts.noheadings:
|
||||||
|
text = format_heading(1, '%s module' % module)
|
||||||
|
else:
|
||||||
|
text = ''
|
||||||
|
# text += format_heading(2, ':mod:`%s` Module' % module)
|
||||||
|
text += format_directive(module, package)
|
||||||
|
write_file(makename(package, module), text, opts)
|
||||||
|
|
||||||
|
|
||||||
|
def create_package_file(root, master_package, subroot, py_files, opts, subs, is_namespace):
|
||||||
|
# type: (unicode, unicode, unicode, List[unicode], Any, List[unicode], bool) -> None
|
||||||
|
"""Build the text of the file and write the file."""
|
||||||
|
text = format_heading(1, ('%s package' if not is_namespace else "%s namespace")
|
||||||
|
% makename(master_package, subroot))
|
||||||
|
|
||||||
|
if opts.modulefirst and not is_namespace:
|
||||||
|
text += format_directive(subroot, master_package)
|
||||||
|
text += '\n'
|
||||||
|
|
||||||
|
# build a list of directories that are szvpackages (contain an INITPY file)
|
||||||
|
subs = [sub for sub in subs if path.isfile(path.join(root, sub, INITPY))]
|
||||||
|
# if there are some package directories, add a TOC for theses subpackages
|
||||||
|
if subs:
|
||||||
|
text += format_heading(2, 'Subpackages')
|
||||||
|
text += '.. toctree::\n\n'
|
||||||
|
for sub in subs:
|
||||||
|
text += ' %s.%s\n' % (makename(master_package, subroot), sub)
|
||||||
|
text += '\n'
|
||||||
|
|
||||||
|
submods = [path.splitext(sub)[0] for sub in py_files
|
||||||
|
if not shall_skip(path.join(root, sub), opts) and
|
||||||
|
sub != INITPY]
|
||||||
|
if submods:
|
||||||
|
text += format_heading(2, 'Submodules')
|
||||||
|
if opts.separatemodules:
|
||||||
|
text += '.. toctree::\n\n'
|
||||||
|
for submod in submods:
|
||||||
|
modfile = makename(master_package, makename(subroot, submod))
|
||||||
|
text += ' %s\n' % modfile
|
||||||
|
|
||||||
|
# generate separate file for this module
|
||||||
|
if not opts.noheadings:
|
||||||
|
filetext = format_heading(1, '%s module' % modfile)
|
||||||
|
else:
|
||||||
|
filetext = ''
|
||||||
|
filetext += format_directive(makename(subroot, submod),
|
||||||
|
master_package)
|
||||||
|
write_file(modfile, filetext, opts)
|
||||||
|
else:
|
||||||
|
for submod in submods:
|
||||||
|
modfile = makename(master_package, makename(subroot, submod))
|
||||||
|
if not opts.noheadings:
|
||||||
|
text += format_heading(2, '%s module' % modfile)
|
||||||
|
text += format_directive(makename(subroot, submod),
|
||||||
|
master_package)
|
||||||
|
text += '\n'
|
||||||
|
text += '\n'
|
||||||
|
|
||||||
|
if not opts.modulefirst and not is_namespace:
|
||||||
|
text += format_heading(2, 'Module contents')
|
||||||
|
text += format_directive(subroot, master_package)
|
||||||
|
|
||||||
|
write_file(makename(master_package, subroot), text, opts)
|
||||||
|
|
||||||
|
|
||||||
|
def create_modules_toc_file(modules, opts, name='modules'):
|
||||||
|
# type: (List[unicode], Any, unicode) -> None
|
||||||
|
"""Create the module's index."""
|
||||||
|
text = format_heading(1, '%s' % opts.header, escape=False)
|
||||||
|
text += '.. toctree::\n'
|
||||||
|
text += ' :maxdepth: %s\n\n' % opts.maxdepth
|
||||||
|
|
||||||
|
modules.sort()
|
||||||
|
prev_module = '' # type: unicode
|
||||||
|
for module in modules:
|
||||||
|
# look if the module is a subpackage and, if yes, ignore it
|
||||||
|
if module.startswith(prev_module + '.'):
|
||||||
|
continue
|
||||||
|
prev_module = module
|
||||||
|
text += ' %s\n' % module
|
||||||
|
|
||||||
|
write_file(name, text, opts)
|
||||||
|
|
||||||
|
|
||||||
|
def shall_skip(module, opts):
|
||||||
|
# type: (unicode, Any) -> bool
|
||||||
|
"""Check if we want to skip this module."""
|
||||||
|
# skip if the file doesn't exist and not using implicit namespaces
|
||||||
|
if not opts.implicit_namespaces and not path.exists(module):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# skip it if there is nothing (or just \n or \r\n) in the file
|
||||||
|
if path.exists(module) and path.getsize(module) <= 2:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# skip if it has a "private" name and this is selected
|
||||||
|
filename = path.basename(module)
|
||||||
|
if filename != '__init__.py' and filename.startswith('_') and \
|
||||||
|
not opts.includeprivate:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def recurse_tree(rootpath, excludes, opts):
|
||||||
|
# type: (unicode, List[unicode], Any) -> List[unicode]
|
||||||
|
"""
|
||||||
|
Look for every file in the directory tree and create the corresponding
|
||||||
|
ReST files.
|
||||||
|
"""
|
||||||
|
followlinks = getattr(opts, 'followlinks', False)
|
||||||
|
includeprivate = getattr(opts, 'includeprivate', False)
|
||||||
|
implicit_namespaces = getattr(opts, 'implicit_namespaces', False)
|
||||||
|
|
||||||
|
# check if the base directory is a package and get its name
|
||||||
|
if INITPY in os.listdir(rootpath) or implicit_namespaces:
|
||||||
|
root_package = rootpath.split(path.sep)[-1]
|
||||||
|
else:
|
||||||
|
# otherwise, the base is a directory with packages
|
||||||
|
root_package = None
|
||||||
|
|
||||||
|
toplevels = []
|
||||||
|
for root, subs, files in walk(rootpath, followlinks=followlinks):
|
||||||
|
# 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 and
|
||||||
|
not is_excluded(path.join(root, f), excludes))
|
||||||
|
is_pkg = INITPY in py_files
|
||||||
|
is_namespace = INITPY not in py_files and implicit_namespaces
|
||||||
|
if is_pkg:
|
||||||
|
py_files.remove(INITPY)
|
||||||
|
py_files.insert(0, INITPY)
|
||||||
|
elif root != rootpath:
|
||||||
|
# only accept non-package at toplevel unless using implicit namespaces
|
||||||
|
if not implicit_namespaces:
|
||||||
|
del subs[:]
|
||||||
|
continue
|
||||||
|
# remove hidden ('.') and private ('_') directories, as well as
|
||||||
|
# excluded dirs
|
||||||
|
if includeprivate:
|
||||||
|
exclude_prefixes = ('.',) # type: Tuple[unicode, ...]
|
||||||
|
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:
|
||||||
|
# we are in a package with something to document
|
||||||
|
if subs or len(py_files) > 1 or not shall_skip(path.join(root, INITPY), opts):
|
||||||
|
subpackage = root[len(rootpath):].lstrip(path.sep).\
|
||||||
|
replace(path.sep, '.')
|
||||||
|
# if this is not a namespace or
|
||||||
|
# a namespace and there is something there to document
|
||||||
|
if not is_namespace or len(py_files) > 0:
|
||||||
|
create_package_file(root, root_package, subpackage,
|
||||||
|
py_files, opts, subs, is_namespace)
|
||||||
|
toplevels.append(makename(root_package, subpackage))
|
||||||
|
else:
|
||||||
|
# if we are at the root level, we don't require it to be a package
|
||||||
|
assert root == rootpath and root_package is None
|
||||||
|
for py_file in py_files:
|
||||||
|
if not shall_skip(path.join(rootpath, py_file), opts):
|
||||||
|
module = path.splitext(py_file)[0]
|
||||||
|
create_module_file(root_package, module, opts)
|
||||||
|
toplevels.append(module)
|
||||||
|
|
||||||
|
return toplevels
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_excludes(rootpath, excludes):
|
||||||
|
# type: (unicode, List[unicode]) -> List[unicode]
|
||||||
|
"""Normalize the excluded directory list."""
|
||||||
|
return [path.abspath(exclude) for exclude in excludes]
|
||||||
|
|
||||||
|
|
||||||
|
def is_excluded(root, excludes):
|
||||||
|
# type: (unicode, List[unicode]) -> bool
|
||||||
|
"""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".
|
||||||
|
"""
|
||||||
|
for exclude in excludes:
|
||||||
|
if fnmatch(root, exclude):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=sys.argv[1:]):
|
||||||
|
# type: (List[str]) -> int
|
||||||
|
"""Parse and check the command line arguments."""
|
||||||
|
parser = optparse.OptionParser(
|
||||||
|
usage="""\
|
||||||
|
usage: %prog [options] -o <output_path> <module_path> [exclude_pattern, ...]
|
||||||
|
|
||||||
|
Look recursively in <module_path> for Python modules and packages and create
|
||||||
|
one reST file with automodule directives per package in the <output_path>.
|
||||||
|
|
||||||
|
The <exclude_pattern>s can be file and/or directory patterns 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',
|
||||||
|
help='Directory to place all output', default='')
|
||||||
|
parser.add_option('-d', '--maxdepth', action='store', dest='maxdepth',
|
||||||
|
help='Maximum depth of submodules to show in the TOC '
|
||||||
|
'(default: 4)', type='int', default=4)
|
||||||
|
parser.add_option('-f', '--force', action='store_true', dest='force',
|
||||||
|
help='Overwrite existing files')
|
||||||
|
parser.add_option('-l', '--follow-links', action='store_true',
|
||||||
|
dest='followlinks', default=False,
|
||||||
|
help='Follow symbolic links. Powerful when combined '
|
||||||
|
'with collective.recipe.omelette.')
|
||||||
|
parser.add_option('-n', '--dry-run', action='store_true', dest='dryrun',
|
||||||
|
help='Run the script without creating files')
|
||||||
|
parser.add_option('-e', '--separate', action='store_true',
|
||||||
|
dest='separatemodules',
|
||||||
|
help='Put documentation for each module on its own page')
|
||||||
|
parser.add_option('-P', '--private', action='store_true',
|
||||||
|
dest='includeprivate',
|
||||||
|
help='Include "_private" modules')
|
||||||
|
parser.add_option('-T', '--no-toc', action='store_true', dest='notoc',
|
||||||
|
help='Don\'t create a table of contents file')
|
||||||
|
parser.add_option('-E', '--no-headings', action='store_true',
|
||||||
|
dest='noheadings',
|
||||||
|
help='Don\'t create headings for the module/package '
|
||||||
|
'packages (e.g. when the docstrings already contain '
|
||||||
|
'them)')
|
||||||
|
parser.add_option('-M', '--module-first', action='store_true',
|
||||||
|
dest='modulefirst',
|
||||||
|
help='Put module documentation before submodule '
|
||||||
|
'documentation')
|
||||||
|
parser.add_option('--implicit-namespaces', action='store_true',
|
||||||
|
dest='implicit_namespaces',
|
||||||
|
help='Interpret module paths according to PEP-0420 '
|
||||||
|
'implicit namespaces specification')
|
||||||
|
parser.add_option('-s', '--suffix', action='store', dest='suffix',
|
||||||
|
help='file suffix (default: rst)', default='rst')
|
||||||
|
parser.add_option('-F', '--full', action='store_true', dest='full',
|
||||||
|
help='Generate a full project with sphinx-quickstart')
|
||||||
|
parser.add_option('-a', '--append-syspath', action='store_true',
|
||||||
|
dest='append_syspath',
|
||||||
|
help='Append module_path to sys.path, used when --full is given')
|
||||||
|
parser.add_option('-H', '--doc-project', action='store', dest='header',
|
||||||
|
help='Project name (default: root module name)')
|
||||||
|
parser.add_option('-A', '--doc-author', action='store', dest='author',
|
||||||
|
type='str',
|
||||||
|
help='Project author(s), used when --full is given')
|
||||||
|
parser.add_option('-V', '--doc-version', action='store', dest='version',
|
||||||
|
help='Project version, used when --full is given')
|
||||||
|
parser.add_option('-R', '--doc-release', action='store', dest='release',
|
||||||
|
help='Project release, used when --full is given, '
|
||||||
|
'defaults to --doc-version')
|
||||||
|
parser.add_option('--version', action='store_true', dest='show_version',
|
||||||
|
help='Show version information and exit')
|
||||||
|
group = parser.add_option_group('Extension options')
|
||||||
|
for ext in EXTENSIONS:
|
||||||
|
group.add_option('--ext-' + ext, action='store_true',
|
||||||
|
dest='ext_' + ext, default=False,
|
||||||
|
help='enable %s extension' % ext)
|
||||||
|
|
||||||
|
(opts, args) = parser.parse_args(argv)
|
||||||
|
|
||||||
|
if opts.show_version:
|
||||||
|
print('Sphinx (sphinx-apidoc) %s' % __display_version__)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if not args:
|
||||||
|
parser.error('A package path is required.')
|
||||||
|
|
||||||
|
rootpath, excludes = args[0], args[1:]
|
||||||
|
if not opts.destdir:
|
||||||
|
parser.error('An output directory is required.')
|
||||||
|
if opts.header is None:
|
||||||
|
opts.header = path.abspath(rootpath).split(path.sep)[-1]
|
||||||
|
if opts.suffix.startswith('.'):
|
||||||
|
opts.suffix = opts.suffix[1:]
|
||||||
|
if not path.isdir(rootpath):
|
||||||
|
print('%s is not a directory.' % rootpath, file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
if not path.isdir(opts.destdir):
|
||||||
|
if not opts.dryrun:
|
||||||
|
os.makedirs(opts.destdir)
|
||||||
|
rootpath = path.abspath(rootpath)
|
||||||
|
excludes = normalize_excludes(rootpath, excludes)
|
||||||
|
modules = recurse_tree(rootpath, excludes, opts)
|
||||||
|
if opts.full:
|
||||||
|
from sphinx import quickstart as qs
|
||||||
|
modules.sort()
|
||||||
|
prev_module = '' # type: unicode
|
||||||
|
text = ''
|
||||||
|
for module in modules:
|
||||||
|
if module.startswith(prev_module + '.'):
|
||||||
|
continue
|
||||||
|
prev_module = module
|
||||||
|
text += ' %s\n' % module
|
||||||
|
d = dict(
|
||||||
|
path = opts.destdir,
|
||||||
|
sep = False,
|
||||||
|
dot = '_',
|
||||||
|
project = opts.header,
|
||||||
|
author = opts.author or 'Author',
|
||||||
|
version = opts.version or '',
|
||||||
|
release = opts.release or opts.version or '',
|
||||||
|
suffix = '.' + opts.suffix,
|
||||||
|
master = 'index',
|
||||||
|
epub = True,
|
||||||
|
ext_autodoc = True,
|
||||||
|
ext_viewcode = True,
|
||||||
|
ext_todo = True,
|
||||||
|
makefile = True,
|
||||||
|
batchfile = True,
|
||||||
|
mastertocmaxdepth = opts.maxdepth,
|
||||||
|
mastertoctree = text,
|
||||||
|
language = 'en',
|
||||||
|
module_path = rootpath,
|
||||||
|
append_syspath = opts.append_syspath,
|
||||||
|
)
|
||||||
|
enabled_exts = {'ext_' + ext: getattr(opts, 'ext_' + ext)
|
||||||
|
for ext in EXTENSIONS if getattr(opts, 'ext_' + ext)}
|
||||||
|
d.update(enabled_exts)
|
||||||
|
|
||||||
|
if isinstance(opts.header, binary_type):
|
||||||
|
d['project'] = d['project'].decode('utf-8')
|
||||||
|
if isinstance(opts.author, binary_type):
|
||||||
|
d['author'] = d['author'].decode('utf-8')
|
||||||
|
if isinstance(opts.version, binary_type):
|
||||||
|
d['version'] = d['version'].decode('utf-8')
|
||||||
|
if isinstance(opts.release, binary_type):
|
||||||
|
d['release'] = d['release'].decode('utf-8')
|
||||||
|
|
||||||
|
if not opts.dryrun:
|
||||||
|
qs.generate(d, silent=True, overwrite=opts.force)
|
||||||
|
elif not opts.notoc:
|
||||||
|
create_modules_toc_file(modules, opts)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
# So program can be started with "python -m sphinx.apidoc ..."
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -15,7 +15,7 @@ from collections import namedtuple
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from sphinx.apidoc import main as apidoc_main
|
from sphinx.ext.apidoc import main as apidoc_main
|
||||||
|
|
||||||
from sphinx.testing.util import remove_unicode_literals
|
from sphinx.testing.util import remove_unicode_literals
|
||||||
|
|
Loading…
Reference in New Issue
Block a user