3 # GObject-Introspection - a framework for introspecting GObject libraries
4 # Copyright (C) 2010 Zach Goldberg
5 # Copyright (C) 2011 Johan Dahlin
6 # Copyright (C) 2011 Shaun McCance
8 # This program is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU General Public License
10 # as published by the Free Software Foundation; either version 2
11 # of the License, or (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
28 from xml.sax import saxutils
29 from mako.lookup import TemplateLookup
31 from . import ast, xmlwriter
32 from .utils import to_underscores
34 def make_page_id(node, recursive=False):
35 if isinstance(node, ast.Namespace):
41 if hasattr(node, '_chain') and node._chain:
42 parent = node._chain[-1]
47 return '%s.%s' % (node.namespace.name, node.name)
49 if isinstance(node, (ast.Property, ast.Signal, ast.VFunction)):
50 return '%s-%s' % (make_page_id(parent, recursive=True), node.name)
52 return '%s.%s' % (make_page_id(parent, recursive=True), node.name)
54 def get_node_kind(node):
55 if isinstance(node, ast.Namespace):
56 node_kind = 'namespace'
57 elif isinstance(node, (ast.Class, ast.Interface)):
59 elif isinstance(node, ast.Record):
61 elif isinstance(node, ast.Function):
64 elif node.is_constructor:
65 node_kind = 'constructor'
67 node_kind = 'function'
68 elif isinstance(node, ast.Enum):
70 elif isinstance(node, ast.Property) and node.parent is not None:
71 node_kind = 'property'
72 elif isinstance(node, ast.Signal) and node.parent is not None:
74 elif isinstance(node, ast.VFunction) and node.parent is not None:
81 class TemplatedScanner(object):
82 def __init__(self, specs):
83 self.specs = self.unmangle_specs(specs)
84 self.regex = self.make_regex(self.specs)
86 def unmangle_specs(self, specs):
87 mangled = re.compile('<<([a-zA-Z_:]+)>>')
88 specdict = dict((name.lstrip('!'), spec) for name, spec in specs)
90 def unmangle(spec, name=None):
91 def replace_func(match):
92 child_spec_name = match.group(1)
94 if ':' in child_spec_name:
95 pattern_name, child_spec_name = child_spec_name.split(':', 1)
99 child_spec = specdict[child_spec_name]
100 # Force all child specs of this one to be unnamed
101 unmangled = unmangle(child_spec, None)
102 if pattern_name and name:
103 return '(?P<%s_%s>%s)' % (name, pattern_name, unmangled)
107 return mangled.sub(replace_func, spec)
109 return [(name, unmangle(spec, name)) for name, spec in specs]
111 def make_regex(self, specs):
112 regex = '|'.join('(?P<%s>%s)' % (name, spec) for name, spec in specs
113 if not name.startswith('!'))
114 return re.compile(regex)
116 def get_properties(self, name, match):
117 groupdict = match.groupdict()
118 properties = {name: groupdict.pop(name)}
120 for group, value in groupdict.iteritems():
121 if group.startswith(name):
122 key = group[len(name):]
123 properties[key] = value
126 def scan(self, text):
129 match = self.regex.search(text, pos)
133 start = match.start()
135 yield ('other', text[pos:start], None)
138 name = match.lastgroup
139 yield (name, match.group(0), self.get_properties(name, match))
142 yield ('other', text[pos:], None)
144 class DocstringScanner(TemplatedScanner):
147 ('!alpha', r'[a-zA-Z0-9_]+'),
148 ('!alpha_dash', r'[a-zA-Z0-9_-]+'),
149 ('property', r'#<<type_name:alpha>>:(<<property_name:alpha_dash>>)'),
150 ('signal', r'#<<type_name:alpha>>::(<<signal_name:alpha_dash>>)'),
151 ('type_name', r'#(<<type_name:alpha>>)'),
152 ('enum_value', r'%(<<member_name:alpha>>)'),
153 ('parameter', r'@<<param_name:alpha>>'),
154 ('function_call', r'<<symbol_name:alpha>>\(\)'),
157 super(DocstringScanner, self).__init__(specs)
159 class DocFormatter(object):
160 def __init__(self, transformer):
161 self._transformer = transformer
162 self._scanner = DocstringScanner()
164 def escape(self, text):
165 return saxutils.escape(text)
167 def should_render_node(self, node):
168 if isinstance(node, ast.Constant):
173 def format(self, node, doc):
178 for para in doc.split('\n\n'):
180 result += self.format_inline(node, para)
184 def _resolve_type(self, ident):
186 matches = self._transformer.split_ctype_namespaces(ident)
189 for namespace, name in matches:
190 node = namespace.get(name)
195 def _resolve_symbol(self, symbol):
197 matches = self._transformer.split_csymbol_namespaces(symbol)
200 for namespace, name in matches:
201 node = namespace.get_by_symbol(symbol)
206 def _find_thing(self, list_, name):
208 if item.name == name:
210 raise KeyError("Could not find %s" % (name, ))
212 def _process_other(self, node, match, props):
213 return self.escape(match)
215 def _process_property(self, node, match, props):
216 type_node = self._resolve_type(props['type_name'])
217 if type_node is None:
221 prop = self._find_thing(type_node.properties, props['property_name'])
222 except (AttributeError, KeyError), e:
225 return self.format_xref(prop)
227 def _process_signal(self, node, match, props):
228 type_node = self._resolve_type(props['type_name'])
229 if type_node is None:
233 signal = self._find_thing(type_node.signals, props['signal_name'])
234 except (AttributeError, KeyError), e:
237 return self.format_xref(signal)
239 def _process_type_name(self, node, match, props):
240 type_ = self._resolve_type(props['type_name'])
244 return self.format_xref(type_)
246 def _process_enum_value(self, node, match, props):
247 member_name = props['member_name']
250 return '<code>%s</code>' % (self.fundamentals[member_name], )
254 enum_value = self._resolve_symbol(member_name)
256 return self.format_xref(enum_value)
260 def _process_parameter(self, node, match, props):
262 parameter = node.get_parameter(props['param_name'])
263 except (AttributeError, ValueError), e:
266 return '<code>%s</code>' % (self.format_parameter_name(node, parameter), )
268 def _process_function_call(self, node, match, props):
269 func = self._resolve_symbol(props['symbol_name'])
273 return self.format_xref(func)
275 def _process_token(self, node, tok):
276 kind, match, props = tok
279 'other': self._process_other,
280 'property': self._process_property,
281 'signal': self._process_signal,
282 'type_name': self._process_type_name,
283 'enum_value': self._process_enum_value,
284 'parameter': self._process_parameter,
285 'function_call': self._process_function_call,
288 return dispatch[kind](node, match, props)
290 def get_parameters(self, node):
291 raise NotImplementedError
293 def format_inline(self, node, para):
294 tokens = self._scanner.scan(para)
295 words = [self._process_token(node, tok) for tok in tokens]
296 return ''.join(words)
298 def format_parameter_name(self, node, parameter):
299 if isinstance(parameter.type, ast.Varargs):
302 return parameter.argname
304 def format_function_name(self, func):
305 raise NotImplementedError
307 def format_type(self, type_):
308 raise NotImplementedError
310 def format_page_name(self, node):
311 if isinstance(node, ast.Namespace):
313 elif isinstance(node, ast.Function):
314 return self.format_function_name(node)
315 elif isinstance(node, ast.Property) and node.parent is not None:
316 return '%s:%s' % (self.format_page_name(node.parent), node.name)
317 elif isinstance(node, ast.Signal) and node.parent is not None:
318 return '%s::%s' % (self.format_page_name(node.parent), node.name)
319 elif isinstance(node, ast.VFunction) and node.parent is not None:
320 return '%s::%s' % (self.format_page_name(node.parent), node.name)
322 return make_page_id(node)
324 def format_xref(self, node, **attrdict):
326 attrs = [('xref', 'index')] + attrdict.items()
327 return xmlwriter.build_xml_tag('link', attrs)
328 elif isinstance(node, ast.Member):
329 # Enum/BitField members are linked to the main enum page.
330 return self.format_xref(node.parent, **attrdict) + '.' + node.name
332 attrs = [('xref', make_page_id(node))] + attrdict.items()
333 return xmlwriter.build_xml_tag('link', attrs)
335 def format_property_flags(self, property_, construct_only=False):
337 if property_.readable and not construct_only:
339 if property_.writable and not construct_only:
340 flags.append("Write")
341 if property_.construct:
342 flags.append("Construct")
343 if property_.construct_only:
344 flags.append("Construct Only")
346 return " / ".join(flags)
348 def to_underscores(self, string):
349 return to_underscores(string)
351 def get_class_hierarchy(self, node):
352 parent_chain = [node]
354 while node.parent_type:
355 node = self._transformer.lookup_typenode(node.parent_type)
356 parent_chain.append(node)
358 parent_chain.reverse()
361 class DocFormatterC(DocFormatter):
363 mime_type = "text/x-csrc"
371 def format_type(self, type_):
372 if isinstance(type_, ast.Array):
373 return self.format_type(type_.element_type) + '*'
374 elif type_.ctype is not None:
376 elif type_.target_fundamental:
377 return type_.target_fundamental
379 node = self._transformer.lookup_typenode(type_)
380 return getattr(node, 'ctype')
382 def format_function_name(self, func):
383 if isinstance(func, (ast.Function)):
388 def get_parameters(self, node):
389 return node.all_parameters
391 class DocFormatterIntrospectableBase(DocFormatter):
392 def should_render_node(self, node):
393 if isinstance(node, ast.Record) and node.is_gtype_struct_for is not None:
396 if not getattr(node, "introspectable", True):
399 return super(DocFormatterIntrospectableBase, self).should_render_node(node)
401 class DocFormatterPython(DocFormatterIntrospectableBase):
403 mime_type = "text/python"
411 def should_render_node(self, node):
412 if getattr(node, "is_constructor", False):
415 return super(DocFormatterPython, self).should_render_node(node)
417 def is_method(self, node):
418 if getattr(node, "is_method", False):
421 if isinstance(node, (ast.VFunction)):
426 def format_parameter_name(self, node, parameter):
427 # Force "self" for the first parameter of a method
428 if self.is_method(node) and parameter is node.instance_parameter:
430 elif isinstance(parameter.type, ast.Varargs):
433 return parameter.argname
435 def format_fundamental_type(self, name):
436 fundamental_types = {
438 "gunichar": "unicode",
451 "GParam": "GLib.Param",
452 "PyObject": "object",
454 "GVariant": "GLib.Variant",
457 return fundamental_types.get(name, name)
459 def format_type(self, type_):
460 if isinstance(type_, (ast.List, ast.Array)):
461 return '[' + self.format_type(type_.element_type) + ']'
462 elif isinstance(type_, ast.Map):
463 return '{%s: %s}' % (self.format_type(type_.key_type),
464 self.format_type(type_.value_type))
465 elif type_.target_giname is not None:
466 return type_.target_giname
468 return self.format_fundamental_type(type_.target_fundamental)
470 def format_function_name(self, func):
471 if func.parent is not None:
472 return "%s.%s" % (self.format_page_name(func.parent), func.name)
476 def get_parameters(self, node):
477 return node.all_parameters
479 class DocFormatterGjs(DocFormatterIntrospectableBase):
481 mime_type = "text/x-gjs"
489 def is_method(self, node):
490 if getattr(node, "is_method", False):
493 if isinstance(node, (ast.VFunction)):
498 def format_fundamental_type(self, name):
499 fundamental_types = {
501 "gunichar": "String",
504 "gboolean": "Boolean",
513 "gchararray": "String",
514 "GParam": "GLib.Param",
515 "PyObject": "Object",
517 "GVariant": "GLib.Variant",
520 return fundamental_types.get(name, name)
522 def format_type(self, type_):
523 if isinstance(type_, (ast.List, ast.Array)):
524 return '[' + self.format_type(type_.element_type) + ']'
525 elif isinstance(type_, ast.Map):
526 return '{%s: %s}' % (self.format_type(type_.key_type),
527 self.format_type(type_.value_type))
528 elif type_.target_fundamental == "none":
530 elif type_.target_giname is not None:
531 return type_.target_giname
533 return self.format_fundamental_type(type_.target_fundamental)
535 def format_function_name(self, func):
537 return "%s.prototype.%s" % (self.format_page_name(func.parent), func.name)
538 elif func.is_constructor:
539 return "%s.%s" % (self.format_page_name(func.parent), func.name)
543 def get_parameters(self, node):
545 for param in node.parameters:
546 if param.direction == ast.PARAM_DIRECTION_OUT:
548 if param.closure_name is not None:
549 skip.append(node.get_parameter(param.closure_name))
550 if param.destroy_name is not None:
551 skip.append(node.get_parameter(param.destroy_name))
552 if isinstance(param.type, ast.Array) and param.type.length_param_name is not None:
553 skip.append(node.get_parameter(param.type.length_param_name))
556 for param in node.parameters:
557 if param not in skip:
563 "python": DocFormatterPython,
564 "gjs": DocFormatterGjs,
567 class DocWriter(object):
568 def __init__(self, transformer, language):
569 self._transformer = transformer
572 formatter_class = LANGUAGES[language.lower()]
574 raise SystemExit("Unsupported language: %s" % (language, ))
576 self._formatter = formatter_class(self._transformer)
577 self._language = self._formatter.language
579 self._lookup = self._get_template_lookup()
581 def _get_template_lookup(self):
582 if 'UNINSTALLED_INTROSPECTION_SRCDIR' in os.environ:
583 top_srcdir = os.environ['UNINSTALLED_INTROSPECTION_SRCDIR']
584 srcdir = os.path.join(top_srcdir, 'giscanner')
586 srcdir = os.path.dirname(__file__)
588 template_dir = os.path.join(srcdir, 'doctemplates')
590 return TemplateLookup(directories=[template_dir],
591 module_directory=tempfile.mkdtemp(),
592 output_encoding='utf-8')
594 def write(self, output):
598 # directory already made
601 self._walk_node(output, self._transformer.namespace, [])
602 self._transformer.namespace.walk(lambda node, chain: self._walk_node(output, node, chain))
604 def _walk_node(self, output, node, chain):
605 if isinstance(node, ast.Function) and node.moved_to is not None:
607 if getattr(node, 'disguised', False):
609 if self._formatter.should_render_node(node):
610 self._render_node(node, chain, output)
614 def _render_node(self, node, chain, output):
615 namespace = self._transformer.namespace
617 # A bit of a hack...maybe this should be an official API
618 node._chain = list(chain)
620 page_kind = get_node_kind(node)
621 template_name = '%s/%s.tmpl' % (self._language, page_kind)
622 page_id = make_page_id(node)
624 template = self._lookup.get_template(template_name)
625 result = template.render(namespace=namespace,
629 formatter=self._formatter)
631 output_file_name = os.path.join(os.path.abspath(output),
633 fp = open(output_file_name, 'w')