# Tracpydoc plugin from trac.core import * from trac.web.chrome import INavigationContributor, ITemplateProvider, \ add_stylesheet from trac.web.main import IRequestHandler from trac.util.text import shorten_line, to_unicode from trac.util.html import html, Markup from trac.wiki.api import IWikiSyntaxProvider, IWikiMacroProvider try: from trac.Search import ISearchSource except ImportError: # 0.11 from trac.search.api import ISearchSource import inspect, os from pydoc import ispackage import urllib import pydoc import re import time import sys import os import imp from fnmatch import fnmatch try: import threading except ImportError: import dummy_threading as threading def any(gen): for p in gen: if p: return True return False class TracHTMLDoc(pydoc.HTMLDoc): _cleanup_re = re.compile(r'(?:bg)?color="[^"]+"') _cleanup_heading_re = re.compile(r'href="([^"]+).html"') _cleanup_html_re = re.compile(r'\.html($|#)') _cleanup_inline_re = re.compile(r'index(?:
)?|' r'%s' % \ (self.env.href.pydoc(target).encode('utf-8'), label or target) def modulelink(self, obj): return self._pydoc_link(obj.__name__) def modpkglink(self, (name, path, ispackage, shadowed)): return self._pydoc_link('%s%s' % (path and path + '.' or '', name), name) def namelink(self, name, *dicts): for dict in dicts: if name in dict: return '%s' % \ (re.sub(self._cleanup_html_re, r'\1', dict[name]), name) return name def _path_links(self, path): links = [] seen_so_far = [] for mod in path.split('.'): seen_so_far.append(mod) links.append(self._pydoc_link('.'.join(seen_so_far), mod)) return links def _dotted_path_links(self, path): return '.'.join(self._path_links(path)) def classlink(self, object, modname): module = object.__module__ path = '%s.%s' % (module, object.__name__) return self._dotted_path_links(path) def heading(self, *args): return re.sub(self._cleanup_heading_re, r'href="\1"', self._cleanup('heading', *args)) def section(self, *args): return self._cleanup('section', *args) def grey(self, *args): return self._cleanup('grey', *args) def _cleanup(self, kind, *args): return re.sub(self._cleanup_inline_re, '', re.sub(self._cleanup_re, 'class="pydoc%s"' % kind, getattr(pydoc.HTMLDoc, kind)(self, *args))) class PyDoc(Component): """ Allow browsing of Python documentation through Trac. """ implements(INavigationContributor, ITemplateProvider, IRequestHandler) def __init__(self): self.doc = TracHTMLDoc(self.env) syspath = self.config.get('pydoc', 'sys.path') if syspath: self.syspath = [os.path.normpath(p) for p in syspath.split(os.pathsep)] else: self.syspath = sys.path self.includes, self.excludes = self.get_filters() show_private = self.config.get('pydoc', 'show_private', '') self.show_private = [p.rstrip('.*') for p in show_private.split()] self.makedoc_lock = threading.Lock() def filter_match(self, file): includes, excludes = self.get_filters() for match in excludes: if fnmatch(file, match): return 0 for match in includes: if fnmatch(file, match): return 1 return not includes def get_filters(self): return ([p for p in self.config.get('pydoc', 'include', '').split() if p], [p for p in self.config.get('pydoc', 'exclude', '').split() if p]) def load_object(self, fullobject): """ Load an arbitrary object from a full dotted path. """ fullspec = fullobject.split('.') i = 0 module = mfile = mdescr = None mpath = self.syspath # Find module if fullspec[0] in sys.builtin_module_names: module = __import__(fullspec[0], None, None) i += 1 else: while i < len(fullspec): try: f, p, mdescr = imp.find_module(fullspec[i], mpath) if mfile: mfile.close() mfile, mpath = f, [p] i += 1 except ImportError: break try: mname = ".".join(fullspec[0:i]) if sys.modules.has_key(mname): module = sys.modules[mname] elif mname and mdescr: module = imp.load_module(mname, mfile, mpath[0], mdescr) finally: if mfile: mfile.close() # Find object object = module while i < len(fullspec): try: object = getattr(object, fullspec[i]) i += 1 except AttributeError: raise ImportError, fullobject return (module, object) def generate_help(self, target, inline=False, visibility=''): """Show documentation for named `target`. If `inline` is set, no header will be generated. For the `visibility` argument, see `PyDocMacro`. """ try: if not target or target == 'index': if inline: doc = '' else: doc = html.h1('Python: Index of Modules') for dir in self.syspath: if os.path.isdir(dir): doc += Markup(self.doc.index(dir, includes=self.includes, excludes=self.excludes)) return doc else: if inline: doc = '' else: doc = html.h1('Python: Documentation for ', Markup(self.doc._dotted_path_links(target))) return doc + Markup(to_unicode(self._makedoc(target, visibility))) except ImportError: return "No Python documentation found for '%s'" % target def _makedoc(self, target, visibility): """Warning: this helper method returns a `str` object.""" module, object = self.load_object(target) try: self.makedoc_lock.acquire() if visibility == 'private' or \ visibility == '' and any([module.__name__.startswith(p) for p in self.show_private]): try: # save pydoc's original visibility function visiblename = pydoc.visiblename # define our own: show everything but imported symbols is_imported_from_other_module = {} for k, v in object.__dict__.iteritems(): if hasattr(v, '__module__') and \ v.__module__ != module.__name__: is_imported_from_other_module[k] = True def show_private(name, all=None): return not is_imported_from_other_module.has_key(name) # install our visibility function pydoc.visiblename = show_private return self.doc.document(object) finally: # restore saved visibility function pydoc.visiblename = visiblename else: return self.doc.document(object) finally: self.makedoc_lock.release() # INavigationContributor methods def get_active_navigation_item(self, req): return 'pydoc' def get_navigation_items(self, req): yield 'mainnav', 'pydoc', html.A('PyDoc', href= req.href.pydoc()) # IRequestHandler methods def match_request(self, req): return req.path_info.startswith('/pydoc') def process_request(self, req): add_stylesheet(req, 'pydoc/css/pydoc.css') target = req.path_info[7:] req.hdf['trac.href.pydoc'] = req.href.pydoc() req.hdf['pydoc.trail'] = [Markup(to_unicode(x)) for x in self.doc._path_links(target)[:-1]] req.hdf['pydoc.trail_last'] = target.split('.')[-1] req.hdf['pydoc.content'] = self.generate_help(target) req.hdf['title'] = target return 'pydoc.cs', None # ITemplateProvider methods def get_templates_dirs(self): from pkg_resources import resource_filename return [resource_filename(__name__, 'templates')] def get_htdocs_dirs(self): from pkg_resources import resource_filename return [('pydoc', resource_filename(__name__, 'htdocs'))] class PyDocWiki(Component): """Provide wiki pydoc:object link syntax.""" implements(IWikiSyntaxProvider) # IWikiSyntaxProvider methods def get_wiki_syntax(self): return [] def get_link_resolvers(self): yield ('pydoc', self._pydoc_formatter) def _pydoc_formatter(self, formatter, ns, object, label): object = urllib.unquote(object) label = urllib.unquote(label) if not object or object == 'index': return html.a(label, class_='wiki', href=formatter.href.pydoc()) else: try: _, target = PyDoc(self.env).load_object(object) doc = pydoc.getdoc(target) if doc: doc = doc.strip().splitlines()[0] return html.a(label, class_='wiki', title=shorten_line(doc), href=formatter.href.pydoc(object)) except ImportError: return html.a(label, class_='missing wiki', href=formatter.href.pydoc(object)) class PyDocMacro(Component): """Show the Python documentation for the given `target`. An optional second argument (`visibility`) can be set in order to control the type of documentation that will be shown: * using "public", only show the documentation for exported symbols * using "private", all the documentation will be shown If the `visibility` argument is omitted, the private documentation will be shown if the `target`'s module is listed in the `[pydoc] show_private` configuration setting. """ implements(IWikiMacroProvider) # IWikiMacroProvider methods def get_macros(self): yield 'pydoc' def get_macro_description(self, name): return self.__doc__ def render_macro(self, req, name, content): args = content.split(',') target = args and args.pop(0) visibility = args and args.pop(0).strip() or '' add_stylesheet(req, 'pydoc/css/pydoc.css') return PyDoc(self.env).generate_help(target, inline=True, visibility=visibility) class PyDocSearch(Component): """ Provide searching of Python documentation. """ implements(ISearchSource) # ISearchSource methods def get_search_filters(self, req): yield ('pydoc', 'Python Documentation', 0) def get_search_results(self, req, query, filters): results = [] if 'pydoc' in filters: if not isinstance(query, list): query = query.split() query = [q.lower() for q in query] matched = PyDoc(self.env).filter_match def callback(path, modname, desc): for q in query: if (path and not matched(path)) and (modname and not matched(modname)): return if q in modname.lower() or q in desc.lower(): results.append((self.env.href.pydoc(modname), modname, int(time.time()), 'pydoc', desc or '')) return pydoc.ModuleScanner().run(callback) return results