Ronald Oussoren avatar Ronald Oussoren committed 95ed2cf

My own version of Leopard's metadata scanning tools.

This is mostly for historical interest, I've used these
tools to shape my thoughts about the bridgesupport tools
and provide feedback to the bridgesupport tools.

I am planning further development though, mostly to
experiment with solutions to problems I'm finding in the
metadata system. The most important of which are
dealing with the C++ type bool (cannot be represented
in metadata at the moment) and unicode strings (no nice
representation at the moment).

Comments (0)

Files changed (18)

pyobjc-metadata/Doc/xmlformat.txt

+========================
+BridgeSupport XML format
+========================
+
+-------------------
+A short description
+-------------------
+
+Introduction
+============
+
+This project implements a generator for the XML metadata format introduced in
+Leopard and shared between PyObjC and RubyCocoa. Hopefully other bridges will
+start using this format as well.
+
+At the time I'm writing this the XML format is in flux is desperately missing
+some documentation. This document tries to fill the gap but is not reference
+documentation for the medatadata format.
+
+Document structure
+==================
+
+The document is an ``signatures`` element with an optional ``version`` 
+attribute. This document describes version ``1.0``.
+
+The child elements of the ``signatures`` describe the various objects that
+can be wrapped. Classes are special: instead of describing all classes in the
+metadata we only describe classes and methods that require special treatment,
+that is those were the normal Objective-C runtime metadata doesn't contain
+enough information to wrap them correctly.
+
+The following section describe the various elements in the metadata format.
+
+64-bit support
+--------------
+
+The metadata file supports 32-bit and 64-bit builds in a single file. This
+is done by adding a 64-bit version of attributes when the attribute has 
+different values on 64-bit and 32-bit builds. When the 64-bit version is not
+present the value is assumed to be the same as on 32-bit builds.
+
+BOOLEAN attributes
+------------------
+
+Attributes that are marked as ``BOOLEAN`` can have two values: ``true`` or
+``false`` (with the obvious interpretation for those values).
+
+``depends_on``
+--------------
+
+This elements is used to describe direct framework dependencies.
+
+Attributes:
+
+ * ``path``: Absolute path to the framework
+
+
+``constant``
+------------
+
+This element is used to describe C-level variables that refer to constants (
+such as "``NSString* NSWorkspaceCopyOperation``).
+
+Attributes:
+
+ * ``name``: Name of the constant
+
+ * ``type``, ``type64``: Encoded type of the constant.
+
+ * ``magic_cookie`` (BOOLEAN): if true this is a magic value, not a real object.
+    
+
+Note that the *value* of the constant is not described, this can be retrieved
+from the actual framework using the ``CFBundle`` API.
+
+``enum``
+--------
+
+This element is used to describe ``enum`` labels and ``#define``-s that map 
+onto numbers.
+
+Attributes:
+
+  * ``name``: name of the constant
+
+  * ``value``, ``value64``: the value of the constant. 
+  
+    If the value contains a dot (``"."``) the value is a floating-point
+    number, otherwise it is an integer (of arbitrary size).
+
+  * ``be_value``, ``le_value``: when a constant has a different value
+    on big- and little-endian systems the ``value`` attribute won't be
+    present and these two will be. 
+
+    There will be very few values without a ``value`` attribute, basically
+    only "four character codes".
+
+``string_constant``
+-------------------
+
+This element is used to describe ``#define``-s that map onto string values 
+(either C-style strings or literal NSString values).
+
+Attributes:
+
+  * ``name``: name of the contant
+
+  * ``value``, ``value64``: the value of the constant
+  
+    The value is the value of the literal.
+
+  * ``nsstring`` (BOOLEAN): if true the value is an NSString, otherwise it is a plain C string
+    (defaults to ``false``).
+
+
+``null_const``
+--------------
+
+This is used to describe null-pointer aliases.
+
+Attributes:
+
+  * ``name``: name of the value
+
+The C value of these values is always ``NULL`` and these should be mapped
+onto a "no-value" value by the brides (such as ``None`` in Python).
+
+``cftype``
+-----------
+
+This element describes CoreFoundation based types.
+
+Attributes:
+
+ * ``name``: name of the type (such as ``CFArrayRef``).
+
+ * ``type``, ``type64``: Encoding string for this type
+
+ * ``tollfree``: if available this type is toll-free bridged to the named 
+   Objective-C class
+
+ * ``gettypeid_func``: if available this is the function that should be called
+   to get the ``CFTypeID`` for this type. 
+
+   This attribute must be present if ``tollfree`` isn't present and is optional
+   otherwise.
+
+``opaque``
+-----------
+
+Used to describe opaque types, that is 'pointer to struct' types where the
+fields of the struct aren't public.
+
+Attributes:
+
+  * ``name``: name of the type (such as ``NSZone``)
+
+  * ``type``, ``type64``: Encoding string for this type
+
+``struct``
+----------
+
+Used to describe structs that are defined in the framework.
+
+Attributes:
+
+ * ``name``: name of the struct
+
+ * ``type``, ``type64``: Encoding string for the this type. This is a verbose
+   encoding that includes the field names, which isn't the same as the
+   result of ``@encode(name)``.
+
+``informal_protocol``
+---------------------
+
+This element describes informal protocols (but not formal ones, those can be
+extracted from the Objective-C runtime).
+
+Attributes:
+
+  * ``name``: name of the informal protocol
+
+This element has subelements that describe the methods that are part of the
+protocol. All subelements have ``method`` as their tag.
+
+
+``method``
+...........
+
+The ``method`` subelement of ``informal_protocol`` has the following attributes:
+
+ * ``selector``: name of the method
+
+ * ``classmethod`` (BOOLEAN): is this a class method? Defaults to ``false``.
+
+ * ``encoding``, ``encoding64``: encoding of the method signature, as it would 
+   exist in the Objective-C runtime
+
+ * ``suggestion``: This method shouldn't be used. The attribute value contains
+   a human-reable message.
+
+ * ``variadic`` (BOOLEAN): If true this is a varargs method, defaults to 
+   ``false``.
+
+
+``class``
+---------
+
+This attribute is used to describe classes in the framework that contain methods were the metadata in the Objective-C runtime isn't good enough to wrap them.
+
+Attributes:
+
+ * ``name``: name of the class
+
+This element has subelements of type ``method`` for all methods that require
+further annotations. Furthermore subelements of type ``property`` are used
+to describe Objective-C 2.0 properties that aren't present in the header files.
+
+``method``
+..........
+
+This is used to describe metadata for methods, and only those bits of metadata
+that cannot be derived from the information in the Objective-C runtime.
+
+Attributes:
+
+ * ``selector``: The method name
+
+ * ``classmethod`` (BOOLEAN): Is this a class method? Defaults to ``false``
+
+ * ``suggestion``: This method shouldn't be used. The attribute value contains
+   a human-reable message.
+
+ * ``variadic`` (BOOLEAN): If true this is a varargs function, defaults to false
+
+ * ``c_array_delimited_by_null`` (BOOLEAN): Only valid when variadic is true,
+    defaults to ``False``. The variadic arguments are a null-terminated array.
+
+This element has subelements ``retval`` (present at most once) and ``argument``
+(can be present multiple times) to describe the return value and arguments. See
+the section `Method and function argument and return values`_ for more 
+information on these elements.
+
+When the ``suggestion`` attribute is present the bridge should emit a warning
+containing the suggestion. It may not be possible to call the method at all.
+
+``function``
+------------
+
+This is used to describe functions in the framework.
+
+Attributes
+
+ * ``name``: Function name
+
+ * ``suggestion``: This method shouldn't be used. The attribute value contains
+   a human-reable message.
+
+ * ``variadic`` (BOOLEAN): If true this is a varargs function, defaults to false
+
+ * ``c_array_delimited_by_null`` (BOOLEAN): Only valid when variadic is true,
+    defaults to ``False``. The variadic arguments are a null-terminated array.
+ 
+ * ``inline`` (BOOLEAN): If true the function is a static inline function
+ 
+   NOTE: static inline functions cannot be loading from a bundle using the 
+   CFBundle APIs because they aren't included as public functions. A bridge will
+   have to implement some other mechanism to make this functions available.
+
+This element has subelements ``retval`` (present at most once) and ``argument``
+(can be present multiple times) to describe the return value and arguments. See
+the section `Method and function argument and return values`_ for more 
+information on these elements.
+
+When the ``retval`` element is not present the function returns ``void``. 
+
+All function arguments will be present in the right order and don't need an
+``index`` attribute.
+
+When the ``suggestion`` attribute is present the bridge should emit a warning
+containing the suggestion. It may not be possible to call the function at all.
+
+
+
+Method and function argument and return values
+-----------------------------------------------
+
+``retval``
+...........
+
+Attributes
+
+ * ``already_retained`` (BOOLEAN): If true the function returns an object that
+   is already retained, that is the user is responsible for releasing the 
+   object when it is no longer needed. Only valid when the return type is an
+   object (either a ``NSObject*`` or a ``CFTypeRef``). Defaults to ``false``.
+
+ * ``type``, ``type64``: The return type. Only present when this is different
+   from the information in the Objective-C runtime.
+
+ * ``c_array_length_in_arg``: The return value is an array whose length is
+   present in another argument. The value is the index of the argument
+   that contains the array size. This argument must be an integer or NSRange
+   value or an ``inout`` or ``out`` argument of type integer. If the value
+   is a (pointer to a) value of type NSRange the ``length`` is used as the 
+   array size.
+
+   The value can also be two integers seperated by a comma, in which cases the
+   first integer is the index of the argument that contains the length of the
+   array on input and the second is index of the argument that contains the 
+   size on output.
+
+ * ``c_array_delimited_by_null`` (BOOLEAN): The return value is a 
+   NULL-terminated array.  The basic type of the array must be a pointer-type 
+   (object, opaque) or an integer type (in which case it is a 0-terminated 
+   array). Defaults to ``false``.
+
+ * ``c_array_of_fixed_length``: The return value is an array of a fixed size.
+   The value of this attribute is said size.
+
+ * ``c_array_of_variable_length`` (BOOLEAN): The return value is an array of 
+   unspecified size.  Note that bridges cannot know or infer the size of 
+   the array and have to raise an error when a method/function with this
+   attribute is called. Defaults to ``false``.
+
+ * ``unicode_string`` (BOOLEAN): the value is a unicode string instead of 
+   an array of short integers.
+
+   Needed because the encoded type for a unicode string and an array of short
+   integers is the same. 
+   
+   Also used on for values of type ``UniChar` (that is: single unicode 
+   characters).
+
+   Defaults to false.
+
+ * ``bool_is_BOOL`` (BOOLEAN): the type encoding says that the type is ``bool``,
+   but it actually a ``BOOL``. Defaults to ``true`` (for backward compatiblity
+   reasons).
+
+   NOTE: this attribute is only present for functions, bridges can reconstruct
+   this value from the objc runtime + metadata for plain methods.
+
+``arg``
+...........
+
+Attributes
+
+ * ``index``: Index of the argument. The first argument has index 0, and for
+   selectors the two implict arguments are not counted. This attribute is 
+   optional on function arguments, all function arguments are present in the
+   right order.
+
+ * ``type``, ``type64``: Encoded type of the argument. Only present when this
+   is different from the type in the Objective-C runtime.
+
+ * ``type_modifier``: One of ``_C_IN``, ``_C_OUT`` or ``_C_INOUT`` to describe
+   if a pointer argument is an input, output or input/output argument. 
+   
+   This attribute is optional, but should be present on all pointer arguments
+   as there is no default value.
+
+ * ``c_array_length_in_arg``: The return value is an array whose length is
+   present in another argument. The value is the index of the argument
+   that contains the array size. This argument must be an integer value or
+   an ``inout`` or ``out`` argument of type integer.
+
+ * ``c_array_delimited_by_null``: The value is a NULL-terminated array.
+   The basic type of the array must be a pointer-type (object, opaque) or
+   an integer type (in which case it is a 0-terminated array). Defaults
+   to ``false``.
+
+ * ``c_array_of_fixed_length``: The value is an array of a fixed size.
+   The value of this attribute is said size.
+
+ * ``c_array_of_variable_length`` (BOOLEAN): The return value is an array of 
+   unspecified size.  Defaults to ``false``.
+
+   Bridges should assume that the length of the array passed in by the caller
+   is the size of the array expected by the function/method. Note that this
+   could cause crashes when the actually expected size is different.
+
+ * ``c_array_length_in_result`` (BOOLEAN): The value is an array whose size is 
+     is the return value. There MUST also be ``c_array_of_fixed_length`` or
+    ``c_array_length_in_arg`` attribute that describes how large the buffer
+    must be on input.  This attribute cannot be present on a ``rettype`` element
+    and defaults to ``false``.
+
+ * ``null_accepted`` (BOOLEAN): if true the pointer argument is allowed to
+    be ``NULL``, otherwise it is not. Defaults to ``true``.
+
+ * ``printf_format`` (BOOLEAN): if true this argument is a printf-style format.
+
+   Only one of the arguments may have this property set to true, and only on
+   a variadic function or method. Defaults to ``false``.
+
+ * ``already_retained`` (BOOLEAN): If true the function returns an object that
+   is already retained, that is the user is responsible for releasing the 
+   object when it is no longer needed. Only valid when the return type is an
+   object (either a ``NSObject*`` or a ``CFTypeRef``). Defaults to ``false``.
+
+ * ``unicode_string`` (BOOLEAN): the value is a unicode string instead of 
+   an array of short integers.
+
+   Needed because the encoded type for a unicode string and an array of short
+   integers is the same.
+
+   Also used on for values of type ``UniChar` (that is: single unicode 
+   characters).
+
+   Defaults to false.
+
+ * ``function_poiner`` (BOOLEAN): if true the argument is a callback function. 
+   The signature of the callback is described by subelements of this element.
+
+ * ``function_pointer_lifetime``: the livetime of a callback argument. 
+   Defaults to ``undetermined``: Valid values are:
+
+    * ``call``: The callback function is only used during the call to the
+      function/method and won't be stored for later use.
+
+    * ``undetermined``: The called function/method  may store a reference to 
+      the callback function and may therefore call the function when the 
+      call to this method/function has ended.
+
+    Other lifetime values may be added in the future.
+
+   NOTE: currently not supported by PyObjC.
+
+ * ``sel_of_type``: if the type of the argument is ``SEL`` this attribute
+   can contain the encoded signature for the selector. This can be used by
+   bridges for additional type-checking.
+
+
+Exceptions file
+===============
+
+The exceptions file has the same format as the metadata file, but with same
+minor additions as described below.
+
+1. All elements can have a BOOLEAN attribute ``ignore`` (defaulting to 
+  ``false``). If the ``ignore`` attribute is false the element won't be 
+   present in the actual metadata file
+
+2. All elements can have an attribute ``comment`` that contains arbitrary
+   text that is not further interpreted.
+
+   This attribute is meant to be used as a place where metadata maintainers
+   can leave documentation/notes when that's needed. Use this instead of 
+   XML comments to ensure that tools keep the comment when rescanning a 
+   framework.
+
+3. Function arguments will have an ``index`` attribute and only the function
+   arguments/return values that require manual markup will be present.
+
+4. The ``type_modifier`` attribute may have an empty value, which means the
+   metadata maintainer has looked at an argument/return-value node and 
+   indicates that it doesn't need further markup (needed because the 
+   both scanners are pessimistic by design as to what arguments require
+   markup)
+ 
+5. Structs may have  ``type`` and ``type64`` attributes in the exeption file
+   (as the always have in the normal metadat file). If such fields are present
+   those encodings are used instead of the output of ``@encode`` for this type
+   and metadata will be generated for all methods that have a return or argument
+   value of this type.

