2 # GObject-Introspection - a framework for introspecting GObject libraries
3 # Copyright (C) 2008-2010 Johan Dahlin
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.
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.
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
21 # AnnotationParser - extract annotations from gtk-doc comments
26 from .odict import odict
28 # Tags - annotations applied to comment blocks
31 TAG_STABILITY = 'stability'
32 TAG_DEPRECATED = 'deprecated'
33 TAG_RETURNS = 'returns'
34 TAG_ATTRIBUTES = 'attributes'
35 TAG_RENAME_TO = 'rename to'
37 TAG_UNREF_FUNC = 'unref func'
38 TAG_REF_FUNC = 'ref func'
39 TAG_SET_VALUE_FUNC = 'set value func'
40 TAG_GET_VALUE_FUNC = 'get value func'
41 TAG_TRANSFER = 'transfer'
43 _ALL_TAGS = [TAG_VFUNC,
58 # Options - annotations for parameters and return values
59 OPT_ALLOW_NONE = 'allow-none'
61 OPT_ATTRIBUTE = 'attribute'
62 OPT_CLOSURE = 'closure'
63 OPT_DESTROY = 'destroy'
64 OPT_ELEMENT_TYPE = 'element-type'
65 OPT_FOREIGN = 'foreign'
68 OPT_INOUT_ALT = 'in-out'
71 OPT_TRANSFER = 'transfer'
74 OPT_CONSTRUCTOR = 'constructor'
96 # Array options - array specific annotations
97 OPT_ARRAY_FIXED_SIZE = 'fixed-size'
98 OPT_ARRAY_LENGTH = 'length'
99 OPT_ARRAY_ZERO_TERMINATED = 'zero-terminated'
102 OPT_OUT_CALLER_ALLOCATES = 'caller-allocates'
103 OPT_OUT_CALLEE_ALLOCATES = 'callee-allocates'
106 OPT_SCOPE_ASYNC = 'async'
107 OPT_SCOPE_CALL = 'call'
108 OPT_SCOPE_NOTIFIED = 'notified'
111 OPT_TRANSFER_NONE = 'none'
112 OPT_TRANSFER_CONTAINER = 'container'
113 OPT_TRANSFER_FULL = 'full'
114 OPT_TRANSFER_FLOATING = 'floating'
117 class DocBlock(object):
119 def __init__(self, name):
121 self.options = DocOptions()
128 def __cmp__(self, other):
129 return cmp(self.name, other.name)
132 return '<DocBlock %r %r>' % (self.name, self.options)
134 def set_position(self, position):
135 self.position = position
136 self.options.position = position
139 return self.tags.get(name)
141 def to_gtk_doc(self):
145 options += ' '.join('(%s)' % o for o in self.options)
147 if 'SECTION' not in self.name:
151 for name, tag in self.tags.iteritems():
152 if name in self.params:
153 lines.append(tag.to_gtk_doc_param())
158 for l in self.comment.split('\n'):
163 lines.append(tag.to_gtk_doc_tag())
166 #comment += '# %d \"%s\"\n' % (
167 # self.position.line,
168 # self.position.filename)
173 comment += ' * %s\n' % (line, )
180 for tag in self.tags.values():
184 class DocTag(object):
186 def __init__(self, block, name):
189 self.options = DocOptions()
195 return '<DocTag %r %r>' % (self.name, self.options)
197 def _validate_option(self, name, value, required=False,
198 n_params=None, choices=None):
199 if required and value is None:
200 message.warn('%s annotation needs a value' % (
201 name, ), self.position)
204 if n_params is not None:
210 s = '%d values' % (n_params, )
211 if ((n_params > 0 and (value is None or value.length() != n_params)) or
212 n_params == 0 and value is not None):
216 length = value.length()
217 message.warn('%s annotation needs %s, not %d' % (
218 name, s, length), self.position)
221 if choices is not None:
222 valuestr = value.one()
223 if valuestr not in choices:
224 message.warn('invalid %s annotation value: %r' % (
225 name, valuestr, ), self.position)
228 def set_position(self, position):
229 self.position = position
230 self.options.position = position
232 def _get_gtk_doc_value(self):
233 def serialize_one(option, value, fmt, fmt2):
235 if type(value) != str:
236 value = ' '.join((serialize_one(k, v, '%s=%s', '%s')
237 for k, v in value.all().iteritems()))
238 return fmt % (option, value)
240 return fmt2 % (option, )
242 for option, value in self.options.iteritems():
244 serialize_one(option, value, '(%s %s)', '(%s)'))
246 return ' '.join(annotations) + ': '
250 def to_gtk_doc_param(self):
251 return '@%s: %s%s' % (self.name, self._get_gtk_doc_value(), self.comment)
253 def to_gtk_doc_tag(self):
254 return '%s: %s%s' % (self.name.capitalize(),
255 self._get_gtk_doc_value(),
259 for option in self.options:
260 value = self.options[option]
261 if option == OPT_ALLOW_NONE:
262 self._validate_option('allow-none', value, n_params=0)
263 elif option == OPT_ARRAY:
266 for name, v in value.all().iteritems():
267 if name in [OPT_ARRAY_ZERO_TERMINATED, OPT_ARRAY_FIXED_SIZE]:
270 except (TypeError, ValueError):
273 'array option %s needs a value' % (
275 positions=self.position)
278 'invalid array %s option value %r, '
279 'must be an integer' % (name, v, ),
280 positions=self.position)
282 elif name == OPT_ARRAY_LENGTH:
285 'array option length needs a value',
286 positions=self.position)
290 'invalid array annotation value: %r' % (
291 name, ), self.position)
293 elif option == OPT_ATTRIBUTE:
294 self._validate_option('attribute', value, n_params=2)
295 elif option == OPT_CLOSURE:
296 if value is not None and value.length() > 1:
298 'closure takes at maximium 1 value, %d given' % (
299 value.length()), self.position)
301 elif option == OPT_DESTROY:
302 self._validate_option('destroy', value, n_params=1)
303 elif option == OPT_ELEMENT_TYPE:
304 self._validate_option('element-type', value, required=True)
307 'element-type takes at least one value, none given',
310 if value.length() > 2:
312 'element-type takes at maximium 2 values, %d given' % (
313 value.length()), self.position)
315 elif option == OPT_FOREIGN:
316 self._validate_option('foreign', value, n_params=0)
317 elif option == OPT_IN:
318 self._validate_option('in', value, n_params=0)
319 elif option in [OPT_INOUT, OPT_INOUT_ALT]:
320 self._validate_option('inout', value, n_params=0)
321 elif option == OPT_OUT:
324 if value.length() > 1:
326 'out annotation takes at maximium 1 value, %d given' % (
327 value.length()), self.position)
329 value_str = value.one()
330 if value_str not in [OPT_OUT_CALLEE_ALLOCATES,
331 OPT_OUT_CALLER_ALLOCATES]:
332 message.warn("out annotation value is invalid: %r" % (
333 value_str), self.position)
335 elif option == OPT_SCOPE:
336 self._validate_option(
337 'scope', value, required=True,
339 choices=[OPT_SCOPE_ASYNC,
342 elif option == OPT_SKIP:
343 self._validate_option('skip', value, n_params=0)
344 elif option == OPT_TRANSFER:
345 self._validate_option(
346 'transfer', value, required=True,
348 choices=[OPT_TRANSFER_FULL,
349 OPT_TRANSFER_CONTAINER,
351 OPT_TRANSFER_FLOATING])
352 elif option == OPT_TYPE:
353 self._validate_option('type', value, required=True,
355 elif option == OPT_CONSTRUCTOR:
356 self._validate_option('constructor', value, n_params=0)
357 elif option == OPT_METHOD:
358 self._validate_option('method', value, n_params=0)
360 message.warn('invalid annotation option: %s' % (option, ),
364 class DocOptions(object):
368 def __getitem__(self, item):
369 for key, value in self.values:
374 def __nonzero__(self):
375 return bool(self.values)
378 return (k for k, v in self.values)
380 def add(self, name, value):
381 self.values.append((name, value))
383 def get(self, item, default=None):
384 for key, value in self.values:
389 def getall(self, item):
390 for key, value in self.values:
395 return iter(self.values)
398 class DocOption(object):
400 def __init__(self, tag, option):
404 # (annotation option1=value1 option2=value2) etc
405 for p in option.split(' '):
407 name, value = p.split('=', 1)
411 self._dict[name] = value
413 self._array.append(name)
415 self._array.append((name, value))
418 return '<DocOption %r>' % (self._array, )
421 return len(self._array)
424 assert len(self._array) == 1
425 return self._array[0]
434 class AnnotationParser(object):
435 COMMENT_HEADER_RE = re.compile(r'^\*[ \t]*\n[\t ]')
436 COMMENT_HEADER_START_RE = re.compile(r'\n[\t ]')
437 WHITESPACE_RE = re.compile(r'^\s*$')
438 OPTION_RE = re.compile(r'\([A-Za-z]+[^(]*\)')
439 RETURNS_RE = re.compile(r'^return(s?)( value)?:', re.IGNORECASE)
444 def parse(self, comments):
445 for comment in comments:
446 self._parse_comment(comment)
449 def _parse_comment(self, cmt):
450 # We're looking for gtk-doc comments here, they look like this:
454 # Or, alternatively, with options:
456 # * symbol: (name value) ...
458 # symbol is currently one of:
459 # - function: gtk_widget_show
460 # - signal: GtkWidget::destroy
461 # - property: GtkWidget:visible
463 comment, filename, lineno = cmt
464 comment = comment.lstrip()
465 if not self.COMMENT_HEADER_RE.search(comment):
467 comment = self.COMMENT_HEADER_RE.sub('', comment, count=1)
468 comment = comment.strip()
469 if not comment.startswith('* '):
471 comment = comment[2:]
473 match = self.COMMENT_HEADER_START_RE.search(comment)
477 block_header = comment[:pos]
478 block_header = block_header.strip()
479 cpos = block_header.find(': ')
480 block_name = block_header
481 raw_name = block_header
483 block_name = block_name[:cpos].strip()
484 if block_name.endswith(':'):
485 block_name = block_name[:-1]
486 block = DocBlock(block_name)
487 block.set_position(message.Position(filename, lineno))
490 block.options = self.parse_options(block, block_header[cpos+2:])
492 parsing_parameters = True
493 last_param_tag = None
495 # Second phase: parse parameters, return values, Tag: format
498 # Valid lines look like:
499 # * @foo: some comment here
500 # * @baz: (inout): This has an annotation
501 # * @bar: (out) (allow-none): this is a long parameter comment
502 # * that gets wrapped to the next line.
504 # * Some documentation for the function.
506 # * Returns: (transfer none): A value
508 # offset of the first doctag in relation to the start of
509 # the docblock, we parsed /** and the xxx: lines already
511 for line in comment[pos+1:].split('\n'):
513 if not line.startswith('*'):
516 nostar_line = line[1:]
517 is_whitespace = self.WHITESPACE_RE.match(nostar_line) is not None
518 if parsing_parameters and is_whitespace:
519 # As soon as we find a line that's just whitespace,
520 # we're done parsing the parameters.
521 parsing_parameters = False
525 comment_lines.append('')
529 # Explicitly only accept parameters of the form "* @foo" with one space.
530 is_parameter = nostar_line.startswith(' @')
532 # Strip the rest of the leading whitespace for the rest of
533 # the code; may not actually be necessary, but still doing
534 # it to avoid regressions.
535 line = nostar_line.lstrip()
537 # Look for a parameter or return value. Both of these can
538 # have parenthesized options.
539 first_colonspace_index = line.find(': ')
540 is_return_value = self.RETURNS_RE.search(line)
542 if ((is_parameter or is_return_value)
543 and first_colonspace_index > 0):
544 # Skip lines which has non-whitespace before first (
545 first_paren = line[first_colonspace_index+1:].find('(')
546 if (first_paren != -1 and
547 line[first_colonspace_index+1:first_paren].strip()):
548 parse_options = False
551 argname = line[1:first_colonspace_index]
553 argname = TAG_RETURNS
554 tag = DocTag(block, argname)
555 tag.set_position(block.position.offset(lineno))
556 line_after_first_colon_space = line[first_colonspace_index + 2:]
557 second_colon_index = line_after_first_colon_space.find(':')
558 if second_colon_index >= 0:
559 second_colon_index += first_colonspace_index + 2
560 assert line[second_colon_index] == ':'
561 found_options = False
562 if second_colon_index > first_colonspace_index:
564 line[first_colonspace_index+2:second_colon_index]
565 if ')' in value_line:
566 after_last_paren = value_line[value_line.rfind(')'):]
567 if not after_last_paren.rstrip().endswith(')'):
568 parse_options = False
569 if parse_options and self.OPTION_RE.search(value_line):
570 # The OPTION_RE is a little bit heuristic. If
571 # we found two colons, we scan inside for something
572 # that looks like (foo).
573 # *Ideally* we'd change the gtk-doc format to
574 # require double colons, and then there'd be
575 # no ambiguity. I.e.:
576 # @foo:: Some documentation here
577 # But that'd be a rather incompatible change.
579 tag.comment = line[second_colon_index+1:].strip()
580 tag.options = self.parse_options(tag, value_line)
581 if not found_options:
582 # We didn't find any options, so just take the whole thing
584 tag.comment = line[first_colonspace_index+2:].strip()
585 block.tags[argname] = tag
588 block.params.append(argname)
589 elif (not is_parameter) and parsing_parameters and last_param_tag:
590 # We need to handle continuation lines on parameters. The
591 # conditional above - if a line doesn't start with '@', we're
592 # not yet in the documentation block for the whole function,
593 # and we've seen at least one parameter.
594 last_param_tag.comment += (' ' + line.strip())
595 elif first_colonspace_index > 0:
596 # The line is of the form "Tag: some value here", like:
598 tag_name = line[:first_colonspace_index]
599 if tag_name.lower() in _ALL_TAGS:
600 tag_name = tag_name.lower()
601 tag = DocTag(block, tag_name)
602 tag.value = line[first_colonspace_index+2:]
603 tag.position = block.position.offset(lineno)
604 block.tags[tag_name] = tag
606 comment_lines.append(line)
607 elif not parsing_parameters:
608 comment_lines.append(line)
610 block.comment = '\n'.join(comment_lines).strip()
612 self._blocks[block.name] = block
615 def parse_options(cls, tag, value):
619 options = DocOptions()
620 options.position = tag.position
622 for i, c in enumerate(value):
623 if c == '(' and opened == -1:
625 if c == ')' and opened != -1:
626 segment = value[opened:i]
627 parts = segment.split(' ', 1)
630 elif len(parts) == 1:
635 if option is not None:
636 option = DocOption(tag, option)
637 options.add(name, option)