mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Enable automatic formatting for `sphinx/ext/coverage.py
`
This commit is contained in:
parent
2a7c107d49
commit
aecb60c057
@ -415,7 +415,6 @@ exclude = [
|
|||||||
"sphinx/domains/python/_object.py",
|
"sphinx/domains/python/_object.py",
|
||||||
"sphinx/domains/rst.py",
|
"sphinx/domains/rst.py",
|
||||||
"sphinx/domains/std/__init__.py",
|
"sphinx/domains/std/__init__.py",
|
||||||
"sphinx/ext/coverage.py",
|
|
||||||
"sphinx/ext/doctest.py",
|
"sphinx/ext/doctest.py",
|
||||||
"sphinx/ext/duration.py",
|
"sphinx/ext/duration.py",
|
||||||
"sphinx/ext/extlinks.py",
|
"sphinx/ext/extlinks.py",
|
||||||
|
@ -62,12 +62,17 @@ def _add_line(sizes: list[int], separator: str) -> str:
|
|||||||
return '+' + ''.join((separator * (size + 1)) + '+' for size in sizes)
|
return '+' + ''.join((separator * (size + 1)) + '+' for size in sizes)
|
||||||
|
|
||||||
|
|
||||||
def _add_row(col_widths: list[int], columns: list[str], separator: str) -> Iterator[str]:
|
def _add_row(
|
||||||
yield ''.join(f'| {column: <{col_widths[i]}}' for i, column in enumerate(columns)) + '|'
|
col_widths: list[int], columns: list[str], separator: str
|
||||||
|
) -> Iterator[str]:
|
||||||
|
row = ''.join(f'| {column: <{col_widths[i]}}' for i, column in enumerate(columns))
|
||||||
|
yield f'{row}|'
|
||||||
yield _add_line(col_widths, separator)
|
yield _add_line(col_widths, separator)
|
||||||
|
|
||||||
|
|
||||||
def _load_modules(mod_name: str, ignored_module_exps: Iterable[re.Pattern[str]]) -> Set[str]:
|
def _load_modules(
|
||||||
|
mod_name: str, ignored_module_exps: Iterable[re.Pattern[str]]
|
||||||
|
) -> Set[str]:
|
||||||
"""Recursively load all submodules.
|
"""Recursively load all submodules.
|
||||||
|
|
||||||
:param mod_name: The name of a module to load submodules for.
|
:param mod_name: The name of a module to load submodules for.
|
||||||
@ -87,7 +92,7 @@ def _load_modules(mod_name: str, ignored_module_exps: Iterable[re.Pattern[str]])
|
|||||||
return modules
|
return modules
|
||||||
|
|
||||||
search_locations = mod.__spec__.submodule_search_locations
|
search_locations = mod.__spec__.submodule_search_locations
|
||||||
for (_, sub_mod_name, sub_mod_ispkg) in pkgutil.iter_modules(search_locations):
|
for _, sub_mod_name, sub_mod_ispkg in pkgutil.iter_modules(search_locations):
|
||||||
if sub_mod_name == '__main__':
|
if sub_mod_name == '__main__':
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -138,16 +143,20 @@ def _determine_py_coverage_modules(
|
|||||||
# if there are additional modules then we warn but continue scanning
|
# if there are additional modules then we warn but continue scanning
|
||||||
if additional_modules := seen_modules - modules:
|
if additional_modules := seen_modules - modules:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
__('the following modules are documented but were not specified '
|
__(
|
||||||
'in coverage_modules: %s'),
|
'the following modules are documented but were not specified '
|
||||||
|
'in coverage_modules: %s'
|
||||||
|
),
|
||||||
', '.join(additional_modules),
|
', '.join(additional_modules),
|
||||||
)
|
)
|
||||||
|
|
||||||
# likewise, if there are missing modules we warn but continue scanning
|
# likewise, if there are missing modules we warn but continue scanning
|
||||||
if missing_modules := modules - seen_modules:
|
if missing_modules := modules - seen_modules:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
__('the following modules are specified in coverage_modules '
|
__(
|
||||||
'but were not documented'),
|
'the following modules are specified in coverage_modules '
|
||||||
|
'but were not documented'
|
||||||
|
),
|
||||||
', '.join(missing_modules),
|
', '.join(missing_modules),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -160,8 +169,10 @@ class CoverageBuilder(Builder):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
name = 'coverage'
|
name = 'coverage'
|
||||||
epilog = __('Testing of coverage in the sources finished, look at the '
|
epilog = __(
|
||||||
'results in %(outdir)s' + os.path.sep + 'python.txt.')
|
'Testing of coverage in the sources finished, look at the '
|
||||||
|
'results in %(outdir)s' + os.path.sep + 'python.txt.'
|
||||||
|
)
|
||||||
|
|
||||||
def init(self) -> None:
|
def init(self) -> None:
|
||||||
self.c_sourcefiles: list[str] = []
|
self.c_sourcefiles: list[str] = []
|
||||||
@ -170,24 +181,28 @@ class CoverageBuilder(Builder):
|
|||||||
self.c_sourcefiles.extend(glob.glob(pattern))
|
self.c_sourcefiles.extend(glob.glob(pattern))
|
||||||
|
|
||||||
self.c_regexes: list[tuple[str, re.Pattern[str]]] = []
|
self.c_regexes: list[tuple[str, re.Pattern[str]]] = []
|
||||||
for (name, exp) in self.config.coverage_c_regexes.items():
|
for name, exp in self.config.coverage_c_regexes.items():
|
||||||
try:
|
try:
|
||||||
self.c_regexes.append((name, re.compile(exp)))
|
self.c_regexes.append((name, re.compile(exp)))
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning(__('invalid regex %r in coverage_c_regexes'), exp)
|
logger.warning(__('invalid regex %r in coverage_c_regexes'), exp)
|
||||||
|
|
||||||
self.c_ignorexps: dict[str, list[re.Pattern[str]]] = {}
|
self.c_ignorexps: dict[str, list[re.Pattern[str]]] = {
|
||||||
for (name, exps) in self.config.coverage_ignore_c_items.items():
|
name: compile_regex_list('coverage_ignore_c_items', exps)
|
||||||
self.c_ignorexps[name] = compile_regex_list('coverage_ignore_c_items',
|
for name, exps in self.config.coverage_ignore_c_items.items()
|
||||||
exps)
|
}
|
||||||
self.mod_ignorexps = compile_regex_list('coverage_ignore_modules',
|
self.mod_ignorexps = compile_regex_list(
|
||||||
self.config.coverage_ignore_modules)
|
'coverage_ignore_modules', self.config.coverage_ignore_modules
|
||||||
self.cls_ignorexps = compile_regex_list('coverage_ignore_classes',
|
)
|
||||||
self.config.coverage_ignore_classes)
|
self.cls_ignorexps = compile_regex_list(
|
||||||
self.fun_ignorexps = compile_regex_list('coverage_ignore_functions',
|
'coverage_ignore_classes', self.config.coverage_ignore_classes
|
||||||
self.config.coverage_ignore_functions)
|
)
|
||||||
self.py_ignorexps = compile_regex_list('coverage_ignore_pyobjects',
|
self.fun_ignorexps = compile_regex_list(
|
||||||
self.config.coverage_ignore_pyobjects)
|
'coverage_ignore_functions', self.config.coverage_ignore_functions
|
||||||
|
)
|
||||||
|
self.py_ignorexps = compile_regex_list(
|
||||||
|
'coverage_ignore_pyobjects', self.config.coverage_ignore_pyobjects
|
||||||
|
)
|
||||||
|
|
||||||
def get_outdated_docs(self) -> str:
|
def get_outdated_docs(self) -> str:
|
||||||
return 'coverage overview'
|
return 'coverage overview'
|
||||||
@ -209,7 +224,7 @@ class CoverageBuilder(Builder):
|
|||||||
c_objects[obj[2]] = obj[1]
|
c_objects[obj[2]] = obj[1]
|
||||||
for filename in self.c_sourcefiles:
|
for filename in self.c_sourcefiles:
|
||||||
undoc: set[tuple[str, str]] = set()
|
undoc: set[tuple[str, str]] = set()
|
||||||
with open(filename, encoding="utf-8") as f:
|
with open(filename, encoding='utf-8') as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
for key, regex in self.c_regexes:
|
for key, regex in self.c_regexes:
|
||||||
match = regex.match(line)
|
match = regex.match(line)
|
||||||
@ -231,7 +246,7 @@ class CoverageBuilder(Builder):
|
|||||||
|
|
||||||
def write_c_coverage(self) -> None:
|
def write_c_coverage(self) -> None:
|
||||||
output_file = os.path.join(self.outdir, 'c.txt')
|
output_file = os.path.join(self.outdir, 'c.txt')
|
||||||
with open(output_file, 'w', encoding="utf-8") as op:
|
with open(output_file, 'w', encoding='utf-8') as op:
|
||||||
if self.config.coverage_write_headline:
|
if self.config.coverage_write_headline:
|
||||||
write_header(op, 'Undocumented C API elements', '=')
|
write_header(op, 'Undocumented C API elements', '=')
|
||||||
op.write('\n')
|
op.write('\n')
|
||||||
@ -242,19 +257,23 @@ class CoverageBuilder(Builder):
|
|||||||
op.write(' * %-50s [%9s]\n' % (name, typ))
|
op.write(' * %-50s [%9s]\n' % (name, typ))
|
||||||
if self.config.coverage_show_missing_items:
|
if self.config.coverage_show_missing_items:
|
||||||
if self.app.quiet:
|
if self.app.quiet:
|
||||||
logger.warning(__('undocumented c api: %s [%s] in file %s'),
|
logger.warning(
|
||||||
name, typ, filename)
|
__('undocumented c api: %s [%s] in file %s'),
|
||||||
|
name,
|
||||||
|
typ,
|
||||||
|
filename,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.info(red('undocumented ') + 'c ' + 'api ' +
|
logger.info(
|
||||||
'%-30s' % (name + " [%9s]" % typ) +
|
red('undocumented ')
|
||||||
red(' - in file ') + filename)
|
+ f'c api {f"{name} [{typ:>9}]":<30}'
|
||||||
|
+ red(' - in file ')
|
||||||
|
+ filename
|
||||||
|
)
|
||||||
op.write('\n')
|
op.write('\n')
|
||||||
|
|
||||||
def ignore_pyobj(self, full_name: str) -> bool:
|
def ignore_pyobj(self, full_name: str) -> bool:
|
||||||
return any(
|
return any(exp.search(full_name) for exp in self.py_ignorexps)
|
||||||
exp.search(full_name)
|
|
||||||
for exp in self.py_ignorexps
|
|
||||||
)
|
|
||||||
|
|
||||||
def build_py_coverage(self) -> None:
|
def build_py_coverage(self) -> None:
|
||||||
seen_objects = frozenset(self.env.domaindata['py']['objects'])
|
seen_objects = frozenset(self.env.domaindata['py']['objects'])
|
||||||
@ -263,7 +282,10 @@ class CoverageBuilder(Builder):
|
|||||||
skip_undoc = self.config.coverage_skip_undoc_in_source
|
skip_undoc = self.config.coverage_skip_undoc_in_source
|
||||||
|
|
||||||
modules = _determine_py_coverage_modules(
|
modules = _determine_py_coverage_modules(
|
||||||
self.config.coverage_modules, seen_modules, self.mod_ignorexps, self.py_undoc,
|
self.config.coverage_modules,
|
||||||
|
seen_modules,
|
||||||
|
self.mod_ignorexps,
|
||||||
|
self.py_undoc,
|
||||||
)
|
)
|
||||||
for mod_name in modules:
|
for mod_name in modules:
|
||||||
ignore = False
|
ignore = False
|
||||||
@ -336,8 +358,7 @@ class CoverageBuilder(Builder):
|
|||||||
attr = safe_getattr(obj, attr_name)
|
attr = safe_getattr(obj, attr_name)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
continue
|
continue
|
||||||
if not (inspect.ismethod(attr) or
|
if not (inspect.ismethod(attr) or inspect.isfunction(attr)):
|
||||||
inspect.isfunction(attr)):
|
|
||||||
continue
|
continue
|
||||||
if attr_name[0] == '_':
|
if attr_name[0] == '_':
|
||||||
# starts with an underscore, ignore it
|
# starts with an underscore, ignore it
|
||||||
@ -380,7 +401,11 @@ class CoverageBuilder(Builder):
|
|||||||
else:
|
else:
|
||||||
value = 100.0
|
value = 100.0
|
||||||
|
|
||||||
table.append([module, '%.2f%%' % value, '%d' % len(self.py_undocumented[module])])
|
table.append([
|
||||||
|
module,
|
||||||
|
f'{value:.2f}%',
|
||||||
|
str(len(self.py_undocumented[module])),
|
||||||
|
])
|
||||||
|
|
||||||
if all_objects:
|
if all_objects:
|
||||||
table.append([
|
table.append([
|
||||||
@ -397,7 +422,7 @@ class CoverageBuilder(Builder):
|
|||||||
def write_py_coverage(self) -> None:
|
def write_py_coverage(self) -> None:
|
||||||
output_file = os.path.join(self.outdir, 'python.txt')
|
output_file = os.path.join(self.outdir, 'python.txt')
|
||||||
failed = []
|
failed = []
|
||||||
with open(output_file, 'w', encoding="utf-8") as op:
|
with open(output_file, 'w', encoding='utf-8') as op:
|
||||||
if self.config.coverage_write_headline:
|
if self.config.coverage_write_headline:
|
||||||
write_header(op, 'Undocumented Python objects', '=')
|
write_header(op, 'Undocumented Python objects', '=')
|
||||||
|
|
||||||
@ -427,27 +452,37 @@ class CoverageBuilder(Builder):
|
|||||||
for func in undoc['funcs']:
|
for func in undoc['funcs']:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
__('undocumented python function: %s :: %s'),
|
__('undocumented python function: %s :: %s'),
|
||||||
name, func)
|
name,
|
||||||
|
func,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
for func in undoc['funcs']:
|
for func in undoc['funcs']:
|
||||||
logger.info(red('undocumented ') + 'py ' + 'function ' +
|
logger.info(
|
||||||
'%-30s' % func + red(' - in module ') + name)
|
red('undocumented ')
|
||||||
|
+ f'py function {func:<30}'
|
||||||
|
+ red(' - in module ')
|
||||||
|
+ name
|
||||||
|
)
|
||||||
op.write('\n')
|
op.write('\n')
|
||||||
if undoc['classes']:
|
if undoc['classes']:
|
||||||
op.write('Classes:\n')
|
op.write('Classes:\n')
|
||||||
for class_name, methods in sorted(
|
for class_name, methods in sorted(undoc['classes'].items()):
|
||||||
undoc['classes'].items()):
|
|
||||||
if not methods:
|
if not methods:
|
||||||
op.write(' * %s\n' % class_name)
|
op.write(' * %s\n' % class_name)
|
||||||
if self.config.coverage_show_missing_items:
|
if self.config.coverage_show_missing_items:
|
||||||
if self.app.quiet:
|
if self.app.quiet:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
__('undocumented python class: %s :: %s'),
|
__('undocumented python class: %s :: %s'),
|
||||||
name, class_name)
|
name,
|
||||||
|
class_name,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.info(red('undocumented ') + 'py ' +
|
logger.info(
|
||||||
'class ' + '%-30s' % class_name +
|
red('undocumented ')
|
||||||
red(' - in module ') + name)
|
+ f'py class {class_name:<30}'
|
||||||
|
+ red(' - in module ')
|
||||||
|
+ name
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
op.write(' * %s -- missing methods:\n\n' % class_name)
|
op.write(' * %s -- missing methods:\n\n' % class_name)
|
||||||
op.writelines(' - %s\n' % x for x in methods)
|
op.writelines(' - %s\n' % x for x in methods)
|
||||||
@ -455,27 +490,36 @@ class CoverageBuilder(Builder):
|
|||||||
if self.app.quiet:
|
if self.app.quiet:
|
||||||
for meth in methods:
|
for meth in methods:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
__('undocumented python method:'
|
__(
|
||||||
' %s :: %s :: %s'),
|
'undocumented python method:'
|
||||||
name, class_name, meth)
|
' %s :: %s :: %s'
|
||||||
|
),
|
||||||
|
name,
|
||||||
|
class_name,
|
||||||
|
meth,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
for meth in methods:
|
for meth in methods:
|
||||||
logger.info(red('undocumented ') + 'py ' +
|
logger.info(
|
||||||
'method ' + '%-30s' %
|
red('undocumented ')
|
||||||
(class_name + '.' + meth) +
|
+ f'py method {f"{class_name}.{meth}":<30}'
|
||||||
red(' - in module ') + name)
|
+ red(' - in module ')
|
||||||
|
+ name
|
||||||
|
)
|
||||||
op.write('\n')
|
op.write('\n')
|
||||||
|
|
||||||
if failed:
|
if failed:
|
||||||
write_header(op, 'Modules that failed to import')
|
write_header(op, 'Modules that failed to import')
|
||||||
op.writelines(' * %s -- %s\n' % x for x in failed)
|
op.writelines(f' * {name} -- {err}\n' for name, err in failed)
|
||||||
|
|
||||||
def finish(self) -> None:
|
def finish(self) -> None:
|
||||||
# dump the coverage data to a pickle file too
|
# dump the coverage data to a pickle file too
|
||||||
picklepath = os.path.join(self.outdir, 'undoc.pickle')
|
picklepath = os.path.join(self.outdir, 'undoc.pickle')
|
||||||
with open(picklepath, 'wb') as dumpfile:
|
with open(picklepath, 'wb') as dumpfile:
|
||||||
pickle.dump((self.py_undoc, self.c_undoc,
|
pickle.dump(
|
||||||
self.py_undocumented, self.py_documented), dumpfile)
|
(self.py_undoc, self.c_undoc, self.py_undocumented, self.py_documented),
|
||||||
|
dumpfile,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup(app: Sphinx) -> ExtensionMetadata:
|
def setup(app: Sphinx) -> ExtensionMetadata:
|
||||||
|
Loading…
Reference in New Issue
Block a user