gimarshallingtests: Add string_ to boxed structure
[platform/upstream/gobject-introspection.git] / giscanner / mallardwriter.py
1 #!/usr/bin/env python
2 # -*- Mode: Python -*-
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
7 #
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.
12 #
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.
17 #
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
21 # 02110-1301, USA.
22 #
23
24 import os
25 import re
26 import tempfile
27
28 from xml.sax import saxutils
29 from mako.template import Template
30 from mako.lookup import TemplateLookup
31
32 from . import ast
33 from .utils import to_underscores
34
35 def make_page_id(node):
36     if isinstance(node, ast.Namespace):
37         return 'index'
38
39     namespace = node.namespace
40     if isinstance(node, (ast.Class, ast.Interface)):
41         return '%s.%s' % (namespace.name, node.name)
42     elif isinstance(node, ast.Record):
43         return '%s.%s' % (namespace.name, node.name)
44     elif isinstance(node, ast.Function):
45         if node.parent is not None:
46             return '%s.%s.%s' % (namespace.name, node.parent.name, node.name)
47         else:
48             return '%s.%s' % (namespace.name, node.name)
49     elif isinstance(node, ast.Enum):
50         return '%s.%s' % (namespace.name, node.name)
51     elif isinstance(node, ast.Property) and node.parent is not None:
52         return '%s.%s-%s' % (namespace.name, node.parent.name, node.name)
53     elif isinstance(node, ast.Signal) and node.parent is not None:
54         return '%s.%s-%s' % (namespace.name, node.parent.name, node.name)
55     elif isinstance(node, ast.VFunction) and node.parent is not None:
56         return '%s.%s-%s' % (namespace.name, node.parent.name, node.name)
57     else:
58         return '%s.%s' % (namespace.name, node.name)
59
60 def make_template_name(node, language):
61     if isinstance(node, ast.Namespace):
62         node_kind = 'namespace'
63     elif isinstance(node, (ast.Class, ast.Interface)):
64         node_kind = 'class'
65     elif isinstance(node, ast.Record):
66         node_kind = 'record'
67     elif isinstance(node, ast.Function):
68         node_kind = 'function'
69     elif isinstance(node, ast.Enum):
70         node_kind = 'enum'
71     elif isinstance(node, ast.Property) and node.parent is not None:
72         node_kind = 'property'
73     elif isinstance(node, ast.Signal) and node.parent is not None:
74         node_kind = 'signal'
75     elif isinstance(node, ast.VFunction) and node.parent is not None:
76         node_kind = 'vfunc'
77     else:
78         node_kind = 'default'
79
80     return 'mallard-%s-%s.tmpl' % (language, node_kind)
81
82 class TemplatedScanner(object):
83     def __init__(self, specs):
84         self.specs = self.unmangle_specs(specs)
85         self.regex = self.make_regex(self.specs)
86
87     def unmangle_specs(self, specs):
88         mangled = re.compile('<<([a-zA-Z_:]+)>>')
89         specdict = dict((name.lstrip('!'), spec) for name, spec in specs)
90
91         def unmangle(spec, name=None):
92             def replace_func(match):
93                 child_spec_name = match.group(1)
94
95                 if ':' in child_spec_name:
96                     pattern_name, child_spec_name = child_spec_name.split(':', 1)
97                 else:
98                     pattern_name = None
99
100                 child_spec = specdict[child_spec_name]
101                 # Force all child specs of this one to be unnamed
102                 unmangled = unmangle(child_spec, None)
103                 if pattern_name and name:
104                     return '(?P<%s_%s>%s)' % (name, pattern_name, unmangled)
105                 else:
106                     return unmangled
107
108             return mangled.sub(replace_func, spec)
109
110         return [(name, unmangle(spec, name)) for name, spec in specs]
111
112     def make_regex(self, specs):
113         regex = '|'.join('(?P<%s>%s)' % (name, spec) for name, spec in specs
114                          if not name.startswith('!'))
115         return re.compile(regex)
116
117     def get_properties(self, name, match):
118         groupdict = match.groupdict()
119         properties = {name: groupdict.pop(name)}
120         name = name + "_"
121         for group, value in groupdict.iteritems():
122             if group.startswith(name):
123                 key = group[len(name):]
124                 properties[key] = value
125         return properties
126
127     def scan(self, text):
128         pos = 0
129         while True:
130             match = self.regex.search(text, pos)
131             if match is None:
132                 break
133
134             start = match.start()
135             if start > pos:
136                 yield ('other', text[pos:start], None)
137
138             pos = match.end()
139             name = match.lastgroup
140             yield (name, match.group(0), self.get_properties(name, match))
141
142         if pos < len(text):
143             yield ('other', text[pos:], None)
144
145 class DocstringScanner(TemplatedScanner):
146     def __init__(self):
147         specs = [
148             ('!alpha', r'[a-zA-Z0-9_]+'),
149             ('!alpha_dash', r'[a-zA-Z0-9_-]+'),
150             ('property', r'#<<type_name:alpha>>:(<<property_name:alpha_dash>>)'),
151             ('signal', r'#<<type_name:alpha>>::(<<signal_name:alpha_dash>>)'),
152             ('type_name', r'#(<<type_name:alpha>>)'),
153             ('fundamental', r'%(<<fundamental:alpha>>)'),
154             ('parameter', r'@<<param_name:alpha>>'),
155             ('function_call', r'<<symbol_name:alpha>>\(\)'),
156         ]
157
158         super(DocstringScanner, self).__init__(specs)
159
160 class MallardFormatter(object):
161     def __init__(self, transformer):
162         self._transformer = transformer
163         self._scanner = DocstringScanner()
164
165     def escape(self, text):
166         return saxutils.escape(text)
167
168     def format(self, node, doc):
169         if doc is None:
170             return ''
171
172         result = ''
173         for para in doc.split('\n\n'):
174             result += '<p>'
175             result += self.format_inline(node, para)
176             result += '</p>'
177         return result
178
179     def _resolve_type(self, ident):
180         try:
181             matches = self._transformer.split_ctype_namespaces(ident)
182         except ValueError:
183             return None
184         for namespace, name in matches:
185             node = namespace.get(name)
186             if node:
187                 return node
188         return None
189
190     def _resolve_symbol(self, symbol):
191         try:
192             matches = self._transformer.split_csymbol_namespaces(symbol)
193         except ValueError:
194             return None
195         for namespace, name in matches:
196             node = namespace.get_by_symbol(symbol)
197             if node:
198                 return node
199         return None
200
201     def _find_thing(self, list_, name):
202         for item in list_:
203             if item.name == name:
204                 return item
205         raise KeyError("Could not find %s" % (name, ))
206
207     def _process_other(self, node, match, props):
208         return self.escape(match)
209
210     def _process_property(self, node, match, props):
211         type_node = self._resolve_type(props['type_name'])
212         if type_node is None:
213             return match
214
215         try:
216             prop = self._find_thing(type_node.properties, props['property_name'])
217         except (AttributeError, KeyError), e:
218             return match
219
220         return self.format_xref(prop)
221
222     def _process_signal(self, node, match, props):
223         type_node = self._resolve_type(props['type_name'])
224         if type_node is None:
225             return match
226
227         try:
228             signal = self._find_thing(type_node.signals, props['signal_name'])
229         except (AttributeError, KeyError), e:
230             return match
231
232         return self.format_xref(signal)
233
234     def _process_type_name(self, node, match, props):
235         type_ = self._resolve_type(props['type_name'])
236         if type_ is None:
237             return match
238
239         return self.format_xref(type_)
240
241     def _process_fundamental(self, node, match, props):
242         return self.fundamentals.get(props['fundamental'], match)
243
244     def _process_parameter(self, node, match, props):
245         try:
246             parameter = node.get_parameter(props['param_name'])
247         except (AttributeError, ValueError), e:
248             return match
249
250         return '<code>%s</code>' % (self.format_parameter_name(node, parameter), )
251
252     def _process_function_call(self, node, match, props):
253         func = self._resolve_symbol(props['symbol_name'])
254         if func is None:
255             return match
256
257         return self.format_xref(func)
258
259     def _process_token(self, node, tok):
260         kind, match, props = tok
261
262         dispatch = {
263             'other': self._process_other,
264             'property': self._process_property,
265             'signal': self._process_signal,
266             'type_name': self._process_type_name,
267             'fundamental': self._process_fundamental,
268             'parameter': self._process_parameter,
269             'function_call': self._process_function_call,
270         }
271
272         return dispatch[kind](node, match, props)
273
274     def format_inline(self, node, para):
275         tokens = self._scanner.scan(para)
276         words = [self._process_token(node, tok) for tok in tokens]
277         return ''.join(words)
278
279     def format_parameter_name(self, node, parameter):
280         return parameter.argname
281
282     def format_function_name(self, func):
283         raise NotImplementedError
284
285     def format_type(self, type_):
286         raise NotImplementedError
287
288     def format_page_name(self, node):
289         namespace = node.namespace
290         if isinstance(node, ast.Namespace):
291             return 'Index'
292         elif isinstance(node, ast.Function):
293             return self.format_function_name(node)
294         elif isinstance(node, ast.Property) and node.parent is not None:
295             return '%s.%s:%s' % (namespace.name, node.parent.name, node.name)
296         elif isinstance(node, ast.Signal) and node.parent is not None:
297             return '%s.%s::%s' % (namespace.name, node.parent.name, node.name)
298         elif isinstance(node, ast.VFunction) and node.parent is not None:
299             return '%s.%s::%s' % (namespace.name, node.parent.name, node.name)
300         else:
301             return make_page_id(node)
302
303     def format_xref(self, node):
304         return '<link xref="%s">%s</link>' % (make_page_id(node), self.format_page_name(node))
305
306     def format_property_flags(self, property_, construct_only=False):
307         flags = []
308         if property_.readable and not construct_only:
309             flags.append("Read")
310         if property_.writable and not construct_only:
311             flags.append("Write")
312         if property_.construct:
313             flags.append("Construct")
314         if property_.construct_only:
315             flags.append("Construct Only")
316
317         return " / ".join(flags)
318
319     def to_underscores(self, string):
320         return to_underscores(string)
321
322     def get_class_hierarchy(self, node):
323         parent_chain = [node]
324
325         while node.parent:
326             node = self._transformer.lookup_giname(str(node.parent))
327             parent_chain.append(node)
328
329         parent_chain.reverse()
330         return parent_chain
331
332 class MallardFormatterC(MallardFormatter):
333     language = "C"
334
335     fundamentals = {
336         "TRUE": "TRUE",
337         "FALSE": "FALSE",
338         "NULL": "NULL",
339     }
340
341     def format_type(self, type_):
342         if isinstance(type_, ast.Array):
343             return self.format_type(type_.element_type) + '*'
344         elif type_.ctype is not None:
345             return type_.ctype
346         else:
347             return type_.target_fundamental
348
349     def format_function_name(self, func):
350         return func.symbol
351
352 class MallardFormatterPython(MallardFormatter):
353     language = "Python"
354
355     fundamentals = {
356         "TRUE": "True",
357         "FALSE": "False",
358         "NULL": "None",
359     }
360
361     def is_method(self, node):
362         if getattr(node, "is_method", False):
363             return True
364
365         if isinstance(node, (ast.VFunction)):
366             return True
367
368         return False
369
370     def format_parameter_name(self, node, parameter):
371         # Force "self" for the first parameter of a method
372         if self.is_method(node) and parameter is node.parameters[0]:
373             return "self"
374         else:
375             return parameter.argname
376
377     def format_fundamental_type(self, name):
378         fundamental_types = {
379             "utf8": "unicode",
380             "gunichar": "unicode",
381             "gchar": "str",
382             "guchar": "str",
383             "gboolean": "bool",
384             "gint": "int",
385             "guint": "int",
386             "glong": "int",
387             "gulong": "int",
388             "gint64": "int",
389             "guint64": "int",
390             "gfloat": "float",
391             "gdouble": "float",
392             "gchararray": "str",
393             "GParam": "GLib.Param",
394             "PyObject": "object",
395             "GStrv": "[str]",
396             "GVariant": "GLib.Variant",
397             }
398
399         return fundamental_types.get(name, name)
400
401     def format_type(self, type_):
402         if isinstance(type_, ast.Array):
403             return '[' + self.format_type(type_.element_type) + ']'
404         elif isinstance(type_, ast.Map):
405             return '{%s: %s}' % (self.format_type(type_.key_type),
406                                  self.format_type(type_.value_type))
407         elif type_.target_giname is not None:
408             return type_.target_giname
409         else:
410             return self.format_fundamental_type(type_.target_fundamental)
411
412     def format_function_name(self, func):
413         if func.parent is not None:
414             return "%s.%s" % (func.parent.name, func.name)
415         else:
416             return func.name
417
418 LANGUAGES = {
419     "c": MallardFormatterC,
420     "python": MallardFormatterPython,
421 }
422
423 class MallardWriter(object):
424     def __init__(self, transformer, language):
425         self._transformer = transformer
426
427         try:
428             formatter_class = LANGUAGES[language.lower()]
429         except KeyError:
430             raise SystemExit("Unsupported language: %s" % (language, ))
431
432         self._formatter = formatter_class(self._transformer)
433         self._language = self._formatter.language
434
435         self._lookup = self._get_template_lookup()
436
437     def _get_template_lookup(self):
438         if 'UNINSTALLED_INTROSPECTION_SRCDIR' in os.environ:
439             top_srcdir = os.environ['UNINSTALLED_INTROSPECTION_SRCDIR']
440             srcdir = os.path.join(top_srcdir, 'giscanner')
441         else:
442             srcdir = os.path.dirname(__file__)
443
444         template_dir = os.path.join(srcdir, 'doctemplates', self._language)
445
446         return TemplateLookup(directories=[template_dir],
447                               module_directory=tempfile.gettempdir(),
448                               output_encoding='utf-8')
449
450     def write(self, output):
451         nodes = [self._transformer.namespace]
452         for node in self._transformer.namespace.itervalues():
453             if isinstance(node, ast.Function) and node.moved_to is not None:
454                 continue
455             if getattr(node, 'disguised', False):
456                 continue
457             if isinstance(node, ast.Record) and \
458                self._language == 'Python' and \
459                node.is_gtype_struct_for is not None:
460                 continue
461             nodes.append(node)
462             if isinstance(node, (ast.Class, ast.Interface, ast.Record)):
463                 nodes += getattr(node, 'methods', [])
464                 nodes += getattr(node, 'static_methods', [])
465                 nodes += getattr(node, 'virtual_methods', [])
466                 nodes += getattr(node, 'properties', [])
467                 nodes += getattr(node, 'signals', [])
468                 if self._language == 'C':
469                     nodes += getattr(node, 'constructors', [])
470         for node in nodes:
471             self._render_node(node, output)
472
473     def _render_node(self, node, output):
474         namespace = self._transformer.namespace
475
476         template_name = make_template_name(node, self._language)
477         page_id = make_page_id(node)
478
479         template = self._lookup.get_template(template_name)
480         result = template.render(namespace=namespace,
481                                  node=node,
482                                  page_id=page_id,
483                                  formatter=self._formatter)
484
485         output_file_name = os.path.join(os.path.abspath(output),
486                                         page_id + '.page')
487         fp = open(output_file_name, 'w')
488         fp.write(result)
489         fp.close()