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
|
from docutils import nodes
from sphinx.ext.viewcode import collect_pages
from sphinx.pycode import ModuleAnalyzer
import imp
from sphinx import addnodes
import re
from sphinx.util.compat import Directive
import os
from docutils.statemachine import StringList
from sphinx.environment import NoUri
import sys
py2k = sys.version_info < (3, 0)
if py2k:
text_type = unicode
else:
text_type = str
def view_source(name, rawtext, text, lineno, inliner,
options={}, content=[]):
env = inliner.document.settings.env
node = _view_source_node(env, text, None)
return [node], []
def _view_source_node(env, text, state):
# pretend we're using viewcode fully,
# install the context it looks for
if not hasattr(env, '_viewcode_modules'):
env._viewcode_modules = {}
modname = text
text = modname.split(".")[-1] + ".py"
# imitate sphinx .<modname> syntax
if modname.startswith("."):
# see if the modname needs to be corrected in terms
# of current module context
base_module = env.temp_data.get('autodoc:module')
if base_module is None:
base_module = env.temp_data.get('py:module')
if base_module:
modname = base_module + modname
urito = env.app.builder.get_relative_uri
# we're showing code examples which may have dependencies
# which we really don't want to have required so load the
# module by file, not import (though we are importing)
# the top level module here...
pathname = None
for token in modname.split("."):
file_, pathname, desc = imp.find_module(token, [pathname] if pathname else None)
if file_:
file_.close()
# unlike viewcode which silently traps exceptions,
# I want this to totally barf if the file can't be loaded.
# a failed build better than a complete build missing
# key content
analyzer = ModuleAnalyzer.for_file(pathname, modname)
# copied from viewcode
analyzer.find_tags()
if not isinstance(analyzer.code, text_type):
code = analyzer.code.decode(analyzer.encoding)
else:
code = analyzer.code
if state is not None:
docstring = _find_mod_docstring(analyzer)
if docstring:
# get rid of "foo.py" at the top
docstring = re.sub(r"^[a-zA-Z_0-9]+\.py", "", docstring)
# strip
docstring = docstring.strip()
# yank only first paragraph
docstring = docstring.split("\n\n")[0].strip()
else:
docstring = None
entry = code, analyzer.tags, {}
env._viewcode_modules[modname] = entry
pagename = '_modules/' + modname.replace('.', '/')
try:
refuri = urito(env.docname, pagename)
except NoUri:
# if we're in the latex builder etc., this seems
# to be what we get
refuri = None
if docstring:
# embed the ref with the doc text so that it isn't
# a separate paragraph
if refuri:
docstring = "`%s <%s>`_ - %s" % (text, refuri, docstring)
else:
docstring = "``%s`` - %s" % (text, docstring)
para = nodes.paragraph('', '')
state.nested_parse(StringList([docstring]), 0, para)
return_node = para
else:
if refuri:
refnode = nodes.reference('', '',
nodes.Text(text, text),
refuri=urito(env.docname, pagename)
)
else:
refnode = nodes.Text(text, text)
if state:
return_node = nodes.paragraph('', '', refnode)
else:
return_node = refnode
return return_node
from sphinx.pycode.pgen2 import token
def _find_mod_docstring(analyzer):
"""attempt to locate the module-level docstring.
Note that sphinx autodoc just uses ``__doc__``. But we don't want
to import the module, so we need to parse for it.
"""
analyzer.tokenize()
for type_, parsed_line, start_pos, end_pos, raw_line in analyzer.tokens:
if type_ == token.COMMENT:
continue
elif type_ == token.STRING:
return eval(parsed_line)
else:
return None
def _parse_content(content):
d = {}
d['text'] = []
idx = 0
for line in content:
idx += 1
m = re.match(r' *\:(.+?)\:(?: +(.+))?', line)
if m:
attrname, value = m.group(1, 2)
d[attrname] = value or ''
else:
break
d["text"] = content[idx:]
return d
def _comma_list(text):
return re.split(r"\s*,\s*", text.strip())
class AutoSourceDirective(Directive):
has_content = True
def run(self):
content = _parse_content(self.content)
env = self.state.document.settings.env
self.docname = env.docname
sourcefile = self.state.document.current_source.split(os.pathsep)[0]
dir_ = os.path.dirname(sourcefile)
files = [
f for f in os.listdir(dir_) if f.endswith(".py")
and f != "__init__.py"
]
if "files" in content:
# ordered listing of files to include
files = [fname for fname in _comma_list(content["files"])
if fname in set(files)]
node = nodes.paragraph('', '',
nodes.Text("Listing of files:", "Listing of files:")
)
bullets = nodes.bullet_list()
for fname in files:
modname, ext = os.path.splitext(fname)
# relative lookup
modname = "." + modname
link = _view_source_node(env, modname, self.state)
list_node = nodes.list_item('',
link
)
bullets += list_node
node += bullets
return [node]
def setup(app):
app.add_role('viewsource', view_source)
app.add_directive('autosource', AutoSourceDirective)
# from sphinx.ext.viewcode
app.connect('html-collect-pages', collect_pages)
|