Imported Upstream version 1.39.3
[platform/upstream/gobject-introspection.git] / 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 from . import ast
25 from .xmlwriter import XMLWriter
26
27 # Bump this for *incompatible* changes to the .gir.
28 # Compatible changes we just make inline
29 COMPATIBLE_GIR_VERSION = '1.2'
30
31
32 class GIRWriter(XMLWriter):
33
34     def __init__(self, namespace):
35         super(GIRWriter, self).__init__()
36         self.write_comment(
37             'This file was automatically generated from C sources - DO NOT EDIT!\n'
38             'To affect the contents of this file, edit the original C definitions,\n'
39             'and/or use gtk-doc annotations. ')
40         self._write_repository(namespace)
41
42     def _write_repository(self, namespace):
43         attrs = [
44             ('version', COMPATIBLE_GIR_VERSION),
45             ('xmlns', 'http://www.gtk.org/introspection/core/1.0'),
46             ('xmlns:c', 'http://www.gtk.org/introspection/c/1.0'),
47             ('xmlns:glib', 'http://www.gtk.org/introspection/glib/1.0')]
48         with self.tagcontext('repository', attrs):
49             for include in sorted(namespace.includes):
50                 self._write_include(include)
51             for pkg in sorted(set(namespace.exported_packages)):
52                 self._write_pkgconfig_pkg(pkg)
53             for c_include in sorted(set(namespace.c_includes)):
54                 self._write_c_include(c_include)
55             self._namespace = namespace
56             self._write_namespace(namespace)
57             self._namespace = None
58
59     def _write_include(self, include):
60         attrs = [('name', include.name), ('version', include.version)]
61         self.write_tag('include', attrs)
62
63     def _write_pkgconfig_pkg(self, package):
64         attrs = [('name', package)]
65         self.write_tag('package', attrs)
66
67     def _write_c_include(self, c_include):
68         attrs = [('name', c_include)]
69         self.write_tag('c:include', attrs)
70
71     def _write_namespace(self, namespace):
72         attrs = [('name', namespace.name),
73                  ('version', namespace.version),
74                  ('shared-library', ','.join(namespace.shared_libraries)),
75                  ('c:identifier-prefixes', ','.join(namespace.identifier_prefixes)),
76                  ('c:symbol-prefixes', ','.join(namespace.symbol_prefixes))]
77         with self.tagcontext('namespace', attrs):
78             # We define a custom sorting function here because
79             # we want aliases to be first.  They're a bit
80             # special because the typelib compiler expands them.
81             def nscmp(a, b):
82                 if isinstance(a, ast.Alias):
83                     if isinstance(b, ast.Alias):
84                         return cmp(a.name, b.name)
85                     else:
86                         return -1
87                 elif isinstance(b, ast.Alias):
88                     return 1
89                 else:
90                     return cmp(a, b)
91             for node in sorted(namespace.itervalues(), cmp=nscmp):
92                 self._write_node(node)
93
94     def _write_node(self, node):
95         if isinstance(node, ast.Function):
96             self._write_function(node)
97         elif isinstance(node, ast.Enum):
98             self._write_enum(node)
99         elif isinstance(node, ast.Bitfield):
100             self._write_bitfield(node)
101         elif isinstance(node, (ast.Class, ast.Interface)):
102             self._write_class(node)
103         elif isinstance(node, ast.Callback):
104             self._write_callback(node)
105         elif isinstance(node, ast.Record):
106             self._write_record(node)
107         elif isinstance(node, ast.Union):
108             self._write_union(node)
109         elif isinstance(node, ast.Boxed):
110             self._write_boxed(node)
111         elif isinstance(node, ast.Member):
112             # FIXME: atk_misc_instance singleton
113             pass
114         elif isinstance(node, ast.Alias):
115             self._write_alias(node)
116         elif isinstance(node, ast.Constant):
117             self._write_constant(node)
118         else:
119             print 'WRITER: Unhandled node', node
120
121     def _append_version(self, node, attrs):
122         if node.version:
123             attrs.append(('version', node.version))
124
125     def _write_generic(self, node):
126         for key, value in node.attributes.items():
127             self.write_tag('attribute', [('name', key), ('value', value)])
128
129         if hasattr(node, 'doc') and node.doc:
130             self.write_tag('doc', [('xml:space', 'preserve')],
131                            node.doc)
132
133         if hasattr(node, 'version_doc') and node.version_doc:
134             self.write_tag('doc-version', [('xml:space', 'preserve')],
135                            node.version_doc)
136
137         if hasattr(node, 'deprecated_doc') and node.deprecated_doc:
138             self.write_tag('doc-deprecated', [('xml:space', 'preserve')],
139                            node.deprecated_doc)
140
141         if hasattr(node, 'stability_doc') and node.stability_doc:
142             self.write_tag('doc-stability', [('xml:space', 'preserve')],
143                            node.stability_doc)
144
145     def _append_node_generic(self, node, attrs):
146         if node.skip or not node.introspectable:
147             attrs.append(('introspectable', '0'))
148
149         if node.deprecated or node.deprecated_doc:
150             # The deprecated attribute used to contain node.deprecated_doc as an attribute. As
151             # an xml attribute cannot preserve whitespace, deprecated_doc has been moved into
152             # it's own tag, written in _write_generic() above. We continue to write the deprecated
153             # attribute for backwards compatibility
154             attrs.append(('deprecated', '1'))
155
156         if node.deprecated:
157             attrs.append(('deprecated-version', node.deprecated))
158
159         if node.stability:
160             attrs.append(('stability', node.stability))
161
162     def _append_throws(self, func, attrs):
163         if func.throws:
164             attrs.append(('throws', '1'))
165
166     def _write_alias(self, alias):
167         attrs = [('name', alias.name)]
168         if alias.ctype is not None:
169             attrs.append(('c:type', alias.ctype))
170         self._append_node_generic(alias, attrs)
171         with self.tagcontext('alias', attrs):
172             self._write_generic(alias)
173             self._write_type_ref(alias.target)
174
175     def _write_callable(self, callable, tag_name, extra_attrs):
176         attrs = [('name', callable.name)]
177         attrs.extend(extra_attrs)
178         self._append_version(callable, attrs)
179         self._append_node_generic(callable, attrs)
180         self._append_throws(callable, attrs)
181         with self.tagcontext(tag_name, attrs):
182             self._write_generic(callable)
183             self._write_return_type(callable.retval, parent=callable)
184             self._write_parameters(callable)
185
186     def _write_function(self, func, tag_name='function'):
187         if func.internal_skipped:
188             return
189         attrs = []
190         if hasattr(func, 'symbol'):
191             attrs.append(('c:identifier', func.symbol))
192         if func.shadowed_by:
193             attrs.append(('shadowed-by', func.shadowed_by))
194         elif func.shadows:
195             attrs.append(('shadows', func.shadows))
196         if func.moved_to is not None:
197             attrs.append(('moved-to', func.moved_to))
198         self._write_callable(func, tag_name, attrs)
199
200     def _write_method(self, method):
201         self._write_function(method, tag_name='method')
202
203     def _write_static_method(self, method):
204         self._write_function(method, tag_name='function')
205
206     def _write_constructor(self, method):
207         self._write_function(method, tag_name='constructor')
208
209     def _write_return_type(self, return_, parent=None):
210         if not return_:
211             return
212
213         attrs = []
214         if return_.transfer:
215             attrs.append(('transfer-ownership', return_.transfer))
216         if return_.skip:
217             attrs.append(('skip', '1'))
218         with self.tagcontext('return-value', attrs):
219             self._write_generic(return_)
220             self._write_type(return_.type, parent=parent)
221
222     def _write_parameters(self, callable):
223         if not callable.parameters and callable.instance_parameter is None:
224             return
225         with self.tagcontext('parameters'):
226             if callable.instance_parameter:
227                 self._write_parameter(callable, callable.instance_parameter, 'instance-parameter')
228             for parameter in callable.parameters:
229                 self._write_parameter(callable, parameter)
230
231     def _write_parameter(self, parent, parameter, nodename='parameter'):
232         attrs = []
233         if parameter.argname is not None:
234             attrs.append(('name', parameter.argname))
235         if (parameter.direction is not None) and (parameter.direction != 'in'):
236             attrs.append(('direction', parameter.direction))
237             attrs.append(('caller-allocates',
238                           '1' if parameter.caller_allocates else '0'))
239         if parameter.transfer:
240             attrs.append(('transfer-ownership',
241                           parameter.transfer))
242         if parameter.allow_none:
243             attrs.append(('allow-none', '1'))
244         if parameter.scope:
245             attrs.append(('scope', parameter.scope))
246         if parameter.closure_name is not None:
247             idx = parent.get_parameter_index(parameter.closure_name)
248             attrs.append(('closure', '%d' % (idx, )))
249         if parameter.destroy_name is not None:
250             idx = parent.get_parameter_index(parameter.destroy_name)
251             attrs.append(('destroy', '%d' % (idx, )))
252         if parameter.skip:
253             attrs.append(('skip', '1'))
254         with self.tagcontext(nodename, attrs):
255             self._write_generic(parameter)
256             self._write_type(parameter.type, parent=parent)
257
258     def _type_to_name(self, typeval):
259         if not typeval.resolved:
260             raise AssertionError("Caught unresolved type %r (ctype=%r)" % (typeval, typeval.ctype))
261         assert typeval.target_giname is not None
262         prefix = self._namespace.name + '.'
263         if typeval.target_giname.startswith(prefix):
264             return typeval.target_giname[len(prefix):]
265         return typeval.target_giname
266
267     def _write_type_ref(self, ntype):
268         """ Like _write_type, but only writes the type name rather than the full details """
269         assert isinstance(ntype, ast.Type), ntype
270         attrs = []
271         if ntype.ctype:
272             attrs.append(('c:type', ntype.ctype))
273         if isinstance(ntype, ast.Array):
274             if ntype.array_type != ast.Array.C:
275                 attrs.insert(0, ('name', ntype.array_type))
276         elif isinstance(ntype, ast.List):
277             if ntype.name:
278                 attrs.insert(0, ('name', ntype.name))
279         elif isinstance(ntype, ast.Map):
280             attrs.insert(0, ('name', 'GLib.HashTable'))
281         else:
282             if ntype.target_giname:
283                 attrs.insert(0, ('name', self._type_to_name(ntype)))
284             elif ntype.target_fundamental:
285                 attrs.insert(0, ('name', ntype.target_fundamental))
286
287         self.write_tag('type', attrs)
288
289     def _write_type(self, ntype, relation=None, parent=None):
290         assert isinstance(ntype, ast.Type), ntype
291         attrs = []
292         if ntype.complete_ctype:
293             attrs.append(('c:type', ntype.complete_ctype))
294         elif ntype.ctype:
295             attrs.append(('c:type', ntype.ctype))
296         if isinstance(ntype, ast.Varargs):
297             self.write_tag('varargs', [])
298         elif isinstance(ntype, ast.Array):
299             if ntype.array_type != ast.Array.C:
300                 attrs.insert(0, ('name', ntype.array_type))
301             # we insert an explicit 'zero-terminated' attribute
302             # when it is false, or when it would not be implied
303             # by the absence of length and fixed-size
304             if not ntype.zeroterminated:
305                 attrs.insert(0, ('zero-terminated', '0'))
306             elif (ntype.zeroterminated
307                   and (ntype.size is not None or ntype.length_param_name is not None)):
308                 attrs.insert(0, ('zero-terminated', '1'))
309             if ntype.size is not None:
310                 attrs.append(('fixed-size', '%d' % (ntype.size, )))
311             if ntype.length_param_name is not None:
312                 if isinstance(parent, ast.Callable):
313                     length = parent.get_parameter_index(ntype.length_param_name)
314                 elif isinstance(parent, ast.Compound):
315                     length = parent.get_field_index(ntype.length_param_name)
316                 else:
317                     assert False, "parent not a callable or compound: %r" % parent
318                 attrs.insert(0, ('length', '%d' % (length, )))
319
320             with self.tagcontext('array', attrs):
321                 self._write_type(ntype.element_type)
322         elif isinstance(ntype, ast.List):
323             if ntype.name:
324                 attrs.insert(0, ('name', ntype.name))
325             with self.tagcontext('type', attrs):
326                 self._write_type(ntype.element_type)
327         elif isinstance(ntype, ast.Map):
328             attrs.insert(0, ('name', 'GLib.HashTable'))
329             with self.tagcontext('type', attrs):
330                 self._write_type(ntype.key_type)
331                 self._write_type(ntype.value_type)
332         else:
333             # REWRITEFIXME - enable this for 1.2
334             if ntype.target_giname:
335                 attrs.insert(0, ('name', self._type_to_name(ntype)))
336             elif ntype.target_fundamental:
337                 # attrs = [('fundamental', ntype.target_fundamental)]
338                 attrs.insert(0, ('name', ntype.target_fundamental))
339             elif ntype.target_foreign:
340                 attrs.insert(0, ('foreign', '1'))
341             self.write_tag('type', attrs)
342
343     def _append_registered(self, node, attrs):
344         assert isinstance(node, ast.Registered)
345         if node.get_type:
346             attrs.extend([('glib:type-name', node.gtype_name),
347                           ('glib:get-type', node.get_type)])
348
349     def _write_enum(self, enum):
350         attrs = [('name', enum.name)]
351         self._append_version(enum, attrs)
352         self._append_node_generic(enum, attrs)
353         self._append_registered(enum, attrs)
354         attrs.append(('c:type', enum.ctype))
355         if enum.error_domain:
356             attrs.append(('glib:error-domain', enum.error_domain))
357
358         with self.tagcontext('enumeration', attrs):
359             self._write_generic(enum)
360             for member in enum.members:
361                 self._write_member(member)
362             for method in sorted(enum.static_methods):
363                 self._write_static_method(method)
364
365     def _write_bitfield(self, bitfield):
366         attrs = [('name', bitfield.name)]
367         self._append_version(bitfield, attrs)
368         self._append_node_generic(bitfield, attrs)
369         self._append_registered(bitfield, attrs)
370         attrs.append(('c:type', bitfield.ctype))
371         with self.tagcontext('bitfield', attrs):
372             self._write_generic(bitfield)
373             for member in bitfield.members:
374                 self._write_member(member)
375             for method in sorted(bitfield.static_methods):
376                 self._write_static_method(method)
377
378     def _write_member(self, member):
379         attrs = [('name', member.name),
380                  ('value', str(member.value)),
381                  ('c:identifier', member.symbol)]
382         if member.nick is not None:
383             attrs.append(('glib:nick', member.nick))
384         with self.tagcontext('member', attrs):
385             self._write_generic(member)
386
387     def _write_constant(self, constant):
388         attrs = [('name', constant.name),
389                  ('value', constant.value),
390                  ('c:type', constant.ctype)]
391         self._append_version(constant, attrs)
392         self._append_node_generic(constant, attrs)
393         with self.tagcontext('constant', attrs):
394             self._write_generic(constant)
395             self._write_type(constant.value_type)
396
397     def _write_class(self, node):
398         attrs = [('name', node.name),
399                  ('c:symbol-prefix', node.c_symbol_prefix),
400                  ('c:type', node.ctype)]
401         self._append_version(node, attrs)
402         self._append_node_generic(node, attrs)
403         if isinstance(node, ast.Class):
404             tag_name = 'class'
405             if node.parent_type is not None:
406                 attrs.append(('parent',
407                               self._type_to_name(node.parent_type)))
408             if node.is_abstract:
409                 attrs.append(('abstract', '1'))
410         else:
411             assert isinstance(node, ast.Interface)
412             tag_name = 'interface'
413         attrs.append(('glib:type-name', node.gtype_name))
414         if node.get_type is not None:
415             attrs.append(('glib:get-type', node.get_type))
416         if node.glib_type_struct is not None:
417             attrs.append(('glib:type-struct',
418                           self._type_to_name(node.glib_type_struct)))
419         if isinstance(node, ast.Class):
420             if node.fundamental:
421                 attrs.append(('glib:fundamental', '1'))
422             if node.ref_func:
423                 attrs.append(('glib:ref-func', node.ref_func))
424             if node.unref_func:
425                 attrs.append(('glib:unref-func', node.unref_func))
426             if node.set_value_func:
427                 attrs.append(('glib:set-value-func', node.set_value_func))
428             if node.get_value_func:
429                 attrs.append(('glib:get-value-func', node.get_value_func))
430         with self.tagcontext(tag_name, attrs):
431             self._write_generic(node)
432             if isinstance(node, ast.Class):
433                 for iface in sorted(node.interfaces):
434                     self.write_tag('implements',
435                                    [('name', self._type_to_name(iface))])
436             if isinstance(node, ast.Interface):
437                 for iface in sorted(node.prerequisites):
438                     self.write_tag('prerequisite',
439                                    [('name', self._type_to_name(iface))])
440             if isinstance(node, ast.Class):
441                 for method in sorted(node.constructors):
442                     self._write_constructor(method)
443             for method in sorted(node.static_methods):
444                 self._write_static_method(method)
445             for vfunc in sorted(node.virtual_methods):
446                 self._write_vfunc(vfunc)
447             for method in sorted(node.methods):
448                 self._write_method(method)
449             for prop in sorted(node.properties):
450                 self._write_property(prop)
451             for field in node.fields:
452                 self._write_field(field, node)
453             for signal in sorted(node.signals):
454                 self._write_signal(signal)
455
456     def _write_boxed(self, boxed):
457         attrs = [('glib:name', boxed.name)]
458         if boxed.c_symbol_prefix is not None:
459             attrs.append(('c:symbol-prefix', boxed.c_symbol_prefix))
460         self._append_registered(boxed, attrs)
461         with self.tagcontext('glib:boxed', attrs):
462             self._write_generic(boxed)
463             for method in sorted(boxed.constructors):
464                 self._write_constructor(method)
465             for method in sorted(boxed.methods):
466                 self._write_method(method)
467             for method in sorted(boxed.static_methods):
468                 self._write_static_method(method)
469
470     def _write_property(self, prop):
471         attrs = [('name', prop.name)]
472         self._append_version(prop, attrs)
473         self._append_node_generic(prop, attrs)
474         # Properties are assumed to be readable (see also generate.c)
475         if not prop.readable:
476             attrs.append(('readable', '0'))
477         if prop.writable:
478             attrs.append(('writable', '1'))
479         if prop.construct:
480             attrs.append(('construct', '1'))
481         if prop.construct_only:
482             attrs.append(('construct-only', '1'))
483         if prop.transfer:
484             attrs.append(('transfer-ownership', prop.transfer))
485         with self.tagcontext('property', attrs):
486             self._write_generic(prop)
487             self._write_type(prop.type)
488
489     def _write_vfunc(self, vf):
490         attrs = []
491         if vf.invoker:
492             attrs.append(('invoker', vf.invoker))
493         self._write_callable(vf, 'virtual-method', attrs)
494
495     def _write_callback(self, callback):
496         attrs = []
497         if callback.ctype != callback.name:
498             attrs.append(('c:type', callback.ctype))
499         self._write_callable(callback, 'callback', attrs)
500
501     def _write_record(self, record, extra_attrs=[]):
502         is_gtype_struct = False
503         attrs = list(extra_attrs)
504         if record.name is not None:
505             attrs.append(('name', record.name))
506         if record.ctype is not None:  # the record might be anonymous
507             attrs.append(('c:type', record.ctype))
508         if record.disguised:
509             attrs.append(('disguised', '1'))
510         if record.foreign:
511             attrs.append(('foreign', '1'))
512         if record.is_gtype_struct_for is not None:
513             is_gtype_struct = True
514             attrs.append(('glib:is-gtype-struct-for',
515                           self._type_to_name(record.is_gtype_struct_for)))
516         self._append_version(record, attrs)
517         self._append_node_generic(record, attrs)
518         self._append_registered(record, attrs)
519         if record.c_symbol_prefix:
520             attrs.append(('c:symbol-prefix', record.c_symbol_prefix))
521         with self.tagcontext('record', attrs):
522             self._write_generic(record)
523             if record.fields:
524                 for field in record.fields:
525                     self._write_field(field, record, is_gtype_struct)
526             for method in sorted(record.constructors):
527                 self._write_constructor(method)
528             for method in sorted(record.methods):
529                 self._write_method(method)
530             for method in sorted(record.static_methods):
531                 self._write_static_method(method)
532
533     def _write_union(self, union):
534         attrs = []
535         if union.name is not None:
536             attrs.append(('name', union.name))
537         if union.ctype is not None:  # the union might be anonymous
538             attrs.append(('c:type', union.ctype))
539         self._append_version(union, attrs)
540         self._append_node_generic(union, attrs)
541         self._append_registered(union, attrs)
542         if union.c_symbol_prefix:
543             attrs.append(('c:symbol-prefix', union.c_symbol_prefix))
544         with self.tagcontext('union', attrs):
545             self._write_generic(union)
546             if union.fields:
547                 for field in union.fields:
548                     self._write_field(field, union)
549             for method in sorted(union.constructors):
550                 self._write_constructor(method)
551             for method in sorted(union.methods):
552                 self._write_method(method)
553             for method in sorted(union.static_methods):
554                 self._write_static_method(method)
555
556     def _write_field(self, field, parent, is_gtype_struct=False):
557         if field.anonymous_node:
558             if isinstance(field.anonymous_node, ast.Callback):
559                 attrs = [('name', field.name)]
560                 self._append_node_generic(field, attrs)
561                 with self.tagcontext('field', attrs):
562                     self._write_callback(field.anonymous_node)
563             elif isinstance(field.anonymous_node, ast.Record):
564                 self._write_record(field.anonymous_node)
565             elif isinstance(field.anonymous_node, ast.Union):
566                 self._write_union(field.anonymous_node)
567             else:
568                 raise AssertionError("Unknown field anonymous: %r" % (field.anonymous_node, ))
569         else:
570             attrs = [('name', field.name)]
571             self._append_node_generic(field, attrs)
572             # Fields are assumed to be read-only
573             # (see also girparser.c and generate.c)
574             if not field.readable:
575                 attrs.append(('readable', '0'))
576             if field.writable:
577                 attrs.append(('writable', '1'))
578             if field.bits:
579                 attrs.append(('bits', str(field.bits)))
580             if field.private:
581                 attrs.append(('private', '1'))
582             with self.tagcontext('field', attrs):
583                 self._write_generic(field)
584                 self._write_type(field.type, parent=parent)
585
586     def _write_signal(self, signal):
587         attrs = [('name', signal.name)]
588         if signal.when:
589             attrs.append(('when', signal.when))
590         if signal.no_recurse:
591             attrs.append(('no-recurse', '1'))
592         if signal.detailed:
593             attrs.append(('detailed', '1'))
594         if signal.action:
595             attrs.append(('action', '1'))
596         if signal.no_hooks:
597             attrs.append(('no-hooks', '1'))
598
599         self._append_version(signal, attrs)
600         self._append_node_generic(signal, attrs)
601         with self.tagcontext('glib:signal', attrs):
602             self._write_generic(signal)
603             self._write_return_type(signal.retval)
604             self._write_parameters(signal)