pyobjc-metadata/LICENSE.txt

+(This is the MIT license)
+
+Copyright 2005, 2006 - Ronald Oussoren, Bob Ippolito
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

pyobjc-metadata/Lib/PyObjCMetaData/__init__.py

+"""
+Header file scanner for generating MetaData.xml files.
+
+This package is unstable, the interface may change at any moment.
+"""

pyobjc-metadata/Lib/PyObjCMetaData/commands.py

+"""
+Some distutils commands that make it easier to work with the metadata in a
+project:
+
+* update_metadata 
+
+  This command runs the metadata generator and refreshes the 
+  PyObjC.bridgesupport file.
+
+* update_exceptions
+
+  This command runs the metadata generator and refreshes the metadata
+  exceptions file.
+
+* build_html
+
+  Convert documentation in the 'Doc' directory from text to HTML using
+  docutils. Requires docutils to be installed.
+
+* bdist_mpkg
+
+  A subclass of the generic bdist_mpkg command that will automaticly include
+  documentation and examples as well (when these are present).
+
+  (This one is not actually defined here, we just change the configuration
+  of the standard bdist_mpkg command).
+
+* update_static_inlines
+
+  Update wrapper module for static inline functions
+
+"""
+__all__ = [ 'extra_cmdclass', 'extra_options' ]
+
+from setuptools import Command
+from distutils.dep_util import newer
+from distutils.command.build import build as base_build
+import subprocess
+import os, sys
+from PyObjCMetaData.et import *
+
+# Additional schemes for bdist_mpkg
+CUSTOM_SCHEMES=dict(
+    docs=dict(
+        description=u"(Optional) Documentation for the %(framework)s wrapper",
+        prefix=u'/Developer/Documentation/Python/PyObjC/%(framework)s',
+        source='Doc',
+    ),
+    examples=dict(
+        description=u"(Optional) Examples for the %(framework)s wrapper",
+        prefix=u'/Developer/Examples/Python/PyObjC/%(framework)s',
+        source='Examples',
+    )
+)
+
+
+#
+# Implementation for update_metadata and update_exceptions
+#
+
+
+def _path_to_script(scriptname):
+    """
+    Return the full path of a python script. 
+
+    Scripts are usually installed in ${sys.prefix}/bin, but Apple has tweaked
+    the system python on Leopard to install in /usr/local/bin instead.
+    """
+    if sys.prefix.startswith('/System') and os.uname()[2] >= '9.':
+        return os.path.join('/usr/local/bin', scriptname)
+    return os.path.join(sys.prefix, 'bin', scriptname)
+
+
+class BaseCommand (Command):
+    user_options = []
+
+    def initialize_options(self):
+        pass
+
+    def finalize_options(self):
+        pass
+
+
+    def get_files(self):
+        files = [ fn for fn in os.listdir('Exceptions') if fn.endswith('.bridgesupport') ]
+        if not files:
+            raise RuntimeError, "Cannot find exception file"
+
+        for fn in files:
+            exceptionsfile = os.path.join('Exceptions', fn)
+            framework = os.path.splitext(fn)[0]
+            metadatafile = os.path.join('Lib', framework, 
+                    'PyObjC.bridgesupport')
+            if not os.path.exists(metadatafile):
+                for subdir in os.listdir('Lib'):
+                    metadatafile = os.path.join('Lib', subdir, framework,
+                            'PyObjC.bridgesupport')
+                    if os.path.exists(metadatafile):
+                        break
+                else:
+                    metadatafile = os.path.join('Lib', framework, 
+                        'PyObjC.bridgesupport')
+
+            yield framework, exceptionsfile, metadatafile
+
+class update_exceptions (BaseCommand):
+    description = "Update the metadata exceptions file"
+
+    def run(self):
+        for framework, exceptionsfile, metadatafile in self.get_files():
+            result = subprocess.call([
+                _path_to_script('pyobjc-metadata-gen'), 
+                '-v',
+                '-f', framework,
+                '-F', 'both',
+                '-e', exceptionsfile,
+                '-o', metadatafile,
+                '-O', exceptionsfile,
+                ])
+            if result != 0:
+                raise RuntimeError, "Updating metadata failed"
+
+class update_metadata (BaseCommand):
+    description = "Update the metadata file"
+
+    def run(self):
+        for framework, exceptionsfile, metadatafile in self.get_files():
+            result = subprocess.call([
+                _path_to_script('pyobjc-metadata-gen'), 
+                '-v',
+                '-f', framework,
+                '-F', 'final-md',
+                '-e', exceptionsfile,
+                '-o', metadatafile])
+            if result != 0:
+                raise RuntimeError, "Updating metadata failed"
+
+#
+# Implementation for the build_html command
+# 
+
+def rest2HTML(irrelevant, dirName, names):
+    while '.svn' in names:
+        names.remove('.svn')
+    for aName in names:
+        if aName.endswith('.txt'):
+            anInputPath = os.path.join(dirName, aName)
+            if irrelevant is not None and anInputPath in irrelevant:
+                continue
+            anOutputPath = anInputPath[:-4] + '.html'
+            if not newer(anInputPath, anOutputPath):
+                print '- %s (skipped: up to date)' % (anInputPath,)
+                continue
+            print '- %s'%(anInputPath)
+            outfile = file(anOutputPath, 'w')
+            ret = subprocess.call([TOOL, anInputPath], stdout=outfile)
+            outfile.close()
+            if ret:
+                os.remove(anOutputPath)
+
+def _iter_paths():
+    try:
+        import DocArticle
+    except ImportError:
+        return
+    yield os.path.dirname(os.path.dirname(DocArticle.__file__))
+    for path in os.environ.get('PATH', '/usr/bin:/usr/local/bin').split(':'):
+        yield path
+
+for path in _iter_paths():
+    TOOL = os.path.join(path, 'docarticle.py')
+    if os.path.exists(TOOL):
+        break
+else:
+    TOOL = None
+
+class build_html(Command):
+    description = "Generate HTML from ReST documentation"
+    user_options = []
+
+    def initialize_options(self):
+        self.finalized = False
+
+    def finalize_options(self):
+        self.finalized = True
+
+    def run(self):
+        if TOOL is None:
+            print "*** Can't generate HTML, docutils is not installed"
+            return
+
+        os.path.walk('Doc', rest2HTML, [])
+
+
+INLINES_HEADER="""\
+#include "Python.h"
+#import <%(framework)s/%(framework)s.h>
+
+typedef void (*FUNCTION)(void);
+
+struct function_map {
+    const char* name;
+    FUNCTION    function;
+} function_map[] = {
+"""
+
+INLINES_FOOTER="""\
+    { 0, 0 }
+};
+
+static PyMethodDef mod_methods[] = {
+        { 0, 0, 0, 0 } /* sentinel */
+};
+
+void init_inlines(void)
+{
+    PyObject* m = Py_InitModule4("_inlines", mod_methods, NULL, NULL, PYTHON_API_VERSION);
+
+    PyModule_AddObject(m, "_inline_list_", 
+        PyCObject_FromVoidPtr(function_map, NULL));
+}
+"""
+
+class update_static_inlines (BaseCommand):
+    def run(self):
+        for  framework, exceptionsfile, metadatafile in self.get_files():
+            meta = ET.parse(metadatafile).getroot()
+            inlines = [ e.get('name') for e in meta.findall('function') if e.get('inline', 'false') == 'true' ]
+            fn = os.path.join('Modules', '_%s_inlines.m'%(framework,))
+            if not inlines:
+                if os.path.exists(fn):
+                    print " *** %s exists, but no inlines left"%(fn,)
+
+                else:
+                    # No inlines.m present and no inlines: just go to the
+                    # next metadata file.
+                    continue
+
+            else:
+                if not os.path.exists(fn):
+                    print " *** added %s, please add a new extension to setup.py"%(fn,)
+
+            fp = open(fn, 'w')
+            fp.write(INLINES_HEADER % locals())
+            for nm in inlines:
+                fp.write('\t{"%s", (FUNCTION)&%s },\n'%(nm, nm))
+            fp.write(INLINES_FOOTER)
+
+
+#
+# Setup our public interface
+#
+
+def _mpkg_options(framework):
+    """
+    Generate additional options for bdist_mpkg.
+    """
+
+    custom_schemes = CUSTOM_SCHEMES.copy()
+    if not os.path.exists('Doc'):
+        del custom_schemes['docs']
+    if not os.path.exists('Examples'):
+        del custom_schemes['examples']
+
+    for scheme in custom_schemes.values():
+        for k in scheme.keys():
+            scheme[k] = scheme[k] % locals()
+
+    opts = dict(
+        custom_schemes=custom_schemes,
+        readme='README.txt',
+        license='LICENSE.txt',
+    )
+
+    if 'docs' in custom_schemes:
+        opts['scheme_command'] = dict(docs='build_html')
+
+    if not os.path.exists('README.txt'):
+        del opts['readme']
+
+    if not os.path.exists('LICENSE.txt'):
+        del opts['license']
+
+    return opts
+
+
+extra_cmdclass = {
+    'update_static_inlines': update_static_inlines,
+    'update_metadata': update_metadata, 
+    'update_exceptions': update_exceptions,
+}
+
+if os.path.exists('Doc'):
+    extra_cmdclass['build_html'] = build_html
+
+def extra_options(framework):
+    """
+    Return a dictionary with additional distutils options.
+
+    Usage:
+
+    setup=(
+        ...
+        options=extra_options('AppKit'),
+    )
+    """
+    return {
+        'bdist_mpkg': _mpkg_options(framework),
+    }

