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
32 from .utils import to_underscores
34 def make_page_id(namespace, node):
35 if isinstance(node, ast.Namespace):
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)
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)
55 return '%s.%s' % (namespace.name, node.name)
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)):
62 elif isinstance(node, ast.Record):
64 elif isinstance(node, ast.Function):
65 node_kind = 'function'
66 elif isinstance(node, ast.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:
72 elif isinstance(node, ast.VFunction) and node.parent is not None:
77 return 'mallard-%s-%s.tmpl' % (language, node_kind)
79 class TemplatedScanner(object):
80 def __init__(self, specs):
81 self.specs = self.unmangle_specs(specs)
82 self.regex = self.make_regex(self.specs)
84 def unmangle_specs(self, specs):
85 mangled = re.compile('<<([a-zA-Z_:]+)>>')
86 specdict = dict((name.lstrip('!'), spec) for name, spec in specs)
88 def unmangle(spec, name=None):
89 def replace_func(match):
90 child_spec_name = match.group(1)
92 if ':' in child_spec_name:
93 pattern_name, child_spec_name = child_spec_name.split(':', 1)
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)
105 return mangled.sub(replace_func, spec)
107 return [(name, unmangle(spec, name)) for name, spec in specs]
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)
114 def get_properties(self, name, match):
115 groupdict = match.groupdict()
116 properties = {name: groupdict.pop(name)}
118 for group, value in groupdict.iteritems():
119 if group.startswith(name):
120 key = group[len(name):]
121 properties[key] = value
124 def scan(self, text):
127 match = self.regex.search(text, pos)
131 start = match.start()
133 yield ('other', text[pos:start], None)
136 name = match.lastgroup
137 yield (name, match.group(0), self.get_properties(name, match))
140 yield ('other', text[pos:], None)
142 class DocstringScanner(TemplatedScanner):
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>>\(\)'),
154 super(DocstringScanner, self).__init__(specs)
156 class MallardFormatter(object):
157 def __init__(self, transformer):
158 self._transformer = transformer
159 self._scanner = DocstringScanner()
161 def escape(self, text):
162 return saxutils.escape(text)
164 def format(self, doc):
169 for para in doc.split('\n\n'):
171 result += self.format_inline(para)
175 def _process_other(self, namespace, match, props):
176 return self.escape(match)
178 def _find_thing(self, list_, name):
180 if item.name == name:
182 raise KeyError("Could not find %s" % (name, ))
184 def _process_property(self, namespace, match, props):
185 type_node = namespace.get_by_ctype(props['type_name'])
186 if type_node is None:
190 node = self._find_thing(type_node.properties, props['property_name'])
191 except (AttributeError, KeyError), e:
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)
197 def _process_signal(self, namespace, match, props):
198 type_node = namespace.get_by_ctype(props['type_name'])
199 if type_node is None:
203 node = self._find_thing(type_node.signals, props['signal_name'])
204 except (AttributeError, KeyError), e:
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)
210 def _process_type_name(self, namespace, match, props):
211 node = namespace.get_by_ctype(props['type_name'])
214 xref_name = "%s.%s" % (namespace.name, node.name)
215 return '<link xref="%s">%s</link>' % (make_page_id(namespace, node), xref_name)
217 def _process_function_call(self, namespace, match, props):
218 node = namespace.get_by_symbol(props['symbol_name'])
222 return '<link xref="%s">%s</link>' % (make_page_id(namespace, node),
223 self.format_function_name(node))
225 def _process_fundamental(self, namespace, match, props):
226 raise NotImplementedError
228 def _process_token(self, tok):
229 namespace = self._transformer.namespace
231 kind, match, props = tok
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,
242 return dispatch[kind](namespace, match, props)
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)
249 def format_function_name(self, func):
250 raise NotImplementedError
252 def format_type(self, type_):
253 raise NotImplementedError
255 def format_property_flags(self, property_):
257 if property_.readable:
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")
266 return " / ".join(flags)
268 def to_underscores(self, string):
269 return to_underscores(string)
271 def get_class_hierarchy(self, node):
272 parent_chain = [node]
275 node = self._transformer.lookup_giname(str(node.parent))
276 parent_chain.append(node)
278 parent_chain.reverse()
281 class MallardFormatterC(MallardFormatter):
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:
290 return type_.target_fundamental
292 def format_function_name(self, func):
295 def _process_fundamental(self, namespace, match, props):
296 return props['fundamental']
298 class MallardFormatterPython(MallardFormatter):
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
310 return type_.target_fundamental
312 def format_function_name(self, func):
313 if func.parent is not None:
314 return "%s.%s" % (func.parent.name, func.name)
318 def _process_fundamental(self, namespace, match, props):
325 return translation.get(props['fundamental'], match)
328 "c": MallardFormatterC,
329 "python": MallardFormatterPython,
332 class MallardWriter(object):
333 def __init__(self, transformer, language):
334 self._transformer = transformer
337 formatter_class = LANGUAGES[language.lower()]
339 raise SystemExit("Unsupported language: %s" % (language, ))
341 self._formatter = formatter_class(self._transformer)
342 self._language = self._formatter.language
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:
349 if getattr(node, 'disguised', False):
351 if isinstance(node, ast.Record) and \
352 self._language == 'Python' and \
353 node.is_gtype_struct_for is not None:
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', [])
365 self._render_node(node, output)
367 def _render_node(self, node, output):
368 namespace = self._transformer.namespace
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')
374 template_dir = os.path.dirname(__file__)
376 template_name = make_template_name(node, self._language)
377 page_id = make_page_id(namespace, node)
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,
386 formatter=self._formatter)
388 output_file_name = os.path.join(os.path.abspath(output),
390 fp = open(output_file_name, 'w')