#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#
# Copyright 2015-2020, Vincenzo Arcidiacono;
# Licensed under the EUPL (the 'Licence');
# You may not use this work except in compliance with the Licence.
# You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl
"""
It is a patch to sphinx.ext.autosummary.
"""
import warnings
import logging
import os.path as osp
from sphinx import package_dir
from sphinx.util.osutil import ensuredir
from sphinx.util.inspect import safe_getattr
from jinja2.sandbox import SandboxedEnvironment
from sphinx.jinja2glue import BuiltinTemplateLoader
from jinja2 import FileSystemLoader, TemplateNotFound
from sphinx.ext.autosummary import import_by_name, get_documenter
from sphinx.ext.autosummary.generate import (
_simple_warn, _simple_info, find_autosummary_in_files
)
logger = logging.getLogger(__name__)
warnings.filterwarnings(
'ignore', category=DeprecationWarning, module='docutils'
)
[docs]def get_members(app, obj, typ, include_public=(), imported=False):
items = []
for name in dir(obj):
try:
obj_name = safe_getattr(obj, name)
try:
documenter = get_documenter(app, obj_name, obj)
except TypeError:
documenter = get_documenter(obj_name, obj)
except AttributeError:
continue
if documenter.objtype == typ:
try:
cond = imported or (obj_name.__module__ == obj.__name__)
except AttributeError:
cond = True
if cond:
items.append(name)
public = [x for x in items
if x in include_public or not x.startswith('_')]
return public, items
[docs]def generate_autosummary_docs(
sources, output_dir=None, suffix='.rst', warn=_simple_warn,
info=_simple_info, base_path=None, builder=None, template_dir=None,
app=None):
showed_sources = list(sorted(sources))
if len(showed_sources) > 20:
showed_sources = showed_sources[:10] + ['...'] + showed_sources[-10:]
info('[autosummary] generating autosummary for: %s' %
', '.join(showed_sources))
if output_dir:
info('[autosummary] writing to %s' % output_dir)
if base_path is not None:
sources = [osp.join(base_path, filename) for filename in sources]
# create our own templating environment
template_dirs = [osp.join(package_dir, 'ext', 'autosummary', 'templates')]
if builder is not None:
# allow the user to override the templates
template_loader = BuiltinTemplateLoader()
template_loader.init(builder, dirs=template_dirs)
else:
if template_dir:
template_dirs.insert(0, template_dir)
template_loader = FileSystemLoader(template_dirs)
template_env = SandboxedEnvironment(loader=template_loader)
# read
items = find_autosummary_in_files(sources)
# remove possible duplicates
items = list(dict([(item, True) for item in items]).keys())
# keep track of new files
new_files = []
# write
# noinspection PyTypeChecker
for name, path, template_name in sorted(items, key=str):
if path is None:
# The corresponding autosummary:: directive did not have
# a :toctree: option
continue
path = output_dir or osp.abspath(path)
ensuredir(path)
try:
name, obj, parent, mod_name = import_by_name(name)
except ImportError as e:
warn('[autosummary] failed to import %r: %s' % (name, e))
continue
fn = osp.join(path, name + suffix)
# skip it if it exists
if osp.isfile(fn):
continue
new_files.append(fn)
f = open(fn, 'w')
try:
try:
doc = get_documenter(app, obj, parent)
except TypeError:
doc = get_documenter(obj, parent)
if template_name is not None:
template = template_env.get_template(template_name)
else:
try:
template = template_env.get_template('autosummary/%s.rst'
% doc.objtype)
except TemplateNotFound:
template = template_env.get_template('autosummary/base.rst')
ns = {}
if doc.objtype == 'module':
ns['members'] = dir(obj)
ns['functions'], ns['all_functions'] = \
get_members(app, obj, 'function')
ns['classes'], ns['all_classes'] = \
get_members(app, obj, 'class')
ns['exceptions'], ns['all_exceptions'] = \
get_members(app, obj, 'exception')
ns['data'], ns['all_data'] = \
get_members(app, obj, 'data', imported=True)
ns['data'] = ', '.join(ns['data'])
ns['all_data'] = ', '.join(ns['all_data'])
ns['dispatchers'], ns['all_dispatchers'] = \
get_members(app, obj, 'dispatcher', imported=True)
elif doc.objtype == 'class':
ns['members'] = dir(obj)
ns['methods'], ns['all_methods'] = \
get_members(app, obj, 'method', ['__init__'], True)
ns['attributes'], ns['all_attributes'] = \
get_members(app, obj, 'attribute')
parts = name.split('.')
if doc.objtype in ('method', 'attribute'):
mod_name = '.'.join(parts[:-2])
cls_name = parts[-2]
obj_name = '.'.join(parts[-2:])
ns['class'] = cls_name
else:
mod_name, obj_name = '.'.join(parts[:-1]), parts[-1]
ns['fullname'] = name
ns['module'] = mod_name
ns['objname'] = obj_name
ns['name'] = parts[-1]
ns['objtype'] = doc.objtype
ns['underline'] = len(name) * '='
rendered = template.render(**ns)
f.write(rendered)
finally:
f.close()
# descend recursively to new files
if new_files:
generate_autosummary_docs(new_files, output_dir=output_dir,
suffix=suffix, warn=warn, info=info,
base_path=base_path, builder=builder,
template_dir=template_dir, app=app)
[docs]def process_generate_options(app):
genfiles = app.config.autosummary_generate
if genfiles and not hasattr(genfiles, '__len__'):
env = app.builder.env
genfiles = [env.doc2path(x, base=None) for x in env.found_docs
if osp.isfile(env.doc2path(x))]
if not genfiles:
return
ext = tuple(app.config.source_suffix)
genfiles = [genfile + (not genfile.endswith(ext) and ext[0] or '')
for genfile in genfiles]
try:
from sphinx.ext.autosummary import get_rst_suffix
suffix = get_rst_suffix(app)
except ImportError:
suffix = '.rst'
if suffix is None:
logger.warning('autosummary generats .rst files internally. '
'But your source_suffix does not contain .rst. Skipped.')
return
generate_autosummary_docs(genfiles, builder=app.builder,
warn=logger.warning, info=logger.info,
suffix=suffix, base_path=app.srcdir, app=app)
[docs]def setup(app):
app.setup_extension('sphinx.ext.autosummary')
# replace callback process_generate_options of 'builder-inited' event.
import sphinx.ext.autosummary as mdl
event = 'builder-inited'
try:
listeners = app._listeners[event]
except AttributeError: # Sphinx 1.6.2
listeners = app.events.listeners[event]
for listener_id, callback in listeners.items():
if callback is mdl.process_generate_options:
listeners[listener_id] = process_generate_options