Initial prototype of framer: a tool to build the frame for extension modules.
diff --git a/Tools/framer/README.txt b/Tools/framer/README.txt
new file mode 100644
index 0000000..4a93a4d
--- /dev/null
+++ b/Tools/framer/README.txt
@@ -0,0 +1,8 @@
+framer is a tool to generate boilerplate code for C extension types.
+
+The boilerplate is generated from a specification object written in
+Python.  The specification uses the class statement to describe the
+extension module and any extension types it contains.  From the
+specification, framer can generate all the boilerplate C code,
+including function definitions, argument handling code, and type
+objects.
diff --git a/Tools/framer/TODO.txt b/Tools/framer/TODO.txt
new file mode 100644
index 0000000..8586c8e
--- /dev/null
+++ b/Tools/framer/TODO.txt
@@ -0,0 +1,6 @@
+Add spec for getsets.
+Generate a distutils setup script.
+Handle operator overloading.
+Generate traverse and clear methods for GC.
+Handle mapping, sequence, buffer protocols.
+Finish the todo list.
diff --git a/Tools/framer/example.py b/Tools/framer/example.py
new file mode 100644
index 0000000..b77f475
--- /dev/null
+++ b/Tools/framer/example.py
@@ -0,0 +1,127 @@
+"""Generate the skeleton for cStringIO as an example of framer."""
+
+from framer.bases import Module, Type
+from framer.member import member
+
+class cStringIO(Module):
+    """A simple fast partial StringIO replacement.
+
+    This module provides a simple useful replacement for the StringIO
+    module that is written in C.  It does not provide the full
+    generality of StringIO, but it provides enough for most
+    applications and is especially useful in conjunction with the
+    pickle module.
+    
+    Usage:
+
+    from cStringIO import StringIO
+
+    an_output_stream = StringIO()
+    an_output_stream.write(some_stuff)
+    ...
+    value = an_output_stream.getvalue()
+    
+    an_input_stream = StringIO(a_string)
+    spam = an_input_stream.readline()
+    spam = an_input_stream.read(5)
+    an_input_stream.seek(0)             # OK, start over
+    spam = an_input_stream.read()       # and read it all
+    """
+
+    __file__ = "cStringIO.c"
+
+    def StringIO(o):
+        """Return a StringIO-like stream for reading or writing"""
+    StringIO.pyarg = "|O"
+
+    class InputType(Type):
+        "Simple type for treating strings as input file streams"
+        
+        abbrev = "input"
+
+        struct = """\
+        typedef struct {
+                PyObject_HEAD
+                char *buf;
+                int pos;
+                int size;
+                PyObject *pbuf;
+        } InputObject;
+        """
+
+        def flush(self):
+            """Does nothing"""
+
+        def getvalue(self):
+            """Get the string value.
+
+            If use_pos is specified and is a true value, then the
+            string returned will include only the text up to the
+            current file position.
+            """
+
+        def isatty(self):
+            """Always returns False"""
+
+        def read(self, s):
+            """Return s characters or the rest of the string."""
+        read.pyarg = "|i"
+
+        def readline(self):
+            """Read one line."""
+
+        def readlines(self, hint):
+            """Read all lines."""
+        readlines.pyarg = "|i"
+
+        def reset(self):
+            """Reset the file position to the beginning."""
+
+        def tell(self):
+            """Get the current position."""
+
+        def truncate(self, pos):
+            """Truncate the file at the current position."""
+        truncate.pyarg = "|i"
+
+        def seek(self, position, mode=0):
+            """Set the current position.
+
+            The optional mode argument can be 0 for absolute, 1 for relative,
+            and 2 for relative to EOF.  The default is absolute.
+            """
+        seek.pyarg = "i|i"
+
+        def close(self):
+            pass
+
+    class OutputType(InputType):
+        "Simple type for output strings."
+
+        abbrev = "output"
+
+        struct = """\
+        typedef struct {
+                PyObject_HEAD
+                char *buf;
+                int pos;
+                int size;
+                int softspace;
+        } OutputObject;
+        """
+
+        softspace = member()
+
+        def close(self):
+            """Explicitly release resources."""
+
+        def write(self, s):
+            """Write a string to the file."""
+            # XXX Hack: writing None resets the buffer
+
+        def writelines(self, lines):
+            """Write each string in lines."""
+        
+
+cStringIO.gen()
+
diff --git a/Tools/framer/framer/__init__.py b/Tools/framer/framer/__init__.py
new file mode 100644
index 0000000..ab73a30
--- /dev/null
+++ b/Tools/framer/framer/__init__.py
@@ -0,0 +1,8 @@
+"""A tool to generate basic framework for C extension types.
+
+The basic ideas is the same as modulator, but the code generates code
+using many of the new features introduced in Python 2.2.  It also
+takes a more declarative approach to generating code.
+"""
+
+
diff --git a/Tools/framer/framer/bases.py b/Tools/framer/framer/bases.py
new file mode 100644
index 0000000..61052ab
--- /dev/null
+++ b/Tools/framer/framer/bases.py
@@ -0,0 +1,221 @@
+"""Provides the Module and Type base classes that user code inherits from."""
+
+__all__ = ["Module", "Type", "member"]
+
+from framer import struct, template
+from framer.function import Function, Method
+from framer.member import member
+from framer.slots import *
+from framer.util import cstring, unindent
+
+from types import FunctionType
+
+def sortitems(dict):
+    L = dict.items()
+    L.sort()
+    return L
+
+# The Module and Type classes are implemented using metaclasses,
+# because most of the methods are class methods.  It is easier to use
+# metaclasses than the cumbersome classmethod() builtin.  They have
+# class methods because they are exposed to user code as base classes.
+
+class BaseMetaclass(type):
+    """Shared infrastructure for generating modules and types."""
+
+    # just methoddef so far
+
+    def dump_methoddef(self, f, functions, vars):
+        def p(templ, vars=vars): # helper function to generate output
+            print >> f, templ % vars
+
+        if not functions:
+            return
+        p(template.methoddef_start)
+        for name, func in sortitems(functions):
+            if func.__doc__:
+                p(template.methoddef_def_doc, func.vars)
+            else:
+                p(template.methoddef_def, func.vars)
+        p(template.methoddef_end)
+
+class ModuleMetaclass(BaseMetaclass):
+    """Provides methods for Module class."""
+
+    def gen(self):
+        self.analyze()
+        self.initvars()
+        f = open(self.__filename, "w")
+        self.dump(f)
+        f.close()
+
+    def analyze(self):
+        self.name = getattr(self, "abbrev", self.__name__)
+        self.__functions = {}
+        self.__types = {}
+        self.__members = False
+
+        for name, obj in self.__dict__.iteritems():
+            if isinstance(obj, FunctionType):
+                self.__functions[name] = Function(obj, self)
+            elif isinstance(obj, TypeMetaclass):
+                obj._TypeMetaclass__module = self.name
+                obj.analyze()
+                self.__types[name] = obj
+                if obj.has_members():
+                    self.__members = True
+                    
+    def initvars(self):
+        v = self.__vars = {}
+        filename = getattr(self, "__file__", None)
+        if filename is None:
+            filename = self.__name__ + "module.c"
+        self.__filename = v["FileName"] = filename
+        name = v["ModuleName"] = self.__name__
+        v["MethodDefName"] = "%s_methods" % name
+        v["ModuleDocstring"] = cstring(unindent(self.__doc__))
+
+    def dump(self, f):
+        def p(templ, vars=self.__vars): # helper function to generate output
+            print >> f, templ % vars
+
+        p(template.module_start)
+        if self.__members:
+            p(template.member_include)
+        print >> f
+        
+        if self.__doc__:
+            p(template.module_doc)
+
+        for name, type in sortitems(self.__types):
+            type.dump(f)
+
+        for name, func  in sortitems(self.__functions):
+            func.dump(f)
+
+        self.dump_methoddef(f, self.__functions, self.__vars)
+
+        p(template.module_init_start)
+        for name, type in sortitems(self.__types):
+            type.dump_init(f)
+            
+        p("}")
+
+class Module:
+    __metaclass__ = ModuleMetaclass
+
+class TypeMetaclass(BaseMetaclass):
+
+    def dump(self, f):
+        self.initvars()
+
+        # defined after initvars() so that __vars is defined
+        def p(templ, vars=self.__vars):
+            print >> f, templ % vars
+
+        if self.struct is not None:
+            print >> f, unindent(self.struct, False)
+
+        if self.__doc__:
+            p(template.docstring)
+
+        for name, func in sortitems(self.__methods):
+            func.dump(f)
+            
+        self.dump_methoddef(f, self.__methods, self.__vars)
+        self.dump_memberdef(f)
+        self.dump_slots(f)
+
+    def has_members(self):
+        if self.__members:
+            return True
+        else:
+            return False
+
+    def analyze(self):
+        # called by ModuleMetaclass analyze()
+        self.name = getattr(self, "abbrev", self.__name__)
+        src = getattr(self, "struct", None)
+        if src is not None:
+            self.__struct = struct.parse(src)
+        else:
+            self.__struct = None
+        self.__methods = {}
+        self.__members = {}
+        for cls in self.__mro__:
+            for k, v in cls.__dict__.iteritems():
+                if isinstance(v, FunctionType):
+                    self.__methods[k] = Method(v, self)
+                if isinstance(v, member):
+                    self.__members[k] = v
+                    assert self.__struct is not None
+                    v.register(k, self.__struct)
+        self.analyze_slots()
+
+    def analyze_slots(self):
+        self.__slots = {}
+        for s in Slots:
+            if s.special is not None:
+                meth = self.__methods.get(s.special)
+                if meth is not None:
+                    self.__slots[s] = meth
+        self.__slots[TP_NAME] = '"%s.%s"' % (self.__module, self.__name__)
+        if self.__doc__:
+            self.__slots[TP_DOC] = "%s_doc" % self.name
+        if self.__struct is not None:
+            self.__slots[TP_BASICSIZE] = "sizeof(%s)" % self.__struct.name
+            self.__slots[TP_DEALLOC] = "%s_dealloc" % self.name
+        if self.__methods:
+            self.__slots[TP_METHODS] = "%s_methods" % self.name
+        if self.__members:
+            self.__slots[TP_MEMBERS] = "%s_members" % self.name
+
+    def initvars(self):
+        v = self.__vars = {}
+        v["TypeName"] = self.__name__
+        v["CTypeName"] = "Py%s_Type" % self.__name__
+        v["MethodDefName"] = self.__slots[TP_METHODS]
+        if self.__doc__:
+            v["DocstringVar"] = self.__slots[TP_DOC]
+            v["Docstring"] = cstring(unindent(self.__doc__))
+        if self.__struct is not None:
+            v["StructName"] = self.__struct.name
+        if self.__members:
+            v["MemberDefName"] = self.__slots[TP_MEMBERS]
+
+    def dump_memberdef(self, f):
+        def p(templ, vars=self.__vars):
+            print >> f, templ % vars
+
+        if not self.__members:
+            return
+        p(template.memberdef_start)
+        for name, slot in sortitems(self.__members):
+            slot.dump(f)
+        p(template.memberdef_end)
+
+    def dump_slots(self, f):
+        def p(templ, vars=self.__vars):
+            print >> f, templ % vars
+
+        if self.struct:
+           p(template.dealloc_func, {"name" : self.__slots[TP_DEALLOC]})
+
+        p(template.type_struct_start)
+        for s in Slots[:-5]: # XXX
+            val = self.__slots.get(s, s.default)
+            ntabs = 4 - (4 + len(val)) / 8
+            line = "        %s,%s/* %s */" % (val, "\t" * ntabs, s.name)
+            print >> f, line
+        p(template.type_struct_end)
+
+    def dump_init(self, f):
+        def p(templ):
+            print >> f, templ % self.__vars
+
+        p(template.type_init_type)
+        p(template.module_add_type)
+
+class Type:
+    __metaclass__ = TypeMetaclass
+
diff --git a/Tools/framer/framer/function.py b/Tools/framer/framer/function.py
new file mode 100644
index 0000000..595cc8d
--- /dev/null
+++ b/Tools/framer/framer/function.py
@@ -0,0 +1,173 @@
+"""Functions."""
+
+from framer import template
+from framer.util import cstring, unindent
+
+METH_O = "METH_O"
+METH_NOARGS = "METH_NOARGS"
+METH_VARARGS = "METH_VARARGS"
+
+def parsefmt(fmt):
+    for c in fmt:
+        if c == '|':
+            continue
+        yield c
+
+class Argument:
+
+    def __init__(self, name):
+        self.name = name
+        self.ctype = "PyObject *"
+        self.default = None
+
+    def __str__(self):
+        return "%s%s" % (self.ctype, self.name)
+
+    def setfmt(self, code):
+        self.ctype = self._codes[code]
+        if self.ctype[-1] != "*":
+            self.ctype += " "
+
+    _codes = {"O": "PyObject *",
+              "i": "int",
+              }
+
+    def decl(self):
+        if self.default is None:
+            return str(self) + ";"
+        else:
+            return "%s = %s;" % (self, self.default)
+
+class _ArgumentList(object):
+
+    # these instance variables should be initialized by subclasses
+    ml_meth = None
+    fmt = None
+
+    def __init__(self, args):
+        self.args = map(Argument, args)
+
+    def __len__(self):
+        return len(self.args)
+
+    def __getitem__(self, i):
+        return self.args[i]
+
+    def dump_decls(self, f):
+        pass
+        
+class NoArgs(_ArgumentList):
+
+    def __init__(self, args):
+        assert len(args) == 0
+        super(NoArgs, self).__init__(args)
+        self.ml_meth = METH_NOARGS
+
+    def c_args(self):
+        return "PyObject *self"
+
+class OneArg(_ArgumentList):
+    
+    def __init__(self, args):
+        assert len(args) == 1
+        super(OneArg, self).__init__(args)
+        self.ml_meth = METH_O
+
+    def c_args(self):
+        return "PyObject *self, %s" % self.args[0]
+
+class VarArgs(_ArgumentList):
+
+    def __init__(self, args, fmt=None):
+        super(VarArgs, self).__init__(args)
+        self.ml_meth = METH_VARARGS
+        if fmt is not None:
+            self.fmt = fmt
+            i = 0
+            for code in parsefmt(fmt):
+                self.args[i].setfmt(code)
+                i += 1
+
+    def c_args(self):
+        return "PyObject *self, PyObject *args"
+
+    def targets(self):
+        return ", ".join(["&%s" % a.name for a in self.args])
+
+    def dump_decls(self, f):
+        for a in self.args:
+            print >> f, "        %s" % a.decl()
+
+def ArgumentList(func, method):
+    code = func.func_code
+    args = code.co_varnames[:code.co_argcount]
+    if method:
+        args = args[1:]
+    pyarg = getattr(func, "pyarg", None)
+    if pyarg is not None:
+        args = VarArgs(args, pyarg)
+        if func.func_defaults:
+            L = list(func.func_defaults)
+            ndefault = len(L)
+            i = len(args) - ndefault
+            while L:
+                args[i].default = L.pop(0)
+        return args
+    else:
+        if len(args) == 0:
+            return NoArgs(args)
+        elif len(args) == 1:
+            return OneArg(args)
+        else:
+            return VarArgs(args)
+
+class Function:
+
+    method = False
+
+    def __init__(self, func, parent):
+        self._func = func
+        self._parent = parent
+        self.analyze()
+        self.initvars()
+
+    def dump(self, f):
+        def p(templ, vars=None): # helper function to generate output
+            if vars is None:
+                vars = self.vars
+            print >> f, templ % vars
+
+        if self.__doc__:
+            p(template.docstring)
+            
+        d = {"name" : self.vars["CName"],
+             "args" : self.args.c_args(),
+             }
+        p(template.funcdef_start, d)
+
+        self.args.dump_decls(f)
+
+        if self.args.ml_meth == METH_VARARGS:
+            p(template.varargs)
+        
+        p(template.funcdef_end)
+
+    def analyze(self):
+        self.__doc__ = self._func.__doc__
+        self.args = ArgumentList(self._func, self.method)
+        
+    def initvars(self):
+        v = self.vars = {}
+        v["PythonName"] = self._func.__name__
+        s = v["CName"] = "%s_%s" % (self._parent.name, self._func.__name__)
+        v["DocstringVar"] = s + "_doc"
+        v["MethType"] = self.args.ml_meth
+        if self.__doc__:
+            v["Docstring"] = cstring(unindent(self.__doc__))
+        if self.args.fmt is not None:
+            v["ArgParse"] = self.args.fmt
+            v["ArgTargets"] = self.args.targets()
+
+class Method(Function):
+
+    method = True
diff --git a/Tools/framer/framer/member.py b/Tools/framer/framer/member.py
new file mode 100644
index 0000000..5faf462
--- /dev/null
+++ b/Tools/framer/framer/member.py
@@ -0,0 +1,73 @@
+from framer import template
+from framer.util import cstring, unindent
+
+T_SHORT = "T_SHORT"
+T_INT = "T_INT"
+T_LONG = "T_LONG"
+T_FLOAT = "T_FLOAT"
+T_DOUBLE = "T_DOUBLE"
+T_STRING = "T_STRING"
+T_OBJECT = "T_OBJECT"
+T_CHAR = "T_CHAR"
+T_BYTE = "T_BYTE"
+T_UBYTE = "T_UBYTE"
+T_UINT = "T_UINT"
+T_ULONG = "T_ULONG"
+T_STRING_INPLACE = "T_STRING_INPLACE"
+T_OBJECT_EX = "T_OBJECT_EX"
+
+RO = READONLY = "READONLY"
+READ_RESTRICTED = "READ_RESTRICTED"
+WRITE_RESTRICTED = "WRITE_RESTRICTED"
+RESTRICT = "RESTRICTED"
+
+c2t = {"int" : T_INT,
+       "unsigned int" : T_UINT,
+       "long" : T_LONG,
+       "unsigned long" : T_LONG,
+       "float" : T_FLOAT,
+       "double" : T_DOUBLE,
+       "char *" : T_CHAR,
+       "PyObject *" : T_OBJECT,
+       }
+
+class member(object):
+
+    def __init__(self, cname=None, type=None, flags=None, doc=None):
+        self.type = type
+        self.flags = flags
+        self.cname = cname
+        self.doc = doc
+        self.name = None
+        self.struct = None
+
+    def register(self, name, struct):
+        self.name = name
+        self.struct = struct
+        self.initvars()
+
+    def initvars(self):
+        v = self.vars = {}
+        v["PythonName"] = self.name
+        if self.cname is not None:
+            v["CName"] = self.cname
+        else:
+            v["CName"] = self.name
+        v["Flags"] = self.flags or "0"
+        v["Type"] = self.get_type()
+        if self.doc is not None:
+            v["Docstring"] = cstring(unindent(self.doc))
+        v["StructName"] = self.struct.name
+
+    def get_type(self):
+        """Deduce type code from struct specification if not defined"""
+        if self.type is not None:
+            return self.type
+        ctype = self.struct.get_type(self.name)
+        return c2t[ctype]
+
+    def dump(self, f):
+        if self.doc is None:
+            print >> f, template.memberdef_def % self.vars
+        else:
+            print >> f, template.memberdef_def_doc % self.vars
diff --git a/Tools/framer/framer/slots.py b/Tools/framer/framer/slots.py
new file mode 100644
index 0000000..d369c9a
--- /dev/null
+++ b/Tools/framer/framer/slots.py
@@ -0,0 +1,64 @@
+"""Descriptions of all the slots in Python's type objects."""
+
+class Slot(object):
+    def __init__(self, name, cast=None, special=None, default="0"):
+        self.name = name
+        self.cast = cast
+        self.special = special
+        self.default = default
+
+Slots = (Slot("ob_size"),
+         Slot("tp_name"),
+         Slot("tp_basicsize"),
+         Slot("tp_itemsize"),
+         Slot("tp_dealloc", "destructor"),
+         Slot("tp_print", "printfunc"),
+         Slot("tp_getattr", "getattrfunc"),
+         Slot("tp_setattr", "setattrfunc"),
+         Slot("tp_compare", "cmpfunc", "__cmp__"),
+         Slot("tp_repr", "reprfunc", "__repr__"),
+         Slot("tp_as_number"),
+         Slot("tp_as_sequence"),
+         Slot("tp_as_mapping"),
+         Slot("tp_hash", "hashfunc", "__hash__"),
+         Slot("tp_call", "ternaryfunc", "__call__"),
+         Slot("tp_str", "reprfunc", "__str__"),
+         Slot("tp_getattro", "getattrofunc", "__getattr__", # XXX
+              "PyObject_GenericGetAttr"),
+         Slot("tp_setattro", "setattrofunc", "__setattr__"),
+         Slot("tp_as_buffer"),
+         Slot("tp_flags", default="Py_TPFLAGS_DEFAULT"),
+         Slot("tp_doc"),
+         Slot("tp_traverse", "traverseprox"),
+         Slot("tp_clear", "inquiry"),
+         Slot("tp_richcompare", "richcmpfunc"),
+         Slot("tp_weaklistoffset"),
+         Slot("tp_iter", "getiterfunc", "__iter__"),
+         Slot("tp_iternext", "iternextfunc", "__next__"), # XXX
+         Slot("tp_methods"),
+         Slot("tp_members"),
+         Slot("tp_getset"),
+         Slot("tp_base"),
+         Slot("tp_dict"),
+         Slot("tp_descr_get", "descrgetfunc"),
+         Slot("tp_descr_set", "descrsetfunc"),
+         Slot("tp_dictoffset"),
+         Slot("tp_init", "initproc", "__init__"),
+         Slot("tp_alloc", "allocfunc"),
+         Slot("tp_new", "newfunc"),
+         Slot("tp_free", "freefunc"),
+         Slot("tp_is_gc", "inquiry"),
+         Slot("tp_bases"),
+         Slot("tp_mro"),
+         Slot("tp_cache"),
+         Slot("tp_subclasses"),
+         Slot("tp_weaklist"),
+         )
+
+# give some slots symbolic names
+TP_NAME = Slots[1]
+TP_BASICSIZE = Slots[2]
+TP_DEALLOC = Slots[4]
+TP_DOC = Slots[20]
+TP_METHODS = Slots[27]
+TP_MEMBERS = Slots[28]
diff --git a/Tools/framer/framer/struct.py b/Tools/framer/framer/struct.py
new file mode 100644
index 0000000..3948740
--- /dev/null
+++ b/Tools/framer/framer/struct.py
@@ -0,0 +1,52 @@
+"""Rudimentary parser for C struct definitions."""
+
+import re
+
+PyObject_HEAD = "PyObject_HEAD"
+PyObject_VAR_HEAD = "PyObject_VAR_HEAD"
+
+rx_name = re.compile("} (\w+);")
+
+class Struct:
+    def __init__(self, name, head, members):
+        self.name = name
+        self.head = head
+        self.members = members
+
+    def get_type(self, name):
+        for _name, type in self.members:
+            if name == _name:
+                return type
+        raise ValueError, "no member named %s" % name
+
+def parse(s):
+    """Parse a C struct definition.
+
+    The parser is very restricted in what it will accept.
+    """
+
+    lines = filter(None, s.split("\n")) # get non-empty lines
+    assert lines[0].strip() == "typedef struct {"
+    pyhead = lines[1].strip()
+    assert (pyhead.startswith("PyObject") and
+            pyhead.endswith("HEAD"))
+    members = []
+    for line in lines[2:]:
+        line = line.strip()
+        if line.startswith("}"):
+            break
+        
+        assert line.endswith(";")
+        line = line[:-1]
+        words = line.split()
+        name = words[-1]
+        type = " ".join(words[:-1])
+        if name[0] == "*":
+            name = name[1:]
+            type += " *"
+        members.append((name, type))
+    name = None
+    mo = rx_name.search(line)
+    assert mo is not None
+    name = mo.group(1)
+    return Struct(name, pyhead, members)
diff --git a/Tools/framer/framer/structparse.py b/Tools/framer/framer/structparse.py
new file mode 100644
index 0000000..419228a
--- /dev/null
+++ b/Tools/framer/framer/structparse.py
@@ -0,0 +1,46 @@
+"""Rudimentary parser for C struct definitions."""
+
+import re
+
+PyObject_HEAD = "PyObject_HEAD"
+PyObject_VAR_HEAD = "PyObject_VAR_HEAD"
+
+rx_name = re.compile("} (\w+);")
+
+class Struct:
+    def __init__(self, name, head, members):
+        self.name = name
+        self.head = head
+        self.members = members
+
+def parse(s):
+    """Parse a C struct definition.
+
+    The parser is very restricted in what it will accept.
+    """
+
+    lines = filter(None, s.split("\n")) # get non-empty lines
+    assert lines[0].strip() == "typedef struct {"
+    pyhead = lines[1].strip()
+    assert (pyhead.startswith("PyObject") and
+            pyhead.endswith("HEAD"))
+    members = []
+    for line in lines[2:]:
+        line = line.strip()
+        if line.startswith("}"):
+            break
+        
+        assert line.endswith(";")
+        line = line[:-1]
+        words = line.split()
+        name = words[-1]
+        type = " ".join(words[:-1])
+        if name[0] == "*":
+            name = name[1:]
+            type += " *"
+        members.append((name, type))
+    name = None
+    mo = rx_name.search(line)
+    assert mo is not None
+    name = mo.group(1)
+    return Struct(name, pyhead, members)
diff --git a/Tools/framer/framer/template.py b/Tools/framer/framer/template.py
new file mode 100644
index 0000000..70e2591
--- /dev/null
+++ b/Tools/framer/framer/template.py
@@ -0,0 +1,102 @@
+"""framer's C code templates.
+
+Templates use the following variables:
+
+FileName: name of the file that contains the C source code
+ModuleName: name of the module, as in "import ModuleName"
+ModuleDocstring: C string containing the module doc string
+"""
+
+module_start = '#include "Python.h"'
+member_include = '#include "structmember.h"'
+
+module_doc = """\
+PyDoc_STRVAR(%(ModuleName)s_doc,
+%(ModuleDocstring)s);
+"""
+
+methoddef_start = """\
+static struct PyMethodDef %(MethodDefName)s[] = {"""
+
+methoddef_def = """\
+	{"%(PythonName)s", (PyCFunction)%(CName)s, %(MethType)s},"""
+        
+methoddef_def_doc = """\
+	{"%(PythonName)s", (PyCFunction)%(CName)s, %(MethType)s,
+         %(DocstringVar)s},"""
+        
+methoddef_end = """\
+	{NULL, NULL}
+};
+"""
+
+memberdef_start = """\
+#define OFF(X) offsetof(%(StructName)s, X)
+
+static struct PyMemberDef %(MemberDefName)s[] = {"""
+
+memberdef_def_doc = """\
+        {"%(PythonName)s", %(Type)s, OFF(%(CName)s), %(Flags)s,
+         %(Docstring)s},"""
+
+memberdef_def = """\
+        {"%(PythonName)s", %(Type)s, OFF(%(CName)s), %(Flags)s},"""
+
+memberdef_end = """\
+	{NULL}
+};
+
+#undef OFF
+"""
+
+dealloc_func = """static void
+%(name)s(PyObject *ob)
+{
+}
+"""
+
+docstring = """\
+PyDoc_STRVAR(%(DocstringVar)s,
+%(Docstring)s);
+"""
+
+funcdef_start = """\
+static PyObject *
+%(name)s(%(args)s)
+{"""
+
+funcdef_end = """\
+}
+"""
+
+varargs = """\
+        if (!PyArg_ParseTuple(args, \"%(ArgParse)s:%(PythonName)s\",
+                              %(ArgTargets)s))
+                return NULL;"""
+
+module_init_start = """\
+PyMODINIT_FUNC
+init%(ModuleName)s(void)
+{
+        PyObject *mod;
+
+        mod = Py_InitModule3("%(ModuleName)s", %(MethodDefName)s,
+                             %(ModuleName)s_doc);
+        if (mod == NULL)
+                return;
+"""
+
+type_init_type = "        %(CTypeName)s.ob_type = &PyType_Type;"
+module_add_type = """\
+        if (!PyObject_SetAttrString(mod, "%(TypeName)s",
+                                    (PyObject *)&%(CTypeName)s))
+                return;
+"""
+
+type_struct_start = """\
+static PyTypeObject %(CTypeName)s = {
+        PyObject_HEAD_INIT(0)"""
+        
+type_struct_end = """\
+};
+"""
diff --git a/Tools/framer/framer/util.py b/Tools/framer/framer/util.py
new file mode 100644
index 0000000..73f3309
--- /dev/null
+++ b/Tools/framer/framer/util.py
@@ -0,0 +1,35 @@
+def cstring(s, width=70):
+    """Return C string representation of a Python string.
+
+    width specifies the maximum width of any line of the C string.
+    """
+    L = []
+    for l in s.split("\n"):
+        if len(l) < width:
+            L.append(r'"%s\n"' % l)
+
+    return "\n".join(L)
+
+def unindent(s, skipfirst=True):
+    """Return an unindented version of a docstring.
+
+    Removes indentation on lines following the first one, using the
+    leading whitespace of the first indented line that is not blank
+    to determine the indentation.
+    """
+
+    lines = s.split("\n")
+    if skipfirst:
+        first = lines.pop(0)
+        L = [first]
+    else:
+        L = []
+    indent = None
+    for l in lines:
+        ls = l.strip()
+        if ls:
+            indent = len(l) - len(ls)
+            break
+    L += [l[indent:] for l in lines]
+
+    return "\n".join(L)