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.template import Template
30 from mako.lookup import TemplateLookup
33 from .utils import to_underscores
35 def make_page_id(node):
36 if isinstance(node, ast.Namespace):
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)
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)
58 return '%s.%s' % (namespace.name, node.name)
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)):
65 elif isinstance(node, ast.Record):
67 elif isinstance(node, ast.Function):
68 node_kind = 'function'
69 elif isinstance(node, ast.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:
75 elif isinstance(node, ast.VFunction) and node.parent is not None:
80 return 'mallard-%s-%s.tmpl' % (language, node_kind)
82 class TemplatedScanner(object):
83 def __init__(self, specs):
84 self.specs = self.unmangle_specs(specs)
85 self.regex = self.make_regex(self.specs)
87 def unmangle_specs(self, specs):
88 mangled = re.compile('<<([a-zA-Z_:]+)>>')
89 specdict = dict((name.lstrip('!'), spec) for name, spec in specs)
91 def unmangle(spec, name=None):
92 def replace_func(match):
93 child_spec_name = match.group(1)
95 if ':' in child_spec_name:
96 pattern_name, child_spec_name = child_spec_name.split(':', 1)
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)
108 return mangled.sub(replace_func, spec)
110 return [(name, unmangle(spec, name)) for name, spec in specs]
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)
117 def get_properties(self, name, match):
118 groupdict = match.groupdict()
119 properties = {name: groupdict.pop(name)}
121 for group, value in groupdict.iteritems():
122 if group.startswith(name):
123 key = group[len(name):]
124 properties[key] = value
127 def scan(self, text):
130 match = self.regex.search(text, pos)
134 start = match.start()
136 yield ('other', text[pos:start], None)
139 name = match.lastgroup
140 yield (name, match.group(0), self.get_properties(name, match))
143 yield ('other', text[pos:], None)
145 class DocstringScanner(TemplatedScanner):
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>>\(\)'),
158 super(DocstringScanner, self).__init__(specs)
160 class MallardFormatter(object):
161 def __init__(self, transformer):
162 self._transformer = transformer
163 self._scanner = DocstringScanner()
165 def escape(self, text):
166 return saxutils.escape(text)
168 def format(self, node, doc):
173 for para in doc.split('\n\n'):
175 result += self.format_inline(node, para)
179 def _resolve_type(self, ident):
181 matches = self._transformer.split_ctype_namespaces(ident)
184 for namespace, name in matches:
185 node = namespace.get(name)
190 def _resolve_symbol(self, symbol):
192 matches = self._transformer.split_csymbol_namespaces(symbol)
195 for namespace, name in matches:
196 node = namespace.get_by_symbol(symbol)
201 def _find_thing(self, list_, name):
203 if item.name == name:
205 raise KeyError("Could not find %s" % (name, ))
207 def _process_other(self, node, match, props):
208 return self.escape(match)
210 def _process_property(self, node, match, props):
211 type_node = self._resolve_type(props['type_name'])
212 if type_node is None:
216 prop = self._find_thing(type_node.properties, props['property_name'])
217 except (AttributeError, KeyError), e:
220 return self.format_xref(prop)
222 def _process_signal(self, node, match, props):
223 type_node = self._resolve_type(props['type_name'])
224 if type_node is None:
228 signal = self._find_thing(type_node.signals, props['signal_name'])
229 except (AttributeError, KeyError), e:
232 return self.format_xref(signal)
234 def _process_type_name(self, node, match, props):
235 type_ = self._resolve_type(props['type_name'])
239 return self.format_xref(type_)
241 def _process_fundamental(self, node, match, props):
242 return self.fundamentals.get(props['fundamental'], match)
244 def _process_parameter(self, node, match, props):
246 parameter = node.get_parameter(props['param_name'])
247 except (AttributeError, ValueError), e:
250 return '<code>%s</code>' % (self.format_parameter_name(node, parameter), )
252 def _process_function_call(self, node, match, props):
253 func = self._resolve_symbol(props['symbol_name'])
257 return self.format_xref(func)
259 def _process_token(self, node, tok):
260 kind, match, props = tok
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,
272 return dispatch[kind](node, match, props)
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)
279 def format_parameter_name(self, node, parameter):
280 return parameter.argname
282 def format_function_name(self, func):
283 raise NotImplementedError
285 def format_type(self, type_):
286 raise NotImplementedError
288 def format_page_name(self, node):
289 namespace = node.namespace
290 if isinstance(node, ast.Namespace):
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)
301 return make_page_id(node)
303 def format_xref(self, node):
304 return '<link xref="%s">%s</link>' % (make_page_id(node), self.format_page_name(node))
306 def format_property_flags(self, property_, construct_only=False):
308 if property_.readable and not construct_only:
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")
317 return " / ".join(flags)
319 def to_underscores(self, string):
320 return to_underscores(string)
322 def get_class_hierarchy(self, node):
323 parent_chain = [node]
326 node = self._transformer.lookup_giname(str(node.parent))
327 parent_chain.append(node)
329 parent_chain.reverse()
332 class MallardFormatterC(MallardFormatter):
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:
347 return type_.target_fundamental
349 def format_function_name(self, func):
352 class MallardFormatterPython(MallardFormatter):
361 def is_method(self, node):
362 if getattr(node, "is_method", False):
365 if isinstance(node, (ast.VFunction)):
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]:
375 return parameter.argname
377 def format_fundamental_type(self, name):
378 fundamental_types = {
380 "gunichar": "unicode",
393 "GParam": "GLib.Param",
394 "PyObject": "object",
396 "GVariant": "GLib.Variant",
399 return fundamental_types.get(name, name)
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
410 return self.format_fundamental_type(type_.target_fundamental)
412 def format_function_name(self, func):
413 if func.parent is not None:
414 return "%s.%s" % (func.parent.name, func.name)
419 "c": MallardFormatterC,
420 "python": MallardFormatterPython,
423 class MallardWriter(object):
424 def __init__(self, transformer, language):
425 self._transformer = transformer
428 formatter_class = LANGUAGES[language.lower()]
430 raise SystemExit("Unsupported language: %s" % (language, ))
432 self._formatter = formatter_class(self._transformer)
433 self._language = self._formatter.language
435 self._lookup = self._get_template_lookup()
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')
442 srcdir = os.path.dirname(__file__)
444 template_dir = os.path.join(srcdir, 'doctemplates', self._language)
446 return TemplateLookup(directories=[template_dir],
447 module_directory=tempfile.gettempdir(),
448 output_encoding='utf-8')
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:
455 if getattr(node, 'disguised', False):
457 if isinstance(node, ast.Record) and \
458 self._language == 'Python' and \
459 node.is_gtype_struct_for is not None:
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', [])
471 self._render_node(node, output)
473 def _render_node(self, node, output):
474 namespace = self._transformer.namespace
476 template_name = make_template_name(node, self._language)
477 page_id = make_page_id(node)
479 template = self._lookup.get_template(template_name)
480 result = template.render(namespace=namespace,
483 formatter=self._formatter)
485 output_file_name = os.path.join(os.path.abspath(output),
487 fp = open(output_file_name, 'w')