Synopsis - Cross-Reference
File: Synopsis/Formatters/DocBook/__init__.py1# 2# Copyright (C) 2007 Stefan Seefeld 3# All rights reserved. 4# Licensed to the public under the terms of the GNU LGPL (>= 2), 5# see the file COPYING for details. 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 = [] # accumulated set of classes 46 self.classes_once = [] # classes not to be included again 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 # Ignore private base classes, they are not visible anyway. 60 elif inheritance.parent in self.classes_once: 61 return # Do not include virtual base classes more than once. 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 # FIXME: Unfortunately, as of xsl-docbook 1.73, a section role 202 # doesn't translate into a div class attribute, so there 203 # is no way to style summaries and descriptions per se. 204 #self.write('<section role="summary">\n%s<section>\n'%summary) 205 self.write('%s\n'%summary) 206 207 #################### ASG Visitor ########################################### 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 #################### Type Visitor ########################################## 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 # TODO: this needs to be implemented 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 # FIXME: Unfortunately, as of xsl-docbook 1.73, a section role 277 # doesn't translate into a div class attribute, so there 278 # is no way to style summaries and descriptions per se. 279 #self.write('<section role="description">\n%s<section>\n'%detail) 280 self.write('%s\n'%detail) 281 282 #################### ASG Visitor ########################################### 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 # The primary index term is the unqualified name, 291 # the secondary the qualified name. 292 # This will result in index terms being grouped by name, with each 293 # qualified name being listed within that group. 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 #if s[-1] == 's': title = s + 'es Summary' 339 #else: title = s + 's Summary' 340 #self.start_element('section') 341 #self.write_element('title', escape(title)) 342 for d in sorter[s]: 343 if not self.processor.hide_undocumented or d.annotations.get('doc'): 344 d.accept(summary) 345 #self.end_element() 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 #title = section + suffix 360 #self.start_element('section') 361 #self.write_element('title', escape(title)) 362 for d in sorter[section]: 363 d.accept(self) 364 #self.end_element() 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 # Only print qualified names when modules are flattened. 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 # If so desired, flatten inheritance tree 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 # Constructor 427 continue 428 elif d.real_name[-1] == '~' + base.name[-1]: 429 # Destructor 430 continue 431 elif d.real_name[-1] == 'operator=': 432 # Assignment 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 #title = section + ' Summary' 446 #self.start_element('section') 447 #self.write_element('title', escape(title)) 448 for d in sorter[section]: 449 if not self.processor.hide_undocumented or d.annotations.get('doc'): 450 d.accept(summary) 451 #self.end_element() 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 #title = section + suffix 461 #self.start_element('section') 462 #self.write_element('title', escape(title)) 463 for d in sorter[section]: 464 d.accept(self) 465 #self.end_element() 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 # Make sure we have a default markup formatter. 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 Tue May 13 02:39:15 2008 by
synopsis (version 0.10)
synopsis (version 0.10)