diff options
Diffstat (limited to 'paste/docsupport')
| -rw-r--r-- | paste/docsupport/__init__.py | 1 | ||||
| -rw-r--r-- | paste/docsupport/extract.py | 275 | ||||
| -rw-r--r-- | paste/docsupport/findmodules.py | 25 | ||||
| -rw-r--r-- | paste/docsupport/metadata.py | 78 |
4 files changed, 379 insertions, 0 deletions
diff --git a/paste/docsupport/__init__.py b/paste/docsupport/__init__.py new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/paste/docsupport/__init__.py @@ -0,0 +1 @@ +# diff --git a/paste/docsupport/extract.py b/paste/docsupport/extract.py new file mode 100644 index 0000000..effd509 --- /dev/null +++ b/paste/docsupport/extract.py @@ -0,0 +1,275 @@ +import types +import inspect +from cStringIO import StringIO +import textwrap +import findmodules +from paste.docsupport import metadata +from paste.util.classinit import ClassInitMeta + +extractors = [] + +class Extractor(object): + + __metaclass__ = ClassInitMeta + match_type = None + + def __classinit__(cls, new_attrs): + if cls.__bases__ != (object,): + extractors.append(cls) + + def __init__(self, obj, context): + self.obj = obj + self.context = context + + def extract(self): + raise NotImplementedError + + def applies(cls, obj, context): + return isinstance(obj, cls.match_type) + applies = classmethod(applies) + +class ModuleExtractor(Extractor): + + match_type = types.ModuleType + + def extract(self): + objs = getattr(self.obj, '__all__', []) + if not objs: + return + self.context.writeheader(self.obj.__name__, type='Module') + self.context.writedoc(self.obj.__doc__) + for name in objs: + self.context.push_name(name) + self.context.extract(getattr(self.obj, name)) + self.context.pop_name(name) + self.context.endheader() + +class ClassExtractor(Extractor): + + match_type = type + + def extract(self): + self.context.writeheader(self.context.last_name, type='Class') + self.context.writedoc(self.obj.__doc__) + attrs = self.getattrs(self.obj) + for name, value in attrs: + self.context.push_name(name) + self.context.extract(value) + self.context.pop_name(name) + methods = self.getmethods(self.obj) + for name, value in methods: + self.context.push_name(name) + self.context.extract(value) + self.context.pop_name(name) + self.context.endheader() + + def getattrs(self, cls): + bases = inspect.getmro(cls) + attrs = {} + for i, base in enumerate(bases): + for name, value in base.__dict__.items(): + if not isinstance(value, metadata.DocItem): + continue + if name in attrs: + continue + attrs[name] = (i, value.__creationorder__, value) + attrs = attrs.items() + attrs.sort(lambda a, b: cmp(a[1], b[1])) + return [ + (m[0], m[1][2]) for m in attrs] + + def getmethods(self, cls): + bases = inspect.getmro(cls) + methods = {} + for i, base in enumerate(bases): + if base.__dict__.has_key('__all__'): + all = base.__all__ or [] + else: + all = None + for name, value in base.__dict__.items(): + if all is not None and name not in all: + continue + if not isinstance(value, types.FunctionType): + continue + if name in methods: + continue + methods[name] = (i, value.func_code.co_firstlineno, value) + methods = methods.items() + methods.sort(lambda a, b: cmp(a[1], b[1])) + return [ + (m[0], m[1][2]) for m in methods] + +class DetectedExtractor(Extractor): + + def applies(cls, obj, context): + return isinstance(obj, metadata.DocItem) + applies = classmethod(applies) + + def extract(self): + self.obj.writeto(self.context) + +class MethodExtractor(Extractor): + + match_type = types.FunctionType + + def extract(self): + if not self.obj.__doc__: + return + sig = self.make_sig(self.obj) + self.context.writekey('def %s(%s)' % (self.obj.func_name, sig), + monospace=False) + self.context.writedoc(self.obj.__doc__) + if getattr(self.obj, 'returns', None): + returns = self.obj.returns + if isinstance(returns, str): + returns = self.obj.func_globals[returns] + self.context.extract(self.obj.returns) + self.context.endkey() + + def make_sig(self, func): + args, varargs, varkw, defaults = inspect.getargspec(func) + sig = [] + args.reverse() + for arg in args: + if defaults: + sig.append('%s=%r' % (arg, defaults[-1])) + defaults = defaults[:-1] + else: + sig.append(arg) + sig.reverse() + if varargs: + sig.append('*%s' % varargs) + if varkw: + sig.append('**%s' % varkw) + return ', '.join(sig) + + +############################################################ +## Context +############################################################ + +class DocContext(object): + + headerchars = '+=-~.\'_`' + + def __init__(self): + self.out = StringIO() + self.header_level = 0 + self.indent_level = 0 + self.names = [] + + def push_name(self, name): + self.names.append(name) + + def pop_name(self, name=None): + if name is not None: + assert self.names[-1] == name, ( + "Out of order pop (popping %r; expected %r)" + % (self.names[-1], name)) + self.names.pop() + + def last_name__get(self): + return self.names[-1] + last_name = property(last_name__get) + + def writeheader(self, name, type=None): + if self.indent_level: + self.writekey(name, type=type, monospace=False) + return + if type: + name = '%s: %s' % (type, name) + self.write(name + '\n') + self.write(self.headerchars[self.header_level] + * len(name)) + self.write('\n\n') + self.header_level += 1 + + def endheader(self): + if self.indent_level: + self.endkey() + return + self.header_level -= 1 + assert self.header_level >= 0, ( + "Too many endheader() calls.") + + def writedoc(self, doc): + if doc is None: + return + doc = self.clean(doc) + if not doc: + return + self.write(doc) + self.write('\n\n') + + def writelist(self, seq, header=None, with_titles=False): + seq = list(seq) + if not seq: + return + if header: + self.writeheader(header) + for name, value in seq: + if with_titles: + self.writeheader(name) + value.write(self) + if with_titles: + self.endheader() + if header: + self.endheader() + + def writekey(self, key, type=None, monospace=True): + if monospace: + key = '``%s``' % key + if type: + key = '%s: %s' % (type, key) + self.write('%s:\n' % key) + self.indent_level += 2 + + def endkey(self): + self.indent_level -= 2 + assert self.indent_level >= 0, ( + "Too many endkeys or dedents (indent %s)" % self.indent_level) + + def write(self, s): + if self.indent_level: + self.out.write(self.indent(s, self.indent_level)) + else: + self.out.write(s) + + def clean(self, s): + return textwrap.dedent(s).rstrip().lstrip('\n') + + def indent(self, s, indent=2): + new = '\n'.join([' '*indent + l for l in s.splitlines()]) + if s.endswith('\n'): + new += '\n' + return new + + def capture(self, obj): + old_out = self.out + self.out = StringIO() + obj.write(self) + result = self.out.getvalue() + self.out = old_out + return result + + def extract(self, obj): + for extractor in extractors: + if extractor.applies(obj, self): + ext = extractor(obj, self) + ext.extract() + break + else: + print >> sys.stderr, 'No extractor applies to %r\n' % obj + +def build_doc(package): + context = DocContext() + for module in findmodules.find_modules(package): + context.extract(module) + return context.out.getvalue() + +if __name__ == '__main__': + import sys + from paste.util.import_string import import_module + base = import_module(sys.argv[1]) + print build_doc(base) + diff --git a/paste/docsupport/findmodules.py b/paste/docsupport/findmodules.py new file mode 100644 index 0000000..4797310 --- /dev/null +++ b/paste/docsupport/findmodules.py @@ -0,0 +1,25 @@ +""" +Finds all modules in a packages, loads them, and returns them. +""" + +import os +from paste.util.import_string import import_module + +def find_modules(package): + pkg_name = package.__name__ + modules = [] + base = os.path.abspath(package.__file__) + if os.path.basename(os.path.splitext(base)[0]) == '__init__': + base = os.path.dirname(base) + if os.path.isdir(base): + for module_fn in os.listdir(base): + base, ext = os.path.splitext(module_fn) + full = os.path.join(base, module_fn) + if (os.path.isdir(full) + and os.path.exists(os.path.join(full, '__ini__.py'))): + modules.extend(import_module(pkg_name + '.' + base)) + elif ext == '.py': + modules.append(import_module(pkg_name + '.' + base)) + else: + modules.append(package) + return modules diff --git a/paste/docsupport/metadata.py b/paste/docsupport/metadata.py new file mode 100644 index 0000000..aa8879b --- /dev/null +++ b/paste/docsupport/metadata.py @@ -0,0 +1,78 @@ +import sys +import copy +import types +import inspect +from itertools import count +from paste.util.classinit import ClassInitMeta +from paste.util.classinstance import classinstancemethod + +doc_count = count() + +class DocItem(object): + + __metaclass__ = ClassInitMeta + + def __classinit__(cls, new_attrs): + cls.__creationorder__ = doc_count.next() + + def __init__(self): + self.__creationorder__ = doc_count.next() + stack = inspect.stack() + try: + while 1: + name = stack[0][0].f_globals['__name__'] + if name != __name__: + break + stack.pop(0) + self.call_module_name = name + finally: + # Break reference to frames + stack = None + + def get_object(self, name): + return getattr(sys.modules[self.call_module_name], name) + + def writeto(self, context): + raise NotImplementedError + + def writeobj(self, name, context): + """ + Write the named object to the context + """ + if name is None: + return + obj = self.get_object(name) + context.push_name(name) + context.extract(obj) + context.pop_name(name) + + +class WSGIKey(DocItem): + + def __init__(self, name, doc=None, interface=None): + self.name = name + self.doc = doc + self.interface = interface + super(WSGIKey, self).__init__() + + def writeto(self, context): + context.writekey(self.name, type='WSGI Environment Key') + context.writedoc(self.doc) + self.writeobj(self.interface, context) + context.endkey() + +class Attribute(DocItem): + + def __init__(self, doc, name=None, interface=None): + self.doc = doc + self.name = name + self.interface = interface + super(Attribute, self).__init__() + + def writeto(self, context): + name = self.name or context.last_name + context.writekey(self.name, type='Attribute') + context.writedoc(self.doc) + context.writeobj(self.interface, context) + context.endkey() + |
