mallardwriter: Fix formatting the function name
[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
31 from . import ast
32 from .utils import to_underscores
33
34 def make_page_id(namespace, node):
35     if isinstance(node, ast.Namespace):
36         return 'index'
37     elif isinstance(node, (ast.Class, ast.Interface)):
38         return '%s.%s' % (namespace.name, node.name)
39     elif isinstance(node, ast.Record):
40         return '%s.%s' % (namespace.name, node.name)
41     elif isinstance(node, ast.Function):
42         if node.parent is not None:
43             return '%s.%s.%s' % (namespace.name, node.parent.name, node.name)
44         else:
45             return '%s.%s' % (namespace.name, node.name)
46     elif isinstance(node, ast.Enum):
47         return '%s.%s' % (namespace.name, node.name)
48     elif isinstance(node, ast.Property) and node.parent is not None:
49         return '%s.%s-%s' % (namespace.name, node.parent.name, node.name)
50     elif isinstance(node, ast.Signal) and node.parent is not None:
51         return '%s.%s-%s' % (namespace.name, node.parent.name, node.name)
52     elif isinstance(node, ast.VFunction) and node.parent is not None:
53         return '%s.%s-%s' % (namespace.name, node.parent.name, node.name)
54     else:
55         return '%s.%s' % (namespace.name, node.name)
56
57 def make_template_name(node, language):
58     if isinstance(node, ast.Namespace):
59         node_kind = 'namespace'
60     elif isinstance(node, (ast.Class, ast.Interface)):
61         node_kind = 'class'
62     elif isinstance(node, ast.Record):
63         node_kind = 'record'
64     elif isinstance(node, ast.Function):
65         node_kind = 'function'
66     elif isinstance(node, ast.Enum):
67         node_kind = 'enum'
68     elif isinstance(node, ast.Property) and node.parent is not None:
69         node_kind = 'property'
70     elif isinstance(node, ast.Signal) and node.parent is not None:
71         node_kind = 'signal'
72     elif isinstance(node, ast.VFunction) and node.parent is not None:
73         node_kind = 'vfunc'
74     else:
75         node_kind = 'default'
76
77     return 'mallard-%s-%s.tmpl' % (language, node_kind)
78
79 class TemplatedScanner(object):
80     def __init__(self, specs):
81         self.specs = self.unmangle_specs(specs)
82         self.regex = self.make_regex(self.specs)
83
84     def unmangle_specs(self, specs):
85         mangled = re.compile('<<([a-zA-Z_:]+)>>')
86         specdict = dict((name.lstrip('!'), spec) for name, spec in specs)
87
88         def unmangle(spec, name=None):
89             def replace_func(match):
90                 child_spec_name = match.group(1)
91
92                 if ':' in child_spec_name:
93                     pattern_name, child_spec_name = child_spec_name.split(':', 1)
94                 else:
95                     pattern_name = None
96
97                 child_spec = specdict[child_spec_name]
98                 # Force all child specs of this one to be unnamed
99                 unmangled = unmangle(child_spec, None)
100                 if pattern_name and name:
101                     return '(?P<%s_%s>%s)' % (name, pattern_name, unmangled)
102                 else:
103                     return unmangled
104
105             return mangled.sub(replace_func, spec)
106
107         return [(name, unmangle(spec, name)) for name, spec in specs]
108
109     def make_regex(self, specs):
110         regex = '|'.join('(?P<%s>%s)' % (name, spec) for name, spec in specs
111                          if not name.startswith('!'))
112         return re.compile(regex)
113
114     def get_properties(self, name, match):
115         groupdict = match.groupdict()
116         properties = {name: groupdict.pop(name)}
117         name = name + "_"
118         for group, value in groupdict.iteritems():
119             if group.startswith(name):
120                 key = group[len(name):]
121                 properties[key] = value
122         return properties
123
124     def scan(self, text):
125         pos = 0
126         while True:
127             match = self.regex.search(text, pos)
128             if match is None:
129                 break
130
131             start = match.start()
132             if start > pos:
133                 yield ('other', text[pos:start], None)
134
135             pos = match.end()
136             name = match.lastgroup
137             yield (name, match.group(0), self.get_properties(name, match))
138
139         if pos < len(text):
140             yield ('other', text[pos:], None)
141
142 class DocstringScanner(TemplatedScanner):
143     def __init__(self):
144         specs = [
145             ('!alpha', r'[a-zA-Z0-9_]+'),
146             ('!alpha_dash', r'[a-zA-Z0-9_-]+'),
147             ('property', r'#<<type_name:alpha>>:(<<property_name:alpha_dash>>)'),
148             ('signal', r'#<<type_name:alpha>>::(<<signal_name:alpha_dash>>)'),
149             ('type_name', r'#(<<type_name:alpha>>)'),
150             ('fundamental', r'%(<<fundamental:alpha>>)'),
151             ('function_call', r'<<symbol_name:alpha>>\(\)'),
152         ]
153
154         super(DocstringScanner, self).__init__(specs)
155
156 class MallardFormatter(object):
157     def __init__(self, transformer):
158         self._transformer = transformer
159         self._scanner = DocstringScanner()
160
161     def escape(self, text):
162         return saxutils.escape(text)
163
164     def format(self, doc):
165         if doc is None:
166             return ''
167
168         result = ''
169         for para in doc.split('\n\n'):
170             result += '<p>'
171             result += self.format_inline(para)
172             result += '</p>'
173         return result
174
175     def _process_other(self, namespace, match, props):
176         return self.escape(match)
177
178     def _find_thing(self, list_, name):
179         for item in list_:
180             if item.name == name:
181                 return item
182         raise KeyError("Could not find %s" % (name, ))
183
184     def _process_property(self, namespace, match, props):
185         type_node = namespace.get_by_ctype(props['type_name'])
186         if type_node is None:
187             return match
188
189         try:
190             node = self._find_thing(type_node.properties, props['property_name'])
191         except (AttributeError, KeyError), e:
192             return match
193
194         xref_name = "%s.%s:%s" % (namespace.name, type_node.name, node.name)
195         return '<link xref="%s">%s</link>' % (make_page_id(namespace, node), xref_name)
196
197     def _process_signal(self, namespace, match, props):
198         type_node = namespace.get_by_ctype(props['type_name'])
199         if type_node is None:
200             return match
201
202         try:
203             node = self._find_thing(type_node.signals, props['signal_name'])
204         except (AttributeError, KeyError), e:
205             return match
206
207         xref_name = "%s.%s::%s" % (namespace.name, type_node.name, node.name)
208         return '<link xref="%s">%s</link>' % (make_page_id(namespace, node), xref_name)
209
210     def _process_type_name(self, namespace, match, props):
211         node = namespace.get_by_ctype(props['type_name'])
212         if node is None:
213             return match
214         xref_name = "%s.%s" % (namespace.name, node.name)
215         return '<link xref="%s">%s</link>' % (make_page_id(namespace, node), xref_name)
216
217     def _process_function_call(self, namespace, match, props):
218         node = namespace.get_by_symbol(props['symbol_name'])
219         if node is None:
220             return match
221
222         return '<link xref="%s">%s</link>' % (make_page_id(namespace, node),
223                                               self.format_function_name(node))
224
225     def _process_fundamental(self, namespace, match, props):
226         raise NotImplementedError
227
228     def _process_token(self, tok):
229         namespace = self._transformer.namespace
230
231         kind, match, props = tok
232
233         dispatch = {
234             'other': self._process_other,
235             'property': self._process_property,
236             'signal': self._process_signal,
237             'type_name': self._process_type_name,
238             'function_call': self._process_function_call,
239             'fundamental': self._process_fundamental,
240         }
241
242         return dispatch[kind](namespace, match, props)
243
244     def format_inline(self, para):
245         tokens = self._scanner.scan(para)
246         words = [self._process_token(tok) for tok in tokens]
247         return ''.join(words)
248
249     def format_function_name(self, func):
250         raise NotImplementedError
251
252     def format_type(self, type_):
253         raise NotImplementedError
254
255     def format_property_flags(self, property_):
256         flags = []
257         if property_.readable:
258             flags.append("Read")
259         if property_.writable:
260             flags.append("Write")
261         if property_.construct:
262             flags.append("Construct")
263         if property_.construct_only:
264             flags.append("Construct Only")
265
266         return " / ".join(flags)
267
268     def to_underscores(self, string):
269         return to_underscores(string)
270
271     def get_class_hierarchy(self, node):
272         parent_chain = [node]
273
274         while node.parent:
275             node = self._transformer.lookup_giname(str(node.parent))
276             parent_chain.append(node)
277
278         parent_chain.reverse()
279         return parent_chain
280
281 class MallardFormatterC(MallardFormatter):
282     language = "C"
283
284     def format_type(self, type_):
285         if isinstance(type_, ast.Array):
286             return self.format_type(type_.element_type) + '*'
287         elif type_.ctype is not None:
288             return type_.ctype
289         else:
290             return type_.target_fundamental
291
292     def format_function_name(self, func):
293         return func.symbol
294
295     def _process_fundamental(self, namespace, match, props):
296         return props['fundamental']
297
298 class MallardFormatterPython(MallardFormatter):
299     language = "Python"
300
301     def format_type(self, type_):
302         if isinstance(type_, ast.Array):
303             return '[' + self.format_type(type_.element_type) + ']'
304         elif isinstance(type_, ast.Map):
305             return '{%s: %s}' % (self.format_type(type_.key_type),
306                                  self.format_type(type_.value_type))
307         elif type_.target_giname is not None:
308             return type_.target_giname
309         else:
310             return type_.target_fundamental
311
312     def format_function_name(self, func):
313         if func.parent is not None:
314             return "%s.%s" % (func.parent.name, func.name)
315         else:
316             return func.name
317
318     def _process_fundamental(self, namespace, match, props):
319         translation = {
320             "NULL": "None",
321             "TRUE": "True",
322             "FALSE": "False",
323         }
324
325         return translation.get(props['fundamental'], match)
326
327 LANGUAGES = {
328     "c": MallardFormatterC,
329     "python": MallardFormatterPython,
330 }
331
332 class MallardWriter(object):
333     def __init__(self, transformer, language):
334         self._transformer = transformer
335
336         try:
337             formatter_class = LANGUAGES[language.lower()]
338         except KeyError:
339             raise SystemExit("Unsupported language: %s" % (language, ))
340
341         self._formatter = formatter_class(self._transformer)
342         self._language = self._formatter.language
343
344     def write(self, output):
345         nodes = [self._transformer.namespace]
346         for node in self._transformer.namespace.itervalues():
347             if isinstance(node, ast.Function) and node.moved_to is not None:
348                 continue
349             if getattr(node, 'disguised', False):
350                 continue
351             if isinstance(node, ast.Record) and \
352                self._language == 'Python' and \
353                node.is_gtype_struct_for is not None:
354                 continue
355             nodes.append(node)
356             if isinstance(node, (ast.Class, ast.Interface, ast.Record)):
357                 nodes += getattr(node, 'methods', [])
358                 nodes += getattr(node, 'static_methods', [])
359                 nodes += getattr(node, 'virtual_methods', [])
360                 nodes += getattr(node, 'properties', [])
361                 nodes += getattr(node, 'signals', [])
362                 if self._language == 'C':
363                     nodes += getattr(node, 'constructors', [])
364         for node in nodes:
365             self._render_node(node, output)
366
367     def _render_node(self, node, output):
368         namespace = self._transformer.namespace
369
370         if 'UNINSTALLED_INTROSPECTION_SRCDIR' in os.environ:
371             top_srcdir = os.environ['UNINSTALLED_INTROSPECTION_SRCDIR']
372             template_dir = os.path.join(top_srcdir, 'giscanner')
373         else:
374             template_dir = os.path.dirname(__file__)
375
376         template_name = make_template_name(node, self._language)
377         page_id = make_page_id(namespace, node)
378
379         file_name = os.path.join(template_dir, template_name)
380         file_name = os.path.abspath(file_name)
381         template = Template(filename=file_name, output_encoding='utf-8',
382                             module_directory=tempfile.gettempdir())
383         result = template.render(namespace=namespace,
384                                  node=node,
385                                  page_id=page_id,
386                                  formatter=self._formatter)
387
388         output_file_name = os.path.join(os.path.abspath(output),
389                                         page_id + '.page')
390         fp = open(output_file_name, 'w')
391         fp.write(result)
392         fp.close()