EVOLUTION-MANAGER
Edit File: viewcode.py
# -*- coding: utf-8 -*- """ sphinx.ext.viewcode ~~~~~~~~~~~~~~~~~~~ Add links to module code in Python object descriptions. :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import traceback import warnings from docutils import nodes from six import iteritems, text_type import sphinx from sphinx import addnodes from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.locale import _ from sphinx.pycode import ModuleAnalyzer from sphinx.util import get_full_modname, logging, status_iterator from sphinx.util.nodes import make_refnode if False: # For type annotation from typing import Any, Dict, Iterable, Iterator, Set, Tuple # NOQA from sphinx.application import Sphinx # NOQA from sphinx.config import Config # NOQA from sphinx.environment import BuildEnvironment # NOQA logger = logging.getLogger(__name__) def _get_full_modname(app, modname, attribute): # type: (Sphinx, str, unicode) -> unicode try: return get_full_modname(modname, attribute) except AttributeError: # sphinx.ext.viewcode can't follow class instance attribute # then AttributeError logging output only verbose mode. logger.verbose('Didn\'t find %s in %s', attribute, modname) return None except Exception as e: # sphinx.ext.viewcode follow python domain directives. # because of that, if there are no real modules exists that specified # by py:function or other directives, viewcode emits a lot of warnings. # It should be displayed only verbose mode. logger.verbose(traceback.format_exc().rstrip()) logger.verbose('viewcode can\'t import %s, failed with error "%s"', modname, e) return None def doctree_read(app, doctree): # type: (Sphinx, nodes.Node) -> None env = app.builder.env if not hasattr(env, '_viewcode_modules'): env._viewcode_modules = {} # type: ignore if app.builder.name == "singlehtml": return if app.builder.name.startswith("epub") and not env.config.viewcode_enable_epub: return def has_tag(modname, fullname, docname, refname): entry = env._viewcode_modules.get(modname, None) # type: ignore if entry is False: return code_tags = app.emit_firstresult('viewcode-find-source', modname) if code_tags is None: try: analyzer = ModuleAnalyzer.for_module(modname) except Exception: env._viewcode_modules[modname] = False # type: ignore return if not isinstance(analyzer.code, text_type): code = analyzer.code.decode(analyzer.encoding) else: code = analyzer.code analyzer.find_tags() tags = analyzer.tags else: code, tags = code_tags if entry is None or entry[0] != code: entry = code, tags, {}, refname env._viewcode_modules[modname] = entry # type: ignore _, tags, used, _ = entry if fullname in tags: used[fullname] = docname return True for objnode in doctree.traverse(addnodes.desc): if objnode.get('domain') != 'py': continue names = set() # type: Set[unicode] for signode in objnode: if not isinstance(signode, addnodes.desc_signature): continue modname = signode.get('module') fullname = signode.get('fullname') refname = modname if env.config.viewcode_follow_imported_members: new_modname = app.emit_firstresult( 'viewcode-follow-imported', modname, fullname, ) if not new_modname: new_modname = _get_full_modname(app, modname, fullname) modname = new_modname if not modname: continue fullname = signode.get('fullname') if not has_tag(modname, fullname, env.docname, refname): continue if fullname in names: # only one link per name, please continue names.add(fullname) pagename = '_modules/' + modname.replace('.', '/') onlynode = addnodes.only(expr='html') onlynode += addnodes.pending_xref( '', reftype='viewcode', refdomain='std', refexplicit=False, reftarget=pagename, refid=fullname, refdoc=env.docname) onlynode[0] += nodes.inline('', _('[source]'), classes=['viewcode-link']) signode += onlynode def env_merge_info(app, env, docnames, other): # type: (Sphinx, BuildEnvironment, Iterable[unicode], BuildEnvironment) -> None if not hasattr(other, '_viewcode_modules'): return # create a _viewcode_modules dict on the main environment if not hasattr(env, '_viewcode_modules'): env._viewcode_modules = {} # type: ignore # now merge in the information from the subprocess env._viewcode_modules.update(other._viewcode_modules) # type: ignore def missing_reference(app, env, node, contnode): # type: (Sphinx, BuildEnvironment, nodes.Node, nodes.Node) -> nodes.Node # resolve our "viewcode" reference nodes -- they need special treatment if node['reftype'] == 'viewcode': return make_refnode(app.builder, node['refdoc'], node['reftarget'], node['refid'], contnode) def collect_pages(app): # type: (Sphinx) -> Iterator[Tuple[unicode, Dict[unicode, Any], unicode]] env = app.builder.env if not hasattr(env, '_viewcode_modules'): return highlighter = app.builder.highlighter # type: ignore urito = app.builder.get_relative_uri modnames = set(env._viewcode_modules) # type: ignore # app.builder.info(' (%d module code pages)' % # len(env._viewcode_modules), nonl=1) for modname, entry in status_iterator( sorted(iteritems(env._viewcode_modules)), # type: ignore 'highlighting module code... ', "blue", len(env._viewcode_modules), # type: ignore app.verbosity, lambda x: x[0]): if not entry: continue code, tags, used, refname = entry # construct a page name for the highlighted source pagename = '_modules/' + modname.replace('.', '/') # highlight the source using the builder's highlighter if env.config.highlight_language in ('python3', 'default', 'none'): lexer = env.config.highlight_language else: lexer = 'python' highlighted = highlighter.highlight_block(code, lexer, linenos=False) # split the code into lines lines = highlighted.splitlines() # split off wrap markup from the first line of the actual code before, after = lines[0].split('<pre>') lines[0:1] = [before + '<pre>', after] # nothing to do for the last line; it always starts with </pre> anyway # now that we have code lines (starting at index 1), insert anchors for # the collected tags (HACK: this only works if the tag boundaries are # properly nested!) maxindex = len(lines) - 1 for name, docname in iteritems(used): type, start, end = tags[name] backlink = urito(pagename, docname) + '#' + refname + '.' + name lines[start] = ( '<div class="viewcode-block" id="%s"><a class="viewcode-back" ' 'href="%s">%s</a>' % (name, backlink, _('[docs]')) + lines[start]) lines[min(end, maxindex)] += '</div>' # try to find parents (for submodules) parents = [] parent = modname while '.' in parent: parent = parent.rsplit('.', 1)[0] if parent in modnames: parents.append({ 'link': urito(pagename, '_modules/' + parent.replace('.', '/')), 'title': parent}) parents.append({'link': urito(pagename, '_modules/index'), 'title': _('Module code')}) parents.reverse() # putting it all together context = { 'parents': parents, 'title': modname, 'body': (_('<h1>Source code for %s</h1>') % modname + '\n'.join(lines)), } # type: Dict[unicode, Any] yield (pagename, context, 'page.html') if not modnames: return html = ['\n'] # the stack logic is needed for using nested lists for submodules stack = [''] for modname in sorted(modnames): if modname.startswith(stack[-1]): stack.append(modname + '.') html.append('<ul>') else: stack.pop() while not modname.startswith(stack[-1]): stack.pop() html.append('</ul>') stack.append(modname + '.') html.append('<li><a href="%s">%s</a></li>\n' % ( urito('_modules/index', '_modules/' + modname.replace('.', '/')), modname)) html.append('</ul>' * (len(stack) - 1)) context = { 'title': _('Overview: module code'), 'body': (_('<h1>All modules for which code is available</h1>') + ''.join(html)), } yield ('_modules/index', context, 'page.html') def migrate_viewcode_import(app, config): # type: (Sphinx, Config) -> None if config.viewcode_import is not None: warnings.warn('viewcode_import was renamed to viewcode_follow_imported_members. ' 'Please update your configuration.', RemovedInSphinx30Warning, stacklevel=2) def setup(app): # type: (Sphinx) -> Dict[unicode, Any] app.add_config_value('viewcode_import', None, False) app.add_config_value('viewcode_enable_epub', False, False) app.add_config_value('viewcode_follow_imported_members', True, False) app.connect('doctree-read', doctree_read) app.connect('env-merge-info', env_merge_info) app.connect('html-collect-pages', collect_pages) app.connect('missing-reference', missing_reference) # app.add_config_value('viewcode_include_modules', [], 'env') # app.add_config_value('viewcode_exclude_modules', [], 'env') app.add_event('viewcode-find-source') app.add_event('viewcode-follow-imported') return { 'version': sphinx.__display_version__, 'env_version': 1, 'parallel_read_safe': True }