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(node):
35 if isinstance(node, ast.Namespace):
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)
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)
57 return '%s.%s' % (namespace.name, node.name)
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)):
64 elif isinstance(node, ast.Record):
66 elif isinstance(node, ast.Function):
67 node_kind = 'function'
68 elif isinstance(node, ast.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:
74 elif isinstance(node, ast.VFunction) and node.parent is not None:
79 return 'mallard-%s-%s.tmpl' % (language, node_kind)
81 class TemplatedScanner(object):
82 def __init__(self, specs):
83 self.specs = self.unmangle_specs(specs)
84 self.regex = self.make_regex(self.specs)
86 def unmangle_specs(self, specs):
87 mangled = re.compile('<<([a-zA-Z_:]+)>>')
88 specdict = dict((name.lstrip('!'), spec) for name, spec in specs)
90 def unmangle(spec, name=None):
91 def replace_func(match):
92 child_spec_name = match.group(1)
94 if ':' in child_spec_name:
95 pattern_name, child_spec_name = child_spec_name.split(':', 1)
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)
107 return mangled.sub(replace_func, spec)
109 return [(name, unmangle(spec, name)) for name, spec in specs]
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)
116 def get_properties(self, name, match):
117 groupdict = match.groupdict()
118 properties = {name: groupdict.pop(name)}
120 for group, value in groupdict.iteritems():
121 if group.startswith(name):
122 key = group[len(name):]
123 properties[key] = value
126 def scan(self, text):
129 match = self.regex.search(text, pos)
133 start = match.start()
135 yield ('other', text[pos:start], None)
138 name = match.lastgroup
139 yield (name, match.group(0), self.get_properties(name, match))
142 yield ('other', text[pos:], None)
144 class DocstringScanner(TemplatedScanner):
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>>\(\)'),
156 super(DocstringScanner, self).__init__(specs)
158 class MallardFormatter(object):
159 def __init__(self, transformer):
160 self._transformer = transformer
161 self._scanner = DocstringScanner()
163 def escape(self, text):
164 return saxutils.escape(text)
166 def format(self, doc):
171 for para in doc.split('\n\n'):
173 result += self.format_inline(para)
177 def _resolve_type(self, ident):
179 matches = self._transformer.split_ctype_namespaces(ident)
182 for namespace, name in matches:
183 node = namespace.get(name)
188 def _resolve_symbol(self, symbol):
190 matches = self._transformer.split_csymbol_namespaces(symbol)
193 for namespace, name in matches:
194 node = namespace.get_by_symbol(symbol)
199 def _find_thing(self, list_, name):
201 if item.name == name:
203 raise KeyError("Could not find %s" % (name, ))
205 def _process_other(self, namespace, match, props):
206 return self.escape(match)
208 def _process_property(self, namespace, match, props):
209 type_node = self._resolve_type(props['type_name'])
210 if type_node is None:
214 node = self._find_thing(type_node.properties, props['property_name'])
215 except (AttributeError, KeyError), e:
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)
221 def _process_signal(self, namespace, match, props):
222 type_node = self._resolve_type(props['type_name'])
223 if type_node is None:
227 node = self._find_thing(type_node.signals, props['signal_name'])
228 except (AttributeError, KeyError), e:
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)
234 def _process_type_name(self, namespace, match, props):
235 node = self._resolve_type(props['type_name'])
238 xref_name = "%s.%s" % (node.namespace.name, node.name)
239 return '<link xref="%s">%s</link>' % (make_page_id(node), xref_name)
241 def _process_function_call(self, namespace, match, props):
242 node = self._resolve_symbol(props['symbol_name'])
246 return '<link xref="%s">%s</link>' % (make_page_id(node),
247 self.format_function_name(node))
249 def _process_fundamental(self, namespace, match, props):
250 return self.fundamentals.get(props['fundamental'], match)
252 def _process_token(self, tok):
253 namespace = self._transformer.namespace
255 kind, match, props = tok
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,
266 return dispatch[kind](namespace, match, props)
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)
273 def format_function_name(self, func):
274 raise NotImplementedError
276 def format_type(self, type_):
277 raise NotImplementedError
279 def format_property_flags(self, property_, construct_only=False):
281 if property_.readable and not construct_only:
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")
290 return " / ".join(flags)
292 def to_underscores(self, string):
293 return to_underscores(string)
295 def get_class_hierarchy(self, node):
296 parent_chain = [node]
299 node = self._transformer.lookup_giname(str(node.parent))
300 parent_chain.append(node)
302 parent_chain.reverse()
305 class MallardFormatterC(MallardFormatter):
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:
320 return type_.target_fundamental
322 def format_function_name(self, func):
325 class MallardFormatterPython(MallardFormatter):
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
343 return type_.target_fundamental
345 def format_function_name(self, func):
346 if func.parent is not None:
347 return "%s.%s" % (func.parent.name, func.name)
352 "c": MallardFormatterC,
353 "python": MallardFormatterPython,
356 class MallardWriter(object):
357 def __init__(self, transformer, language):
358 self._transformer = transformer
361 formatter_class = LANGUAGES[language.lower()]
363 raise SystemExit("Unsupported language: %s" % (language, ))
365 self._formatter = formatter_class(self._transformer)
366 self._language = self._formatter.language
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:
373 if getattr(node, 'disguised', False):
375 if isinstance(node, ast.Record) and \
376 self._language == 'Python' and \
377 node.is_gtype_struct_for is not None:
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', [])
389 self._render_node(node, output)
391 def _render_node(self, node, output):
392 namespace = self._transformer.namespace
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')
398 template_dir = os.path.dirname(__file__)
400 template_name = make_template_name(node, self._language)
401 page_id = make_page_id(node)
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,
410 formatter=self._formatter)
412 output_file_name = os.path.join(os.path.abspath(output),
414 fp = open(output_file_name, 'w')