]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Use coverage checker instead of automatic member extraction
authorBen Darnell <ben@bendarnell.com>
Thu, 9 Jun 2011 18:03:49 +0000 (11:03 -0700)
committerBen Darnell <ben@bendarnell.com>
Thu, 9 Jun 2011 18:26:35 +0000 (11:26 -0700)
website/Makefile
website/sphinx/conf.py
website/sphinx/sphinx_coverage.py [new file with mode: 0644]
website/sphinx/web.rst

index 45215cffa16906348b8a862e7594ab1396ab97c3..d763dba31db7982e695056c55e7c21d51ee4d69b 100644 (file)
@@ -1,6 +1,11 @@
+SPHINXOPTS=-d sphinx/build/doctrees sphinx
 .PHONY: sphinx
 sphinx:
-       sphinx-build -b html -d sphinx/build/doctrees sphinx sphinx/build/html
+       sphinx-build -b html $(SPHINXOPTS) sphinx/build/html
+
+.PHONY: coverage
+coverage:
+       sphinx-build -b coverage ${SPHINXOPTS} sphinx/build/coverage
 
 clean:
        rm -rf sphinx/build
\ No newline at end of file
index 53addb170e75ca1cb9243c90d5487f6aba447b8b..17e468dc60858d6dc53791531b5e79f884219d63 100644 (file)
@@ -4,17 +4,29 @@ import sys
 sys.path.insert(0, os.path.abspath("../.."))
 import tornado
 
-print tornado.__file__
+# For our version of sphinx_coverage.py.  The version in sphinx 1.0.7
+# has too many false positives; this version comes from upstream HG.
+sys.path.append(os.path.abspath("."))
 
 master_doc = "index"
 
 project = "Tornado"
 copyright = "2011, Facebook"
 
-import tornado
 version = release = tornado.version
 
-extensions = ["sphinx.ext.autodoc"]
+extensions = ["sphinx.ext.autodoc", "sphinx_coverage"]
 
 autodoc_member_order = "bysource"
-autodoc_default_flags = ["members", "undoc-members"]
+
+coverage_skip_undoc_in_source = True
+# I wish this could go in a per-module file...
+coverage_ignore_classes = [
+    # tornado.web
+    "ChunkedTransferEncoding",
+    "GZipContentEncoding",
+    "OutputTransform",
+    "TemplateModule",
+    "url",
+    ]
+    
diff --git a/website/sphinx/sphinx_coverage.py b/website/sphinx/sphinx_coverage.py
new file mode 100644 (file)
index 0000000..5ff81d7
--- /dev/null
@@ -0,0 +1,264 @@
+# -*- 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)
index 32ecb024c6a5d5004d0bf7fe7ce7e913796f593f..20b21ad84efb753d5a1ddd81c2ae662a727bb2da 100644 (file)
@@ -2,12 +2,10 @@
 ===============
 
 .. automodule:: tornado.web
-   :exclude-members: RequestHandler, Application, asynchronous, addslash, removeslash, URLSpec, url
 
    Request handlers
    ----------------
    .. autoclass:: RequestHandler
-      :exclude-members: initialize, prepare, get, post, put, delete, head, options, get_argument, get_arguments, decode_argument, set_status, set_header, write, flush, finish, render, render_string, send_error, get_error_html, cookies, get_cookie, set_cookie, clear_cookie, clear_all_cookies, get_secure_cookie, set_secure_cookie, create_signed_value
 
       **Entry points**
       
       .. automethod:: finish
       .. automethod:: render
       .. automethod:: render_string
+      .. automethod:: redirect
       .. automethod:: send_error
       .. automethod:: get_error_html
+      .. automethod:: clear
+
 
       **Cookies**
 
 
       **Other**
 
+      .. automethod:: async_callback
+      .. automethod:: check_xsrf_cookie
+      .. automethod:: compute_etag
+      .. automethod:: get_browser_locale
+      .. automethod:: get_current_user
+      .. automethod:: get_login_url
+      .. automethod:: get_status
+      .. automethod:: get_template_path
+      .. automethod:: get_user_locale
+      .. automethod:: on_connection_close
+      .. automethod:: require_setting
+      .. automethod:: static_url
+      .. automethod:: xsrf_form_html
+
 
 
    Application configuration
    -----------------------------
    .. autoclass:: Application
+      :members:
 
    .. autoclass:: URLSpec
 
 
    Everything else
    ---------------
+   .. autoexception:: HTTPError
+   .. autoclass:: UIModule
+      :members:
+
+   .. autoclass:: ErrorHandler
+   .. autoclass:: FallbackHandler
+   .. autoclass:: RedirectHandler
+   .. autoclass:: StaticFileHandler
+      :members: