#!/usr/bin/env python2
# -*- Mode: Python; py-indent-offset: 4 -*-
#
# Copyright (C) 2006-2009 John Finlay.
#
# Scan the given public .h files of a GTK module (or module using
# GTK object conventions) and generates a set of scheme defs.
#
# defsgen uses the ctypes module to extract information from the installed
# module library (or libraries) to generate the object, interface, function,
# method, virtual, enum and flags defs. defsgen uses the gobject library
# g_type_* functions. defsgen will try to open the "libgobject-2.0.so" library
# if one is not specified on the command line.
#
# Basically the operation of defsgen is:
#
# - open and initialize the gobject and module libraries
# - read each .h file into a buffer which is scrubbed of extraneous data
# - find all *_get_type() functions prototypes
# - look in the module libraries for the get_type symbols
# - if found run the get_type() function to retrieve the GType
# - find the parent type name and save the object info
# - find each function prototypes in the file and check if it has a symbol in
#   the module libraries - save the info if found
# - extract the virtual prototypes from the Class or Iface structs and save
# - write out the various defs.
#
# The command line options are:
#
#   -l --modulelib   Adds the given module library name to the list to be used
#                    for finding symbols. Mor ethan one modulefile may be
#                    specified. (required)
#   -L --libgobject  Specifies the name of the gobject library (optional but
#                    must be specified if "libgobject-2.0.so" is not availble)
#   -s --separate    Create separate files for objects and function/method defs
#                    using the given name as the base name (optional). If this
#                    is not specified the combined object and function defs
#                    will be output to sys.stdout.
#   -f --defsfile    Extract defs from the given file to filter the output defs
#                    that is don't output defs that are defined in the
#                    defsfile. More than one deffile may be specified.
#   -D --defines     Include portions of the defsfile defs that are conditional
#                    on the given define, for example GDK_TARGET_X11. Only
#                    useful with the --defsfile option
#   -m --modulename  The prefix to be stripped from the front of function names
#                    for the given module
#   -p --useprefix   Use the modulename prefix as a hint to split names into
#                    module and name for object and enum defs. Also used for
#                    generating type codes.
#   --onlyenums      Only produce defs for enums and flags
#   --onlyobjdefs    Only produce defs for objects
#   --onlyvirtuals   Only produce defs for virtuals
#   --genpropgetsets Experimental option to generate prop-getset annotations.
#                    Not supported by codegen.py and friends.
#
# Examples:
#
# python defsgen.py -m pango -l libpango-1.0.so \
#            /usr/local/include/pango-1.0/pango/*.h >/tmp/pango.defs
#
# - Outputs all defs for the pango module.using the library module
#   libpango-1.0.so.
#
# python defsgen.py -m gdk -DGDK_TARGET_X11 -l libgdk-x11-2.0.so \
#            -l libgdk_pixbuf-2.0.so -s /tmp/gdk-2.10 \
#            -f /usr/tmp/pygtk/gtk/gdk-base.defs \
#            /usr/local/include/gtk-2.0/gdk/*.h \
#            /usr/local/include/gtk-2.0/gdk-pixbuf/*.h
#
# - Outputs the gdk module defs that are not contained in the defs file
#   /usr/tmp/pygtk/gtk/gdk-base.defs. Two output files are created:
#   /tmp/gdk-2.10-types.defs and /tmp/gdk-2.10.defs.
#
# Based on the original h2def.py program by
# Toby D. Reeves <toby@max.rl.plh.af.mil> and
# modified by James Henstridge <james@daa.com.au> to output stuff in
# Havoc's new defs format.  Info on this format can be seen at:
# http://www.gnome.org/mailing-lists/archives/gtk-devel-list/2000-January/0085.shtml
# Updated to be PEP-8 compatible and refactored to use OOP
# Extensively modified by John Finlay to use ctypes module to extract GType
# info from the given library and to create virtual defines.
#

import getopt
import os
import re, string
import sys
import ctypes
import defsparser

#------------------ utility defs -----------------

_upperstr_pat1 = re.compile(r'([^A-Z])([A-Z])')
_upperstr_pat2 = re.compile(r'([A-Z][A-Z])([A-Z][0-9a-z])')
_upperstr_pat3 = re.compile(r'^([A-Z])([A-Z])')

def to_upper_str(name):
    """Converts a typename to the equivalent uppercase and underscores
    name.  This is used to form the type conversion macros and enum/flag
    name variables"""
    name = _upperstr_pat1.sub(r'\1_\2', name)
    name = _upperstr_pat2.sub(r'\1_\2', name)
    name = _upperstr_pat3.sub(r'\1_\2', name, count=1)
    return name.upper()

def typecode(typename, prefix, use_prefix):
    """create a typecode (eg. GTK_TYPE_WIDGET) from a typename"""
    tcode = to_upper_str(typename)
    if (use_prefix and prefix and tcode.lower() != prefix
        and tcode.lower().startswith(prefix)):
        l = len(prefix)
        tcode = tcode[:l] + '_TYPE' + tcode[l:]
    else:
        tcode = tcode.replace('_', '_TYPE_', 1)
    return tcode

_class_iface_pat = re.compile(r'\w+(Class|Iface)')

def class_iface_sub(mobj):
    '''Returns matched string if it matches a Class or Iface struct
    otherwise returns the empty string'''
    if not _class_iface_pat.match(mobj.group(1)):
        return ''
    return mobj.group(0)

clean_patterns = [
    # strip comments
    (re.compile(r'/\*.*?\*/', re.DOTALL), ''),
    # compact continued lines
    (re.compile(r"\\\n", re.MULTILINE), ''),
    # remove preprocess directives
    (re.compile(r"""^[#].*?$""", re.MULTILINE), ''),
    # strip DECLS macros and Windows DLL API macros
    (re.compile(r"""G_BEGIN_DECLS|BEGIN_LIBGTOP_DECLS|G_END_DECLS|[A-Z]+_API """,
                re.MULTILINE), ''),
    # remove extern "C"
    (re.compile(r'^\s*(extern)\s+"C"\s+{', re.MULTILINE), ''),
    # remove singleline typedefs of stucts
    (re.compile(r'^typedef\s+struct\s*[^{;\n]*;\s*$', re.MULTILINE), ''),
    # remove all struct definitons but those for object classes and interfaces
    (re.compile(r'^struct\s+(\w+)\s+{[^}]+}\s*;\s*$', re.MULTILINE),
     class_iface_sub),
    # compress multiple whitespace
    (re.compile(r'\s+', re.MULTILINE), ' '),
    # clean up line ends
    (re.compile(r';\s*', re.MULTILINE), '\n'),
    (re.compile(r'^\s*', re.MULTILINE), ''),
    # associate *, &, and [] with type instead of variable
    (re.compile(r' \s* ([*|&]+) \s* ([(\w]+)', re.VERBOSE), r'\1 \2'),
    (re.compile(r'\s+ (\w+) \[ \s* \]', re.VERBOSE), r'[] \1'),
    # make return types that are const work.
    (re.compile(r'\s*\*\s*G_CONST_RETURN\s*\*\s*'), '** '),
    (re.compile(r'G_CONST_RETURN |const '), 'const-'),
    # remove typedefs of callback types
    (re.compile(r'^typedef\s+\w+\s*\*?\s*\(\s*\*\s*\w+\)\s*\([^(]*\)\n',
                re.MULTILINE), ''),
    #strip GSEAL macros from the middle of function declarations:
    (re.compile(r"""GSEAL""", re.VERBOSE), '')
    ]

def clean_buffer(buf):
    """Cleans out extraneous data leaving function prototypes, Class and Iface
    structs."""
    for pat, subst in clean_patterns:
        buf = pat.sub(subst, buf)
    return buf

# ------------------ utility classes -------------------------

class ObjDef(object):
    def __init__(self, name, type_id, parent_name, parent_type, base_name):
        self.name = name
        self.type = type_id
        self.parent_name = parent_name
        self.parent_type = parent_type
        self.base_name = base_name
        self.props = []
        return
    def __cmp__(self, other):
        try:
            res = cmp(self.name, other.name)
        except AttributeError:
            res = cmp(id(self), id(other))
        return res
    def set_properties(self, gobj):
        if self.base_name == 'GObject':
            self.props = self._get_gobject_properties(gobj)
        elif self.base_name == 'GInterface':
            self.props = self._get_ginterface_properties(gobj)

    def _get_gobject_properties(self, gobj):
        klass = gobj.g_type_class_ref(self.type)
        num = ctypes.c_uint()
        plist = gobj.g_object_class_list_properties(klass, ctypes.byref(num))
        props = [plist[i][0].name for i in range(num.value)
                 if self.name == gobj.g_type_name(plist[i][0].owner_type)]
        return props
    def _get_ginterface_properties(self, gobj):
        iface = gobj.g_type_default_interface_ref(self.type)
        num = ctypes.c_uint()
        plist = gobj.g_object_interface_list_properties(iface,
                                                        ctypes.byref(num))
        props = [plist[i][0].name for i in range(num.value)]
        return props

# ------------------ Find object definitions -----------------

split_prefix_pat = re.compile('([A-Z]+[a-z]*)([A-Za-z0-9]+)')

get_type_pat = re.compile(r'''^\s*(GType|GtkType)\s+
([a-z]\w+_get_type)\s*(\(void\)|\(\)).*$''', re.VERBOSE | re.MULTILINE)

defkeys = 'GBoxed GInterface GObject gpointer GEnum GFlags'

def find_defs(buf, gobj, modlib, defs):
    """Find possible gobject, gboxed, interface, gpointer, enum and flags
    definitions in header files.and find parent type."""
    #  find all *_get_type() functions that may represent a GObject
    for m in get_type_pat.findall(buf):
        func_name = m[1]
        for lib in modlib:
            if hasattr(lib, func_name):
                objtype = apply(getattr(lib, func_name))
                obj_name = gobj.g_type_name(objtype)
                parent = gobj.g_type_parent(objtype)
                parent_name = gobj.g_type_name(parent)
                base_name = gobj.g_type_name(gobj.g_type_fundamental(parent))
                #if gobj.g_type_is_a(parent, gobj.GObject):
                #    base_name = 'GObject'
                if base_name in defkeys:
                    obj = ObjDef(obj_name, objtype, parent_name, parent,
                                 base_name)
                    obj.set_properties(gobj)
                    defs[obj.base_name].append(obj)
                break
    return

# ------------------ Find function definitions -----------------

arg_split_pat = re.compile("\s*,\s*")

proto_pat=re.compile(r"""^
\s*((?:-|\w|\&|\*)+)  # return type
\s+                   # skip whitespace
([a-z]\w+)\s*[(]      # match the function name until the opening (
\s*(.*?)\s*[)].*      # group the function arguments
$""", re.IGNORECASE|re.VERBOSE|re.MULTILINE)

def find_func_defs(buf, modlib, deffile, defs, verbose):
    '''Find function prototypes in buf that have symbols in modlib
    and save in defs.'''
    funcs = defs['funcs'][deffile] = []
    for m in proto_pat.findall(buf):
        ret, func, args = m
        if not True in [hasattr(lib, func) for lib in modlib]:
            if verbose:
                sys.stderr.write('no symbol for function: ' + func
                                 + ' from file' + deffile + '\n')
        else:
            args = arg_split_pat.split(args)
            args = [a.replace(' ','-', a.count(' ')-1) for a in args]
            funcs.append((func, ret, args))
    return

virtual_pat = re.compile(r'''^
\s*((?:-|\w|\&|\*)+)   # return type
\s*                    # skip whitespace
\(\s*\*\s*             # opening (
([a-z]\w+)\)           # match the function name until the closing )
\s*\(\s*([^)]*)\).*      # group the function arguments
$''', re.VERBOSE|re.MULTILINE)

class_iface_struct_pat = re.compile(
    r'^struct\s+_(\w+)(?:Class|Iface)\s+{([^}]+)}\s*$', re.MULTILINE)

def find_virt_defs(buf, deffile, defs):
    '''Find virtual function prototypes in buf and save in defs.'''
    virts = defs['virts'][deffile] = []
    # get the Class or Iface structs
    for m in class_iface_struct_pat.findall(buf):
        objname, virtuals  = m
        for v in virtual_pat.findall(virtuals):
            ret, func, args = v
            if 'reserved' in func or args == 'void':
                continue
            args = arg_split_pat.split(args)
            args = [a.replace(' ','-', a.count(' ')-1) for a in args]
            virts.append((func, ret, args, objname))
    return

