tornado.py (4083B)
1 """ 2 sphinxcontrib.autohttp.tornado 3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 5 The sphinx.ext.autodoc-style HTTP API reference builder (from Tornado) 6 for sphinxcontrib.httpdomain. 7 8 :copyright: Copyright 2013 by Rodrigo Machado 9 :license: BSD, see LICENSE for details. 10 11 """ 12 13 import inspect 14 import re 15 import six 16 17 from docutils import nodes 18 from docutils.parsers.rst import directives 19 from docutils.statemachine import ViewList 20 21 from sphinx.util import force_decode 22 from sphinx.util.compat import Directive 23 from sphinx.util.nodes import nested_parse_with_titles 24 from sphinx.util.docstrings import prepare_docstring 25 from sphinx.pycode import ModuleAnalyzer 26 27 from sphinxcontrib import httpdomain 28 from sphinxcontrib.autohttp.common import http_directive, import_object 29 30 31 def translate_tornado_rule(app, rule): 32 buf = six.StringIO() 33 for name, filter, conf in app.router.parse_rule(rule): 34 if filter: 35 buf.write('(') 36 buf.write(name) 37 if filter != app.router.default_filter or conf: 38 buf.write(':') 39 buf.write(filter) 40 if conf: 41 buf.write(':') 42 buf.write(conf) 43 buf.write(')') 44 else: 45 buf.write(name) 46 return buf.getvalue() 47 48 49 def get_routes(app): 50 for spec in app.handlers[0][1]: 51 handler = spec.handler_class 52 doc_methods = list(handler.SUPPORTED_METHODS) 53 if 'HEAD' in doc_methods: 54 doc_methods.remove('HEAD') 55 if 'OPTIONS' in doc_methods: 56 doc_methods.remove('OPTIONS') 57 58 for method in doc_methods: 59 maybe_method = getattr(handler, method.lower(), None) 60 if (inspect.isfunction(maybe_method) or 61 inspect.ismethod(maybe_method)): 62 yield method.lower(), spec.regex.pattern, handler 63 64 65 def normalize_path(path): 66 if path.endswith('$'): 67 path = path[:-1] 68 return path 69 70 71 class AutoTornadoDirective(Directive): 72 73 has_content = True 74 required_arguments = 1 75 option_spec = {'endpoints': directives.unchanged, 76 'undoc-endpoints': directives.unchanged, 77 'include-empty-docstring': directives.unchanged} 78 79 @property 80 def endpoints(self): 81 endpoints = self.options.get('endpoints', None) 82 if not endpoints: 83 return None 84 return frozenset(re.split(r'\s*,\s*', endpoints)) 85 86 @property 87 def undoc_endpoints(self): 88 undoc_endpoints = self.options.get('undoc-endpoints', None) 89 if not undoc_endpoints: 90 return frozenset() 91 return frozenset(re.split(r'\s*,\s*', undoc_endpoints)) 92 93 def make_rst(self): 94 app = import_object(self.arguments[0]) 95 for method, path, handler in get_routes(app): 96 class_name = handler.__name__ 97 method_name = getattr(handler, method).__name__ 98 endpoint = '.'.join((class_name, method_name)) 99 100 if self.endpoints and endpoint not in self.endpoints: 101 continue 102 if endpoint in self.undoc_endpoints: 103 continue 104 105 docstring = getattr(handler, method).__doc__ or '' 106 #if not isinstance(docstring, unicode): 107 # analyzer = ModuleAnalyzer.for_module(view.__module__) 108 # docstring = force_decode(docstring, analyzer.encoding) 109 if not docstring and 'include-empty-docstring' not in self.options: 110 continue 111 docstring = prepare_docstring(docstring) 112 for line in http_directive(method, normalize_path(path), docstring): 113 yield line 114 115 def run(self): 116 node = nodes.section() 117 node.document = self.state.document 118 result = ViewList() 119 for line in self.make_rst(): 120 result.append(line, '<autotornado>') 121 nested_parse_with_titles(self.state, result, node) 122 return node.children 123 124 125 def setup(app): 126 if 'http' not in app.domains: 127 httpdomain.setup(app) 128 app.add_directive('autotornado', AutoTornadoDirective)