diff options
author | Georg Brandl <georg@python.org> | 2008-02-25 20:18:15 +0000 |
---|---|---|
committer | Georg Brandl <georg@python.org> | 2008-02-25 20:18:15 +0000 |
commit | 87aaa5901174d6b729f953badec5a500b7a55847 (patch) | |
tree | 854ef6fb787c27a95721e65ee252ca54a724b98d /sphinx/ext/coverage.py | |
parent | 198c8e72719e74ab4c6a0cbcaf120f9a37845e18 (diff) | |
download | sphinx-git-87aaa5901174d6b729f953badec5a500b7a55847.tar.gz |
Rename sphinx.addons to sphinx.ext.
Diffstat (limited to 'sphinx/ext/coverage.py')
-rw-r--r-- | sphinx/ext/coverage.py | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py new file mode 100644 index 000000000..ed29ceea7 --- /dev/null +++ b/sphinx/ext/coverage.py @@ -0,0 +1,246 @@ +# -*- 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: 2008 by Josip Dzolonga, Georg Brandl. + :license: BSD. +""" + +import os +import re +import glob +import inspect +import cPickle as pickle +from os import path + +from sphinx.builder 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): + """ + Checks the completeness of Python's C-API documentation. + """ + + 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: + warnfunc('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() + + if self.c_sourcefiles: + self.c_undoc = {} + self.build_c_coverage() + self.write_c_coverage() + + def build_c_coverage(self): + # Fetch all the info from the header files + 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 self.env.descrefs: + 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: + 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): + for mod_name in self.env.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 self.env.descrefs: + for exp in self.fun_ignorexps: + if exp.match(name): + break + else: + funcs.append(name) + elif inspect.isclass(obj): + for exp in self.cls_ignorexps: + if exp.match(name): + break + else: + if full_name not in self.env.descrefs: + # not documented at all + classes[name] = [] + continue + + attrs = [] + + for attr_name, attr in inspect.getmembers(obj, inspect.ismethod): + if attr_name[0] == '_': + # starts with an underscore, ignore it + continue + + full_attr_name = '%s.%s' % (full_name, attr_name) + if full_attr_name not in self.env.descrefs: + 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: + 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 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') + + 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_c_path', [], False) + app.add_config_value('coverage_c_regexes', [], False) + 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_ignore_c_items', [], False) + |