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
35 def make_page_id(node, recursive=False):
36 if isinstance(node, ast.Namespace):
42 if hasattr(node, '_chain') and node._chain:
43 parent = node._chain[-1]
48 return '%s.%s' % (node.namespace.name, node.name)
50 if isinstance(node, (ast.Property, ast.Signal, ast.VFunction)):
51 return '%s-%s' % (make_page_id(parent, recursive=True), node.name)
53 return '%s.%s' % (make_page_id(parent, recursive=True), node.name)
56 def get_node_kind(node):
57 if isinstance(node, ast.Namespace):
58 node_kind = 'namespace'
59 elif isinstance(node, (ast.Class, ast.Interface)):
61 elif isinstance(node, ast.Record):
63 elif isinstance(node, ast.Function):
66 elif node.is_constructor:
67 node_kind = 'constructor'
69 node_kind = 'function'
70 elif isinstance(node, ast.Enum):
72 elif isinstance(node, ast.Property) and node.parent is not None:
73 node_kind = 'property'
74 elif isinstance(node, ast.Signal) and node.parent is not None:
76 elif isinstance(node, ast.VFunction) and node.parent is not None:
84 class TemplatedScanner(object):
85 def __init__(self, specs):
86 self.specs = self.unmangle_specs(specs)
87 self.regex = self.make_regex(self.specs)
89 def unmangle_specs(self, specs):
90 mangled = re.compile('<<([a-zA-Z_:]+)>>')
91 specdict = dict((name.lstrip('!'), spec) for name, spec in specs)
93 def unmangle(spec, name=None):
94 def replace_func(match):
95 child_spec_name = match.group(1)
97 if ':' in child_spec_name:
98 pattern_name, child_spec_name = child_spec_name.split(':', 1)
102 child_spec = specdict[child_spec_name]
103 # Force all child specs of this one to be unnamed
104 unmangled = unmangle(child_spec, None)
105 if pattern_name and name:
106 return '(?P<%s_%s>%s)' % (name, pattern_name, unmangled)
110 return mangled.sub(replace_func, spec)
112 return [(name, unmangle(spec, name)) for name, spec in specs]
114 def make_regex(self, specs):
115 regex = '|'.join('(?P<%s>%s)' % (name, spec) for name, spec in specs
116 if not name.startswith('!'))
117 return re.compile(regex)
119 def get_properties(self, name, match):
120 groupdict = match.groupdict()
121 properties = {name: groupdict.pop(name)}
123 for group, value in groupdict.iteritems():
124 if group.startswith(name):
125 key = group[len(name):]
126 properties[key] = value
129 def scan(self, text):
132 match = self.regex.search(text, pos)
136 start = match.start()
138 yield ('other', text[pos:start], None)
141 name = match.lastgroup
142 yield (name, match.group(0), self.get_properties(name, match))
145 yield ('other', text[pos:], None)
148 class DocstringScanner(TemplatedScanner):
151 ('!alpha', r'[a-zA-Z0-9_]+'),
152 ('!alpha_dash', r'[a-zA-Z0-9_-]+'),
153 ('property', r'#<<type_name:alpha>>:(<<property_name:alpha_dash>>)'),
154 ('signal', r'#<<type_name:alpha>>::(<<signal_name:alpha_dash>>)'),
155 ('type_name', r'#(<<type_name:alpha>>)'),
156 ('enum_value', r'%(<<member_name:alpha>>)'),
157 ('parameter', r'@<<param_name:alpha>>'),
158 ('function_call', r'<<symbol_name:alpha>>\(\)'),
161 super(DocstringScanner, self).__init__(specs)
164 class DocFormatter(object):
165 def __init__(self, transformer):
166 self._transformer = transformer
167 self._scanner = DocstringScanner()
169 def escape(self, text):
170 return saxutils.escape(text)
172 def should_render_node(self, node):
173 if isinstance(node, ast.Constant):
178 def format(self, node, doc):
183 for para in doc.split('\n\n'):
185 result += self.format_inline(node, para)
189 def _resolve_type(self, ident):
191 matches = self._transformer.split_ctype_namespaces(ident)
194 for namespace, name in matches:
195 node = namespace.get(name)
200 def _resolve_symbol(self, symbol):
202 matches = self._transformer.split_csymbol_namespaces(symbol)
205 for namespace, name in matches:
206 node = namespace.get_by_symbol(symbol)
211 def _find_thing(self, list_, name):
213 if item.name == name:
215 raise KeyError("Could not find %s" % (name, ))
217 def _process_other(self, node, match, props):
218 return self.escape(match)
220 def _process_property(self, node, match, props):
221 type_node = self._resolve_type(props['type_name'])
222 if type_node is None:
226 prop = self._find_thing(type_node.properties, props['property_name'])
227 except (AttributeError, KeyError):
230 return self.format_xref(prop)
232 def _process_signal(self, node, match, props):
233 type_node = self._resolve_type(props['type_name'])
234 if type_node is None:
238 signal = self._find_thing(type_node.signals, props['signal_name'])
239 except (AttributeError, KeyError):
242 return self.format_xref(signal)
244 def _process_type_name(self, node, match, props):
245 type_ = self._resolve_type(props['type_name'])
249 return self.format_xref(type_)
251 def _process_enum_value(self, node, match, props):
252 member_name = props['member_name']
255 return '<code>%s</code>' % (self.fundamentals[member_name], )
259 enum_value = self._resolve_symbol(member_name)
261 return self.format_xref(enum_value)
265 def _process_parameter(self, node, match, props):
267 parameter = node.get_parameter(props['param_name'])
268 except (AttributeError, ValueError):
271 return '<code>%s</code>' % (self.format_parameter_name(node, parameter), )
273 def _process_function_call(self, node, match, props):
274 func = self._resolve_symbol(props['symbol_name'])
278 return self.format_xref(func)
280 def _process_token(self, node, tok):
281 kind, match, props = tok
284 'other': self._process_other,
285 'property': self._process_property,
286 'signal': self._process_signal,
287 'type_name': self._process_type_name,
288 'enum_value': self._process_enum_value,
289 'parameter': self._process_parameter,
290 'function_call': self._process_function_call,
293 return dispatch[kind](node, match, props)
295 def get_parameters(self, node):
296 raise NotImplementedError
298 def format_inline(self, node, para):
299 tokens = self._scanner.scan(para)
300 words = [self._process_token(node, tok) for tok in tokens]
301 return ''.join(words)
303 def format_parameter_name(self, node, parameter):
304 if isinstance(parameter.type, ast.Varargs):
307 return parameter.argname
309 def format_function_name(self, func):
310 raise NotImplementedError
312 def format_type(self, type_):
313 raise NotImplementedError
315 def format_page_name(self, node):
316 if isinstance(node, ast.Namespace):
318 elif isinstance(node, ast.Function):
319 return self.format_function_name(node)
320 elif isinstance(node, ast.Property) and node.parent is not None:
321 return '%s:%s' % (self.format_page_name(node.parent), node.name)
322 elif isinstance(node, ast.Signal) and node.parent is not None:
323 return '%s::%s' % (self.format_page_name(node.parent), node.name)
324 elif isinstance(node, ast.VFunction) and node.parent is not None:
325 return '%s::%s' % (self.format_page_name(node.parent), node.name)
327 return make_page_id(node)
329 def format_xref(self, node, **attrdict):
331 attrs = [('xref', 'index')] + attrdict.items()
332 return xmlwriter.build_xml_tag('link', attrs)
333 elif isinstance(node, ast.Member):
334 # Enum/BitField members are linked to the main enum page.
335 return self.format_xref(node.parent, **attrdict) + '.' + node.name
337 attrs = [('xref', make_page_id(node))] + attrdict.items()
338 return xmlwriter.build_xml_tag('link', attrs)
340 def format_property_flags(self, property_, construct_only=False):
342 if property_.readable and not construct_only:
344 if property_.writable and not construct_only:
345 flags.append("Write")
346 if property_.construct:
347 flags.append("Construct")
348 if property_.construct_only:
349 flags.append("Construct Only")
351 return " / ".join(flags)
353 def to_underscores(self, string):
354 return to_underscores(string)
356 def get_class_hierarchy(self, node):
357 parent_chain = [node]
359 while node.parent_type:
360 node = self._transformer.lookup_typenode(node.parent_type)
361 parent_chain.append(node)
363 parent_chain.reverse()
367 class DocFormatterC(DocFormatter):
369 mime_type = "text/x-csrc"
377 def format_type(self, type_):
378 if isinstance(type_, ast.Array):
379 return self.format_type(type_.element_type) + '*'
380 elif type_.ctype is not None:
382 elif type_.target_fundamental:
383 return type_.target_fundamental
385 node = self._transformer.lookup_typenode(type_)
386 return getattr(node, 'ctype')
388 def format_function_name(self, func):
389 if isinstance(func, ast.Function):
394 def get_parameters(self, node):
395 return node.all_parameters
398 class DocFormatterIntrospectableBase(DocFormatter):
399 def should_render_node(self, node):
400 if isinstance(node, ast.Record) and node.is_gtype_struct_for is not None:
403 if not getattr(node, "introspectable", True):
406 return super(DocFormatterIntrospectableBase, self).should_render_node(node)
409 class DocFormatterPython(DocFormatterIntrospectableBase):
411 mime_type = "text/python"
419 def should_render_node(self, node):
420 if getattr(node, "is_constructor", False):
423 return super(DocFormatterPython, self).should_render_node(node)
425 def is_method(self, node):
426 if getattr(node, "is_method", False):
429 if isinstance(node, ast.VFunction):
434 def format_parameter_name(self, node, parameter):
435 # Force "self" for the first parameter of a method
436 if self.is_method(node) and parameter is node.instance_parameter:
438 elif isinstance(parameter.type, ast.Varargs):
441 return parameter.argname
443 def format_fundamental_type(self, name):
444 fundamental_types = {
446 "gunichar": "unicode",
459 "GParam": "GLib.Param",
460 "PyObject": "object",
462 "GVariant": "GLib.Variant"}
464 return fundamental_types.get(name, name)
466 def format_type(self, type_):
467 if isinstance(type_, (ast.List, ast.Array)):
468 return '[' + self.format_type(type_.element_type) + ']'
469 elif isinstance(type_, ast.Map):
470 return '{%s: %s}' % (self.format_type(type_.key_type),
471 self.format_type(type_.value_type))
472 elif type_.target_giname is not None:
473 return type_.target_giname
475 return self.format_fundamental_type(type_.target_fundamental)
477 def format_function_name(self, func):
478 if func.parent is not None:
479 return "%s.%s" % (self.format_page_name(func.parent), func.name)
483 def get_parameters(self, node):
484 return node.all_parameters
487 class DocFormatterGjs(DocFormatterIntrospectableBase):
489 mime_type = "text/x-gjs"
497 def is_method(self, node):
498 if getattr(node, "is_method", False):
501 if isinstance(node, ast.VFunction):
506 def format_fundamental_type(self, name):
507 fundamental_types = {
509 "gunichar": "String",
512 "gboolean": "Boolean",
521 "gchararray": "String",
522 "GParam": "GLib.Param",
523 "PyObject": "Object",
525 "GVariant": "GLib.Variant"}
527 return fundamental_types.get(name, name)
529 def format_type(self, type_):
530 if isinstance(type_, (ast.List, ast.Array)):
531 return '[' + self.format_type(type_.element_type) + ']'
532 elif isinstance(type_, ast.Map):
533 return '{%s: %s}' % (self.format_type(type_.key_type),
534 self.format_type(type_.value_type))
535 elif type_.target_fundamental == "none":
537 elif type_.target_giname is not None:
538 return type_.target_giname
540 return self.format_fundamental_type(type_.target_fundamental)
542 def format_function_name(self, func):
544 return "%s.prototype.%s" % (self.format_page_name(func.parent), func.name)
545 elif func.is_constructor:
546 return "%s.%s" % (self.format_page_name(func.parent), func.name)
550 def get_parameters(self, node):
552 for param in node.parameters:
553 if param.direction == ast.PARAM_DIRECTION_OUT:
555 if param.closure_name is not None:
556 skip.append(node.get_parameter(param.closure_name))
557 if param.destroy_name is not None:
558 skip.append(node.get_parameter(param.destroy_name))
559 if isinstance(param.type, ast.Array) and param.type.length_param_name is not None:
560 skip.append(node.get_parameter(param.type.length_param_name))
563 for param in node.parameters:
564 if param not in skip:
571 "python": DocFormatterPython,
572 "gjs": DocFormatterGjs,
576 class DocWriter(object):
577 def __init__(self, transformer, language):
578 self._transformer = transformer
581 formatter_class = LANGUAGES[language.lower()]
583 raise SystemExit("Unsupported language: %s" % (language, ))
585 self._formatter = formatter_class(self._transformer)
586 self._language = self._formatter.language
588 self._lookup = self._get_template_lookup()
590 def _get_template_lookup(self):
591 if 'UNINSTALLED_INTROSPECTION_SRCDIR' in os.environ:
592 top_srcdir = os.environ['UNINSTALLED_INTROSPECTION_SRCDIR']
593 srcdir = os.path.join(top_srcdir, 'giscanner')
595 srcdir = os.path.dirname(__file__)
597 template_dir = os.path.join(srcdir, 'doctemplates')
599 return TemplateLookup(directories=[template_dir],
600 module_directory=tempfile.mkdtemp(),
601 output_encoding='utf-8')
603 def write(self, output):
607 # directory already made
610 self._walk_node(output, self._transformer.namespace, [])
611 self._transformer.namespace.walk(lambda node, chain: self._walk_node(output, node, chain))
613 def _walk_node(self, output, node, chain):
614 if isinstance(node, ast.Function) and node.moved_to is not None:
616 if getattr(node, 'disguised', False):
618 if self._formatter.should_render_node(node):
619 self._render_node(node, chain, output)
623 def _render_node(self, node, chain, output):
624 namespace = self._transformer.namespace
626 # A bit of a hack...maybe this should be an official API
627 node._chain = list(chain)
629 page_kind = get_node_kind(node)
630 template_name = '%s/%s.tmpl' % (self._language, page_kind)
631 page_id = make_page_id(node)
633 template = self._lookup.get_template(template_name)
634 result = template.render(namespace=namespace,
638 formatter=self._formatter)
640 output_file_name = os.path.join(os.path.abspath(output),
642 fp = open(output_file_name, 'w')