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': [
|
||||
'sphinx-build = sphinx:main',
|
||||
'sphinx-quickstart = sphinx.quickstart:main',
|
||||
'sphinx-apidoc = sphinx.apidoc:main',
|
||||
'sphinx-apidoc = sphinx.ext.apidoc:main',
|
||||
'sphinx-autogen = sphinx.ext.autosummary.generate:main',
|
||||
],
|
||||
'distutils.commands': [
|
||||
|
@ -11,5 +11,5 @@
|
||||
import sys
|
||||
|
||||
if __name__ == '__main__':
|
||||
from sphinx.apidoc import main
|
||||
from sphinx.ext.apidoc import main
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
|
435
sphinx/apidoc.py
435
sphinx/apidoc.py
@ -3,437 +3,32 @@
|
||||
sphinx.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/
|
||||
This file has moved to :py:mod:`sphinx.ext.apidoc`.
|
||||
|
||||
: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
|
||||
import warnings
|
||||
|
||||
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'])
|
||||
from sphinx.deprecation import RemovedInSphinx20Warning
|
||||
from sphinx.ext.apidoc import main as _main
|
||||
|
||||
|
||||
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,
|
||||
def main(*args, **kwargs):
|
||||
warnings.warn(
|
||||
'`sphinx.apidoc.main()` has moved to `sphinx.ext.apidoc.main()`.',
|
||||
RemovedInSphinx20Warning,
|
||||
stacklevel=2,
|
||||
)
|
||||
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
|
||||
_main(*args, **kwargs)
|
||||
|
||||
|
||||
# So program can be started with "python -m sphinx.apidoc ..."
|
||||
if __name__ == "__main__":
|
||||
warnings.warn(
|
||||
'`sphinx.apidoc` has moved to `sphinx.ext.apidoc`.',
|
||||
RemovedInSphinx20Warning,
|
||||
stacklevel=2,
|
||||
)
|
||||
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
|
||||
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user