Relicense the giscanner library under LGPLv2+. This has been approved by
[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 #
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 2 of the License, or (at your option) any later version.
9 #
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # Lesser General Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; if not, write to the
17 # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 # Boston, MA 02111-1307, USA.
19 #
20
21 from __future__ import with_statement
22
23 import os
24 from ctypes.util import find_library
25
26 from .ast import (Callback, Class, Constant, Enum, Function, Interface, Member,
27                   Array, Struct, Alias, Union, List, Map, Varargs)
28 from .glibast import (GLibBoxed, GLibEnum, GLibEnumMember,
29                       GLibFlags, GLibObject, GLibInterface)
30 from .xmlwriter import XMLWriter
31
32
33 class GIRWriter(XMLWriter):
34
35     def __init__(self, namespace, shlibs, includes):
36         super(GIRWriter, self).__init__()
37         self._write_repository(namespace, shlibs, includes)
38
39     def _write_repository(self, namespace, shlibs, includes=set()):
40         attrs = [
41             ('version', '1.0'),
42             ('xmlns', 'http://www.gtk.org/introspection/core/1.0'),
43             ('xmlns:c', 'http://www.gtk.org/introspection/c/1.0'),
44             ('xmlns:glib', 'http://www.gtk.org/introspection/glib/1.0'),
45             ]
46         with self.tagcontext('repository', attrs):
47             for include in sorted(includes):
48                 self._write_include(include)
49             self._write_namespace(namespace, shlibs)
50
51     def _write_include(self, include):
52         attrs = [('name', include.name), ('version', include.version)]
53         self.write_tag('include', attrs)
54
55     def _write_namespace(self, namespace, shlibs):
56         libraries = []
57         for l in shlibs:
58             found_libname = find_library(l)
59             if not found_libname:
60                 found_libname = l
61             libraries.append(os.path.basename(found_libname))
62
63         attrs = [('name', namespace.name),
64                  ('version', namespace.version),
65                  ('shared-library', ','.join(libraries))]
66         with self.tagcontext('namespace', attrs):
67             for node in namespace.nodes:
68                 self._write_node(node)
69
70     def _write_node(self, node):
71         if isinstance(node, Function):
72             self._write_function(node)
73         elif isinstance(node, Enum):
74             self._write_enum(node)
75         elif isinstance(node, (Class, Interface)):
76             self._write_class(node)
77         elif isinstance(node, Callback):
78             self._write_callback(node)
79         elif isinstance(node, Struct):
80             self._write_record(node)
81         elif isinstance(node, Union):
82             self._write_union(node)
83         elif isinstance(node, GLibBoxed):
84             self._write_boxed(node)
85         elif isinstance(node, Member):
86             # FIXME: atk_misc_instance singleton
87             pass
88         elif isinstance(node, Alias):
89             self._write_alias(node)
90         elif isinstance(node, Constant):
91             self._write_constant(node)
92         else:
93             print 'WRITER: Unhandled node', node
94
95     def _append_deprecated(self, node, attrs):
96         if node.deprecated:
97             attrs.append(('deprecated', node.deprecated))
98             if node.deprecated_version:
99                 attrs.append(('deprecated-version',
100                               node.deprecated_version))
101
102     def _append_throws(self, func, attrs):
103         if func.throws:
104             attrs.append(('throws', '1'))
105
106     def _write_alias(self, alias):
107         attrs = [('name', alias.name), ('target', alias.target)]
108         if alias.ctype is not None:
109             attrs.append(('c:type', alias.ctype))
110         self.write_tag('alias', attrs)
111
112     def _write_function(self, func, tag_name='function'):
113         attrs = [('name', func.name),
114                  ('c:identifier', func.symbol)]
115         self._append_deprecated(func, attrs)
116         self._append_throws(func, attrs)
117         with self.tagcontext(tag_name, attrs):
118             self._write_return_type(func.retval)
119             self._write_parameters(func.parameters)
120
121     def _write_method(self, method):
122         self._write_function(method, tag_name='method')
123
124     def _write_constructor(self, method):
125         self._write_function(method, tag_name='constructor')
126
127     def _write_return_type(self, return_):
128         if not return_:
129             return
130
131         assert return_.transfer is not None, return_
132
133         attrs = []
134         attrs.append(('transfer-ownership', return_.transfer))
135         with self.tagcontext('return-value', attrs):
136             self._write_type(return_.type)
137
138     def _write_parameters(self, parameters):
139         if not parameters:
140             return
141         with self.tagcontext('parameters'):
142             for parameter in parameters:
143                 self._write_parameter(parameter)
144
145     def _write_parameter(self, parameter):
146         assert parameter.transfer is not None, parameter
147
148         attrs = []
149         if parameter.name is not None:
150             attrs.append(('name', parameter.name))
151         if parameter.direction != 'in':
152             attrs.append(('direction', parameter.direction))
153         attrs.append(('transfer-ownership',
154                      parameter.transfer))
155         if parameter.allow_none:
156             attrs.append(('allow-none', '1'))
157         with self.tagcontext('parameter', attrs):
158             self._write_type(parameter.type)
159
160     def _type_to_string(self, ntype):
161         if isinstance(ntype, basestring):
162             return ntype
163         return ntype.name
164
165     def _write_type(self, ntype, relation=None):
166         if isinstance(ntype, basestring):
167             typename = ntype
168             type_cname = None
169         else:
170             typename = ntype.name
171             type_cname = ntype.ctype
172         if isinstance(ntype, Varargs):
173             with self.tagcontext('varargs', []):
174                 pass
175             return
176         if isinstance(ntype, Array):
177             attrs = []
178             if not ntype.zeroterminated:
179                 attrs.append(('zero-terminated', '0'))
180             if ntype.length_param_index >= 0:
181                 attrs.append(('length', '%d' % (ntype.length_param_index, )))
182             attrs.append(('c:type', ntype.ctype))
183             if ntype.size is not None:
184                 attrs.append(('fixed-size', ntype.size))
185             with self.tagcontext('array', attrs):
186                 self._write_type(ntype.element_type)
187             return
188         attrs = [('name', self._type_to_string(ntype))]
189         # FIXME: figure out if type references a basic type
190         #        or a boxed/class/interface etc. and skip
191         #        writing the ctype if the latter.
192         if type_cname is not None:
193             attrs.append(('c:type', type_cname))
194         if isinstance(ntype, List) and ntype.element_type:
195             with self.tagcontext('type', attrs):
196                 self._write_type(ntype.element_type)
197             return
198         if isinstance(ntype, Map) and ntype.key_type:
199             with self.tagcontext('type', attrs):
200                 self._write_type(ntype.key_type)
201                 self._write_type(ntype.value_type)
202             return
203         # Not a special type, just write it out
204         self.write_tag('type', attrs)
205
206     def _write_enum(self, enum):
207         attrs = [('name', enum.name)]
208         self._append_deprecated(enum, attrs)
209         if isinstance(enum, GLibFlags):
210             tag_name = 'bitfield'
211         else:
212             tag_name = 'enumeration'
213         if isinstance(enum, GLibEnum) or isinstance(enum, GLibFlags):
214             attrs.extend([('glib:type-name', enum.type_name),
215                           ('glib:get-type', enum.get_type),
216                           ('c:type', enum.ctype)])
217         else:
218             attrs.append(('c:type', enum.symbol))
219         with self.tagcontext(tag_name, attrs):
220             for member in enum.members:
221                 self._write_member(member)
222
223     def _write_member(self, member):
224         attrs = [('name', member.name),
225                  ('value', str(member.value)),
226                  ('c:identifier', member.symbol)]
227         if isinstance(member, GLibEnumMember):
228             attrs.append(('glib:nick', member.nick))
229         self.write_tag('member', attrs)
230
231     def _write_constant(self, constant):
232         attrs = [('name', constant.name),
233                  ('value', str(constant.value))]
234         with self.tagcontext('constant', attrs):
235             self._write_type(constant.type)
236
237     def _write_class(self, node):
238         attrs = [('name', node.name),
239                  ('c:type', node.ctype)]
240         self._append_deprecated(node, attrs)
241         if isinstance(node, Class):
242             tag_name = 'class'
243             if node.parent is not None:
244                 attrs.append(('parent', node.parent))
245             if node.is_abstract:
246                 attrs.append(('abstract', '1'))
247         else:
248             tag_name = 'interface'
249         if isinstance(node, (GLibObject, GLibInterface)):
250             attrs.append(('glib:type-name', node.type_name))
251             if node.get_type:
252                 attrs.append(('glib:get-type', node.get_type))
253         with self.tagcontext(tag_name, attrs):
254             if isinstance(node, GLibObject):
255                 for iface in node.interfaces:
256                     self.write_tag('implements', [('name', iface)])
257             if isinstance(node, Class):
258                 for method in node.constructors:
259                     self._write_constructor(method)
260             for method in node.methods:
261                 self._write_method(method)
262             for prop in node.properties:
263                 self._write_property(prop)
264             for field in node.fields:
265                 self._write_field(field)
266             for signal in node.signals:
267                 self._write_signal(signal)
268
269     def _write_boxed(self, boxed):
270         attrs = [('c:type', boxed.ctype),
271                  ('glib:name', boxed.name)]
272         attrs.extend(self._boxed_attrs(boxed))
273         with self.tagcontext('glib:boxed', attrs):
274             self._write_boxed_ctors_methods(boxed)
275
276     def _write_property(self, prop):
277         attrs = [('name', prop.name)]
278         # Properties are assumed to be readable (see also generate.c)
279         if not prop.readable:
280             attrs.append(('readable', '0'))
281         if prop.writable:
282             attrs.append(('writable', '1'))
283         if prop.construct:
284             attrs.append(('construct', '1'))
285         if prop.construct_only:
286             attrs.append(('construct-only', '1'))
287         with self.tagcontext('property', attrs):
288             self._write_type(prop.type)
289
290     def _write_callback(self, callback):
291         # FIXME: reuse _write_function
292         attrs = [('name', callback.name), ('c:type', callback.ctype)]
293         self._append_deprecated(callback, attrs)
294         with self.tagcontext('callback', attrs):
295             self._write_return_type(callback.retval)
296             self._write_parameters(callback.parameters)
297
298     def _boxed_attrs(self, boxed):
299         return [('glib:type-name', boxed.type_name),
300                 ('glib:get-type', boxed.get_type)]
301
302     def _write_boxed_ctors_methods(self, boxed):
303         for method in boxed.constructors:
304             self._write_constructor(method)
305         for method in boxed.methods:
306             self._write_method(method)
307
308     def _write_record(self, record):
309         attrs = [('name', record.name),
310                  ('c:type', record.symbol)]
311         self._append_deprecated(record, attrs)
312         if isinstance(record, GLibBoxed):
313             attrs.extend(self._boxed_attrs(record))
314         with self.tagcontext('record', attrs):
315             if record.fields:
316                 for field in record.fields:
317                     self._write_field(field)
318             if isinstance(record, GLibBoxed):
319                 self._write_boxed_ctors_methods(record)
320
321     def _write_union(self, union):
322         attrs = [('name', union.name),
323                  ('c:type', union.symbol)]
324         self._append_deprecated(union, attrs)
325         if isinstance(union, GLibBoxed):
326             attrs.extend(self._boxed_attrs(union))
327         with self.tagcontext('union', attrs):
328             if union.fields:
329                 for field in union.fields:
330                     self._write_field(field)
331             if isinstance(union, GLibBoxed):
332                 self._write_boxed_ctors_methods(union)
333
334     def _write_field(self, field):
335         if isinstance(field, Function):
336             self._write_method(field)
337             return
338
339         if isinstance(field, Callback):
340             self._write_callback(field)
341             return
342
343         attrs = [('name', field.name)]
344         # Fields are assumed to be read-only
345         # (see also girparser.c and generate.c)
346         if not field.readable:
347             attrs.append(('readable', '0'))
348         if field.writable:
349             attrs.append(('writable', '1'))
350         if field.bits:
351             attrs.append(('bits', str(field.bits)))
352         with self.tagcontext('field', attrs):
353             self._write_type(field.type)
354
355     def _write_signal(self, signal):
356         attrs = [('name', signal.name)]
357         with self.tagcontext('glib:signal', attrs):
358             self._write_return_type(signal.retval)
359             self._write_parameters(signal.parameters)