0001import sys
0002import pydoc
0003import os.path as path
0004import rst
0005import pudge
0006
0007from inspect import *
0008
0009meta_names = ['all', 'author', 'copyright', 'date', 'license', 'revision',
0010              'version']
0011meta_names = set(['__%s__' % n for n in meta_names])
0012
0013def ispackage(obj):
0014    if ismodule(obj) and hasattr(obj, '__file__'):
0015        file = obj.__file__
0016        ext = ['.py', '.pyc', '.pyo']
0017        for e in ext:
0018            if file.endswith('__init__' + e):
0019                return 1
0020    return 0
0021
0022def qualified_name(obj):
0023    if ismodule(obj):
0024        return obj.__name__
0025    elif isclass(obj) or isfunction(obj):
0026        return obj.__module__ + '.' + obj.__name__
0027    elif ismethod(obj):
0028        return qualified_name(obj.im_class) + obj.func_name
0029
0030
0031class Browser(object):
0032    def __init__(self, module_names):
0033        self.module_names = module_names
0034        self.cache = {}
0035
0036    def find(self, name, contained=0):
0037        """
0038        Locate an object by it's qualified name.
0039        
0040        ``name`` is the qualified name of the package, module, class,
0041        routine, or data (e.g. 'package.module.Class').
0042        """
0043        if contained and not self.is_inside(name):
0044            return
0045        cache = self.cache
0046        if not name:
0047            return None
0048        if cache.has_key(name):
0049            return cache[name]
0050        rslt = None
0051        if '.' in name:
0052            components = name.split('.')
0053            module_name = components[0]
0054            components = components[1:]
0055            module = self.find(module_name)
0056            if module:
0057                rslt = module.find('.'.join(components))
0058        else:
0059            try:
0060                rslt = Object(name, __import__(name), None, 0, self)
0061            except ImportError:
0062                pass
0063        cache[name] = rslt
0064        return rslt
0065
0066    def is_inside(self, name):
0067        for m in self.module_names:
0068            if name.startswith(m):
0069                return 1
0070
0071    def modules(self, recursive=0):
0072        """Iterate over top level packages and modules."""
0073        if recursive:
0074            modules = []
0075            for m in self.module_names:
0076                m = self.find(m)
0077                if m:
0078                    modules.append(m)
0079                    for m in m.modules(recursive=1):
0080                        modules.append(m)
0081                # TODO: warn about module not found
0082            return modules
0083        else:
0084            return [self.find(m) for m in self.module_names if m]
0085
0086
0087def Object(name, obj, parent, module, browser):
0088    cls = None
0089    if ismodule(obj):
0090        cls = ispackage(obj) and Package or Module
0091    elif isclass(obj):
0092        cls = Class
0093    elif isroutine(obj):
0094        cls = Routine
0095    elif not (isframe(obj) or istraceback(obj) or iscode(obj)):
0096        cls = Attribute
0097    if cls is not None:
0098        return cls(name, obj, parent, module, browser)
0099
0100
0101class Name(object):
0102
0103    exclude_members = set(['__doc__', '__dict__'])
0104
0105    def __init__(self, name, obj, parent, module, browser):
0106        self.name = name
0107        self.obj = obj
0108        self.parent = parent
0109        self.in_module = module
0110        self.browser = browser
0111        self._initialize()
0112
0113
0114    def relative_file(self, ext='.py'):
0115        real = self.real_module
0116        if real:
0117            relfile = real.__name__.replace('.', '/')
0118            if ispackage(real):
0119                relfile += '/__init__'
0120            if ext:
0121                relfile += ext
0122            return relfile
0123
0124    def qualified_name(self):
0125        names = [self.name]
0126        p = self.parent
0127        while p:
0128            names.insert(0, p.name)
0129            p = p.parent
0130        return '.'.join(names)
0131
0132    def moduleless_name(self):
0133        names = [self.name]
0134        p = self.parent
0135        while not p.ismodule():
0136            names.insert(0, p.name)
0137            p = p.parent
0138        return '.'.join(names)
0139
0140    def source_lines(self):
0141        try:
0142            (line, code) = getsourcelines(self.obj)
0143        except (IOError, TypeError):
0144            pass
0145        else:
0146            if isinstance(line, list):
0147                # XXX work around python bug
0148                (line, code) = (code, line)
0149            if code:
0150                return (line, line + len(code))
0151        return (0, 0)
0152
0153    def isvisible(self):
0154        name = self.name
0155        if name.startswith('__') and name.endswith('__'):
0156            return 1
0157        try:
0158            all = self.parent.obj.__all__
0159        except AttributeError:
0160            pass
0161        else:
0162            return name in all
0163        if name.startswith('_'):
0164            return 0
0165        return not self.isalias()
0166
0167    def ispackage(self):
0168        return ispackage(self.obj)
0169
0170    def ismodule(self):
0171        return ismodule(self.obj)
0172
0173    def isclass(self):
0174        return isclass(self.obj)
0175
0176    def isroutine(self):
0177        return isroutine(self.obj)
0178
0179    def isdata(self):
0180        return pydoc.isdata(self.obj)
0181
0182    def isalias(self):
0183        if self.in_module == 0:
0184            return 0
0185        return self.actual_module != self.in_module
0186
0187    def ismeta(self):
0188        return self.name in meta_names
0189
0190    def hasdoc(self):
0191        if self.doc(strip=0):
0192            return 1
0193
0194    def doc(self, strip=1, blurbless=0, html=0):
0195        if not self.obj:
0196            return ''
0197        ds = getattr(self.obj, '__doc__', None)
0198        if ds and strip:
0199            ds = rst.trim(ds)
0200        if ds and blurbless:
0201            parts = ds.split('\n\n', 1)
0202            if len(parts) > 1:
0203                ds = parts[1].strip()
0204            else:
0205                return ''
0206        if ds and html:
0207            ds = rst.to_html(ds)
0208        return ds or ''
0209
0210    def blurb(self, html=1):
0211        ds = self.doc()
0212        if ds:
0213            parts = ds.split('\n\n')
0214            ds = parts[0]
0215        if ds and html:
0216            ds = rst.to_html(ds).strip()
0217            if ds.startswith('<p>') and ds.endswith('</p>'):
0218                ds = ds[3:-4]
0219        return ds
0220
0221    def html(self, blurbless=0):
0222        from warnings import warn
0223        warn("Name.html() is deprecated. Use Name.doc(html=1) instead.",
0224             DeprecationWarning, stacklevel=2)
0225        return self.doc(blurbless=blurbless, html=1)
0226
0227    def formatargs(self):
0228        return _format_args(self.obj)
0229
0230    def filtered_members(self):
0231        excludes = self.exclude_members
0232        for n, v in getmembers(self.obj):
0233            if n not in excludes:
0234                yield n, v
0235
0236    def members(self):
0237        if not hasattr(self, '_members'):
0238            members = self._load_members()
0239            self.__dict__['members'] = members
0240        return members
0241    members = property(members)
0242
0243    def members_with(self, predicate):
0244        for m in self.members.values():
0245            if predicate(m):
0246                yield m
0247
0248    def members_with_any(self, *predicates):
0249        predicates = ['is' + p for p in predicates]
0250        members = self.members.values()
0251        for m in self.members.values():
0252            for p in predicates:
0253                if getattr(m, p)():
0254                    yield m
0255                    break
0256
0257    def members_with_all(self, *predicates):
0258        predicates = ['is' + p for p in predicates]
0259        members = self.members.values()
0260        for m in self.members.values():
0261            for p in predicates:
0262                if not getattr(m, p)():
0263                    break
0264            else:
0265                yield m
0266
0267    def classes(self, visible_only=1):
0268        return self.all(visible_only, predicate=lambda m: m.isclass())
0269
0270    def routines(self, visible_only=1):
0271        return self.all(visible_only, predicate=lambda m: m.isroutine())
0272
0273    def modules(self, visible_only=1, recursive=0):
0274        for m in self.all(visible_only, predicate=lambda m: m.ismodule()):
0275            yield m
0276            if recursive:
0277                for m in m.modules(visible_only, 1):
0278                    yield m
0279
0280    def attributes(self, visible_only=1, meta=0):
0281        predicate = lambda m: m.isdata() and (meta or not m.ismeta())
0282        return self.all(visible_only, predicate)
0283
0284    def all(self, visible_only=1, predicate=None):
0285        for m in self.members.values():
0286            if (visible_only and not m.isvisible()) or                  (predicate and not predicate(m)):
0288                continue
0289            yield m
0290
0291    def find(self, name):
0292        parts = name.split('.', 1)
0293        member_name, rest = parts[0], parts[1:]
0294        members = self.members
0295        if members.has_key(member_name):
0296            member = members[member_name]
0297            if rest:
0298                return member.find(rest[0])
0299            return member
0300        else:
0301            pudge.log.debug('%r not found in %r', member_name, self.qualified_name())
0302
0303    def _initialize(self):
0304        pass
0305
0306    def _load_members(self):
0307        Object = self._member_object
0308        members = {}
0309        for name, value in self.filtered_members():
0310            member = Object(name, value)
0311            members[name] = member
0312        return members
0313
0314    def _member_object(self, name, obj):
0315        return Object(name, obj, self, self.in_module, self.browser)
0316
0317
0318class Module(Name):
0319    exclude_members = Name.exclude_members.union(['__builtins__',
0320                                                  '__file__',
0321                                                  '__name__',
0322                                                  '__path__'])
0323    type_name = 'module'
0324
0325    def _initialize(self):
0326        name = self.obj.__name__
0327        if '.' in name:
0328            self.actual_module = sys.modules[name.rsplit('.', 1)[0]]
0329        else:
0330            self.actual_module = None
0331        self.real_module = self.obj
0332
0333    def _member_object(self, name, obj):
0334        return Object(name, obj, self, self.obj, self.browser)
0335
0336
0337class Package(Module):
0338
0339    def _fill_modules_and_packages(self, members):
0340        object = self.obj
0341        names = dict(members)
0342        qname = self.qualified_name()
0343        from pydoc import safeimport, ispackage
0344        # XXX: why does this only look at __path__[0]?
0345        for file in os.listdir(object.__path__[0]):
0346            p = path.join(object.__path__[0], file)
0347            modname = getmodulename(file)
0348            if modname == '__init__':
0349                continue
0350            if modname and file.endswith('.py'):
0351                if modname in names:
0352                    value = names[modname]
0353                    # TODO check that the module is what we expect
0354                    if not ismodule(value):
0355                        # TODO: use logging here if possible
0356                        pudge.log.warn('%s.%s hides module with same name',
0357                                       qname, modname)
0358                    continue
0359                module = safeimport('%s.%s' % (qname, modname))
0360            elif ispackage(p):
0361                module = safeimport('%s.%s' % (qname, file))
0362                modname = file
0363            else:
0364                continue
0365            members.append((modname, module))
0366
0367    def filtered_members(self):
0368        sup = super(Package, self)
0369        members = list(sup.filtered_members())
0370        self._fill_modules_and_packages(members)
0371        return members
0372
0373
0374
0375class Class(Name):
0376    type_name = 'class'
0377
0378    exclude_members = Name.exclude_members.union(
0379        ['__class__', '__delattr__', '__getattribute__',
0380         '__hash__', '__module__', '__new__', '__reduce__',
0381         '__reduce_ex__', '__repr__', '__setattr__', '__str__',
0382         '__weakref__', '__metaclass__', '__getattr__'])
0383
0384    def _initialize(self):
0385        self.actual_module = sys.modules[self.obj.__module__]
0386        self.real_module = self.actual_module
0387
0388class Routine(Name):
0389    type_name = 'routine'
0390    def _initialize(self):
0391        obj = self.obj
0392        if hasattr(obj, '__module__'):
0393            # for functions
0394            look_in = obj
0395        elif hasattr(obj, 'im_class'):
0396            # for methods
0397            look_in = obj.im_class
0398        elif hasattr(obj, '__objclass__'):
0399            # for methodwrappers
0400            look_in = obj.__objclass__
0401        modname = getattr(look_in, '__module__', None)
0402        self.actual_module = modname and sys.modules[modname]
0403        self.real_module = self.actual_module
0404
0405    def ismethod(self):
0406        return ismethod(self.obj)
0407
0408    def isfunction(self):
0409        return isfunction(self.obj)
0410
0411    def ismethoddescriptor(self):
0412        return ismethoddescriptor(self.obj)
0413
0414    def _load_members(self):
0415        return {}
0416
0417class Attribute(Name):
0418    type_name = 'attribute'
0419
0420    def _initialize(self):
0421        self.actual_module = self.in_module
0422        self.real_module = self.actual_module
0423
0424    def _load_members(self):
0425        return {}
0426
0427    def doc(self, strip=1, blurbless=0, html=0):
0428        return ''
0429
0430
0431def _format_args(obj):
0432    try:
0433        (args, varargs, varkw, defaults) = getargspec(obj)
0434    except TypeError:
0435        return "(...)"
0436    rslt = ['(']
0437    pos = 0
0438    positional_arg_count = len(args) - len(defaults or [])
0439    for name in args:
0440        if pos > 0:
0441            rslt.append(', ')
0442        rslt.append(name)
0443        default_pos = pos - positional_arg_count
0444        if default_pos >= 0:
0445            rslt.append('=')
0446            value = defaults[default_pos]
0447            rslt.append(repr(defaults[default_pos]))
0448        pos+=1
0449    rslt.append(')')
0450    return ''.join(rslt)