pyobjc-metadata/Lib/PyObjCMetaData/compare.py

+#!/usr/bin/env python
+"""
+Report about the differences in two metadata files.
+
+The script is incomplete at the moment: it doesn't look inside nested
+elements yet (function, class, informal_protocol).
+"""
+from PyObjCMetaData.et import *
+import objc
+import sys
+import os
+from scanframework import indentET, HEADER
+
+def find_node(root, type, name, nameAttr='name'):
+    for node in root.findall(type):
+        if node.get(nameAttr) == name:
+            return node
+    return None
+
+def filter64(value):
+    result = {}
+    for k in value:
+        if k.endswith('64'): continue
+        result[k] = value[k]
+
+    # Make sure all irrelavant bits of the signature string get ignored
+    if 'type' in value:
+        result['type'] = ''.join(objc.splitSignature(value['type']))
+
+    return result
+
+def diffDict(first, second):
+    first = filter64(first)
+    second = filter64(second)
+    result = []
+    for k in sorted(first):
+        if k not in second:
+            result.append('<%s'%(k,))
+        elif first[k] != second[k]:
+            result.append('!%s[%s!=%s]'%(k, first[k], second[k]))
+    for k in sorted(second):
+        if k.endswith('64'): 
+            # Ignore 64-bit variants for now
+            continue
+        if k not in first:
+            result.append('>%s'%(k,))
+    return ' '.join(result)
+
+def diffSimple(first, second, tag):
+    for f in first.findall(tag):
+        s = find_node(second, tag, f.get('name'))
+        if s is None:
+            print "< %s %s"%(tag, f.get('name'))
+        elif filter64(f.attrib)  != filter64(s.attrib):
+            print "! %s %s"%(tag, f.get('name'))
+            print "  ", diffDict(f.attrib, s.attrib)
+    for s in second.findall(tag):
+        f = find_node(first, tag, s.get('name'))
+        if f is None:
+            print "> %s %s"%(tag, s.get('name'))
+
+
+def diffFunction(first, second, prefix='function', nameAttr='name'):
+    if filter64(first.attrib) != filter64(second.attrib):
+        print '! %s %s'%(prefix, first.get(nameAttr))
+        print '  ', diffDict(first.attrib, second.attrib)
+
+    f_r = None
+    f_a = []
+    s_r = None
+    s_a = []
+
+    for n in first:
+        if n.tag == 'retval':
+            if f_r is not None:
+                print '** < %s.retval duplicate for %s'%(prefix, first.get(nameAttr))
+            f_r = n
+        elif n.tag == 'arg':
+            f_a.append(n)
+
+        else:
+            print '** < %s.%s for %s: unknown tag'%(prefix, n.tag, first.get(nameAttr))
+
+    for n in second:
+        if n.tag == 'retval':
+            if s_r is not None:
+                print '** > %s.retval duplicate for %s'%(prefix, second.get(nameAttr))
+            s_r = n
+        elif n.tag == 'arg':
+            s_a.append(n)
+
+        else:
+            print '** < %s.%s for %s: unknown tag'%(prefix, n.tag, first.get(nameAttr))
+
+    if f_r is None:
+        if s_r is not None and (s_r.get('type') != objc._C_VOID or len(s_r.attrib) != 1):
+                print '> %s.retval %s'%(prefix, first.get(nameAttr))
+    else:
+        if s_r is None and (f_r.get('type') != objc._C_VOID or len(f_r.attrib) != 1):
+                print '> %s.retval %s'%(prefix, first.get(nameAttr))
+        elif s_r is not None:
+            if filter64(s_r.attrib) != filter64(f_r.attrib):
+                print '! %s.retval %s'%(prefix, first.get(nameAttr))
+                print '  ', diffDict(f_r.attrib, s_r.attrib)
+
+    for idx in range(max(len(f_a), len(s_a))):
+        try:
+            f = f_a[idx]
+        except IndexError:
+            f = None
+
+        try:
+            s = s_a[idx]
+        except IndexError:
+            s = None
+
+        if s is None:
+            print '< %s.argument[%d] %s'%(prefix, idx, first.get(nameAttr))
+        elif f is None:
+            print '> %s.argument[%d] %s'%(prefix, idx, first.get(nameAttr))
+        else:
+            if f.attrib != s.attrib:
+                print '! %s.argument[%d] %s'%(prefix, idx, first.get(nameAttr))
+                print '  ', diffDict(f.attrib, s.attrib)
+
+def diffFunctionList(first, second, tag, nameAttr='name', prefix=None):
+    if prefix is None:
+        prefix = tag
+    for f in first.findall(tag):
+        s = find_node(second, tag, f.get(nameAttr), nameAttr=nameAttr)
+        if s is None:
+            print "< %s %s"%(prefix, f.get(nameAttr))
+        else:
+            diffFunction(f, s, prefix, nameAttr)
+    for s in second.findall(tag):
+        f = find_node(first, tag, s.get(nameAttr), nameAttr=nameAttr)
+        if f is None:
+            print "> %s %s"%(prefix, s.get(nameAttr))
+
+def diffClassList(first, second):
+    for f in first.findall('class'):
+        s = find_node(second, 'class', f.get('name'))
+        if s is None:
+            print "< %s %s"%('class', f.get('name'))
+        else:
+            diffFunctionList(f, s, 'method', nameAttr='selector', prefix='class[%s].method'%(f.get('name')))
+    for s in second.findall('class'):
+        f = find_node(first, 'class', s.get('name'))
+        if f is None:
+            print "> %s %s"%('class', s.get('name'))
+
+def diffProtocolList(first, second):
+    for f in first.findall('informal_protocol'):
+        s = find_node(second, 'informal_protocol', f.get('name'))
+        if s is None:
+            print "< %s %s"%('informal_protocol', f.get('name'))
+        else:
+            diffFunctionList(f, s, 'method', nameAttr='selector', prefix='informal_protocol[%s].method'%(f.get('name')))
+
+    for s in second.findall('informal_protocol'):
+        f = find_node(first, 'informal_protocol', s.get('name'))
+        if f is None:
+            print "> %s %s"%('informal_protocol', s.get('name'))
+
+def main():
+    if len(sys.argv) == 2:
+        fwk = sys.argv[1]
+    
+        for dirpath, dirnames, filenames in os.walk(os.path.expanduser('~/Projects/pyobjc-leopard/BridgeSupport~dst')):
+            if fwk + '.bridgesupport' in filenames:
+                first = os.path.join(dirpath, fwk + '.bridgesupport')
+                break
+        else:
+            raise RuntimeError("No Leopard data for %s"%(fwk,))
+
+        second = os.path.join(os.path.expanduser('~'),
+                'Projects', 'pyobjc-leopard', 'stable', 'pyobjc-framework-%s'%(fwk),
+                'Lib', fwk, 'PyObjC.bridgesupport')
+
+        if not os.path.exists(second):
+            test = os.path.join(os.path.expanduser('~'),
+                    'Projects', 'pyobjc-leopard', 'stable', 'pyobjc-framework-Cocoa',
+                    'Lib', fwk, 'PyObjC.bridgesupport')
+
+            if not os.path.exists(test):
+                test = os.path.join(os.path.expanduser('~'),
+                        'Projects', 'pyobjc-leopard', 'stable', 'pyobjc-framework-Quartz',
+                        'Lib', fwk, 'PyObjC.bridgesupport')
+
+                if os.path.exists(test):
+                    second = test
+
+            else:
+                second = test
+
+    elif len(sys.argv) == 3:
+        first = sys.argv[1]
+        second = sys.argv[2]
+
+    else:
+            print >>sys.stderr, "Usage: %s orig new"
+            sys.exit(1)
+
+
+
+    print "Comparing %s and %s"%(first, second)
+
+    first = ET.parse(first).getroot()
+    second = ET.parse(second).getroot()
+
+    diffSimple(first, second, 'struct')
+    diffSimple(first, second, 'cftype')
+    diffSimple(first, second, 'opaque')
+    diffSimple(first, second, 'constant')
+    diffSimple(first, second, 'string_constant')
+    diffSimple(first, second, 'enum')
+    diffFunctionList(first, second, 'function')
+    diffFunctionList(first, second, 'static_inline')
+    diffClassList(first, second)
+    diffProtocolList(first, second)
+
+
+if __name__ == "__main__":
+    main()

