diff options
Diffstat (limited to 'doc/build/builder/viewsource.py')
-rw-r--r-- | doc/build/builder/viewsource.py | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/doc/build/builder/viewsource.py b/doc/build/builder/viewsource.py new file mode 100644 index 000000000..3f6b8263a --- /dev/null +++ b/doc/build/builder/viewsource.py @@ -0,0 +1,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(":")[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) |