+++ /dev/null
-# -*- coding: utf-8 -*-
-"""
- sphinx.ext.coverage
- ~~~~~~~~~~~~~~~~~~~
-
- Check Python modules and C API for coverage. Mostly written by Josip
- Dzolonga for the Google Highly Open Participation contest.
-
- :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
- :license: BSD, see LICENSE for details.
-"""
-
-import re
-import glob
-import inspect
-import cPickle as pickle
-from os import path
-
-from sphinx.builders import Builder
-
-
-# utility
-def write_header(f, text, char='-'):
- f.write(text + '\n')
- f.write(char * len(text) + '\n')
-
-def compile_regex_list(name, exps, warnfunc):
- lst = []
- for exp in exps:
- try:
- lst.append(re.compile(exp))
- except Exception:
- warnfunc('invalid regex %r in %s' % (exp, name))
- return lst
-
-
-class CoverageBuilder(Builder):
-
- name = 'coverage'
-
- def init(self):
- self.c_sourcefiles = []
- for pattern in self.config.coverage_c_path:
- pattern = path.join(self.srcdir, pattern)
- self.c_sourcefiles.extend(glob.glob(pattern))
-
- self.c_regexes = []
- for (name, exp) in self.config.coverage_c_regexes.items():
- try:
- self.c_regexes.append((name, re.compile(exp)))
- except Exception:
- self.warn('invalid regex %r in coverage_c_regexes' % exp)
-
- self.c_ignorexps = {}
- for (name, exps) in self.config.coverage_ignore_c_items.iteritems():
- self.c_ignorexps[name] = compile_regex_list(
- 'coverage_ignore_c_items', exps, self.warn)
- self.mod_ignorexps = compile_regex_list(
- 'coverage_ignore_modules', self.config.coverage_ignore_modules,
- self.warn)
- self.cls_ignorexps = compile_regex_list(
- 'coverage_ignore_classes', self.config.coverage_ignore_classes,
- self.warn)
- self.fun_ignorexps = compile_regex_list(
- 'coverage_ignore_functions', self.config.coverage_ignore_functions,
- self.warn)
-
- def get_outdated_docs(self):
- return 'coverage overview'
-
- def write(self, *ignored):
- self.py_undoc = {}
- self.build_py_coverage()
- self.write_py_coverage()
-
- self.c_undoc = {}
- self.build_c_coverage()
- self.write_c_coverage()
-
- def build_c_coverage(self):
- # Fetch all the info from the header files
- c_objects = self.env.domaindata['c']['objects']
- for filename in self.c_sourcefiles:
- undoc = []
- f = open(filename, 'r')
- try:
- for line in f:
- for key, regex in self.c_regexes:
- match = regex.match(line)
- if match:
- name = match.groups()[0]
- if name not in c_objects:
- for exp in self.c_ignorexps.get(key, ()):
- if exp.match(name):
- break
- else:
- undoc.append((key, name))
- continue
- finally:
- f.close()
- if undoc:
- self.c_undoc[filename] = undoc
-
- def write_c_coverage(self):
- output_file = path.join(self.outdir, 'c.txt')
- op = open(output_file, 'w')
- try:
- if self.config.coverage_write_headline:
- write_header(op, 'Undocumented C API elements', '=')
- op.write('\n')
-
- for filename, undoc in self.c_undoc.iteritems():
- write_header(op, filename)
- for typ, name in undoc:
- op.write(' * %-50s [%9s]\n' % (name, typ))
- op.write('\n')
- finally:
- op.close()
-
- def build_py_coverage(self):
- objects = self.env.domaindata['py']['objects']
- modules = self.env.domaindata['py']['modules']
-
- skip_undoc = self.config.coverage_skip_undoc_in_source
-
- for mod_name in modules:
- ignore = False
- for exp in self.mod_ignorexps:
- if exp.match(mod_name):
- ignore = True
- break
- if ignore:
- continue
-
- try:
- mod = __import__(mod_name, fromlist=['foo'])
- except ImportError, err:
- self.warn('module %s could not be imported: %s' %
- (mod_name, err))
- self.py_undoc[mod_name] = {'error': err}
- continue
-
- funcs = []
- classes = {}
-
- for name, obj in inspect.getmembers(mod):
- # diverse module attributes are ignored:
- if name[0] == '_':
- # begins in an underscore
- continue
- if not hasattr(obj, '__module__'):
- # cannot be attributed to a module
- continue
- if obj.__module__ != mod_name:
- # is not defined in this module
- continue
-
- full_name = '%s.%s' % (mod_name, name)
-
- if inspect.isfunction(obj):
- if full_name not in objects:
- for exp in self.fun_ignorexps:
- if exp.match(name):
- break
- else:
- if skip_undoc and not obj.__doc__:
- continue
- funcs.append(name)
- elif inspect.isclass(obj):
- for exp in self.cls_ignorexps:
- if exp.match(name):
- break
- else:
- if full_name not in objects:
- if skip_undoc and not obj.__doc__:
- continue
- # not documented at all
- classes[name] = []
- continue
-
- attrs = []
-
- for attr_name in dir(obj):
- if attr_name not in obj.__dict__:
- continue
- attr = getattr(obj, attr_name)
- if not (inspect.ismethod(attr) or
- inspect.isfunction(attr)):
- continue
- if attr_name[0] == '_':
- # starts with an underscore, ignore it
- continue
- if skip_undoc and not attr.__doc__:
- # skip methods without docstring if wished
- continue
-
- full_attr_name = '%s.%s' % (full_name, attr_name)
- if full_attr_name not in objects:
- attrs.append(attr_name)
-
- if attrs:
- # some attributes are undocumented
- classes[name] = attrs
-
- self.py_undoc[mod_name] = {'funcs': funcs, 'classes': classes}
-
- def write_py_coverage(self):
- output_file = path.join(self.outdir, 'python.txt')
- op = open(output_file, 'w')
- failed = []
- try:
- if self.config.coverage_write_headline:
- write_header(op, 'Undocumented Python objects', '=')
- keys = self.py_undoc.keys()
- keys.sort()
- for name in keys:
- undoc = self.py_undoc[name]
- if 'error' in undoc:
- failed.append((name, undoc['error']))
- else:
- if not undoc['classes'] and not undoc['funcs']:
- continue
-
- write_header(op, name)
- if undoc['funcs']:
- op.write('Functions:\n')
- op.writelines(' * %s\n' % x for x in undoc['funcs'])
- op.write('\n')
- if undoc['classes']:
- op.write('Classes:\n')
- for name, methods in sorted(undoc['classes'].iteritems()):
- if not methods:
- op.write(' * %s\n' % name)
- else:
- op.write(' * %s -- missing methods:\n' % name)
- op.writelines(' - %s\n' % x for x in methods)
- op.write('\n')
-
- if failed:
- write_header(op, 'Modules that failed to import')
- op.writelines(' * %s -- %s\n' % x for x in failed)
- finally:
- op.close()
-
- def finish(self):
- # dump the coverage data to a pickle file too
- picklepath = path.join(self.outdir, 'undoc.pickle')
- dumpfile = open(picklepath, 'wb')
- try:
- pickle.dump((self.py_undoc, self.c_undoc), dumpfile)
- finally:
- dumpfile.close()
-
-
-def setup(app):
- app.add_builder(CoverageBuilder)
- app.add_config_value('coverage_ignore_modules', [], False)
- app.add_config_value('coverage_ignore_functions', [], False)
- app.add_config_value('coverage_ignore_classes', [], False)
- app.add_config_value('coverage_c_path', [], False)
- app.add_config_value('coverage_c_regexes', {}, False)
- app.add_config_value('coverage_ignore_c_items', {}, False)
- app.add_config_value('coverage_write_headline', True, False)
- app.add_config_value('coverage_skip_undoc_in_source', False, False)