pyobjc-metadata/Lib/PyObjCMetaData/et.py

+"""
+ElementTree tools and imports
+
+NOTE: _escape_attrib and ElementTree._write are copied from the ElementTree
+source with minor modifications to generate single-quoted attributes because
+that gives more pleasant output for this application.
+"""
+__all__ = ('ET', 'ElementTree')
+import string
+
+try:
+    # Python 2.5
+    import xml.etree.ElementTree as ET 
+except ImportError:
+    # And earlier (with separate install)
+    try:
+        import cElementTree as ET
+
+    except ImportError:
+        import elementtree.ElementTree as ET
+
+def _escape_attrib(text, encoding=None, replace=string.replace):
+    # escape attribute value
+    try:
+        if encoding:
+            try:
+                text = ET._encode(text, encoding)
+            except UnicodeError:
+                return ET._encode_entity(text)
+        text = replace(text, "&", "&amp;")
+        text = replace(text, "'", "&apos;") 
+        #text = replace(text, "\"", "&quot;")
+        text = replace(text, "<", "&lt;")
+        text = replace(text, ">", "&gt;")
+        return text
+    except (TypeError, AttributeError):
+        ET._raise_serialization_error(text)
+
+class ElementTree (ET.ElementTree):
+    """
+    A subclass of ET.ElementTree that overrides the _write method to get
+    a nicer output with single-quoted attributes (because our output contains
+    attribute values with embedded double-quotes).
+    """
+
+    def _write(self, file, node, encoding, namespaces):
+        # write XML to file
+        tag = node.tag
+        if tag is ET.Comment:
+            file.write("<!-- %s -->" % ET._escape_cdata(node.text, encoding))
+        elif tag is ET.ProcessingInstruction:
+            file.write("<?%s?>" % ET._escape_cdata(node.text, encoding))
+        else:
+            items = node.items()
+            xmlns_items = [] # new namespaces in this scope
+            try:
+                if isinstance(tag, ET.QName) or tag[:1] == "{":
+                    tag, xmlns = ET.fixtag(tag, namespaces)
+                    if xmlns: xmlns_items.append(xmlns)
+            except TypeError:
+                ET._raise_serialization_error(tag)
+            file.write("<" + ET._encode(tag, encoding))
+            if items or xmlns_items:
+                items.sort() # lexical order
+                for k, v in items:
+                    try:
+                        if isinstance(k, ET.QName) or k[:1] == "{":
+                            k, xmlns = ET.fixtag(k, namespaces)
+                            if xmlns: ET.xmlns_items.append(xmlns)
+                    except TypeError:
+                        ET._raise_serialization_error(k)
+                    try:
+                        if isinstance(v, ET.QName):
+                            v, xmlns = ET.fixtag(v, namespaces)
+                            if xmlns: xmlns_items.append(xmlns)
+                    except TypeError:
+                        ET._raise_serialization_error(v)
+                    file.write(" %s=\'%s\'" % (ET._encode(k, encoding),
+                                               _escape_attrib(v, encoding)))
+                for k, v in xmlns_items:
+                    file.write(" %s=\'%s\'" % (ET._encode(k, encoding),
+                                               _escape_attrib(v, encoding)))
+            if node.text or len(node):
+                file.write(">")
+                if node.text:
+                    file.write(ET._escape_cdata(node.text, encoding))
+                for n in node:
+                    self._write(file, n, encoding, namespaces)
+                file.write("</" + ET._encode(tag, encoding) + ">")
+            else:
+                file.write(" />")
+            for k, v in xmlns_items:
+                del namespaces[v]
+        if node.tail:
+            file.write(ET._escape_cdata(node.tail, encoding))

