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