# Tracpydoc plugin
from genshi.builder import tag
from genshi.core import Markup
from trac.core import *
from trac.web.chrome import INavigationContributor, ITemplateProvider, \
add_stylesheet
from trac.web.main import IRequestHandler
from trac.util.compat import any
from trac.util.text import shorten_line, to_unicode
from trac.wiki.api import IWikiSyntaxProvider, IWikiMacroProvider
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
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 = tag.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 = tag.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', tag.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:]
data = {'trail': [Markup(to_unicode(x)) for x in
self.doc._path_links(target)],
'generate_help': self.generate_help,
'target': target}
return 'pydoc.html', data, 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 tag.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 tag.a(label, class_='wiki', title=shorten_line(doc),
href=formatter.href.pydoc(object))
except ImportError:
return tag.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