1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
|
"""
sphinxcontrib.autohttp.flask
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The sphinx.ext.autodoc-style HTTP API reference builder (from Flask)
for sphinxcontrib.httpdomain.
:copyright: Copyright 2011 by Hong Minhee
:license: BSD, see LICENSE for details.
"""
import re
import itertools
import six
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.statemachine import ViewList
from sphinx.util import force_decode
from sphinx.util.compat import Directive
from sphinx.util.nodes import nested_parse_with_titles
from sphinx.util.docstrings import prepare_docstring
from sphinx.pycode import ModuleAnalyzer
from sphinxcontrib import httpdomain
from sphinxcontrib.autohttp.common import http_directive, import_object
def translate_werkzeug_rule(rule):
from werkzeug.routing import parse_rule
buf = six.StringIO()
for conv, arg, var in parse_rule(rule):
if conv:
buf.write('(')
if conv != 'default':
buf.write(conv)
buf.write(':')
buf.write(var)
buf.write(')')
else:
buf.write(var)
return buf.getvalue()
def get_routes(app, endpoint=None, order=None):
endpoints = []
for rule in app.url_map.iter_rules(endpoint):
url_with_endpoint = (
six.text_type(next(app.url_map.iter_rules(rule.endpoint))),
rule.endpoint
)
if url_with_endpoint not in endpoints:
endpoints.append(url_with_endpoint)
if order == 'path':
endpoints.sort()
endpoints = [e for _, e in endpoints]
for endpoint in endpoints:
methodrules = {}
for rule in app.url_map.iter_rules(endpoint):
methods = rule.methods.difference(['OPTIONS', 'HEAD'])
path = translate_werkzeug_rule(rule.rule)
for method in methods:
if method in methodrules:
methodrules[method].append(path)
else:
methodrules[method] = [path]
for method, paths in methodrules.items():
yield method, paths, endpoint
def quickref_directive(method, path, content):
rcomp = re.compile("^\s*.. :quickref:\s*(?P<quick>.*)$")
method = method.lower().strip()
if isinstance(content, six.string_types):
content = content.splitlines()
description=""
name=""
ref = path.replace("<","(").replace(">",")").replace("/","-").replace(":","-")
for line in content:
qref = rcomp.match(line)
if qref:
quickref = qref.group("quick")
parts = quickref.split(";",1)
if len(parts)>1:
name = parts[0]
description= parts[1]
else:
description= quickref
break
row ={}
row['name'] = name
row['operation'] = ' - `%s %s <#%s-%s>`_' % (method.upper(), path, method.lower(), ref)
row['description'] = description
return row
class AutoflaskBase(Directive):
has_content = True
required_arguments = 1
option_spec = {'endpoints': directives.unchanged,
'blueprints': directives.unchanged,
'modules': directives.unchanged,
'order': directives.unchanged,
'undoc-endpoints': directives.unchanged,
'undoc-blueprints': directives.unchanged,
'undoc-modules': directives.unchanged,
'undoc-static': directives.unchanged,
'include-empty-docstring': directives.unchanged}
@property
def endpoints(self):
endpoints = self.options.get('endpoints', None)
if not endpoints:
return None
return re.split(r'\s*,\s*', endpoints)
@property
def undoc_endpoints(self):
undoc_endpoints = self.options.get('undoc-endpoints', None)
if not undoc_endpoints:
return frozenset()
return frozenset(re.split(r'\s*,\s*', undoc_endpoints))
@property
def blueprints(self):
blueprints = self.options.get('blueprints', None)
if not blueprints:
return None
return frozenset(re.split(r'\s*,\s*', blueprints))
@property
def undoc_blueprints(self):
undoc_blueprints = self.options.get('undoc-blueprints', None)
if not undoc_blueprints:
return frozenset()
return frozenset(re.split(r'\s*,\s*', undoc_blueprints))
@property
def modules(self):
modules = self.options.get('modules', None)
if not modules:
return frozenset()
return frozenset(re.split(r'\s*,\s*', modules))
@property
def undoc_modules(self):
undoc_modules = self.options.get('undoc-modules', None)
if not undoc_modules:
return frozenset()
return frozenset(re.split(r'\s*,\s*', undoc_modules))
@property
def order(self):
order = self.options.get('order', None)
if order not in (None, 'path'):
raise ValueError('Invalid value for :order:')
return order
def make_rst(self, qref=False):
app = import_object(self.arguments[0])
if self.endpoints:
routes = itertools.chain(*[get_routes(app, endpoint, self.order)
for endpoint in self.endpoints])
else:
routes = get_routes(app, order=self.order)
for method, paths, endpoint in routes:
try:
blueprint, _, endpoint_internal = endpoint.rpartition('.')
if self.blueprints and blueprint not in self.blueprints:
continue
if blueprint in self.undoc_blueprints:
continue
except ValueError:
pass # endpoint is not within a blueprint
if endpoint in self.undoc_endpoints:
continue
try:
static_url_path = app.static_url_path # Flask 0.7 or higher
except AttributeError:
static_url_path = app.static_path # Flask 0.6 or under
if ('undoc-static' in self.options and endpoint == 'static' and
static_url_path + '/(path:filename)' in paths):
continue
view = app.view_functions[endpoint]
if self.modules and view.__module__ not in self.modules:
continue
if self.undoc_modules and view.__module__ in self.modules:
continue
docstring = view.__doc__ or ''
if hasattr(view, 'view_class'):
meth_func = getattr(view.view_class, method.lower(), None)
if meth_func and meth_func.__doc__:
docstring = meth_func.__doc__
if not isinstance(docstring, six.text_type):
analyzer = ModuleAnalyzer.for_module(view.__module__)
docstring = force_decode(docstring, analyzer.encoding)
if not docstring and 'include-empty-docstring' not in self.options:
continue
docstring = prepare_docstring(docstring)
if qref == True:
for path in paths:
row = quickref_directive(method, path, docstring)
yield row
else:
for line in http_directive(method, paths, docstring):
yield line
|