0001# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
0002# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
0003import textwrap
0004import os
0005import pkg_resources
0006from command import Command, BadCommand
0007import fnmatch
0008import re
0009import traceback
0010from cStringIO import StringIO
0011import inspect
0012import types
0013
0014class EntryPointCommand(Command):
0015
0016    usage = "ENTRY_POINT"
0017    summary = "Show information about entry points"
0018
0019    description = """\
0020    Shows information about one or many entry points (you can use
0021    wildcards for entry point names).  Entry points are used for Egg
0022    plugins, and are named resources -- like an application, template
0023    plugin, or other resource.  Entry points have a [group] which
0024    defines what kind of object they describe, and inside groups each
0025    entry point is named.
0026    """
0027
0028    max_args = 2
0029
0030    parser = Command.standard_parser(verbose=False)
0031    parser.add_option('--list', '-l',
0032                      dest='list_entry_points',
0033                      action='store_true',
0034                      help='List all the kinds of entry points on the system')
0035    parser.add_option('--egg', '-e',
0036                      dest='show_egg',
0037                      help="Show all the entry points for the given Egg")
0038    parser.add_option('--regex',
0039                      dest='use_regex',
0040                      action='store_true',
0041                      help="Make pattern match as regular expression, not just a wildcard pattern")
0042
0043    def command(self):
0044        if self.options.list_entry_points:
0045            return self.list_entry_points()
0046        if self.options.show_egg:
0047            return self.show_egg(self.options.show_egg)
0048        if not self.args:
0049            raise BadCommand("You must give an entry point (or --list)")
0050        pattern = self.get_pattern(self.args[0])
0051        groups = self.get_groups_by_pattern(pattern)
0052        if not groups:
0053            raise BadCommand('No group matched %s' % self.args[0])
0054        ep_pat = None
0055        if len(self.args) > 1:
0056            ep_pat = self.get_pattern(self.args[1])
0057        for group in groups:
0058            desc = self.get_group_description(group)
0059            print '[%s]' % group
0060            if desc:
0061                print self.wrap(desc)
0062                print
0063            by_dist = {}
0064            self.print_entry_points_by_group(group, ep_pat)
0065
0066    def print_entry_points_by_group(self, group, ep_pat):
0067        env = pkg_resources.Environment()
0068        project_names = list(env)
0069        project_names.sort()
0070        for project_name in project_names:
0071            dists = list(env[project_name])
0072            assert dists
0073            dist = dists[0]
0074            entries = dist.get_entry_map(group).values()
0075            if ep_pat:
0076                entries = [e for e in entries
0077                           if ep_pat.search(e.name)]
0078            if not entries:
0079                continue
0080            if len(dists) > 1:
0081                print '%s (+ %i older versions)' % (
0082                    dist, len(dists)-1)
0083            else:
0084                print '%s' % dist
0085            entries.sort(lambda a, b: cmp(a.name, b.name))
0086            for entry in entries:
0087                print self._ep_description(entry)
0088                desc = self.get_entry_point_description(entry, group)
0089                if desc and desc.description:
0090                    print self.wrap(desc.description, indent=4)
0091
0092    def show_egg(self, egg_name):
0093        group_pat = None
0094        if self.args:
0095            group_pat = self.get_pattern(self.args[0])
0096        ep_pat = None
0097        if len(self.args) > 1:
0098            ep_pat = self.get_pattern(self.args[1])
0099        if egg_name.startswith('egg:'):
0100            egg_name = egg_name[4:]
0101        dist = pkg_resources.get_distribution(egg_name)
0102        entry_map = dist.get_entry_map()
0103        entry_groups = entry_map.items()
0104        entry_groups.sort()
0105        for group, points in entry_groups:
0106            if group_pat and not group_pat.search(group):
0107                continue
0108            print '[%s]' % group
0109            points = points.items()
0110            points.sort()
0111            for name, entry in points:
0112                if ep_pat:
0113                    if not ep_pat.search(name):
0114                        continue
0115                print self._ep_description(entry)
0116                desc = self.get_entry_point_description(entry, group)
0117                if desc and desc.description:
0118                    print self.wrap(desc.description, indent=2)
0119                print
0120
0121    def wrap(self, text, indent=0):
0122        text = dedent(text)
0123        width = int(os.environ.get('COLUMNS', 70)) - indent
0124        text = '\n'.join([line.rstrip() for line in text.splitlines()])
0125        paras = text.split('\n\n')
0126        new_paras = []
0127        for para in paras:
0128            if para.lstrip() == para:
0129                # leading whitespace means don't rewrap
0130                para = '\n'.join(textwrap.wrap(para, width))
0131            new_paras.append(para)
0132        text = '\n\n'.join(new_paras)
0133        lines = [' '*indent + line
0134                 for line in text.splitlines()]
0135        return '\n'.join(lines)
0136
0137    def _ep_description(self, ep, pad_name=None):
0138        name = ep.name
0139        if pad_name is not None:
0140            name = name + ' '*(pad_name-len(name))
0141        dest = ep.module_name
0142        if ep.attrs:
0143            dest = dest + ':' + '.'.join(ep.attrs)
0144        return '%s = %s' % (name, dest)
0145
0146    def get_pattern(self, s):
0147        if not s:
0148            return None
0149        if self.options.use_regex:
0150            return re.compile(s)
0151        else:
0152            return re.compile(fnmatch.translate(s), re.I)
0153
0154    def list_entry_points(self):
0155        pattern = self.get_pattern(self.args and self.args[0])
0156        groups = self.get_groups_by_pattern(pattern)
0157        print '%i entry point groups found:' % len(groups)
0158        for group in groups:
0159            desc = self.get_group_description(group)
0160            print '[%s]' % group
0161            if desc:
0162                if hasattr(desc, 'description'):
0163                    desc = desc.description
0164                print self.wrap(desc, indent=2)
0165
0166    def get_groups_by_pattern(self, pattern):
0167        env = pkg_resources.Environment()
0168        eps = {}
0169        for project_name in env:
0170            for dist in env[project_name]:
0171                for name in pkg_resources.get_entry_map(dist):
0172                    if pattern and not pattern.search(name):
0173                        continue
0174                    if (not pattern
0175                        and name.startswith('paste.description.')):
0176                        continue
0177                    eps[name] = None
0178        eps = eps.keys()
0179        eps.sort()
0180        return eps
0181
0182    def get_group_description(self, group):
0183        for entry in pkg_resources.iter_entry_points('paste.entry_point_description'):
0184            if entry.name == group:
0185                ep = entry.load()
0186                if hasattr(ep, 'description'):
0187                    return ep.description
0188                else:
0189                    return ep
0190        return None
0191
0192    def get_entry_point_description(self, ep, group):
0193        try:
0194            return self._safe_get_entry_point_description(ep, group)
0195        except Exception, e:
0196            out = StringIO()
0197            traceback.print_exc(file=out)
0198            return ErrorDescription(e, out.getvalue())
0199
0200    def _safe_get_entry_point_description(self, ep, group):
0201        ep.dist.activate()
0202        meta_group = 'paste.description.'+group
0203        meta = ep.dist.get_entry_info(meta_group, ep.name)
0204        if not meta:
0205            generic = list(pkg_resources.iter_entry_points(
0206                meta_group, 'generic'))
0207            if not generic:
0208                return super_generic(ep.load())
0209            # @@: Error if len(generic) > 1?
0210            obj = generic[0].load()
0211            desc = obj(ep, group)
0212        else:
0213            desc = meta.load()
0214        return desc
0215
0216class EntryPointDescription(object):
0217
0218    def __init__(self, group):
0219        self.group = group
0220
0221    # Should define:
0222    # * description
0223
0224class SuperGeneric(object):
0225
0226    def __init__(self, doc_object):
0227        self.doc_object = doc_object
0228        self.description = dedent(self.doc_object.__doc__)
0229        try:
0230            if isinstance(self.doc_object, (type, types.ClassType)):
0231                func = self.doc_object.__init__.im_func
0232            elif (hasattr(self.doc_object, '__call__')
0233                  and not isinstance(self.doc_object, types.FunctionType)):
0234                func = self.doc_object.__call__
0235            else:
0236                func = self.doc_object
0237            if hasattr(func, '__paste_sig__'):
0238                sig = func.__paste_sig__
0239            else:
0240                sig = inspect.getargspec(func)
0241                sig = inspect.formatargspec(*sig)
0242        except TypeError:
0243            sig = None
0244        if sig:
0245            if self.description:
0246                self.description = '%s\n\n%s' % (
0247                    sig, self.description)
0248            else:
0249                self.description = sig
0250
0251def dedent(s):
0252    if s is None:
0253        return s
0254    s = s.strip('\n').strip('\r')
0255    return textwrap.dedent(s)
0256
0257def super_generic(obj):
0258    desc = SuperGeneric(obj)
0259    if not desc.description:
0260        return None
0261    return desc
0262
0263class ErrorDescription(object):
0264
0265    def __init__(self, exc, tb):
0266        self.exc = exc
0267        self.tb = '\n'.join(tb)
0268        self.description = 'Error loading: %s' % exc