1"""
2Process the (optional) BrigeSupport.xml for a framework.
3
4XXX: This file needs to be rewritten in C for optimal speed.
5"""
6__all__ = ('initFrameworkWrapper', )
7
8import objc
9import pkg_resources
10import new, sys, os, struct
11import textwrap
12
13import objc._bindglobals
14
15from objc import function, registerMetaDataForSelector
16
17
18# Import ElementTree from one of the various locations where
19# it might be found
20try:
21        # Python 2.5
22        import xml.etree.cElementTree as ET
23except ImportError:
24        # And earlier (with separate install)
25        try:
26            import cElementTree as ET
27
28        except ImportError:
29            import elementtree.ElementTree as ET
30
31
32# Are we in a 64-bit build:
33is64Bit = (sys.maxint > 2147483647)
34# Are we in a little-endian build:
35isLittleEndian = (sys.byteorder == 'little')
36
37# Cannot import Foundation, we're in the framework loading code.
38NSAutoreleasePool = objc.lookUpClass('NSAutoreleasePool')
39_gBridgeSupportDirectories = (
40        '/System/Library/BridgeSupport',
41
42# Don't use the rest of the default search path to avoid relying on data that
43# might be on a single system. That would make it harder to create standalone
44# apps in some cases.
45#        '/Library/BridgeSupport',
46#        os.path.expanduser('~/Library/BridgeSupport'),
47    )
48
49for method in ('alloc', 'copy', 'copyWithZone:', 'mutableCopy', 'mutableCopyWithZone:'):
50    objc.registerMetaDataForSelector('NSObject', method,
51            dict(
52                retval=dict(already_retained=True),
53            ))
54
55def _parseBridgeSupport(data, globals, frameworkName, *args, **kwds):
56    try:
57        try:
58            objc.parseBridgeSupport(data, globals, frameworkName, *args, **kwds)
59        except objc.internal_error, e:
60            import warnings
61            warnings.warn("Error parsing BridgeSupport data for %s: %s" % (frameworkName, e), RuntimeWarning)
62    finally:
63        # Add formal protocols to the protocols submodule, for backward
64        # compatibility with earlier versions of PyObjC
65        if 'protocols' in globals:
66            for p in objc.protocolsForProcess():
67                setattr(globals['protocols'], p.__name__, p)
68
69def initFrameworkWrapper(frameworkName,
70        frameworkPath, frameworkIdentifier, globals, inlineTab=None,
71        scan_classes=None, frameworkResourceName=None):
72    """
73    Load the named framework, using the identifier if that has result otherwise
74    using the path. Also loads the information in the bridgesupport file (
75    either one embedded in the framework or one in a BrigeSupport library
76    directory).
77    """
78    if frameworkResourceName is None:
79        frameworkResourceName = frameworkName
80
81    if frameworkIdentifier is None:
82        if scan_classes is None:
83            bundle = objc.loadBundle(
84                frameworkName,
85                globals,
86                bundle_path=frameworkPath)
87        else:
88            bundle = objc.loadBundle(
89                frameworkName,
90                globals,
91                bundle_path=frameworkPath,
92                scan_classes=scan_classes)
93
94    else:
95        try:
96            if scan_classes is None:
97                bundle = objc.loadBundle(
98                    frameworkName,
99                    globals,
100                    bundle_identifier=frameworkIdentifier)
101
102            else:
103                bundle = objc.loadBundle(
104                    frameworkName,
105                    globals,
106                    bundle_identifier=frameworkIdentifier,
107                    scan_classes=scan_classes)
108
109        except ImportError:
110            if scan_classes is None:
111                bundle = objc.loadBundle(
112                    frameworkName,
113                    globals,
114                    bundle_path=frameworkPath)
115            else:
116                bundle = objc.loadBundle(
117                    frameworkName,
118                    globals,
119                    bundle_path=frameworkPath,
120                    scan_classes=scan_classes)
121
122
123    # Make the objc module available, because it contains a lot of useful
124    # functionality.
125    globals['objc'] = objc
126
127    # Explicitly push objc.super into the globals dict, that way super
128    # calls will behave as expected in all cases.
129    globals['super'] = objc.super
130
131    if 1:
132        # Look for metadata in the Python wrapper and prefer that over the
133        # data in the framework or in system locations.
134        # Needed because the system bridgesupport files are buggy.
135        try:
136            exists = pkg_resources.resource_exists(
137                    frameworkResourceName, "PyObjC.bridgesupport")
138
139        except ImportError:
140            pass
141
142        else:
143            if exists:
144                data = pkg_resources.resource_string(frameworkResourceName,
145                    "PyObjC.bridgesupport")
146                if data:
147                    _parseBridgeSupport(data, globals, frameworkName, inlineTab=inlineTab)
148                return bundle
149
150    # Look for metadata in the framework bundle
151    path = bundle.pathForResource_ofType_inDirectory_(frameworkName, 'bridgesupport', 'BridgeSupport')
152    if path is not None:
153        dylib_path = bundle.pathForResource_ofType_inDirectory_(frameworkName, 'dylib', 'BridgeSupport')
154        data = open(path, 'rb').read()
155        if dylib_path is not None:
156            _parseBridgeSupport(data, globals, frameworkName, dylib_path)
157        else:
158            _parseBridgeSupport(data, globals, frameworkName)
159
160        # Check if we have additional metadata bundled with PyObjC
161        try:
162            exists = pkg_resources.resource_exists(
163                frameworkResourceName, "PyObjCOverrides.bridgesupport")
164
165        except ImportError:
166            pass
167
168        else:
169            if exists:
170                data = pkg_resources.resource_string(frameworkResourceName,
171                    "PyObjCOverrides.bridgesupport")
172                if data:
173                    _parseBridgeSupport(data, globals, frameworkName, inlineTab=inlineTab)
174
175        return bundle
176
177    # If there is no metadata there look for metadata in the standard Library
178    # locations
179    fn = frameworkName + '.bridgesupport'
180    for dn in _gBridgeSupportDirectories:
181        path = os.path.join(dn, fn)
182        if os.path.exists(path):
183            data = open(path, 'rb').read()
184            doc = ET.fromstring(data)
185
186            dylib_path = os.path.join(dn, frameworkName + '.dylib')
187            if os.path.exists(dylib_path):
188                _parseBridgeSupport(data, globals, frameworkName, dylib_path)
189            else:
190                _parseBridgeSupport(data, globals, frameworkName)
191
192            # Check if we have additional metadata bundled with PyObjC
193            try:
194                exists = pkg_resources.resource_exists(
195                    frameworkResourceName, "PyObjCOverrides.bridgesupport")
196
197            except ImportError:
198                pass
199
200            else:
201                if exists:
202                    data = pkg_resources.resource_string(frameworkResourceName,
203                        "PyObjCOverrides.bridgesupport")
204                    if data:
205                        _parseBridgeSupport(data, globals, frameworkName, inlineTab=inlineTab)
206            return bundle
207
208    # And if that fails look for the metadata in the framework wrapper
209    if pkg_resources.resource_exists(
210            frameworkName, "PyObjC.bridgesupport"):
211        data = pkg_resources.resource_string(frameworkResourceName,
212            "PyObjC.bridgesupport")
213        if data:
214            _parseBridgeSupport(data, globals, frameworkName, inlineTab=inlineTab)
215        return bundle
216
217    return bundle
218
219def _setupCFClasses(globalDict, cftypes):
220    """
221    Foundation types have a fully procedural C-interface, but that can
222    easily be transformed into an OO interface. This function performs that
223    transformation.
224
225    Function are transformed into methods by looking for functions whose name
226    starts with the type and and whose first argument is of the type. As a
227    special case 'Create' functions are transformed into class methods.
228
229    Note that this function *adds* an OO-interface, the fully procedural API
230    won't be removed.
231
232    Example:
233
234        url = CFURL.createWithFileSystemPath("/tmp")
235        print url.copyHostName()
236
237    In the procedural API:
238        url = CFURLCreateWithFileSystemPath(None, "/tmp")
239        print CFURLCopyHostName(url)
240
241    XXX: need to add information about this feature to the documentation
242    """
243    for name, encoding in cftypes:
244        if name.endswith('Ref'):
245            name = name[:-3]
246        tp = globalDict[name + 'Ref']
247
248        for funcname in globalDict:
249            if not funcname.startswith(name):
250                continue
251            f = globalDict[funcname]
252            if not isinstance(f, function):
253                continue
254            metadata = f.__metadata__()
255
256            rest = funcname[len(name):]
257            if not rest[0].isupper():
258                continue
259
260            rest = rest[0].lower() + rest[1:]
261
262            if rest.startswith('create') and metadata['retval']['type'] == encoding:
263                if len(metadata['arguments']) >= 1 and metadata['arguments'][0]['type'] == '^{__CFAllocator=}':
264                    argcount = len(metadata['arguments']) - 1
265                    argPrefix= 'None, '
266                    decorator = classmethod
267
268                else:
269                    argcount = len(metadata['arguments'])
270                    argPrefix = ''
271                    decorator = classmethod
272
273            else:
274                argcount = len(metadata['arguments']) - 1
275                argPrefix= 'self, '
276                decorator = lambda x: x
277
278                if 'arguments' not in metadata or len(metadata['arguments']) == 0:
279                    if metadata['retval']['type'] == encoding:
280                        argPrefix = ''
281                        decorator = classmethod
282                        argcount = 0
283
284                    else:
285                        continue
286
287                elif metadata['arguments'][0]['type'] != encoding:
288                    continue
289
290            # We don't have argument names, therefore just count the
291            # arguments...
292            argList = ', '.join([ 'x%d'%(i) for i in range(argcount)])
293            funcdef = textwrap.dedent('''\
294                def %(rest)s(self, %(argList)s):
295                    return %(funcname)s(%(argPrefix)s%(argList)s)
296                ''')
297            funcdef = funcdef % locals()
298            g = {}
299            g.update(globalDict)
300            exec funcdef in g
301            func = g[rest]
302
303            setattr(tp, rest, decorator(func))
304
305            # XXX: for compatibility with MacPython we migth want to add
306            # `funcname` as a method as well (although preferably a version
307            # that gives a DeprecationWarning)
308
309
310_ivar_dict = objc._ivar_dict()
311del objc._ivar_dict
312def _structConvenience(structname, structencoding):
313    def makevar(self, name=None):
314        if name is None:
315            return objc.ivar(type=structencoding)
316        else:
317            return objc.ivar(name=name, type=structencoding)
318    _ivar_dict[structname] = classmethod(makevar)
319
320
321# Fake it for basic C types
322_structConvenience("bool", objc._C_BOOL)
323_structConvenience("char", objc._C_CHR)
324_structConvenience("int", objc._C_INT)
325_structConvenience("short", objc._C_SHT)
326_structConvenience("long", objc._C_LNG)
327_structConvenience("long_long", objc._C_LNG_LNG)
328_structConvenience("unsigned_char", objc._C_UCHR)
329_structConvenience("unsigned_int", objc._C_UINT)
330_structConvenience("unsigned_short", objc._C_USHT)
331_structConvenience("unsigned_long", objc._C_ULNG)
332_structConvenience("unsigned_long_long", objc._C_ULNG_LNG)
333_structConvenience("float", objc._C_FLT)
334_structConvenience("double", objc._C_DBL)
335_structConvenience("BOOL", objc._C_NSBOOL)
336_structConvenience("UniChar", objc._C_UNICHAR)
337_structConvenience("char_text", objc._C_CHAR_AS_TEXT)
338_structConvenience("char_int", objc._C_CHAR_AS_INT)
339
340objc._setStructConvenience(_structConvenience)
341del objc._setStructConvenience
342
343
344# XXX: this is a nice-to-have, but adds a full second to the
345# load time of importing Quartz.
346#objc._setSetupCFClasses(_setupCFClasses)
347#del objc._setSetupCFClasses
348
349# Optimize usage of global variables
350objc._bindglobals.bind_all(sys.modules[__name__])
351