# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php import textwrap import os import pkg_resources from command import Command, BadCommand import fnmatch import re import traceback from cStringIO import StringIO import inspect import types class EntryPointCommand(Command): usage = "ENTRY_POINT" summary = "Show information about entry points" description = """\ Shows information about one or many entry points (you can use wildcards for entry point names). Entry points are used for Egg plugins, and are named resources -- like an application, template plugin, or other resource. Entry points have a [group] which defines what kind of object they describe, and inside groups each entry point is named. """ max_args = 2 parser = Command.standard_parser(verbose=False) parser.add_option('--list', '-l', dest='list_entry_points', action='store_true', help='List all the kinds of entry points on the system') parser.add_option('--egg', '-e', dest='show_egg', help="Show all the entry points for the given Egg") parser.add_option('--regex', dest='use_regex', action='store_true', help="Make pattern match as regular expression, not just a wildcard pattern") def command(self): if self.options.list_entry_points: return self.list_entry_points() if self.options.show_egg: return self.show_egg(self.options.show_egg) if not self.args: raise BadCommand("You must give an entry point (or --list)") pattern = self.get_pattern(self.args[0]) groups = self.get_groups_by_pattern(pattern) if not groups: raise BadCommand('No group matched %s' % self.args[0]) ep_pat = None if len(self.args) > 1: ep_pat = self.get_pattern(self.args[1]) for group in groups: desc = self.get_group_description(group) print '[%s]' % group if desc: print self.wrap(desc) print by_dist = {} self.print_entry_points_by_group(group, ep_pat) def print_entry_points_by_group(self, group, ep_pat): env = pkg_resources.Environment() project_names = list(env) project_names.sort() for project_name in project_names: dists = list(env[project_name]) assert dists dist = dists[0] entries = dist.get_entry_map(group).values() if ep_pat: entries = [e for e in entries if ep_pat.search(e.name)] if not entries: continue if len(dists) > 1: print '%s (+ %i older versions)' % ( dist, len(dists)-1) else: print '%s' % dist entries.sort(lambda a, b: cmp(a.name, b.name)) for entry in entries: print self._ep_description(entry) desc = self.get_entry_point_description(entry, group) if desc and desc.description: print self.wrap(desc.description, indent=4) def show_egg(self, egg_name): group_pat = None if self.args: group_pat = self.get_pattern(self.args[0]) ep_pat = None if len(self.args) > 1: ep_pat = self.get_pattern(self.args[1]) if egg_name.startswith('egg:'): egg_name = egg_name[4:] dist = pkg_resources.get_distribution(egg_name) entry_map = dist.get_entry_map() entry_groups = entry_map.items() entry_groups.sort() for group, points in entry_groups: if group_pat and not group_pat.search(group): continue print '[%s]' % group points = points.items() points.sort() for name, entry in points: if ep_pat: if not ep_pat.search(name): continue print self._ep_description(entry) desc = self.get_entry_point_description(entry, group) if desc and desc.description: print self.wrap(desc.description, indent=2) print def wrap(self, text, indent=0): text = dedent(text) width = int(os.environ.get('COLUMNS', 70)) - indent text = '\n'.join([line.rstrip() for line in text.splitlines()]) paras = text.split('\n\n') new_paras = [] for para in paras: if para.lstrip() == para: # leading whitespace means don't rewrap para = '\n'.join(textwrap.wrap(para, width)) new_paras.append(para) text = '\n\n'.join(new_paras) lines = [' '*indent + line for line in text.splitlines()] return '\n'.join(lines) def _ep_description(self, ep, pad_name=None): name = ep.name if pad_name is not None: name = name + ' '*(pad_name-len(name)) dest = ep.module_name if ep.attrs: dest = dest + ':' + '.'.join(ep.attrs) return '%s = %s' % (name, dest) def get_pattern(self, s): if not s: return None if self.options.use_regex: return re.compile(s) else: return re.compile(fnmatch.translate(s), re.I) def list_entry_points(self): pattern = self.get_pattern(self.args and self.args[0]) groups = self.get_groups_by_pattern(pattern) print '%i entry point groups found:' % len(groups) for group in groups: desc = self.get_group_description(group) print '[%s]' % group if desc: if hasattr(desc, 'description'): desc = desc.description print self.wrap(desc, indent=2) def get_groups_by_pattern(self, pattern): env = pkg_resources.Environment() eps = {} for project_name in env: for dist in env[project_name]: for name in pkg_resources.get_entry_map(dist): if pattern and not pattern.search(name): continue if (not pattern and name.startswith('paste.description.')): continue eps[name] = None eps = eps.keys() eps.sort() return eps def get_group_description(self, group): for entry in pkg_resources.iter_entry_points('paste.entry_point_description'): if entry.name == group: ep = entry.load() if hasattr(ep, 'description'): return ep.description else: return ep return None def get_entry_point_description(self, ep, group): try: return self._safe_get_entry_point_description(ep, group) except Exception, e: out = StringIO() traceback.print_exc(file=out) return ErrorDescription(e, out.getvalue()) def _safe_get_entry_point_description(self, ep, group): ep.dist.activate() meta_group = 'paste.description.'+group meta = ep.dist.get_entry_info(meta_group, ep.name) if not meta: generic = list(pkg_resources.iter_entry_points( meta_group, 'generic')) if not generic: return super_generic(ep.load()) # @@: Error if len(generic) > 1? obj = generic[0].load() desc = obj(ep, group) else: desc = meta.load() return desc class EntryPointDescription(object): def __init__(self, group): self.group = group # Should define: # * description class SuperGeneric(object): def __init__(self, doc_object): self.doc_object = doc_object self.description = dedent(self.doc_object.__doc__) try: if isinstance(self.doc_object, (type, types.ClassType)): func = self.doc_object.__init__.im_func elif (hasattr(self.doc_object, '__call__') and not isinstance(self.doc_object, types.FunctionType)): func = self.doc_object.__call__ else: func = self.doc_object if hasattr(func, '__paste_sig__'): sig = func.__paste_sig__ else: sig = inspect.getargspec(func) sig = inspect.formatargspec(*sig) except TypeError: sig = None if sig: if self.description: self.description = '%s\n\n%s' % ( sig, self.description) else: self.description = sig def dedent(s): if s is None: return s s = s.strip('\n').strip('\r') return textwrap.dedent(s) def super_generic(obj): desc = SuperGeneric(obj) if not desc.description: return None return desc class ErrorDescription(object): def __init__(self, exc, tb): self.exc = exc self.tb = '\n'.join(tb) self.description = 'Error loading: %s' % exc