enum_pat = re.compile(r'^\s*typedef enum\s+{\s*([^}]*)}\s*([^\s]*)$',
                      re.MULTILINE)
values_splitter = re.compile(r'\s*,\s', re.MULTILINE)

def find_enums(buf, defs):
    for vals, name in enum_pat.findall(buf):
        if name != 'GdkCursorType':
            isflags = '<<' in vals
            entries = [val.split()[0] for val in values_splitter.split(vals)
                       if val.strip()]
            if entries:
                defs['untypedenums'][name] = (isflags, entries)
    return

# ------------------ write definitions -----------------

type_pat = re.compile(r'(?:const-)?([A-Za-z0-9]+)\*?\s+')
pointer_pat = re.compile('(.*)\*$')
func_new_pat = re.compile('(\w+)_new$')
getset_pat = re.compile(r'^(?:get|set)_(.*)$')

def split_prefix(cname, prefix, use_prefix):
    # use the module prefix to split the cname
    pre = prefix.replace('_', '')
    if use_prefix and cname.lower().startswith(pre):
        l = len(pre)
        module = cname[:l]
        name = cname[l:]
    else:
        m = split_prefix_pat.match(cname)
        if m:
            module = m.group(1)
            name = m.group(2)
    return module, name

class DefsWriter:
    def __init__(self, defs, fp=None, prefix=None, verbose=False,
                 defsfiles=None, defines={}, genpropgetsets=False,
                 useprefix=False):
        self.defs = defs
        self.use_prefix = useprefix
        self.objnames = reduce(list.__add__,
                               [[o.name for o in defs[base]]
                                for base in defkeys.split()[:3]])
        self.othernames = reduce(list.__add__,
                                 [[o.name for o in defs[base]]
                                  for base in defkeys.split()[3:]])
        self.objifacedefs = dict(reduce(list.__add__,
                                       [[(o.name, o) for o in defs[base]]
                                        for base in defkeys.split()[1:3]]))
        self.fp = (fp, sys.stdout)[not fp]
        self.prefix = prefix
        self.verbose = verbose
        self.genpropgetsets = genpropgetsets
        self._c_names={}
        for defsfile in defsfiles:
            filter = defsparser.DefsParser(defsfile, defines)
            filter.startParsing()
            self._c_names.update(filter.c_name)
            for vdef in filter.virtuals:
                self._c_names[vdef.of_object + '.' + vdef.name] = None
        return

    def write_func_defs(self, deffiles, onlyvirts=False):
        filter = self._c_names
        for deffile in deffiles:
            self.fp.write('\n;; From %s\n\n' % os.path.basename(deffile))
            if not onlyvirts:
                for func, ret, args in self.defs['funcs'][deffile]:
                    if not func in filter:
                        self._write_func(func, ret, args)
            for virt, ret, args, objname in self.defs['virts'][deffile]:
                if not objname + '.' + virt in filter:
                    self._write_virt(objname, virt, ret, args)
            self.fp.write('\n')
        return

    def write_enum_defs(self, fp=None):
        fp = (fp, self.fp)[not fp]
        klassptrs = {'GEnum':ctypes.POINTER(EnumClass),
                    'GFlags':ctypes.POINTER(FlagsClass)}
        filter = self._c_names
        objs = self.defs['GEnum'] + self.defs ['GFlags']
        #objs.sort()
        fp.write(';; Enumerations and Flags ...\n\n')
        for obj in objs:
            cname = name = obj.name
            tcode = typecode(cname, self.prefix, self.use_prefix)
            if cname in filter:
                continue
            if cname in self.defs['untypedenums']:
                if tcode not in self.defs['typedefines']:
                    # no type define so skip and print as untyped enum
                    continue
                self.defs['untypedenums'].pop(cname, None)
            parent_name = obj.parent_name
            klassptr = klassptrs[parent_name]
            typename = parent_name.lower()[1:]
            module = None
            module, name = split_prefix(cname, self.prefix, self.use_prefix)
            fp.write('(define-' + typename + ' ' + name + '\n')
            if module:
                fp.write('  (in-module "' + module + '")\n')
            fp.write('  (c-name "' + cname + '")\n')
            fp.write('  (gtype-id "' + tcode + '")\n')
            fp.write('  (values\n')
            classref = self.gobj.g_type_class_ref(obj.type)
            itemclass = ctypes.cast(classref, klassptr).contents
            for i in range(itemclass.n_values):
                val = itemclass.values[i]
                fp.write('    \'("%s" "%s")\n' % (val.value_nick,
                                                  val.value_name))
            fp.write('  )\n')
            fp.write(')\n\n')
        if self.defs['untypedenums']:
            self.write_untyped_enum_defs(fp)
        return

    def write_untyped_enum_defs(self, fp):
        fp.write(';; Untyped enumerations and flags ...\n\n')
        filter = self._c_names
        for cname, (isflags, entries) in self.defs['untypedenums'].items():
            if filter and cname in filter: continue
            module, name = split_prefix(cname, self.prefix, self.use_prefix)
            if isflags:
                fp.write('(define-flags ' + name + '\n')
            else:
                fp.write('(define-enum ' + name + '\n')
            if module:
                fp.write('  (in-module "' + module + '")\n')
            fp.write('  (c-name "' + cname + '")\n')
            preindex = entries[0].rfind('_')
            for ent in entries[1:]:
                while (preindex > 0
                       and ent[:preindex] != entries[0][:preindex]):
                    preindex = ent[:preindex].rfind('_')
            fp.write('  (values\n')
            for ent in entries:
                fp.write('    \'("%s" "%s")\n' %
                         (ent[preindex+1:].lower().replace('_', '-'), ent))
            fp.write('  )\n')
            fp.write(')\n\n')
        

    def _write_obj_helper(self, obj, fp):
        base_name = obj.base_name.lower()[1:]
        cmodule = None
        cname = name = obj.name
        type_id = obj.type
        parent_name = obj.parent_name
        cmodule, name = split_prefix(cname, self.prefix, self.use_prefix)
        fp.write('(define-' + base_name + ' ' + name + '\n')
        if cmodule:
            fp.write('  (in-module "' + cmodule + '")\n')
        if base_name == 'object':
            fp.write('  (parent "' + parent_name + '")\n')
        fp.write('  (c-name "' + cname + '")\n')
        fp.write('  (gtype-id "'
                 + typecode(cname, self.prefix, self.use_prefix) + '")\n')
        n = ctypes.c_uint()
        ifaces = self.gobj.g_type_interfaces(type_id, ctypes.byref(n))
        for i in range(n.value):
            iface_name = self.gobj.g_type_name(ifaces[i])
            if iface_name in self.interfaces:
                fp.write('  (implements "%s")\n' % iface_name)
        if base_name == 'interface':
            prereq = self.gobj.g_type_interface_prerequisites(type_id,
                                                              ctypes.byref(n))
            for i in range(n.value):
                fp.write('  (prerequisite "%s")\n'
                         % self.gobj.g_type_name(prereq[i]))
        # should do something about accessible fields
        fp.write(')\n\n')
        return

    def write_obj_defs(self, fp=None):
        fp = (fp, self.fp)[not fp]
        fp.write(';; -*- scheme -*-\n')
        filter = self._c_names
        for base in defkeys.split()[:4]:
            base_name = base.lower()[1:]
            fp.write('; %s definitions ...\n\n' % base_name)
            for obj in self.defs[base]:
                if not obj.name in filter:
                    self._write_obj_helper(obj, fp)
        return

    def _write_func(self, name, ret, args):
        if len(args) >= 1:
            # methods must have at least one argument
            munged_name = name.replace('_', '')
            m = type_pat.match(args[0])
            if m:
                obj = m.group(1)
                if munged_name.startswith(obj.lower()):
                    if obj not in self.othernames:
                        self._write_method(obj, name, ret, args)
                        return
        fname = name
        if self.prefix:
            fname = re.sub('^'+self.prefix+'_', '', fname)

        # it is either a constructor or normal function
        self.fp.write('(define-function ' + fname + '\n')
        self.fp.write('  (c-name "' + name + '")\n')

        # Asume that a constructor function name
        # ends with '_new' and it returns an object pointer.
        m = func_new_pat.match(name)
        r = pointer_pat.match(ret)
        if m and r:
            cname = ''
            # get the type name by using the _get_type function
            func = m.group(1) + '_get_type'
            lib = [l for l in self.modlib if hasattr(l, func)]
            if lib:
                cname = self.gobj.g_type_name(apply(getattr(lib[0], func)))
            if cname and self.gobj.g_type_from_name(r.group(1)):
                self.fp.write('  (is-constructor-of "' + cname + '")\n')
        self._write_return(ret)
        self._write_arguments(args)
        return

    def _write_method(self, obj, name, ret, args):
        regex = ''.join([c+'_?' for c in obj.lower()])
        mname, count = re.subn(regex, '', name, 1)
        if not count and self.prefix:
            mname = re.sub('^'+self.prefix+'_', '', mname)
        self.fp.write('(define-method ' + mname + '\n')
        self.fp.write('  (of-object "' + obj + '")\n')
        self.fp.write('  (c-name "' + name + '")\n')
        m = getset_pat.match(mname)
        if self.genpropgetsets and m and len(args[1:]) <= 1:
            prop = m.group(1)
            if obj in self.objifacedefs:
                oidef = self.objifacedefs[obj]
                if prop.replace('_', '-') in oidef.props:
                    self.fp.write('  (prop-getset "' + prop + '")\n')
        self._write_return(ret)
        self._write_arguments(args[1:])
        return

    def _write_virt(self, obj, name, ret, args):
        self.fp.write('(define-virtual ' + name + '\n')
        self.fp.write('  (of-object "' + obj + '")\n')
        self._write_return(ret)
        self._write_arguments(args[1:])
        return

    def _write_return(self, ret):
        if ret == 'void':
            ret = 'none'
        self.fp.write('  (return-type "' + ret + '")\n')
        return

    def _write_arguments(self, args):
        if args and not 'void' in args:
            varargs = ''
            self.fp.write('  (parameters\n')
            for arg in args:
                if arg == '...':
                    varargs = '  (varargs #t)\n'
                else:
                    tupleArg = tuple(arg.split())
                    if len(tupleArg) == 2:
                        self.fp.write('    \'("%s" "%s")\n' % tupleArg)
            self.fp.write('  )\n' + varargs)
        self.fp.write(')\n\n')

