Small fix to handle @attributes: .... (.....) as a parameter to a function
[gnome.gobject-introspection] / giscanner / annotationparser.py
1 # -*- Mode: Python -*-
2 # GObject-Introspection - a framework for introspecting GObject libraries
3 # Copyright (C) 2008  Johan Dahlin
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program 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
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 # 02110-1301, USA.
19 #
20
21 # AnnotationParser - parses gtk-doc annotations
22
23 import re
24 import sys
25
26 from .ast import (Array, Bitfield, Callback, Class, Enum, Field, Function,
27                   Interface, List, Map, Parameter, Record, Return, Type, Union,
28                   Varargs,
29                   default_array_types,
30                   BASIC_GIR_TYPES,
31                   PARAM_DIRECTION_INOUT,
32                   PARAM_DIRECTION_IN,
33                   PARAM_DIRECTION_OUT,
34                   PARAM_TRANSFER_NONE,
35                   PARAM_TRANSFER_CONTAINER,
36                   PARAM_TRANSFER_FULL,
37                   TYPE_ANY, TYPE_NONE)
38 from .odict import odict
39 from .glibast import GLibBoxed
40
41 # All gtk-doc comments needs to start with this:
42 _COMMENT_HEADER = '*\n '
43
44 # Tags - annotations applyed to comment blocks
45 TAG_VFUNC = 'virtual'
46 TAG_SINCE = 'since'
47 TAG_DEPRECATED = 'deprecated'
48 TAG_RETURNS = 'returns'
49 TAG_RETURNS_ALT = 'return value'
50 TAG_ATTRIBUTES = 'attributes'
51 TAG_RENAME_TO = 'rename to'
52
53 # Options - annotations for parameters and return values
54 OPT_ALLOW_NONE = 'allow-none'
55 OPT_ARRAY = 'array'
56 OPT_ELEMENT_TYPE = 'element-type'
57 OPT_IN = 'in'
58 OPT_INOUT = 'inout'
59 OPT_INOUT_ALT = 'in-out'
60 OPT_OUT = 'out'
61 OPT_SCOPE = 'scope'
62 OPT_TRANSFER = 'transfer'
63 OPT_TYPE = 'type'
64 OPT_CLOSURE = 'closure'
65 OPT_DESTROY = 'destroy'
66 OPT_SKIP = 'skip'
67
68 # Specific option values
69 OPT_VAL_BITFIELD = 'bitfield'
70
71 # Array options - array specific annotations
72 OPT_ARRAY_FIXED_SIZE = 'fixed-size'
73 OPT_ARRAY_LENGTH = 'length'
74 OPT_ARRAY_ZERO_TERMINATED = 'zero-terminated'
75
76 OPT_SCOPE_ASYNC = 'async'
77 OPT_SCOPE_CALL = 'call'
78 OPT_SCOPE_NOTIFIED = 'notified'
79
80 class InvalidAnnotationError(Exception):
81     pass
82
83
84 class DocBlock(object):
85
86     def __init__(self, name, options):
87         self.name = name
88         self.options = options
89         self.value = None
90         self.tags = odict()
91         self.comment = None
92
93     def __repr__(self):
94         return '<DocBlock %r %r>' % (self.name, self.options)
95
96     def get(self, name):
97         if name == TAG_RETURNS:
98             value = self.tags.get(name)
99             if value is None:
100                 return self.tags.get(TAG_RETURNS_ALT)
101             else:
102                 return value
103         else:
104             return self.tags.get(name)
105
106
107 class DocTag(object):
108
109     def __init__(self, name):
110         self.name = name
111         self.options = {}
112         self.comment = None
113
114     def __repr__(self):
115         return '<DocTag %r %r>' % (self.name, self.options)
116
117 class Option(object):
118
119     def __init__(self, option):
120         self._array = []
121         self._dict = {}
122         for p in option.split(' '):
123             if '=' in p:
124                 name, value = p.split('=', 1)
125             else:
126                 name = p
127                 value = None
128             self._dict[name] = value
129             if value is None:
130                 self._array.append(name)
131             else:
132                 self._array.append((name, value))
133
134     def __repr__(self):
135         return '<Option %r>' % (self._array, )
136
137     def one(self):
138         assert len(self._array) == 1
139         return self._array[0]
140
141     def flat(self):
142         return self._array
143
144     def all(self):
145         return self._dict
146
147
148 class AnnotationParser(object):
149
150     def __init__(self, namespace, source_scanner, transformer):
151         self._blocks = {}
152         self._namespace = namespace
153         self._transformer = transformer
154         for comment in source_scanner.get_comments():
155             self._parse_comment(comment)
156
157     def parse(self):
158         aa = AnnotationApplier(self._blocks, self._transformer)
159         aa.parse(self._namespace)
160
161     def _parse_comment(self, comment):
162         # We're looking for gtk-doc comments here, they look like this:
163         # /**
164         #   * symbol:
165         #
166         # Or, alternatively, with options:
167         # /**
168         #   * symbol: (name value) ...
169         #
170         # symbol is currently one of:
171         #  - function: gtk_widget_show
172         #  - signal:   GtkWidget::destroy
173         #  - property: GtkWidget:visible
174         #
175         comment = comment.lstrip()
176         if not comment.startswith(_COMMENT_HEADER):
177             return
178         comment = comment[len(_COMMENT_HEADER):]
179         comment = comment.strip()
180         if not comment.startswith('* '):
181             return
182         comment = comment[2:]
183
184         pos = comment.find('\n ')
185         if pos == -1:
186             return
187         block_header = comment[:pos]
188         block_header = block_header.strip()
189         cpos = block_header.find(': ')
190         if cpos:
191             block_name = block_header[:cpos]
192             block_options, rest = self._parse_options(block_header[cpos+2:])
193             if rest:
194                 return
195         else:
196             block_name, block_options = block_header, {}
197         block = DocBlock(block_name, block_options)
198         comment_lines = []
199         for line in comment[pos+1:].split('\n'):
200             line = line.lstrip()
201             line = line[2:].strip() # Skip ' *'
202             if not line:
203                 continue
204             if line.startswith('@'):
205                 line = line[1:]
206             elif not ': ' in line:
207                 comment_lines.append(line)
208                 continue
209             tag_name, value = self._split_tag_namevalue(line)
210             canon_name = tag_name.lower()
211             if canon_name in block.tags:
212                 print >> sys.stderr, (
213                     "Symbol %s has multiple definition of tag %r" % (
214                     block_name, canon_name, ))
215             block.tags[canon_name] = self._create_tag(canon_name, value)
216         block.comment = '\n'.join(comment_lines)
217         self._blocks[block.name] = block
218
219     def _split_tag_namevalue(self, raw):
220         """Split a line into tag name and value"""
221         parts = raw.split(': ', 1)
222         if len(parts) == 1:
223             tag_name = parts[0]
224             value = ''
225             if tag_name.endswith(':'):
226                 tag_name = tag_name[:-1]
227         else:
228             tag_name, value = parts
229         return (tag_name, value)
230
231     def _create_tag(self, tag_name, value):
232         # Tag: bar
233         # Tag: bar opt1 opt2
234         tag = DocTag(tag_name)
235         tag.value = value
236         options, rest = self._parse_options(tag.value)
237         tag.options = options
238         tag.comment = rest
239         return tag
240
241     def _parse_options(self, value):
242         # (foo)
243         # (bar opt1 opt2...)
244         opened = -1
245         options = {}
246         last = None
247         for i, c in enumerate(value):
248             if c == '(' and opened == -1:
249                 opened = i+1
250             if c == ')' and opened != -1:
251                 segment = value[opened:i]
252                 parts = segment.split(' ', 1)
253                 if len(parts) == 2:
254                     name, option = parts
255                 elif len(parts) == 1:
256                     name = parts[0]
257                     option = None
258                 else:
259                     raise AssertionError
260                 if option is not None:
261                     option = Option(option)
262                 options[name] = option
263                 last = i + 2
264                 opened = -1
265
266         if last is not None:
267             rest = value[last:].strip()
268         else:
269             rest = None
270         return options, rest
271
272
273 class AnnotationApplier(object):
274
275     def __init__(self, blocks, transformer):
276         self._blocks = blocks
277         self._transformer = transformer
278
279     def _get_tag(self, block, tag_name):
280         if block is None:
281             return None
282
283         return block.get(tag_name)
284
285     def parse(self, namespace):
286         self._namespace = namespace
287         for node in namespace.nodes[:]:
288             self._parse_node(node)
289         del self._namespace
290
291     # Boring parsing boilerplate.
292
293     def _parse_node(self, node):
294         if isinstance(node, Function):
295             self._parse_function(node)
296         elif isinstance(node, Enum):
297             self._parse_enum(node)
298         elif isinstance(node, Bitfield):
299             self._parse_bitfield(node)
300         elif isinstance(node, Class):
301             self._parse_class(node)
302         elif isinstance(node, Interface):
303             self._parse_interface(node)
304         elif isinstance(node, Callback):
305             self._parse_callback(node)
306         elif isinstance(node, Record):
307             self._parse_record(node)
308         elif isinstance(node, Union):
309             self._parse_union(node)
310         elif isinstance(node, GLibBoxed):
311             self._parse_boxed(node)
312
313     def _parse_class(self, class_):
314         block = self._blocks.get(class_.type_name)
315         self._parse_node_common(class_, block)
316         self._parse_constructors(class_.constructors)
317         self._parse_methods(class_, class_.methods)
318         self._parse_vfuncs(class_, class_.virtual_methods)
319         self._parse_methods(class_, class_.static_methods)
320         self._parse_properties(class_, class_.properties)
321         self._parse_signals(class_, class_.signals)
322         self._parse_fields(class_, class_.fields)
323         if block:
324             class_.doc = block.comment
325
326     def _parse_interface(self, interface):
327         block = self._blocks.get(interface.type_name)
328         self._parse_node_common(interface, block)
329         self._parse_methods(interface, interface.methods)
330         self._parse_vfuncs(interface, interface.virtual_methods)
331         self._parse_properties(interface, interface.properties)
332         self._parse_signals(interface, interface.signals)
333         self._parse_fields(interface, interface.fields)
334         if block:
335             interface.doc = block.comment
336
337     def _parse_record(self, record):
338         block = self._blocks.get(record.symbol)
339         self._parse_node_common(record, block)
340         self._parse_constructors(record.constructors)
341         self._parse_methods(record, record.methods)
342         self._parse_fields(record, record.fields, block)
343         if block:
344             record.doc = block.comment
345
346     def _parse_boxed(self, boxed):
347         block = self._blocks.get(boxed.name)
348         self._parse_node_common(boxed, block)
349         self._parse_constructors(boxed.constructors)
350         self._parse_methods(boxed, boxed.methods)
351         if block:
352             boxed.doc = block.comment
353
354     def _parse_union(self, union):
355         block = self._blocks.get(union.name)
356         self._parse_node_common(union, block)
357         self._parse_fields(union, union.fields, block)
358         self._parse_constructors(union.constructors)
359         self._parse_methods(union, union.methods)
360         if block:
361             union.doc = block.comment
362
363     def _parse_enum(self, enum):
364         block = self._blocks.get(enum.symbol)
365         self._parse_node_common(enum, block)
366         if block:
367             enum.doc = block.comment
368             type_opt = block.options.get(OPT_TYPE)
369             if type_opt and type_opt.one() == OPT_VAL_BITFIELD:
370                 # This is hack, but hey, it works :-)
371                 enum.__class__ = Bitfield
372
373     def _parse_bitfield(self, bitfield):
374         block = self._blocks.get(bitfield.symbol)
375         self._parse_node_common(bitfield, block)
376         if block:
377             bitfield.doc = block.comment
378
379     def _parse_constructors(self, constructors):
380         for ctor in constructors:
381             self._parse_function(ctor)
382
383     def _parse_fields(self, parent, fields, block=None):
384         for field in fields:
385             self._parse_field(parent, field, block)
386
387     def _parse_properties(self, parent, properties):
388         for prop in properties:
389             self._parse_property(parent, prop)
390
391     def _parse_methods(self, parent, methods):
392         for method in methods:
393             self._parse_method(parent, method)
394
395     def _parse_vfuncs(self, parent, vfuncs):
396         for vfunc in vfuncs:
397             self._parse_vfunc(parent, vfunc)
398
399     def _parse_signals(self, parent, signals):
400         for signal in signals:
401             self._parse_signal(parent, signal)
402
403     def _parse_property(self, parent, prop):
404         block = self._blocks.get('%s:%s' % (parent.type_name, prop.name))
405         self._parse_node_common(prop, block)
406         if block:
407             prop.doc = block.comment
408
409     def _parse_callback(self, callback):
410         block = self._blocks.get(callback.ctype)
411         self._parse_node_common(callback, block)
412         self._parse_params(callback, callback.parameters, block)
413         self._parse_return(callback, callback.retval, block)
414         if block:
415             callback.doc = block.comment
416
417     def _parse_callable(self, callable, block):
418         self._parse_node_common(callable, block)
419         for i, param in enumerate(callable.parameters):
420             if (param.type.ctype != 'GDestroyNotify' and
421                 param.type.name != 'GLib.DestroyNotify'):
422                 continue
423             if i < 2:
424                 break
425             callback_param = callable.parameters[i-2]
426             if callback_param.closure_index != -1:
427                 callback_param.scope = OPT_SCOPE_NOTIFIED
428                 callback_param.transfer = PARAM_TRANSFER_NONE
429
430         self._parse_params(callable, callable.parameters, block)
431         self._parse_return(callable, callable.retval, block)
432         if block:
433             callable.doc = block.comment
434
435     def _parse_function(self, func):
436         block = self._blocks.get(func.symbol)
437         self._parse_callable(func, block)
438         self._parse_rename_to_func(func, block)
439
440     def _parse_signal(self, parent, signal):
441         block = self._blocks.get('%s::%s' % (parent.type_name, signal.name))
442         self._parse_node_common(signal, block)
443         # We're only attempting to name the signal parameters if
444         # the number of parameter tags (@foo) is the same or greater
445         # than the number of signal parameters
446         if block and len(block.tags) > len(signal.parameters):
447             names = block.tags.items()
448         else:
449             names = []
450         for i, param in enumerate(signal.parameters):
451             if names:
452                 name, tag = names[i+1]
453                 param.name = name
454                 options = getattr(tag, 'options', {})
455                 param_type = options.get(OPT_TYPE)
456                 if param_type:
457                     param.type = self._resolve(param_type.one(), param.type)
458             else:
459                 tag = None
460             self._parse_param(signal, param, tag)
461         self._parse_return(signal, signal.retval, block)
462         if block:
463             signal.doc = block.comment
464
465     def _parse_method(self, parent, meth):
466         block = self._blocks.get(meth.symbol)
467         self._parse_function(meth)
468         virtual = self._get_tag(block, TAG_VFUNC)
469         if virtual:
470             invoker_name = virtual.value
471             matched = False
472             for vfunc in parent.virtual_methods:
473                 if vfunc.name == invoker_name:
474                     matched = True
475                     vfunc.invoker = meth.name
476                     break
477             if not matched:
478                 print "warning: unmatched virtual invoker %r for method %r" % \
479                     (invoker_name, meth.symbol)
480
481     def _parse_vfunc(self, parent, vfunc):
482         key = '%s::%s' % (parent.type_name, vfunc.name)
483         self._parse_callable(vfunc, self._blocks.get(key))
484
485     def _parse_field(self, parent, field, block=None):
486         if isinstance(field, Callback):
487             self._parse_callback(field)
488         else:
489             if not block:
490                 return
491             tag = block.get(field.name)
492             if not tag:
493                 return
494             t = tag.options.get('type')
495             if not t:
496                 return
497             field.type.name = self._transformer.resolve_type_name(t.one())
498
499     def _parse_params(self, parent, params, block):
500         for param in params:
501             tag = self._get_tag(block, param.name)
502             self._parse_param(parent, param, tag)
503
504     def _parse_return(self, parent, return_, block):
505         tag = self._get_tag(block, TAG_RETURNS)
506         self._parse_param_ret_common(parent, return_, tag)
507
508     def _parse_param(self, parent, param, tag):
509         options = getattr(tag, 'options', {})
510         if isinstance(parent, Function):
511             scope = options.get(OPT_SCOPE)
512             if scope:
513                 param.scope = scope.one()
514                 param.transfer = PARAM_TRANSFER_NONE
515             elif (param.type.ctype == 'GAsyncReadyCallback' or
516                   param.type.name == 'Gio.AsyncReadyCallback'):
517                 param.scope = OPT_SCOPE_ASYNC
518                 param.transfer = PARAM_TRANSFER_NONE
519
520             destroy = options.get(OPT_DESTROY)
521             if destroy:
522                 param.destroy_index = parent.get_parameter_index(destroy.one())
523                 self._fixup_param_destroy(parent, param)
524             closure = options.get(OPT_CLOSURE)
525             if closure:
526                 param.closure_index = parent.get_parameter_index(closure.one())
527                 self._fixup_param_closure(parent, param)
528         if isinstance(parent, Callback):
529             if OPT_CLOSURE in options:
530                 param.closure_index = parent.get_parameter_index(param.name)
531                 self._fixup_param_closure(parent, param)
532
533         self._parse_param_ret_common(parent, param, tag)
534
535     def _fixup_param_destroy(self, parent, param):
536         for p in parent.parameters:
537             if p is not param and p.destroy_index == param.destroy_index:
538                 p.destroy_index = None
539
540     def _fixup_param_closure(self, parent, param):
541         for p in parent.parameters:
542             if p is not param and p.closure_index == param.closure_index:
543                 p.closure_index = None
544
545     def _parse_param_ret_common(self, parent, node, tag):
546         options = getattr(tag, 'options', {})
547         node.direction = self._extract_direction(node, options)
548         container_type = self._extract_container_type(
549             parent, node, options)
550         if container_type is not None:
551             node.type = container_type
552         if node.direction is None:
553             node.direction = self._guess_direction(node)
554         node.transfer = self._extract_transfer(parent, node, options)
555         param_type = options.get(OPT_TYPE)
556         if param_type:
557             node.type = self._resolve(param_type.one(), node.type)
558
559         if (OPT_ALLOW_NONE in options or
560             node.type.ctype == 'GCancellable*'):
561             node.allow_none = True
562
563         assert node.transfer is not None
564         if tag is not None and tag.comment is not None:
565             node.doc = tag.comment
566
567     def _extract_direction(self, node, options):
568         if (OPT_INOUT in options or
569             OPT_INOUT_ALT in options):
570             direction = PARAM_DIRECTION_INOUT
571         elif OPT_OUT in options:
572             direction = PARAM_DIRECTION_OUT
573         elif OPT_IN in options:
574             direction = PARAM_DIRECTION_IN
575         else:
576             direction = node.direction
577         return direction
578
579     def _guess_array(self, node):
580         ctype = node.type.ctype
581         if ctype is None:
582             return False
583         if not ctype.endswith('*'):
584             return False
585         if node.type.canonical in default_array_types:
586             return True
587         return False
588
589     def _extract_container_type(self, parent, node, options):
590         has_element_type = OPT_ELEMENT_TYPE in options
591         has_array = OPT_ARRAY in options
592
593         # FIXME: This is a hack :-(
594         if (not isinstance(node, Field) and
595             (not has_element_type and
596              (node.direction is None
597               or isinstance(node, Return)
598               or node.direction == PARAM_DIRECTION_IN))):
599             if self._guess_array(node):
600                 has_array = True
601
602         if has_array:
603             container_type = self._parse_array(parent, node, options)
604         elif has_element_type:
605             container_type = self._parse_element_type(parent, node, options)
606         else:
607             container_type = None
608
609         return container_type
610
611     def _parse_array(self, parent, node, options):
612         array_opt = options.get(OPT_ARRAY)
613         if array_opt:
614             array_values = array_opt.all()
615         else:
616             array_values = {}
617
618         element_type = options.get(OPT_ELEMENT_TYPE)
619         if element_type is not None:
620             element_type_node = self._resolve(element_type.one())
621         else:
622             element_type_node = Type(node.type.name) # erase ctype
623
624         container_type = Array(node.type.ctype,
625                                element_type_node)
626         container_type.is_const = node.type.is_const
627         if OPT_ARRAY_ZERO_TERMINATED in array_values:
628             container_type.zeroterminated = array_values.get(
629                 OPT_ARRAY_ZERO_TERMINATED) == '1'
630         length = array_values.get(OPT_ARRAY_LENGTH)
631         if length is not None:
632             param_index = parent.get_parameter_index(length)
633             container_type.length_param_index = param_index
634             # For in parameters we're incorrectly deferring
635             # char/unsigned char to utf8 when a length annotation
636             # is specified.
637             if (isinstance(node, Parameter) and
638                 node.type.name == 'utf8' and
639                 self._guess_direction(node) == PARAM_DIRECTION_IN and
640                 element_type is None):
641                 # FIXME: unsigned char/guchar should be uint8
642                 container_type.element_type = Type('int8')
643         container_type.size = array_values.get(OPT_ARRAY_FIXED_SIZE)
644         return container_type
645
646     def _resolve(self, type_str, orig_node=None):
647         def grab_one(type_str, resolver, top_combiner, combiner):
648             """Return a complete type, and the trailing string part after it.
649             Use resolver() on each identifier, and combiner() on the parts of
650             each complete type. (top_combiner is used on the top-most type.)"""
651             bits = re.split(r'([,<>])', type_str, 1)
652             first, sep, rest = [bits[0], '', ''] if (len(bits)==1) else bits
653             args = [resolver(first)]
654             if sep == '<':
655                 while sep != '>':
656                     next, rest = grab_one(rest, resolver, combiner, combiner)
657                     args.append(next)
658                     sep, rest = rest[0], rest[1:]
659             else:
660                 rest = sep + rest
661             return top_combiner(*args), rest
662         def resolver(ident):
663             return self._transformer.resolve_param_type(Type(ident))
664         def combiner(base, *rest):
665             if not rest:
666                 return base
667             if (base.name in ['GLib.List', 'GLib.SList'] or
668                 base.ctype in ['GList*', 'GSList*']) and len(rest)==1:
669                 return List(base.name, base.ctype, *rest)
670             if (base.name in ['GLib.HashTable'] or
671                 base.ctype in ['GHashTable*']) and len(rest)==2:
672                 return Map(base.name, base.ctype, *rest)
673             print "WARNING: throwing away type parameters:", type_str
674             return base
675         def top_combiner(base, *rest):
676             """For the top most type, recycle orig_node if possible."""
677             if orig_node is not None:
678                 orig_node.name = base.name
679                 base = orig_node # preserve other properties of orig_node
680             return combiner(base, *rest)
681
682         result, rest = grab_one(type_str, resolver, top_combiner, combiner)
683         if rest:
684             print "WARNING: throwing away trailing part of type:", type_str
685         return result
686
687     def _parse_element_type(self, parent, node, options):
688         element_type_opt = options.get(OPT_ELEMENT_TYPE)
689         element_type = element_type_opt.flat()
690         if (node.type.name in ['GLib.List', 'GLib.SList'] or
691             node.type.ctype in ['GList*', 'GSList*']):
692             assert len(element_type) == 1
693             container_type = List(
694                 node.type.name,
695                 node.type.ctype,
696                 self._resolve(element_type[0]))
697         elif (node.type.name in ['GLib.HashTable'] or
698               node.type.ctype in ['GHashTable*']):
699             assert len(element_type) == 2
700             container_type = Map(
701                 node.type.name,
702                 node.type.ctype,
703                 self._resolve(element_type[0]),
704                 self._resolve(element_type[1]))
705         else:
706             print 'FIXME: unhandled element-type container:', node
707         return container_type
708
709     def _extract_transfer(self, parent, node, options):
710         transfer_opt = options.get(OPT_TRANSFER)
711         if transfer_opt is None:
712             transfer = self._guess_transfer(node, options)
713         else:
714             transfer = transfer_opt.one()
715             if transfer is None:
716                 transfer = PARAM_TRANSFER_FULL
717             if transfer not in [PARAM_TRANSFER_NONE,
718                                 PARAM_TRANSFER_CONTAINER,
719                                 PARAM_TRANSFER_FULL]:
720                 raise InvalidAnnotationError(
721                     "transfer for %s of %r is invalid (%r), must be one of "
722                     "none, container, full." % (node, parent.name, transfer))
723         return transfer
724
725     def _parse_node_common(self, node, block):
726         self._parse_version(node, block)
727         self._parse_deprecated(node, block)
728         self._parse_attributes(node, block)
729         self._parse_skip(node, block)
730
731     def _parse_version(self, node, block):
732         since_tag = self._get_tag(block, TAG_SINCE)
733         if since_tag is None:
734             return
735         node.version = since_tag.value
736
737     def _parse_deprecated(self, node, block):
738         deprecated_tag = self._get_tag(block, TAG_DEPRECATED)
739         if deprecated_tag is None:
740             return
741         value = deprecated_tag.value
742         if ': ' in value:
743             version, desc = value.split(': ')
744         else:
745             desc = value
746             version = None
747         node.deprecated = desc
748         if version is not None:
749             node.deprecated_version = version
750
751     def _parse_attributes(self, node, block):
752         annos_tag = self._get_tag(block, TAG_ATTRIBUTES)
753         if annos_tag is None:
754             return
755         for key, value in annos_tag.options.iteritems():
756             if value:
757                 node.attributes.append((key, value.one()))
758
759     def _parse_skip(self, node, block):
760         if block is not None:
761             if OPT_SKIP in block.options:
762                 node.skip = True
763
764     def _parse_rename_to_func(self, node, block):
765         rename_to_tag = self._get_tag(block, TAG_RENAME_TO)
766         if rename_to_tag is None:
767             return
768         new_name = rename_to_tag.value
769
770         shadowed = []
771
772         def shadowed_filter(n):
773             if isinstance(n, Function) and n.symbol == new_name:
774                 shadowed.append(n)
775                 return False
776             return True
777
778         self._namespace.remove_matching(shadowed_filter)
779         if len(shadowed) == 1:
780             # method override; use the same (stripped) name as the overloaded
781             # method referenced.
782             # Note that 'g_timeout_add_full' may specify a new_name of
783             # 'g_timeout_add' but the *real* name desired is the stripped name
784             # of 'g_timeout_add' which is 'timeout_add' (for example).
785             node.name = shadowed[0].name
786         elif len(shadowed) == 0:
787             # literal rename, to force a particular prefix strip or whatever
788             # Example: the "nm-utils" package uses a "NM" prefix in most places
789             # but some functions have an "nm_utils_" prefix; the 'Rename To:'
790             # annotation in this case is used to strip the 'utils_' part off.
791             node.name = new_name
792         else:
793             assert False # more than two shadowed methods?  Shouldn't happen.
794
795     def _guess_direction(self, node):
796         if node.direction:
797             return node.direction
798         is_pointer = False
799         if node.type.ctype:
800             is_pointer = '*' in node.type.ctype
801
802         if is_pointer and node.type.name in BASIC_GIR_TYPES:
803             return PARAM_DIRECTION_OUT
804
805         return PARAM_DIRECTION_IN
806
807     def _guess_transfer(self, node, options):
808         if node.transfer is not None:
809             return node.transfer
810
811         # Anything with 'const' gets none
812         if node.type.is_const:
813             return PARAM_TRANSFER_NONE
814         elif node.type.name in [TYPE_NONE, TYPE_ANY]:
815             return PARAM_TRANSFER_NONE
816         elif isinstance(node.type, Varargs):
817             return PARAM_TRANSFER_NONE
818         elif isinstance(node, Parameter):
819             if node.direction in [PARAM_DIRECTION_INOUT,
820                                   PARAM_DIRECTION_OUT]:
821                 return PARAM_TRANSFER_FULL
822             # This one is a hack for compatibility; the transfer
823             # for string parameters really has no defined meaning.
824             elif node.type.canonical == 'utf8':
825                 return PARAM_TRANSFER_FULL
826             else:
827                 return PARAM_TRANSFER_NONE
828         elif isinstance(node, Return):
829             if (node.type.canonical in BASIC_GIR_TYPES or
830                 (node.type.canonical in [TYPE_NONE, TYPE_ANY] and
831                  node.type.is_const)):
832                 return PARAM_TRANSFER_NONE
833             else:
834                 return PARAM_TRANSFER_FULL
835         elif isinstance(node, Field):
836             return PARAM_TRANSFER_NONE
837         else:
838             raise AssertionError(node)