import sys import inspect class TypeBase(object): callable = False def __init__(self, parent, value): self.parent = parent self.value = value def __repr__(self): return '<%s name="%s" filename="%s">' % ( self.__class__.__name__, self.name, self.filename) @property def filename(self): return self.parent.filename @property def name(self): return '%s.%s' % (self.parent.name, self.basename) @property def doc(self): docstr = self.value.__doc__ if not docstr: return '' docstr = docstr.strip() lines = [ l.rstrip().replace('\t', 8 * ' ') for l in docstr.split('\n')] if not len(lines) or len(lines) == 1 and lines[0] == '': return # pff, probably stinkin' Zope or sth :) # find the first indented line after the first line to determine # indentation, then strip indentation lines[0] = lines[0].rstrip() if len(lines) > 1: indentlineindex = 1 # skip first while True: indentline = lines[indentlineindex] if indentline: break indentlineindex += 1 # we stripped the docstring and have checked whether there were # more than one lines, so indentline must exist here indent = 0 while True: if indentline[indent] != ' ': break indent += 1 lines[1:] = [l and l[indent:] for l in lines[1:]] return '\n'.join(lines) class Module(TypeBase): @property def basename(self): return self.value.__name__ name = basename @property def filename(self): fn = self.value.__file__ if fn.split('.')[-1] in ('pyc', 'pyo', 'pyw'): fn = fn[:-1] return fn class Class(TypeBase): @property def basename(self): return self.value.__name__ class Function(TypeBase): callable = True def __init__(self, parent, value): super(Function, self).__init__(parent, value) self.func = self.value @property def basename(self): return self.func.func_name @property def lineno(self): return self.func.func_code.co_firstlineno @property def args(self): return self.func.func_code.co_varnames[ :self.func.func_code.co_argcount] class Method(Function): def __init__(self, parent, value): super(Method, self).__init__(parent, value) self.func = value.im_func class Attribute(TypeBase): def __init__(self, parent, name, value): self._name = name super(Attribute, self).__init__(parent, value) @property def basename(self): return self._name @property def doc(self): return class Collector(object): def __init__(self, modules): self.modules = modules def collect(self): for module in self.modules: for object in self.collect_module(module): yield object def collect_module(self, module): if module not in sys.modules: __import__(module) mod = sys.modules[module] modobj = Module(None, mod) yield modobj for desc in self.collect_object(modobj): yield desc def collect_object(self, parent): for attr in parent.value.__dict__: value = getattr(parent.value, attr) if inspect.ismodule(value): continue # ignore sub-modules elif inspect.isclass(value): typedesc = Class(parent, value) for desc in self.collect_object(typedesc): yield desc elif inspect.isfunction(value): typedesc = Function(parent, value) elif inspect.ismethod(value): typedesc = Method(parent, value) else: typedesc = Attribute(parent, attr, value) yield typedesc def get_tree(self, descs): descs = list(descs) modules = [ obj for obj in descs if isinstance(obj, Module)] unique_files = [] for module in modules: if module.filename not in unique_files: unique_files.append(module.filename) for module in sorted(modules, key=lambda m: (m.filename, m.basename)): yield module for object in self._get_tree_helper(module, descs): yield object def _get_tree_helper(self, object, descs): currnumdots = object.name.count('.') subobjects = [ o for o in descs if o.filename == object.filename and o.name.count('.') == currnumdots + 1 and o.name.startswith('%s.' % (object.name,))] subobjects.sort(key=lambda o: o.basename) subobjects.sort(key=lambda o: o.__class__.__name__) for subobject in subobjects: yield subobject for subsubobject in self._get_tree_helper(subobject, descs): yield subsubobject if __name__ == '__main__': c = Collector(len(sys.argv) > 1 and sys.argv[1:] or ['pydirs.pydirs']) for obj in c.get_tree(): if isinstance(obj, Module): print print '=' * 79 print 'module %s from %s' % (obj.name, obj.filename.split('/')[-1]) print '=' * 79 elif isinstance(obj, Class): print print 'class %s' % (obj.basename,) print '-' * 79 elif obj.callable: print 'function %s, line %s' % (obj.basename, obj.lineno) else: print 'attribute %s, type %s' % (obj.basename, type(obj.value))