# ---------- ctypes support classes for gobject library functions ----------

GType = ctypes.c_uint

class GTypeClass(ctypes.Structure):
    _fields_ = [('g_type', GType)]

class GTypeInstance(ctypes.Structure):
    _fields_ = [('g_class', ctypes.POINTER(GTypeClass))]

class EnumValue(ctypes.Structure):
    _fields_ = [('value', ctypes.c_int),
                ('value_name', ctypes.c_char_p),
                ('value_nick', ctypes.c_char_p)]

class FlagsValue(ctypes.Structure):
    _fields_ = [('value', ctypes.c_uint),
                ('value_name', ctypes.c_char_p),
                ('value_nick', ctypes.c_char_p)]

class EnumClass(ctypes.Structure):
    _fields_ = [('g_type_class',  GTypeClass),
                ('minimum', ctypes.c_int),
                ('maximum', ctypes.c_int),
                ('n_values', ctypes.c_uint),
                ('values', ctypes.POINTER(EnumValue))]

class FlagsClass(ctypes.Structure):
    _fields_ = [('g_type_class', GTypeClass),
               ('mask', ctypes.c_uint),
               ('n_values', ctypes.c_uint),
               ('values', ctypes.POINTER(FlagsValue))]

class GTypeInterface(ctypes.Structure):
    _fields_ = [('g_type', GType),
                ('g_instance_type', GType)]

