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