Source

pycode / makestub1.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import inspect
import textwrap
import re
from types import (
   ModuleType, ClassType, ObjectType, MethodType, FunctionType, UnboundMethodType,
   BuiltinFunctionType, BuiltinMethodType)
from pprint import pformat




class StubCreator(object):
    
    def __init__(self, module_name):
        mod = __import__(module_name, {}, {}, [])
        for name in module_name.split(".")[1:]:
            mod = getattr(mod, name)
        self.mod = mod
        self.indention_level = 0
        self.indention = " " * 4
        self.code = [
          "# -*- coding: utf-8 -*-",
          "# generated stub module from %r" % self.mod.__file__.rstrip("co")
        ]
        doc = self.mod.__doc__
        if doc:
            self.emit('"""\n%s\n"""' % doc.rstrip("\n"))
        self.emit()
        self.generate(self.mod)

        
    def emit(self, line=""):
        indent = self.indention * self.indention_level
        if line:
            line = indent + line
        self.code.append(line)

        
    def indent(self, count=1):
        self.indention_level += count
        
        
    def dedent(self, count=1):
        self.indention_level = max(0, self.indention_level - count)
       
        
    def is_direct_instance(self, obj, *types):
        for t in types:
            if isinstance(obj, t) and obj.__class__ is t:
                return True
        return False

        
    def is_save(self, root):
        if self.is_direct_instance(root, basestring, int, float, bool):
            return True
        elif self.is_direct_instance(root, tuple, list):
            for item in root:
                if not self.is_save(item):
                    return False
            return True
        elif self.is_direct_instance(root, dict):
            for key, value in root.items():
                if not self.is_save(key):
                    return False
                if not self.is_save(value):
                    return False
            return True
        else:
            return False

            
        
    def generate(self, root):
        for name in dir(root):
            if name == "__all__" and root is self.mod:
                pass
            elif name.startswith("_"):
                continue
            
            try:
                value = getattr(root, name)
            except AttributeError, e:
                print "!", e
                continue

            if not callable(value) and self.is_save(value):
                # constant
                self.emit("%s = %s" % (name, pformat(value)))

            
            elif isinstance(value, (FunctionType, MethodType, UnboundMethodType, BuiltinFunctionType, BuiltinMethodType)):
                # function
                if value.__name__ != name:
                    self.emit("%s = %s" % (name, value.__name__))
                    self.emit()
                else:
                    self.generate_function(value)

            elif isinstance(value, ModuleType):
                # module
                if name != value.__name__:
                    self.emit("import %s as %s" % (value.__name__, name))
                else:
                    self.emit("import %s" % name)
                    
            elif isinstance(value, (ClassType,ObjectType)):
                # class
                if hasattr(value, "__name__"):
                    if value.__name__ != name:
                        self.emit("%s = %s" % (name, value.__name__))
                        self.emit()
                    else:                        
                        self.generate_class(value)


                
    def generate_class(self, cls):
        bases = (b.__name__ for b in getattr(cls, "__bases__", []))
        self.emit("class %s(%s):" % (cls.__name__, ", ".join(bases)))
        self.indent()
        doc = cls.__doc__
        if not doc:
            doc = ""
        self.emit('"""%s"""' % doc)
        self.generate(cls)
        self.dedent()
        self.emit()


    def extract_signature_from_doc(self, doc):
        lines = [l.strip() for l in (doc or "").splitlines() if l.strip()]
        args = []
        if self.indention_level > 0:
            args.append("self")
        for l in lines:
            found = re.match(".*\((?P<args>.*)\)($|[ ]*->)", l)
            if found:
                self.emit("# real signature not available, using information from __doc__")
                for a in found.group("args").split(","):
                    a = a.strip()
                    if "=" in a:
                        a, _sep, v = a.partition("=")
                        a = a.strip()
                    else:
                        v = None
                    a = a.replace(" ", "_")
                    if v and a:
                        a = "%s=%s" % (a, v)
                    if a == "...": # Found in PyQt
                        a = "*args" 
                    if a:
                        args.append(a)
                break
        return "(%s)" % ", ".join(args)

        
    def generate_function(self, func):
        doc = func.__doc__
        try:
            (args, varargs, varkw, defaults) = inspect.getargspec(func)
            signature = inspect.formatargspec(args, varargs, varkw, defaults)
        except TypeError, e:
            signature = self.extract_signature_from_doc(doc)
        if not doc:
            doc = "%s%s" % (func.__name__, signature)
        self.emit("def %s%s:" % (func.__name__, signature))
        self.indent()
        indent = self.indention * self.indention_level
        self.emit('"""\n%s%s\n%s"""' % (indent, doc.rstrip("\n"), indent))
        self.dedent()
        self.emit()

        
    def __str__(self):
        return "\n".join(self.code)

        
    
        
        
s = StubCreator("PyQt4.QtGui")
print s