class GParamSpec(ctypes.Structure):
    _fields_ = [('g_type_instance', GTypeInstance),
                ('name', ctypes.c_char_p),
                ('flags', ctypes.c_uint),
                ('value_type', GType),
                ('owner_type', GType)]

# ------------------ Main function -----------------

def main(args):
    verbose = False
    all = True
    onlyenums = False
    onlyobjdefs = False
    onlyvirtuals = False
    separate = False
    modulename = None
    defsfiles = []
    libgobject = None
    modulelibs = []
    defines = {}
    genpropgetsets = False
    use_prefix = False
    opts, args = getopt.getopt(args[1:], 'vs:m:f:D:L:l:p',
                               ['onlyenums', 'onlyobjdefs', 'onlyvirtuals',
                                'modulename=', 'separate=',
                                'defsfile=', 'defines=', 'genpropgetsets',
                                'libgobject-', 'modulelib=', 'useprefix'])
    for o, v in opts:
        if o == '-v':
            verbose = True
        if o == '--onlyenums':
            onlyenums = True
            all = False
        if o == '--onlyvirtuals':
            onlyvirtuals = True
            all = False
        if o == '--onlyobjdefs':
            onlyobjdefs = True
            all = False
        if o in ('-p', '--useprefix'):
            use_prefix = True
        if o == '--genpropgetsets':
            genpropgetsets = True
        if o in ('-s', '--separate'):
            separate = v
        if o in ('-m', '--modulename'):
            modulename = v
        if o in ('-L', '--libgobject'):
            libgobject = v
        if o in ('-l', '--modulelib'):
            modulelibs.append(v)
        if o in ('-f', '--defsfile'):
            defsfiles.append(v)
        if o in ('-D', '--defines'):
            nameval = v.split('=')
            try:
                defines[nameval[0]] = nameval[1]
            except IndexError:
                defines[nameval[0]] = None

    if not args[0:1]:
        print 'Must specify at least one input file name'
        return -1
    if not modulelibs:
        print 'Must specify one or more modulelib names'
        return -1

    # load the gobject and module libraries and init the gtype system
    if not libgobject:
        if verbose:
            sys.stderr.write('Using "libgobject-2.0.so" as the libobject' \
                             'library name by default\n')
        gobj = ctypes.cdll.LoadLibrary('libgobject-2.0.so')
    else:
        gobj = ctypes.cdll.LoadLibrary(libgobject)

    modlib = [ctypes.cdll.LoadLibrary(lib) for lib in modulelibs]

    gobj.g_type_init()
    gobj.g_type_name.restype = ctypes.c_char_p
    gobj.g_type_from_name.argtypes = [ctypes.c_char_p]
    gobj.g_type_interfaces.restype = ctypes.POINTER(ctypes.c_int)
    gobj.g_type_interface_prerequisites.restype = ctypes.POINTER(ctypes.c_int)
    gobj.g_object_class_list_properties.restype = ctypes.POINTER(ctypes.POINTER(GParamSpec))
    gobj.g_object_interface_list_properties.restype = ctypes.POINTER(ctypes.POINTER(GParamSpec))
    gobj.GObject = gobj.g_type_from_name('GObject')
    gobj.g_object_new(gobj.GObject, None)

    defs = {}
    for key in defkeys.split():
        defs[key] = []
    defs['funcs'] = {}
    defs['virts'] = {}
    defs['untypedenums'] = {}
    defs['typedefines'] = []

    # read in all the object and function definitions
    args.sort()
    type_define_pat = re.compile(
        r'^#define\s+([A-Z]\S+_TYPE_\S+)\s+.*[a-z]\w+_get_type.*$',
        re.MULTILINE)
    for filename in args:
        buf = open(filename).read()
        defs['typedefines'] += type_define_pat.findall(buf)
        buf = clean_buffer(buf)
        find_enums(buf, defs)
        find_defs(buf, gobj, modlib, defs)
        find_func_defs(buf, modlib, filename, defs, verbose)
        find_virt_defs(buf, filename, defs)

    for key in defkeys.split():
        defs[key].sort()

    methods = types = None
    if separate:
        methods = file(separate + '.defs', 'w')
        types = file(separate + '-types.defs', 'w')

    dw = DefsWriter(defs, methods, prefix=modulename, verbose=verbose,
                    defsfiles=defsfiles, defines=defines,
                    genpropgetsets=genpropgetsets, useprefix=use_prefix)
    dw.interfaces = [i.name for i in defs['GInterface']]
    dw.gobj = gobj
    dw.modlib = modlib

    if onlyobjdefs or all:
        dw.write_obj_defs(types)
        if separate:
            print "Wrote object defs to %s-types.defs" % separate
    if onlyenums or all:
        dw.write_enum_defs(types)
        if separate:
            print "Wrote enum and flags defs to %s-types.defs" % separate
    if onlyvirtuals or all:
        dw.write_func_defs(args, onlyvirtuals)
        if separate:
            print "Wrote function and virtual defs to %s.defs" % separate

if __name__ == '__main__':
    sys.exit(main(sys.argv))
