Bug 571548 - Generic attributes
[gnome.gobject-introspection] / giscanner / girwriter.py
1 # -*- Mode: Python -*-
2 # GObject-Introspection - a framework for introspecting GObject libraries
3 # Copyright (C) 2008  Johan Dahlin
4 # Copyright (C) 2008, 2009 Red Hat, Inc.
5 #
6 # This library is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU Lesser General Public
8 # License as published by the Free Software Foundation; either
9 # version 2 of the License, or (at your option) any later version.
10 #
11 # This library is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # Lesser General Public License for more details.
15 #
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with this library; if not, write to the
18 # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 # Boston, MA 02111-1307, USA.
20 #
21
22 from __future__ import with_statement
23
24 import os
25 from ctypes.util import find_library
26
27 from .ast import (Alias, Array, Bitfield, Callback, Class, Constant, Enum,
28                   Function, Interface, List, Map, Member, Struct, Union,
29                   Varargs)
30 from .glibast import (GLibBoxed, GLibEnum, GLibEnumMember,
31                       GLibFlags, GLibObject, GLibInterface,
32                       GLibRecord)
33 from .xmlwriter import XMLWriter
34
35
36 class GIRWriter(XMLWriter):
37
38     def __init__(self, namespace, shlibs, includes, pkgs, c_includes):
39         super(GIRWriter, self).__init__()
40         self.write_comment(
41 '''This file was automatically generated from C sources - DO NOT EDIT!
42 To affect the contents of this file, edit the original C definitions,
43 and/or use gtk-doc annotations. ''')
44         self._write_repository(namespace, shlibs, includes, pkgs, c_includes)
45
46     def _write_repository(self, namespace, shlibs, includes=None,
47                           packages=None, c_includes=None):
48         if includes is None:
49             includes = frozenset()
50         if packages is None:
51             packages = frozenset()
52         if c_includes is None:
53             c_includes = frozenset()
54         attrs = [
55             ('version', '1.0'),
56             ('xmlns', 'http://www.gtk.org/introspection/core/1.0'),
57             ('xmlns:c', 'http://www.gtk.org/introspection/c/1.0'),
58             ('xmlns:glib', 'http://www.gtk.org/introspection/glib/1.0'),
59             ]
60         with self.tagcontext('repository', attrs):
61             for include in sorted(includes):
62                 self._write_include(include)
63             for pkg in sorted(set(packages)):
64                 self._write_pkgconfig_pkg(pkg)
65             for c_include in sorted(set(c_includes)):
66                 self._write_c_include(c_include)
67             self._write_namespace(namespace, shlibs)
68
69     def _write_include(self, include):
70         attrs = [('name', include.name), ('version', include.version)]
71         self.write_tag('include', attrs)
72
73     def _write_pkgconfig_pkg(self, package):
74         attrs = [('name', package)]
75         self.write_tag('package', attrs)
76
77     def _write_c_include(self, c_include):
78         attrs = [('name', c_include)]
79         self.write_tag('c:include', attrs)
80
81     def _write_namespace(self, namespace, shlibs):
82         libraries = []
83         for l in shlibs:
84             found_libname = find_library(l)
85             if not found_libname:
86                 found_libname = l
87             libraries.append(os.path.basename(found_libname))
88
89         attrs = [('name', namespace.name),
90                  ('version', namespace.version),
91                  ('shared-library', ','.join(libraries))]
92         with self.tagcontext('namespace', attrs):
93             # We define a custom sorting function here because
94             # we want aliases to be first.  They're a bit
95             # special because the typelib compiler expands them.
96             def nscmp(a, b):
97                 if isinstance(a, Alias):
98                     if isinstance(b, Alias):
99                         return cmp(a.name, b.name)
100                     else:
101                         return -1
102                 elif isinstance(b, Alias):
103                     return 1
104                 else:
105                     return cmp(a, b)
106             for node in sorted(namespace.nodes, cmp=nscmp):
107                 self._write_node(node)
108
109     def _write_node(self, node):
110         if isinstance(node, Function):
111             self._write_function(node)
112         elif isinstance(node, Enum):
113             self._write_enum(node)
114         elif isinstance(node, Bitfield):
115             self._write_bitfield(node)
116         elif isinstance(node, (Class, Interface)):
117             self._write_class(node)
118         elif isinstance(node, Callback):
119             self._write_callback(node)
120         elif isinstance(node, Struct):
121             self._write_record(node)
122         elif isinstance(node, Union):
123             self._write_union(node)
124         elif isinstance(node, GLibBoxed):
125             self._write_boxed(node)
126         elif isinstance(node, Member):
127             # FIXME: atk_misc_instance singleton
128             pass
129         elif isinstance(node, Alias):
130             self._write_alias(node)
131         elif isinstance(node, Constant):
132             self._write_constant(node)
133         else:
134             print 'WRITER: Unhandled node', node
135
136     def _append_version(self, node, attrs):
137         if node.version:
138             attrs.append(('version', node.version))
139
140     def _write_attributes(self, node):
141         for key, value in node.attributes:
142             self.write_tag('attribute', [('name', key), ('value', value)])
143
144     def _append_deprecated(self, node, attrs):
145         if node.deprecated:
146             attrs.append(('deprecated', node.deprecated))
147             if node.deprecated_version:
148                 attrs.append(('deprecated-version',
149                               node.deprecated_version))
150
151     def _append_throws(self, func, attrs):
152         if func.throws:
153             attrs.append(('throws', '1'))
154
155     def _write_alias(self, alias):
156         attrs = [('name', alias.name), ('target', alias.target)]
157         if alias.ctype is not None:
158             attrs.append(('c:type', alias.ctype))
159         self.write_tag('alias', attrs)
160
161     def _write_function(self, func, tag_name='function'):
162         attrs = [('name', func.name),
163                  ('c:identifier', func.symbol)]
164         if func.doc:
165             attrs.append(('doc', func.doc))
166         self._append_version(func, attrs)
167         self._append_deprecated(func, attrs)
168         self._append_throws(func, attrs)
169         with self.tagcontext(tag_name, attrs):
170             self._write_attributes(func)
171             self._write_return_type(func.retval)
172             self._write_parameters(func.parameters)
173
174     def _write_method(self, method):
175         self._write_function(method, tag_name='method')
176
177     def _write_static_method(self, method):
178         self._write_function(method, tag_name='function')
179
180     def _write_constructor(self, method):
181         self._write_function(method, tag_name='constructor')
182
183     def _write_return_type(self, return_):
184         if not return_:
185             return
186
187         assert return_.transfer is not None, return_
188
189         attrs = []
190         attrs.append(('transfer-ownership', return_.transfer))
191         if return_.doc:
192             attrs.append(('doc', return_.doc))
193         with self.tagcontext('return-value', attrs):
194             self._write_type(return_.type)
195
196     def _write_parameters(self, parameters):
197         if not parameters:
198             return
199         with self.tagcontext('parameters'):
200             for parameter in parameters:
201                 self._write_parameter(parameter)
202
203     def _write_parameter(self, parameter):
204         assert parameter.transfer is not None, parameter
205
206         attrs = []
207         if parameter.name is not None:
208             attrs.append(('name', parameter.name))
209         if parameter.direction != 'in':
210             attrs.append(('direction', parameter.direction))
211         attrs.append(('transfer-ownership',
212                      parameter.transfer))
213         if parameter.allow_none:
214             attrs.append(('allow-none', '1'))
215         if parameter.scope:
216             attrs.append(('scope', parameter.scope))
217         if parameter.closure_index >= 0:
218             attrs.append(('closure', '%d' % parameter.closure_index))
219         if parameter.destroy_index >= 0:
220             attrs.append(('destroy', '%d' % parameter.destroy_index))
221         if parameter.doc:
222             attrs.append(('doc', parameter.doc))
223         with self.tagcontext('parameter', attrs):
224             self._write_type(parameter.type)
225
226     def _type_to_string(self, ntype):
227         if isinstance(ntype, basestring):
228             return ntype
229         return ntype.name
230
231     def _write_type(self, ntype, relation=None):
232         if isinstance(ntype, basestring):
233             typename = ntype
234             type_cname = None
235         else:
236             typename = ntype.name
237             type_cname = ntype.ctype
238         if isinstance(ntype, Varargs):
239             with self.tagcontext('varargs', []):
240                 pass
241             return
242         if isinstance(ntype, Array):
243             attrs = []
244             if not ntype.zeroterminated:
245                 attrs.append(('zero-terminated', '0'))
246             if ntype.length_param_index >= 0:
247                 attrs.append(('length', '%d' % (ntype.length_param_index, )))
248             attrs.append(('c:type', ntype.ctype))
249             if ntype.size is not None:
250                 attrs.append(('fixed-size', ntype.size))
251             with self.tagcontext('array', attrs):
252                 self._write_type(ntype.element_type)
253             return
254         attrs = [('name', self._type_to_string(ntype))]
255         # FIXME: figure out if type references a basic type
256         #        or a boxed/class/interface etc. and skip
257         #        writing the ctype if the latter.
258         if type_cname is not None:
259             attrs.append(('c:type', type_cname))
260         if isinstance(ntype, List) and ntype.element_type:
261             with self.tagcontext('type', attrs):
262                 self._write_type(ntype.element_type)
263             return
264         if isinstance(ntype, Map) and ntype.key_type:
265             with self.tagcontext('type', attrs):
266                 self._write_type(ntype.key_type)
267                 self._write_type(ntype.value_type)
268             return
269         # Not a special type, just write it out
270         self.write_tag('type', attrs)
271
272     def _write_enum(self, enum):
273         attrs = [('name', enum.name)]
274         if enum.doc:
275             attrs.append(('doc', enum.doc))
276         self._append_version(enum, attrs)
277         self._append_deprecated(enum, attrs)
278         if isinstance(enum, GLibEnum):
279             attrs.extend([('glib:type-name', enum.type_name),
280                           ('glib:get-type', enum.get_type),
281                           ('c:type', enum.ctype)])
282             if enum.error_quark:
283                 attrs.append(('glib:error-quark', enum.error_quark))
284         else:
285             attrs.append(('c:type', enum.symbol))
286
287         with self.tagcontext('enumeration', attrs):
288             self._write_attributes(enum)
289             for member in enum.members:
290                 self._write_member(member)
291
292     def _write_bitfield(self, bitfield):
293         attrs = [('name', bitfield.name)]
294         if bitfield.doc:
295             attrs.append(('doc', bitfield.doc))
296         self._append_version(bitfield, attrs)
297         self._append_deprecated(bitfield, attrs)
298         if isinstance(bitfield, GLibFlags):
299             attrs.extend([('glib:type-name', bitfield.type_name),
300                           ('glib:get-type', bitfield.get_type),
301                           ('c:type', bitfield.ctype)])
302         else:
303             attrs.append(('c:type', bitfield.symbol))
304         with self.tagcontext('bitfield', attrs):
305             self._write_attributes(bitfield)
306             for member in bitfield.members:
307                 self._write_member(member)
308
309     def _write_member(self, member):
310         attrs = [('name', member.name),
311                  ('value', str(member.value)),
312                  ('c:identifier', member.symbol)]
313         if isinstance(member, GLibEnumMember):
314             attrs.append(('glib:nick', member.nick))
315         self.write_tag('member', attrs)
316
317     def _write_constant(self, constant):
318         attrs = [('name', constant.name),
319                  ('value', str(constant.value))]
320         with self.tagcontext('constant', attrs):
321             self._write_type(constant.type)
322
323     def _write_class(self, node):
324         attrs = [('name', node.name),
325                  ('c:type', node.ctype)]
326         if node.doc:
327             attrs.append(('doc', node.doc))
328         self._append_version(node, attrs)
329         self._append_deprecated(node, attrs)
330         if isinstance(node, Class):
331             tag_name = 'class'
332             if node.parent is not None:
333                 attrs.append(('parent', node.parent))
334             if node.is_abstract:
335                 attrs.append(('abstract', '1'))
336         else:
337             tag_name = 'interface'
338         if isinstance(node, (GLibObject, GLibInterface)):
339             attrs.append(('glib:type-name', node.type_name))
340             if node.get_type:
341                 attrs.append(('glib:get-type', node.get_type))
342             if node.glib_type_struct:
343                 attrs.append(('glib:type-struct', node.glib_type_struct.name))
344         with self.tagcontext(tag_name, attrs):
345             self._write_attributes(node)
346             if isinstance(node, GLibObject):
347                 for iface in node.interfaces:
348                     self.write_tag('implements', [('name', iface)])
349             if isinstance(node, Interface):
350                 for iface in node.prerequisites:
351                     self.write_tag('prerequisite', [('name', iface)])
352             if isinstance(node, Class):
353                 for method in node.constructors:
354                     self._write_constructor(method)
355                 for method in node.static_methods:
356                     self._write_static_method(method)
357             for method in node.methods:
358                 self._write_method(method)
359             for prop in node.properties:
360                 self._write_property(prop)
361             for field in node.fields:
362                 self._write_field(field)
363             for signal in node.signals:
364                 self._write_signal(signal)
365
366     def _write_boxed(self, boxed):
367         attrs = [('c:type', boxed.ctype),
368                  ('glib:name', boxed.name)]
369         if boxed.doc:
370             attrs.append(('doc', boxed.doc))
371         attrs.extend(self._boxed_attrs(boxed))
372         with self.tagcontext('glib:boxed', attrs):
373             self._write_attributes(boxed)
374             for method in boxed.constructors:
375                 self._write_constructor(method)
376             for method in boxed.methods:
377                 self._write_method(method)
378
379     def _write_property(self, prop):
380         attrs = [('name', prop.name)]
381         self._append_version(prop, attrs)
382         self._append_deprecated(prop, attrs)
383         # Properties are assumed to be readable (see also generate.c)
384         if not prop.readable:
385             attrs.append(('readable', '0'))
386         if prop.writable:
387             attrs.append(('writable', '1'))
388         if prop.construct:
389             attrs.append(('construct', '1'))
390         if prop.construct_only:
391             attrs.append(('construct-only', '1'))
392         if prop.doc:
393             attrs.append(('doc', prop.doc))
394         with self.tagcontext('property', attrs):
395             self._write_attributes(prop)
396             self._write_type(prop.type)
397
398     def _write_callback(self, callback):
399         # FIXME: reuse _write_function
400         attrs = [('name', callback.name), ('c:type', callback.ctype)]
401         if callback.doc:
402             attrs.append(('doc', callback.doc))
403         self._append_version(callback, attrs)
404         self._append_deprecated(callback, attrs)
405         self._append_throws(callback, attrs)
406         with self.tagcontext('callback', attrs):
407             self._write_attributes(callback)
408             self._write_return_type(callback.retval)
409             self._write_parameters(callback.parameters)
410
411     def _boxed_attrs(self, boxed):
412         return [('glib:type-name', boxed.type_name),
413                 ('glib:get-type', boxed.get_type)]
414
415     def _write_record(self, record, extra_attrs=[]):
416         attrs = list(extra_attrs)
417         if record.name is not None:
418             attrs.append(('name', record.name))
419         if record.symbol is not None: # the record might be anonymous
420             attrs.append(('c:type', record.symbol))
421         if record.disguised:
422             attrs.append(('disguised', '1'))
423         if isinstance(record, GLibRecord):
424             if record.is_gtype_struct_for:
425                 attrs.append(('glib:is-gtype-struct-for',
426                               record.is_gtype_struct_for))
427         if record.doc:
428             attrs.append(('doc', record.doc))
429         self._append_version(record, attrs)
430         self._append_deprecated(record, attrs)
431         if isinstance(record, GLibBoxed):
432             attrs.extend(self._boxed_attrs(record))
433         with self.tagcontext('record', attrs):
434             self._write_attributes(record)
435             if record.fields:
436                 for field in record.fields:
437                     self._write_field(field)
438             for method in record.constructors:
439                 self._write_constructor(method)
440             for method in record.methods:
441                 self._write_method(method)
442
443     def _write_union(self, union):
444         attrs = []
445         if union.name is not None:
446             attrs.append(('name', union.name))
447         if union.symbol is not None: # the union might be anonymous
448             attrs.append(('c:type', union.symbol))
449         if union.doc:
450             attrs.append(('doc', union.doc))
451         self._append_version(union, attrs)
452         self._append_deprecated(union, attrs)
453         if isinstance(union, GLibBoxed):
454             attrs.extend(self._boxed_attrs(union))
455         with self.tagcontext('union', attrs):
456             self._write_attributes(union)
457             if union.fields:
458                 for field in union.fields:
459                     self._write_field(field)
460             for method in union.constructors:
461                 self._write_constructor(method)
462             for method in union.methods:
463                 self._write_method(method)
464
465     def _write_field(self, field):
466         if isinstance(field, Function):
467             self._write_method(field)
468             return
469
470         if isinstance(field, Callback):
471             self._write_callback(field)
472         elif isinstance(field, Struct):
473             self._write_record(field)
474         elif isinstance(field, Union):
475             self._write_union(field)
476         else:
477             attrs = [('name', field.name)]
478             # Fields are assumed to be read-only
479             # (see also girparser.c and generate.c)
480             if not field.readable:
481                 attrs.append(('readable', '0'))
482             if field.writable:
483                 attrs.append(('writable', '1'))
484             if field.bits:
485                 attrs.append(('bits', str(field.bits)))
486             with self.tagcontext('field', attrs):
487                 self._write_attributes(field)
488                 self._write_type(field.type)
489
490     def _write_signal(self, signal):
491         attrs = [('name', signal.name)]
492         if signal.doc:
493             attrs.append(('doc', signal.doc))
494         self._append_version(signal, attrs)
495         self._append_deprecated(signal, attrs)
496         with self.tagcontext('glib:signal', attrs):
497             self._write_attributes(signal)
498             self._write_return_type(signal.retval)
499             self._write_parameters(signal.parameters)