270 lines
9.3 KiB
Python
Executable File
270 lines
9.3 KiB
Python
Executable File
# (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
|
|
|