pyobjc-metadata/Lib/PyObjCMetaData/genwrapper.py

+"""
+This is a script for generating a framework wrapping project.
+
+usage: genwrapper.py [options]
+
+options:
+  --version             show program's version number and exit
+  -h, --help            show this help message and exit
+  -f FRAMEWORK, --framework=FRAMEWORK
+                        Generate wrapper for the named framework
+  -o DIR, --output=DIR  Name of the output directory (default: '.')
+  -p NAME, --project-name=NAME
+                        Name of the python project, defaults to 'pyobjc-
+                        framework-FRAMEWORK'
+"""
+import subprocess
+import optparse
+import sys
+import os
+import textwrap
+import plistlib
+import objc
+
+#
+# Templates for the various files in a new framework wrapper:
+#
+
+SETUP_TMPL=textwrap.dedent("""\
+    ''' 
+    Wrappers for framework '%(framework)s'. 
+
+    These wrappers don't include documentation, please check Apple's documention
+    for information on how to use this framework and PyObjC's documentation
+    for general tips and tricks regarding the translation between Python
+    and (Objective-)C frameworks
+    '''
+    import ez_setup
+    ez_setup.use_setuptools()
+
+    from setuptools import setup
+    try:
+        from PyObjCMetaData.commands import extra_cmdclass, extra_options
+    except ImportError:
+        extra_cmdclass = {}
+        extra_options = lambda name: {}
+
+    setup(
+        name=%(project)r,
+        version=%(pyobjc_version)r,
+        description = "Wrappers for the framework %(framework)s on Mac OS X",
+        long_description = __doc__,
+        author='Ronald Oussoren',
+        author_email='pyobjc-dev@lists.sourceforge.net',
+        url='http://pyobjc.sourceforge.net',
+        platforms = [ "MacOS X" ],
+        packages = [ "%(framework)s" ],
+        package_dir = { 
+            '': 'Lib/' 
+        },
+        setup_requires = [ 
+            'bdist_mpkg>=0.4.2',
+        ],
+        install_requires = [ 
+            'pyobjc-core>=%(pyobjc_version)s',
+            %(dependencyList)s
+        ],
+        dependency_links = [],
+        package_data = { 
+            '': ['*.xml'] 
+        },
+        test_suite='%(framework)s.test',
+        cmdclass = extra_cmdclass,
+        options = extra_options('%(framework)s'),
+
+        # The package is actually zip-safe, but py2app isn't zip-aware yet.
+        zip_safe = False,
+    )
+""")
+
+README_TMPL=textwrap.dedent("""\
+    Wrappers for framework '%(framework)s'. 
+
+    These wrappers don't include documentation, please check Apple's documention
+    for information on how to use this framework and PyObjC's documentation
+    for general tips and tricks regarding the translation between Python
+    and (Objective-)C frameworks
+""")
+
+LICENSE_TMPL=textwrap.dedent("""\
+    (This is the MIT license)
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+""")
+
+INIT_TMPL=textwrap.dedent("""\
+    '''
+    Python mapping for the %(framework)s framework.
+
+    This module does not contain docstrings for the wrapped code, check Apple's
+    documentation for details on how to use these functions and classes. 
+    '''
+
+    import objc as _objc
+    %(frameworkDepends)s
+
+    __bundle__ = _objc.initFrameworkWrapper("%(framework)s",
+        frameworkIdentifier="%(bundleIdentifier)s",
+        frameworkPath=_objc.pathForFramework(
+            "%(frameworkPath)s"),
+        globals=globals())
+""")
+
+
+# Note: the test template doesn't define tests, but only contains a template
+# for some tests. This is because it would be rather pointless to check if 
+# the generated wrapper is correct by using information generated by the 
+# metadata tool: if that were absolutely reliable we wouldn't need tests in
+# the first place.
+TEST_TMPL=textwrap.dedent("""\
+    '''
+    Some simple tests to check that the framework is properly wrapped.
+    '''
+    import objc
+    import unittest
+    import %(framework)s
+
+    class Test%(framework)s (unittest.TestCase):
+        def testClasses(self):
+            pass
+            # self.assert_( hasattr(%(framework)s, 'CLASSNAME') )
+            # self.assert_( isinstance(%(framework)s.CLASSNAME, objc.objc_class) )
+            # Tollfree CF-type:
+            # self.assert_( hasattr(%(framework)s, 'CLASSNAMERef') )
+            # self.assert_( %(framework)s.CLASSNAMERef is %(framework)s.CLASSNAME )
+
+            # Not-tollfree CF-type:
+            # self.assert_( hasattr(%(framework)s, 'CLASSNAMERef') )
+            # self.assert_( issubclass(%(framework)s.CLASSNAMERef, objc.lookUpClass('NSCFType')) )
+            # self.assert_( %(framework)s.CLASSNAMERef is not objc.lookUpClass('NSCFType') )
+
+        def testValues(self):
+            # Use this to test for a number of enum and #define values
+            pass
+
+            # Integer values:
+            # self.assert_( hasattr(%(framework)s, 'CONSTANT') )
+            # self.assert_( isinstance(%(framework)s.CONSTANT, (int, long)) )
+            # self.assertEquals(%(framework)s.CONSTANT, 7)
+
+            # String values:
+            # self.assert_( hasattr(%(framework)s, 'CONSTANT') )
+            # self.assert_( isinstance(%(framework)s.CONSTANT, (str, unicode)) )
+            # self.assertEquals(%(framework)s.CONSTANT, 'value')
+
+        def testVariables(self):
+            # Use this to test for global variables, (NSString*'s and the like)
+            pass
+
+            # self.assert_( hasattr(%(framework)s, 'CONSTANT') )
+            # self.assert_( isinstance(%(framework)s.CONSTANT, unicode) )
+
+        def testFunctions(self):
+            # Use this to test for functions
+            pass
+
+            # self.assert_( hasattr(%(framework)s, 'FUNCTION') )
+
+        def testOpaque(self):
+            # Use this to test for opaque pointers
+            pass
+
+            # self.assert_( hasattr(%(framework)s, 'OPAQUE') )
+
+        def testProtocols(self):
+            # Use this to test if informal protocols  are present
+            pass
+
+            # self.assert_( hasattr(%(framework)s, 'protocols') )
+
+            # self.assert_( hasattr(%(framework)s.protocols, 'PROTOCOL') )
+            # self.assert_( isinstance(%(framework)s.protocols.PROTOCOL, objc.informal_protocol) )
+
+        def test_structs(self):
+            # Use this to test struct wrappers
+            pass
+
+            # self.assert_( hasattr(%(framework)s, 'STRUCT') )
+            # o = %(framework)s.STRUCT()
+            # self.assert_( hasattr(o, 'FIELD_NAME') )
+
+
+
+    if __name__ == "__main__":
+        unittest.main()
+
+""")
+
+def getBundleIdentifier(frameworkPath):
+    infoPlist = os.path.join(frameworkPath, "Resources", "Info.plist")
+    if not os.path.exists(infoPlist):
+        infoPlist = os.path.join(frameworkPath, "Resources", "Info-macos.plist")
+    if not os.path.exists(infoPlist):
+        raise RuntimeError, "Framework at %s has no Info.plist?"%(frameworkPath,)
+
+    try:
+        pl = plistlib.readPlist(infoPlist)
+    except:
+        # Some frameworks contain a binary plist file, which plistlib doesn't
+        # like at all.
+        r = subprocess.call(
+            ['plutil', '-convert', 'xml1', '-o', 'Info.plist', infoPlist])
+        if r != 0:
+            raise RuntimeError, "plutil failed"
+        pl = plistlib.readPlist('Info.plist')
+        os.unlink('Info.plist')
+    return pl['CFBundleIdentifier']
+
+def getFrameworkPath(framework):
+
+    # Normal frameworks:
+    for basedir in ('/Library/Frameworks', '/System/Library/Frameworks'):
+        path = os.path.join(basedir, framework + '.framework')
+        if os.path.exists(path):
+            return path
+
+    # Frameworks in an umbrella framework:
+    subpath = 'Frameworks/%s.framework'%(framework,)
+    for basedir in '/Library/Frameworks', '/System/Library/Frameworks':
+        for dn in os.listdir(basedir):
+            possibleMatch = os.path.join(basedir, dn, subpath)
+            if os.path.exists(possibleMatch):
+                return possibleMatch
+
+
+    raise RuntimeError, "Cannot find framework: %s"%(framework)
+
+
+def main():
+    parser = optparse.OptionParser(version="%prog 0.5")    
+    parser.add_option("-f", "--framework", dest="framework",        
+        metavar="FRAMEWORK",        
+        help="Generate wrapper for the named framework")    
+    parser.add_option("-o", "--output", dest="output", metavar="DIR",        
+            help="Name of the output directory (default: '.')", default=".")    
+    parser.add_option("-p", "--project-name", dest="project", metavar="NAME",
+        help="Name of the python project, defaults to 'pyobjc-framework-FRAMEWORK'")
+
+    options, args = parser.parse_args()
+    if args:
+        parser.error("incorrect number of arguments")
+
+    if options.framework is None:
+        parser.error("you must specify a framework to wrap")
+
+    if options.project is None:
+        options.project = "pyobjc-framework-%s"%(options.framework,)
+
+    try:
+        frameworkPath = getFrameworkPath(options.framework)
+    except RuntimeError, msg:
+        parser.error(msg)
+
+    bundleIdentifier = getBundleIdentifier(frameworkPath)
+    
+    if not os.path.exists(options.output):
+        os.makedirs(options.output)
+
+    if os.path.exists(os.path.join(options.output, options.project)):
+        parser.error("framework wrapper already exists")
+
+    framework = options.framework
+    project = options.project
+    pyobjc_version = objc.__version__
+
+    basedir = os.path.join(options.output, options.project)
+    os.mkdir(basedir)
+    os.mkdir(os.path.join(basedir, "Lib"))
+    os.mkdir(os.path.join(basedir, "Lib", options.framework))
+    os.mkdir(os.path.join(basedir, "Lib", options.framework, "test"))
+    os.mkdir(os.path.join(basedir, "Examples"))
+    os.mkdir(os.path.join(basedir, "Exceptions"))
+
+    fp = open(os.path.join(basedir, 'README.txt'), 'w')
+    fp.write(README_TMPL % locals())
+    fp.close()
+
+    fp = open(os.path.join(basedir, 'LICENSE.txt'), 'w')
+    fp.write(LICENSE_TMPL % locals())
+    fp.close()
+
+
+    import __main__
+    bindir = os.path.dirname(os.path.abspath(__main__.__file__))
+
+    proc =  subprocess.Popen([
+            os.path.join(bindir, 'pyobjc-metadata-gen'),
+            '-f', framework,
+            '-F', 'final-md',
+            '-o', os.path.join(basedir, 'Lib', framework, 'PyObjC.bridgesupport'),
+            '-d', '-'
+            ], stdout=subprocess.PIPE)
+    lines = proc.communicate()[0]
+
+    result = proc.returncode
+    if result != 0:
+        print >>sys.stderr, "Generating metadata failed"
+        sys.exit(1)
+
+    result = subprocess.call([
+            os.path.join(bindir, 'pyobjc-metadata-gen'),
+            '-f', framework,
+            '-F', 'excp-templ-md',
+            '-o', os.path.join(basedir, 'Exceptions', framework + '.bridgesupport'),
+            ])
+    if result != 0:
+        print >>sys.stderr, "Generating exceptions template failed"
+        sys.exit(1)
+
+
+    importList = []
+    dependencyList = []
+    for ln in lines.splitlines():
+        importList.append('from %s import *'%(ln.strip(),))
+        dependencyList.append('\'pyobjc-framework-%s>=%s\','%(ln.strip(), pyobjc_version))
+
+    dependencyList = '\n        '.join(dependencyList)
+    fp = open(os.path.join(basedir, 'setup.py'), 'w')
+    fp.write(SETUP_TMPL % locals())
+    fp.close()
+
+    frameworkDepends = '\n'.join(importList)
+        
+    fp = open(
+        os.path.join(basedir, "Lib", framework, '__init__.py'), 'w')
+    fp.write(INIT_TMPL % locals())
+    fp.close()
+
+    fp = open(
+        os.path.join(basedir, "Lib", framework, 'test', '__init__.py'), 'w')
+    fp.close()
+
+    fp = open(
+        os.path.join(basedir, "Lib", framework, 'test', 'test_%s.py'%(framework.lower(),)), 'w')
+    fp.write(TEST_TMPL % locals())
+    fp.close()
+
+    result = subprocess.call([
+            'svn', 'export', 'svn://svn.eby-sarna.com/svnroot/ez_setup'],
+            cwd=basedir)
+    if result != 0:
+        print >>sys.stderr, "Couldn't extract the required 'ez_setup' files"
+        sys.exit(1)
+
+    print textwrap.dedent('''\
+        Generated framework wrappers for %(framework)s.
+        
+        Please check and adjust setup.py, License.txt and ReadMe.txt to tast.
+        Also check and update the metadata exceptions (and if you make changes
+        there update the actual metadata) before releasing this package.
+
+        There is a skeleton for  unittests in Lib/%(framework)s/test, please 
+        add some real tests to that file.
+
+        You'd do well to add some examples as well :-)
+
+        Note: if you use subversion: remove ez_setup.py and add the following
+        svn:externals defintion::
+
+           ez_setup svn://svn.eby-sarna.com/svnroot/ez_setup
+
+        ''') % locals()
+    sys.exit(0)
+
+
+if __name__ == "__main__":
+    main()

