File: Synopsis/Formatters/DocBook/__init__.py 1
2
3
4
5
6
7
8"""a DocBook formatter (producing Docbook 4.5 XML output"""
9
10from Synopsis.Processor import *
11from Synopsis import ASG, DeclarationSorter
12from Synopsis.Formatters import quote_name
13from Synopsis.Formatters.TOC import TOC
14from Syntax import *
15from Markup.Javadoc import Javadoc
16try:
17 from Markup.RST import RST
18except ImportError:
19 from Markup import Formatter as RST
20
21import os
22
23def escape(text):
24
25 for p in [('&', '&'), ('"', '"'), ('<', '<'), ('>', '>'),]:
26 text = text.replace(*p)
27 return text
28
29def reference(name):
30 """Generate an id suitable as a 'linkend' / 'id' attribute, i.e. for linking."""
31
32 return quote_name(str(name))
33
34class Linker:
35 """Helper class to be used to resolve references from doc-strings to declarations."""
36
37 def link(self, decl):
38
39 return reference(decl.name)
40
41
42class _BaseClasses(ASG.Visitor):
43
44 def __init__(self):
45 self.classes = []
46 self.classes_once = []
47
48 def visit_declared_type_id(self, declared):
49 declared.declaration.accept(self)
50
51 def visit_class(self, class_):
52 self.classes.append(class_)
53 for p in class_.parents:
54 p.accept(self)
55
56 def visit_inheritance(self, inheritance):
57
58 if 'private' in inheritance.attributes:
59 return
60 elif inheritance.parent in self.classes_once:
61 return
62 if 'virtual' in inheritance.attributes:
63 self.classes_once.append(inheritance.parent)
64 inheritance.parent.accept(self)
65
66
67_summary_syntax = {
68 'IDL': CxxSummarySyntax,
69 'C++': CxxSummarySyntax,
70 'C': CxxSummarySyntax,
71 'Python': PythonSummarySyntax
72 }
73
74_detail_syntax = {
75 'IDL': CxxDetailSyntax,
76 'C++': CxxDetailSyntax,
77 'C': CxxDetailSyntax,
78 'Python': PythonDetailSyntax
79 }
80
81class ModuleLister(ASG.Visitor):
82 """Maps a module-tree to a (flat) list of modules."""
83
84 def __init__(self):
85
86 self.modules = []
87
88 def visit_module(self, module):
89
90 self.modules.append(module)
91 for d in module.declarations:
92 d.accept(self)
93
94
95class InheritanceFormatter:
96
97 def __init__(self, base_dir, bgcolor):
98
99 self.base_dir = base_dir
100 self.bgcolor = bgcolor
101
102 def format_class(self, class_, format):
103
104 if not class_.parents:
105 return ''
106
107 from Synopsis.Formatters import Dot
108 filename = os.path.join(self.base_dir, escape(str(class_.name)) + '.%s'%format)
109 dot = Dot.Formatter(bgcolor=self.bgcolor)
110 try:
111 dot.process(IR.IR(asg = ASG.ASG(declarations = [class_])),
112 output=filename,
113 format=format,
114 type='single')
115 return filename
116 except InvalidCommand, e:
117 print 'Warning : %s'%str(e)
118 return ''
119
120
121class FormatterBase:
122
123 def __init__(self, processor, output, base_dir,
124 nested_modules, secondary_index, inheritance_graphs, graph_color):
125 self.processor = processor
126 self.output = output
127 self.base_dir = base_dir
128 self.nested_modules = nested_modules
129 self.secondary_index = secondary_index
130 self.inheritance_graphs = inheritance_graphs
131 self.graph_color = graph_color
132 self.__scope = ()
133 self.__scopestack = []
134 self.__indent = 0
135 self.__elements = []
136
137 def scope(self): return self.__scope
138 def push_scope(self, newscope):
139
140 self.__scopestack.append(self.__scope)
141 self.__scope = newscope
142
143 def pop_scope(self):
144
145 self.__scope = self.__scopestack[-1]
146 del self.__scopestack[-1]
147
148 def write(self, text):
149 """Write some text to the output stream, replacing \n's with \n's and
150 indents."""
151
152 indent = ' ' * self.__indent
153 self.output.write(text.replace('\n', '\n'+indent))
154
155 def start_element(self, type, **params):
156 """Write the start of an element, ending with a newline"""
157
158 self.__elements.append(type)
159 param_text = ''
160 if params: param_text = ' ' + ' '.join(['%s="%s"'%(p[0].lower(), p[1]) for p in params.items()])
161 self.write('<' + type + param_text + '>')
162 self.__indent = self.__indent + 2
163 self.write('\n')
164
165 def end_element(self):
166 """Write the end of an element, starting with a newline"""
167
168 type = self.__elements.pop()
169 self.__indent = self.__indent - 2
170 self.write('\n</' + type + '>')
171 self.write('\n')
172
173 def write_element(self, element, body, end = '\n', **params):
174 """Write a single element on one line (though body may contain
175 newlines)"""
176
177 param_text = ''
178 if params: param_text = ' ' + ' '.join(['%s="%s"'%(p[0].lower(), p[1]) for p in params.items()])
179 self.write('<' + element + param_text + '>')
180 self.__indent = self.__indent + 2
181 self.write(body)
182 self.__indent = self.__indent - 2
183 self.write('</' + element + '>' + end)
184
185 def element(self, element, body, **params):
186 """Return but do not write the text for an element on one line"""
187
188 param_text = ''
189 if params: param_text = ' ' + ' '.join(['%s="%s"'%(p[0].lower(), p[1]) for p in params.items()])
190 return '<%s%s>%s</%s>'%(element, param_text, body, element)
191
192
193class SummaryFormatter(FormatterBase, ASG.Visitor):
194 """A SummaryFormatter."""
195
196 def process_doc(self, decl):
197
198 if decl.annotations.get('doc', ''):
199 summary = self.processor.documentation.summary(decl)
200 if summary:
201
202
203
204
205 self.write('%s\n'%summary)
206
207
208
209 def visit_declaration(self, declaration):
210
211 language = declaration.file.annotations['language']
212 syntax = _summary_syntax[language](self.output)
213 declaration.accept(syntax)
214 syntax.finish()
215 self.process_doc(declaration)
216
217
218 visit_module = visit_declaration
219 visit_class = visit_declaration
220 def visit_meta_module(self, meta):
221 self.visit_module(meta.module_declarations[0])
222
223 visit_function = visit_declaration
224
225 def visit_enum(self, enum):
226 print "sorry, <enum> not implemented"
227
228
229class DetailFormatter(FormatterBase, ASG.Visitor):
230
231
232
233
234 def visit_builtin_type_id(self, type):
235
236 self.__type_ref = str(type.name)
237 self.__type_label = str(type.name)
238
239 def visit_unknown_type_id(self, type):
240
241 self.__type_ref = str(type.name)
242 self.__type_label = str(self.scope().prune(type.name))
243
244 def visit_declared_type_id(self, type):
245
246 self.__type_label = str(self.scope().prune(type.name))
247 self.__type_ref = str(type.name)
248
249 def visit_modifier_type_id(self, type):
250
251 type.alias.accept(self)
252 self.__type_ref = ''.join(type.premod) + ' ' + self.__type_ref + ' ' + ''.join(type.postmod)
253 self.__type_label = escape(''.join(type.premod) + ' ' + self.__type_label + ' ' + ''.join(type.postmod))
254
255 def visit_parametrized_type_id(self, type):
256
257 type.template.accept(self)
258 type_label = self.__type_label + '<'
259 parameters_label = []
260 for p in type.parameters:
261 p.accept(self)
262 parameters_label.append(self.__type_label)
263 self.__type_label = type_label + ', '.join(parameters_label) + '>'
264
265 def visit_function_type_id(self, type):
266
267
268 self.__type_ref = 'function_type'
269 self.__type_label = 'function_type'
270
271 def process_doc(self, decl):
272
273 if decl.annotations.get('doc', ''):
274 detail = self.processor.documentation.details(decl)
275 if detail:
276
277
278
279
280 self.write('%s\n'%detail)
281
282
283
284 def visit_declaration(self, declaration):
285 if self.processor.hide_undocumented and not declaration.annotations.get('doc'):
286 return
287 self.start_element('section', id=reference(declaration.name))
288 self.write_element('title', escape(declaration.name[-1]))
289 if isinstance(declaration, ASG.Function):
290
291
292
293
294 indexterm = self.element('primary', escape(declaration.real_name[-1]))
295 if self.secondary_index:
296 indexterm += self.element('secondary', escape(str(declaration.real_name)))
297 self.write_element('indexterm', indexterm, type='functions')
298
299 language = declaration.file.annotations['language']
300 syntax = _detail_syntax[language](self.output)
301 declaration.accept(syntax)
302 syntax.finish()
303 self.process_doc(declaration)
304 self.end_element()
305
306 def generate_module_list(self, modules):
307
308 if modules:
309 modules.sort(cmp=lambda a,b:cmp(a.name, b.name))
310 self.start_element('section')
311 self.write_element('title', modules[0].type.capitalize() + 's')
312 self.start_element('itemizedlist')
313 for m in modules:
314 link = self.element('link', escape(str(m.name)), linkend=reference(m.name))
315 self.write_element('listitem', self.element('para', link))
316 self.end_element()
317 self.end_element()
318
319
320 def format_module_or_group(self, module, title, sort):
321 self.start_element('section', id=reference(module.name))
322 self.write_element('title', title)
323
324 declarations = module.declarations
325 if not self.nested_modules:
326 modules = [d for d in declarations if isinstance(d, ASG.Module)]
327 declarations = [d for d in declarations if not isinstance(d, ASG.Module)]
328 self.generate_module_list(modules)
329
330 sorter = DeclarationSorter.DeclarationSorter(declarations,
331 group_as_section=False)
332 if self.processor.generate_summary:
333 self.start_element('section')
334 self.write_element('title', 'Summary')
335 summary = SummaryFormatter(self.processor, self.output)
336 if sort:
337 for s in sorter:
338
339
340
341
342 for d in sorter[s]:
343 if not self.processor.hide_undocumented or d.annotations.get('doc'):
344 d.accept(summary)
345
346 else:
347 for d in declarations:
348 if not self.processor.hide_undocumented or d.annotations.get('doc'):
349 d.accept(summary)
350 self.end_element()
351 self.write('\n')
352 self.start_element('section')
353 self.write_element('title', 'Details')
354 self.process_doc(module)
355 self.push_scope(module.name)
356 suffix = self.processor.generate_summary and ' Details' or ''
357 if sort:
358 for section in sorter:
359
360
361
362 for d in sorter[section]:
363 d.accept(self)
364
365 else:
366 for d in declarations:
367 d.accept(self)
368 self.pop_scope()
369 self.end_element()
370 self.write('\n')
371 if self.processor.generate_summary:
372 self.end_element()
373 self.write('\n')
374
375 def visit_module(self, module):
376 if module.name:
377
378 name = self.nested_modules and module.name[-1] or str(module.name)
379 title = '%s %s'%(module.type.capitalize(), name)
380 else:
381 title = 'Global %s'%module.type.capitalize()
382
383 self.format_module_or_group(module, title, True)
384
385 def visit_group(self, group):
386 self.format_module_or_group(group, group.name[-1].capitalize(), False)
387
388 def visit_class(self, class_):
389
390 if self.processor.hide_undocumented and not class_.annotations.get('doc'):
391 return
392 self.start_element('section', id=reference(class_.name))
393 title = '%s %s'%(class_.type, class_.name[-1])
394 self.write_element('title', escape(title))
395 indexterm = self.element('primary', escape(class_.name[-1]))
396 if self.secondary_index:
397 indexterm += self.element('secondary', escape(str(class_.name)))
398 self.write_element('indexterm', indexterm, type='types')
399
400 if self.inheritance_graphs:
401 formatter = InheritanceFormatter(os.path.join(self.base_dir, 'images'),
402 self.graph_color)
403 png = formatter.format_class(class_, 'png')
404 svg = formatter.format_class(class_, 'svg')
405 if png or svg:
406 self.start_element('mediaobject')
407 if png:
408 imagedata = self.element('imagedata', '', fileref=png, format='PNG')
409 self.write_element('imageobject', imagedata)
410 if svg:
411 imagedata = self.element('imagedata', '', fileref=svg, format='SVG')
412 self.write_element('imageobject', imagedata)
413 self.end_element()
414
415 declarations = class_.declarations
416
417 if self.processor.inline_inherited_members:
418 declarations = class_.declarations[:]
419 bases = _BaseClasses()
420 for i in class_.parents:
421 bases.visit_inheritance(i)
422 for base in bases.classes:
423 for d in base.declarations:
424 if type(d) == ASG.Operation:
425 if d.real_name[-1] == base.name[-1]:
426
427 continue
428 elif d.real_name[-1] == '~' + base.name[-1]:
429
430 continue
431 elif d.real_name[-1] == 'operator=':
432
433 continue
434 declarations.append(d)
435
436 sorter = DeclarationSorter.DeclarationSorter(declarations,
437 group_as_section=False)
438
439 if self.processor.generate_summary:
440 self.start_element('section')
441 self.write_element('title', 'Summary')
442 summary = SummaryFormatter(self.processor, self.output)
443 summary.process_doc(class_)
444 for section in sorter:
445
446
447
448 for d in sorter[section]:
449 if not self.processor.hide_undocumented or d.annotations.get('doc'):
450 d.accept(summary)
451
452 self.end_element()
453 self.write('\n')
454 self.start_element('section')
455 self.write_element('title', 'Details')
456 self.process_doc(class_)
457 self.push_scope(class_.name)
458 suffix = self.processor.generate_summary and ' Details' or ''
459 for section in sorter:
460
461
462
463 for d in sorter[section]:
464 d.accept(self)
465
466 self.pop_scope()
467 self.end_element()
468 self.write('\n')
469 if self.processor.generate_summary:
470 self.end_element()
471 self.write('\n')
472
473
474 def visit_inheritance(self, inheritance):
475
476 for a in inheritance.attributes: self.element("modifier", a)
477 self.element("classname", str(self.scope().prune((inheritance.parent.name))))
478
479 def visit_parameter(self, parameter):
480
481 parameter.type.accept(self)
482
483 visit_function = visit_declaration
484
485 def visit_enum(self, enum):
486
487 if self.processor.hide_undocumented and not declaration.annotations.get('doc'):
488 return
489
490 self.start_element('section', id=reference(enum.name))
491 self.write_element('title', escape(enum.name[-1]))
492 indexterm = self.element('primary', escape(enum.name[-1]))
493 if self.secondary_index:
494 indexterm += self.element('secondary', escape(str(enum.name)))
495 self.write_element('indexterm', indexterm, type='types')
496
497 language = enum.file.annotations['language']
498 syntax = _detail_syntax[language](self.output)
499 enum.accept(syntax)
500 syntax.finish()
501 self.process_doc(enum)
502 self.end_element()
503
504
505class DocCache:
506 """"""
507
508 def __init__(self, processor, markup_formatters):
509
510 self._processor = processor
511 self._markup_formatters = markup_formatters
512
513 if '' not in self._markup_formatters:
514 self._markup_formatters[''] = Markup.Formatter()
515 for f in self._markup_formatters.values():
516 f.init(self._processor)
517 self._doc_cache = {}
518
519
520 def _process(self, decl):
521 """Return the documentation for the given declaration."""
522
523 key = id(decl)
524 if key not in self._doc_cache:
525 doc = decl.annotations.get('doc')
526 if doc:
527 formatter = self._markup_formatters.get(doc.markup,
528 self._markup_formatters[''])
529 doc = formatter.format(decl)
530 else:
531 doc = Markup.Struct()
532 self._doc_cache[key] = doc
533 return doc
534 else:
535 return self._doc_cache[key]
536
537
538 def summary(self, decl):
539 """"""
540
541 doc = self._process(decl)
542 return doc.summary
543
544
545 def details(self, decl):
546 """"""
547
548 doc = self._process(decl)
549 return doc.details
550
551
552
553class Formatter(Processor):
554 """Generate a DocBook reference."""
555
556 markup_formatters = Parameter({'rst':RST(),
557 'reStructuredText':RST(),
558 'javadoc':Javadoc()},
559 'Markup-specific formatters.')
560 title = Parameter(None, 'title to be used in top-level section')
561 nested_modules = Parameter(False, """Map the module tree to a tree of docbook sections.""")
562 generate_summary = Parameter(False, 'generate scope summaries')
563 hide_undocumented = Parameter(False, 'hide declarations without a doc-string')
564 inline_inherited_members = Parameter(False, 'show inherited members')
565 secondary_index_terms = Parameter(True, 'add fully-qualified names to index')
566 with_inheritance_graphs = Parameter(True, 'whether inheritance graphs should be generated')
567 graph_color = Parameter('#ffcc99', 'base color for inheritance graphs')
568
569 def process(self, ir, **kwds):
570
571 self.set_parameters(kwds)
572 if not self.output: raise MissingArgument('output')
573 self.ir = self.merge_input(ir)
574
575 self.documentation = DocCache(self, self.markup_formatters)
576 self.toc = TOC(Linker())
577 for d in self.ir.asg.declarations:
578 d.accept(self.toc)
579
580 output = open(self.output, 'w')
581 output.write('<section>\n')
582 if self.title:
583 output.write('<title>%s</title>\n'%self.title)
584 detail_formatter = DetailFormatter(self, output,
585 os.path.dirname(self.output),
586 self.nested_modules,
587 self.secondary_index_terms,
588 self.with_inheritance_graphs,
589 self.graph_color)
590
591 declarations = self.ir.asg.declarations
592
593 if not self.nested_modules:
594
595 modules = [d for d in declarations if isinstance(d, ASG.Module)]
596 detail_formatter.generate_module_list(modules)
597
598 module_lister = ModuleLister()
599 for d in self.ir.asg.declarations:
600 d.accept(module_lister)
601 modules = module_lister.modules
602 modules.sort(cmp=lambda a,b:cmp(a.name, b.name))
603 declarations = [d for d in self.ir.asg.declarations
604 if not isinstance(d, ASG.Module)]
605 declarations.sort(cmp=lambda a,b:cmp(a.name, b.name))
606 declarations = modules + declarations
607
608 for d in declarations:
609 d.accept(detail_formatter)
610
611 output.write('</section>\n')
612 output.close()
613
614 return self.ir
615
Generated on Thu Apr 16 16:27:13 2009 by
synopsis (version devel)