pyobjc-metadata/Lib/PyObjCMetaData/lint.py

+"""
+A tool to check the contents of a metadata file.
+
+This is mostly meant to look for missing annotation (as far as that can be done
+using a tool), but will report on other problems (like bad element/attribute
+names) as well.
+
+XXX: How do I recover linenumber information from an ElementTree?
+"""
+import optparse, sys, objc
+from PyObjCMetaData.et import *
+
+gCoreFoundationTypes = [
+    # Some commonly used CF-types (to avoid false positives)
+    '^{__CFString}',
+    'r^{__CFString}',
+    '^{__CFData}',
+    'r^{__CFData}',
+]
+
+default_name_attribute='name'
+name_attribute={
+    'method': 'selector',
+}
+
+mandatory_attributes={
+    'struct': ('name',),
+    'cftype': ('name',),
+    'opaque': ('name',),
+    'constant': ('name',),
+    'string_constant': ('name', 'value'),
+    'enum': ('name',),
+    'function': ('name',),
+    'function_alias': ('name', 'original'),
+    'class': ('name',),
+    'informal_protocol': ('name',),
+    ('informal_protocol', 'method'): ('selector',),
+    ('class', 'method'): ('selector',),
+}
+
+valid_attributes={
+    'struct': ('name', 'type', 'type64', 'opaque'),
+    'cftype': ('name', 'type', 'type64', 'tollfree', 'gettypeid_func'),
+    'opaque': ('name', 'type', 'type64'),
+    'constant': ('name', 'type', 'type64', 'magic_cookie'),
+    'string_constant': ('name', 'value', 'nsstring'),
+    'enum': ('name', 'value', 'value64', 'le_value', 'be_value', 'ignore', 'suggestion'),
+    'function': ('name', 'variadic', 'inline'),
+    'function_alias': ('name', 'original'),
+    'class': ('name',),
+    'informal_protocol': ('name',),
+    ('informal_protocol', 'method'): ('selector', 'type', 'type64', 'class_method'),
+    ('class', 'method'): ('selector', 'class_method', 'variadic', 'ignore', 'suggestion', ),
+    'arg': ('c_array_length_in_arg', 'c_array_of_fixed_length', 'c_array_delimited_by_null', 'c_array_of_variable_length', 'function_pointer', 'sel_of_type', 'sel_of_type64', 'c_array_length_in_retval', 'type_modifier', 'null_accepted', 'printf_format', 'already_retained', 'type', 'type64', 'index'),
+    'retval': ('c_array_length_in_arg', 'c_array_of_fixed_length', 'c_array_delimited_by_null', 'c_array_of_variable_length', 'function_pointer', 'sel_of_type', 'sel_of_type64', 'already_retained', 'type', 'type64'),
+}
+
+boolean_attributes={
+    'struct': ('opaque',),
+    'constant': ('magic_cookie',),
+    'string_constant': ('nsstring',),
+    'enum': ('ignore',),
+    'function': ('variadic', 'inline'),
+    ('informal_protocol', 'method'): ('class_method',),
+    ('class', 'method'): ('class_method', 'variadic', 'ignore', ),
+    'arg': ('c_array_delimitd_by_null', 'c_array_of_variable_length', 'function_pointer', 'c_array_length_in_retval', 'null_accepted', 'printf_format', 'already_retained', ),
+    'retval': ('c_array_delimitd_by_null', 'c_array_of_variable_length', 'function_pointer', 'already_retained'),
+}
+
+encoding_attributes={
+    'struct': ('type', 'type64',),
+    'cftype': ('type', 'type64',),
+    'opaque': ('type', 'type64',),
+    'constant': ('type', 'type64',),
+    ('informal_protocol', 'method'): ('type', 'type64'),
+    'arg': ('type', 'type64',),
+    'retval': ('type', 'type64',),
+}
+
+encoding_string_attributes={
+    ('informal_protocol', 'method'): ('type', 'type64'),
+    'arg': ('sel_of_type', 'sel_of_type64',),
+    'retval': ('sel_of_type', 'sel_of_type64',),
+}
+
+def name(node):
+    return node.get(name_attribute.get(node.tag, default_name_attribute))
+
+def extract_struct_fields(typestr):
+    """ Return (structTag, fieldNames) """
+    assert typestr[0] == '{'
+
+    # XXX: this is harder than it looks at first, really need a recursive parser here.
+
+    nameend=typestr.find('=')
+
+    if nameend != -1:
+        name = typestr[1:nameend]
+
+        return name, () # XXX: this is wrong, as is the code below
+        rest = typestr[nameend+1:-1]
+        fields = []
+        while rest:
+            if rest.startswith('"'):
+                end = rest.find('"', 1)
+            fields.append(rest[1:end])
+            rest = rest[end+1:]
+            end = rest.find('"')
+            if end == -1:
+                assert validate_encoding(rest)
+                rest = ''
+
+            else:
+                assert validate_encoding(rest[:end])
+                rest = rest[end:]
+
+        return name, fields
+
+    else:
+        name = typestr[1:-1]
+        return name, ()
+
+def validate_encoding(typestr):
+    """ Return True iff 'typestr' is a valid encoded type """
+    if not typestr:
+        return False
+
+    # Strip qualifiers
+    while typestr and typestr[0] in (objc._C_IN, objc._C_OUT, objc._C_INOUT, objc._C_CONST, objc._C_ONEWAY):
+        typestr = typestr[1:]
+
+    if not typestr:
+        return False
+
+    if typestr[0] in (objc._C_BOOL, objc._C_CHARPTR, objc._C_CHR, 
+            objc._C_CLASS, objc._C_DBL, objc._C_FLT, objc._C_ID, objc._C_INT, 
+            objc._C_LNG, objc._C_LNG_LNG, objc._C_NSBOOL, objc._C_SEL, objc._C_SHT, 
+            objc._C_UCHR, objc._C_UINT, objc._C_ULNG, objc._C_ULNG_LNG, objc._C_UNDEF, 
+            objc._C_USHT):
+
+        return len(typestr) == 1
+
+    elif typestr[0] == objc._C_ARY_B:
+        if typestr[-1] != objc._C_ARY_E:
+            return False
+
+        typestr = typestr[1:-1]
+        if len(typestr) == 0:
+            return False
+
+        while typestr and typestr[0].isdigit():
+            typestr = typestr[1:]
+
+        if len(typestr) == 0:
+            return False
+
+        return validate_encoding(typestr)
+
+    elif typestr[0] == objc._C_STRUCT_B:
+        return True
+        if typestr[-1] != objc._C_STRUCT_E:
+            return False
+
+        typestr = typestr[1:-1]
+        if '=' in typestr:
+            name = typestr[:typestr.find('=')]
+            rest = typestr[typestr.find('=')+1:]
+            if rest:
+                if rest[0] != '"':
+                    fields = objc.splitSignature(rest)
+                    for fld in fields:
+                        if not validate_signature(fld):
+                            return False
+
+                else:
+                    while rest:
+                        if rest.startswith('"'):
+                            end = rest.find('"', 1)
+                            fn = rest[1:end]
+                            for ch in fn:
+                                if fn != '_' and not fn.isalpha() and not fn.isdigit():
+                                    return False
+                            rest = rest[end+1:]
+                            end = rest.find('"')
+                            if end == -1:
+                                if not validate_encoding(rest):
+                                    return False
+                                rest = ''
+
+                            else:
+                                if not validate_encoding(rest[:end]):
+                                    return False
+                                rest = rest[end:]
+                
+            
+        else:
+            name = typestr
+
+        if name != '?':
+            for ch in name:
+                if ch != '_' and not ch.isalpha() and not ch.isdigit():
+                    return False
+
+        return True
+
+    elif typestr[0] == objc._C_BFLD:
+        typestr = typestr[1:]
+        if len(typestr) == 0:
+            return False
+        while typestr and typestr[0].isdigit():
+            typestr = typestr[1:]
+        return typestr == ''
+
+    return True
+
+def validate_encoding_string(typestr):
+    result = True
+    try:
+        parts = objc.splitSignature(typestr)
+    except:
+        return False
+
+    for el in parts:
+        result = result and validate_encoding(el)
+    return result
+
+def basicValidation(tag, element, parent=None):
+    valids = valid_attributes.get(element.tag, ())
+    if parent is not None:
+        valids = valids + valid_attributes.get((parent.tag, element.tag), ())
+    for k in element.attrib.keys():
+        if k not in valids:
+            print "ERROR: %s: illegal attribute: %r"%(
+                    tag, k)
+
+    for k in boolean_attributes.get(element.tag, ()):
+        v = element.get(k)
+        if v is not None:
+            if v not in ('true', 'false'):
+                print "ERROR: %s: invalid value for attribute %r: %r"%(
+                        tag, k, v)
+
+    for k in encoding_attributes.get(element.tag, ()):
+        v = element.get(k)
+        if v is not None:
+            if not validate_encoding(v):
+                print "ERROR: %s: invalid type encoding for attribute %r: %r"%(
+                        tag, k, v)
+
+    for k in encoding_string_attributes.get(element.tag, ()):
+        v = element.get(k)
+        if v is not None:
+            if not validate_encoding_string(v):
+                print "ERROR: %s: invalid parameter encoding for attribute %r: %r"%(
+                        tag, k, v)
+
+    if parent is not None:
+        for k in boolean_attributes.get((parent.tag, element.tag), ()):
+            v = element.get(k)
+            if v is not None:
+                if v not in ('true', 'false'):
+                    print "ERROR: %s: invalid value for attribute %r: %r"%(
+                            tag, k, v)
+
+        for k in encoding_attributes.get((parent.tag, element.tag), ()):
+            v = element.get(k)
+            if v is not None:
+                if not validate_encoding(v):
+                    print "ERROR: %s: invalid type encoding for attribute %r: %r"%(
+                            tag, k, v)
+
+        for k in encoding_string_attributes.get((parent.tag, element.tag), ()):
+            v = element.get(k)
+            if v is not None:
+                if not validate_encoding_string(v):
+                    print "ERROR: %s: invalid parameter encoding for attribute %r: %r"%(
+                            tag, k, v)
+
+
+def validateArgRetval(tag, element, numArg):
+    length_in_arg = element.get('c_array_length_in_arg')
+    fixed_length = element.get('c_array_of_fixed_length')
+    null_delimited = element.get('c_array_delimited_by_null')
+    variable_length = element.get('c_array_of_variable_length')
+    length_in_retval = element.get('c_array_length_in_retval')
+
+
+    if element.get('null_accepted') is not None:
+        tp = element.get('type')
+        if tp is not None:
+            if tp != '*' and not tp.startswith('^') and not tp.startswith('r^'):
+                print "WARNING: %s: null_accepted but no pointer type"%(
+                        tag,)
+
+
+    if length_in_arg is not None:
+        if null_delimited is not None:
+            print "ERROR: %s: both c_array_length_in_arg and c_array_delimited_by_null"%(tag,)
+
+        if variable_length is not None:
+            print "ERROR: %s: both c_array_length_in_arg and c_array_of_variable_length"%(tag,)
+
+        if ',' in length_in_arg:
+            v = [ v.strip() for v in length_in_arg.split(',') ]
+            if len(v) != 2:
+                print "ERROR: %s: Invalid value for c_array_length_in_arg: %r"(
+                        tag, length_in_arg)
+            try:
+                before, after = [ int(v, 10) for v in v ]
+            except ValueError:
+                print "ERROR: %s: Invalid value for c_array_length_in_arg: %r"(
+                        tag, length_in_arg)
+
+            if numArg is not None:
+                if not (0 <= before < numArg):
+                    print "ERROR: %s: c_array_length_in_arg out of range: %r"(
+                        tag, length_in_arg)
+                if not (0 <= after < numArg):
+                    print "ERROR: %s: c_array_length_in_arg out of range: %r"(
+                        tag, length_in_arg)
+
+            if length_in_retval is not None:
+                print "ERROR: %s: 2 element c_array_length_in_arg and c_array_length_in_retval present"%(tag,)
+        else:
+            try:
+                v = int(length_in_arg, 10)
+            except ValueError:
+                print "ERROR: %s: Invalid value for c_array_length_in_arg: %r" % (
+                        tag, length_in_arg)
+
+            if numArg is not None:
+                if not (0 <= v < numArg):
+                    print "ERROR: %s: c_array_length_in_arg out of range: %r" % (
+                        tag, length_in_arg)
+
+    if fixed_length is not None:
+        if null_delimited is not None:
+            print "ERROR: %s: both c_array_of_fixed_length and c_array_delimited_by_null"%(tag,)
+        if variable_length is not None:
+            print "ERROR: %s: both c_array_of_fixed_length and c_array_of_variable_length"%(tag,)
+        try:
+            v = int(fixed_length, 10)
+        except ValueError:
+            print "ERROR: %s: Invalid value for c_array_of_fixed_length: %r"(
+                    tag, fixed_length)
+
+    if element.get('type'):
+        tp = element.get('type')
+        if tp == '*' or tp.startswith('^') or tp.startswith('r^'):
+            pass
+
+        else:
+            if length_in_arg is not None or fixed_length is not None \
+                or null_delimited is not None or variable_length is not None \
+                or length_in_retval is not None:
+
+
+                    print "WARNING: %s: non-pointer and c_array_* annotation"%(
+                            tag,)
+
+        if tp != ':':
+            if element.get('sel_of_type') is not None \
+                    or element.get('sel_of_type64') is not None:
+
+                print "WARNING: %s: non-selector (%s) and sel_of_type annotation"%(
+                        tag, element.get('type'))
+
+        if tp.startswith('^') or tp.startswith('r^'):
+            if element.tag == 'arg' and element.get('type_modifier') is None:
+                if tp not in gCoreFoundationTypes and tp != '^?':
+                    print "WARNING: %s: pointer-type (%s) without 'type_modifier'"%(
+                        tag, tp)
+
+        if tp == '^?':
+            if element.get('function_pointer') != 'true':
+                print "WARNING: %s: type '^?', but not a function_pointer"%(
+                        tag,)
+
+
+
+
+    modifier = element.get('type_modifier')
+    if modifier is not None:
+        if modifier not in ('n', 'N', 'o'):
+            print "ERROR: %s: Invalid value for 'type_modifier'"%(
+                    tag,)
+
+    if element.get('print_format', 'false') == 'true':
+        tp = element.get('type')
+        if tp is not None:
+            if type not in ('*', '@'):
+                print "WARNING: %s: print_format but type not string or object"%(
+                        tag,)
+def main():
+    parser = optparse.OptionParser(version="%prog 0.1")
+    parser.add_option("-f", "--file", dest="filename", metavar="FILE",
+       help="check the given file")
+    parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
+        default=False, help="Be more verbose")
+
+
+    options, args = parser.parse_args()
+    if args:
+        parser.error("incorrect number of arguments")
+
+    if options.filename is None:
+        parser.error("You must specify the filename to check")
+
+
+    try:
+        doc = ET.parse(options.filename).getroot()
+    except Exception, e:
+        print 'ERROR: Parsing the metadata file failed: %s'%(e,)
+        sys.exit(1)
+
+    if doc.tag != 'signatures':
+        print 'ERROR: Document tag is not "signatures"'
+        sys.exit(1)
+
+    version = doc.get('version')
+    if version is not None:
+        if version not in ('0.9', '1.0'):
+            print "WARNING: Unexpected metadata version: %s"%(version,)
+
+    for k in doc.attrib.keys():
+        if k != 'version':
+            print "ERROR: %r: illegal attribute %r"%(doc.tag, k)
+
+    for element in doc.getchildren():
+        nametag = name_attribute.get(element.tag, default_name_attribute)
+        if element.get(nametag) is None:
+            print "ERROR: %r without a %r"%(element.tag, nametag)
+            continue
+
+        basicValidation("%s %r"%(element.tag, name(element)), element)
+
+
+        if element.tag == 'enum':
+            # TODO: 
+            # - check if we have at least one of the value attributes
+            # - check that the values are decimal numbers (either integer or 
+            #   float)
+            value = element.get('value')
+            value64 = element.get('value64')
+            le_value = element.get('le_value')
+            be_value = element.get('be_value')
+
+            if len(list(element.getchildren())) != 0:
+                print "ERROR: %s %r: Subelements present"%(
+                        element.tag, name(element))
+
+            if le_value is not None or be_value is not None:
+                if le_value is None or be_value is None:
+                    print "WARNING: %s %r: both be_value and le_value should be present" %(
+                            element.tag, name(element))
+                if value is not None:
+                    print "WARNING: %s %r: both 'value' and byte-order specific data"%(element.tag, name(element))
+                if value64 is not None:
+                    print "WARNING: %s %r: both 'value64' and byte-order specific data"%(element.tag, name(element))
+
+            if value is not None and value != '':
+                if '.' in value:
+                    try:
+                        v = float(value)
+                    except ValueError:
+                        print "WARNING: %s %r: invalid value for 'value'"%(element.tag, name(element))
+                else:
+                    try:
+                        v = int(value, 10)
+                    except ValueError:
+                        print "WARNING: %s %r: invalid value for 'value'"%(element.tag, name(element))
+            if value64 is not None and value64 != '':
+                if '.' in value64:
+                    try:
+                        v = float(value64)
+                    except ValueError:
+                        print "WARNING: %s %r: invalid value for 'value64'"%(element.tag, name(element))
+                else:
+                    try:
+                        v = int(value64, 10)
+                    except ValueError:
+                        print "WARNING: %s %r: invalid value for 'value64'"%(element.tag, name(element))
+            if le_value is not None and le_value != '':
+                if '.' in le_value:
+                    try:
+                        v = float(le_value)
+                    except ValueError:
+                        print "WARNING: %s %r: invalid value for 'le_value'"%(element.tag, name(element))
+                else:
+                    try:
+                        v = int(le_value, 10)
+                    except ValueError:
+                        print "WARNING: %s %r: invalid value for 'le_value'"%(element.tag, name(element))
+            if be_value is not None and be_value != '':
+                if '.' in be_value:
+                    try:
+                        v = float(be_value)
+                    except ValueError:
+                        print "WARNING: %s %r: invalid value for 'be_value'"%(element.tag, name(element))
+                else:
+                    try:
+                        v = int(be_value, 10)
+                    except ValueError:
+                        print "WARNING: %s %r: invalid value for 'be_value'"%(element.tag, name(element))
+
+            if not value and not value64 and not le_value and not be_value:
+                print "ERROR: %s %r: no value specified"%(element.tag, name(element))
+
+        elif element.tag == 'struct':
+            type = element.get('type')
+            type64 = element.get('type64')
+            if len(list(element.getchildren())) != 0:
+                print "ERROR: %s %r: Subelements present"%(
+                        element.tag, name(element))
+
+            if not type and not type64:
+                print "ERROR: %s %r: no type specified"%(element.tag, name(element))
+
+
+            if type is None:
+                tag, fields = None, None
+
+            elif not type.startswith('{'):
+                print "ERROR: %s %r: Encoded 'type' is not for a struct"%(
+                    element.tag, name(element))
+                tag, fields = None, None
+            else:
+                try:
+                    tag, fields = extract_struct_fields(type)
+
+                except ValueError:
+                    print "ERROR: %s %r: invalid type encoding for %r"%(
+                        element.tag, name(element), 'type')
+                    tag, fields = None, None
+
+            if type64 is None:
+                tag64, fields64 = None, None
+
+            elif not type64.startswith('{'):
+                print "ERROR: %s %r: Encoded 'type64' is not for a struct"%(
+                    element.tag, name(element))
+                tag64, fields64 = None, None
+            else:
+                try:
+                    tag64, fields64 = extract_struct_fields(type64)
<