Package epydoc :: Package docwriter :: Module html
[hide private]
[frames] | no frames]

Source Code for Module epydoc.docwriter.html

   1  # 
   2  # epydoc -- HTML output generator 
   3  # Edward Loper 
   4  # 
   5  # Created [01/30/01 05:18 PM] 
   6  # $Id: html.py 1804 2008-02-27 19:54:39Z edloper $ 
   7  # 
   8   
   9  """ 
  10  The HTML output generator for epydoc.  The main interface provided by 
  11  this module is the L{HTMLWriter} class. 
  12   
  13  @todo: Add a cache to L{HTMLWriter.url()}? 
  14  """ 
  15  __docformat__ = 'epytext en' 
  16   
  17  import re, os, sys, codecs, sre_constants, pprint, base64 
  18  import urllib 
  19  import __builtin__ 
  20  from epydoc.apidoc import * 
  21  import epydoc.docstringparser 
  22  import time, epydoc, epydoc.markup, epydoc.markup.epytext 
  23  from epydoc.docwriter.html_colorize import PythonSourceColorizer 
  24  from epydoc.docwriter import html_colorize 
  25  from epydoc.docwriter.html_css import STYLESHEETS 
  26  from epydoc.docwriter.html_help import HTML_HELP 
  27  from epydoc.docwriter.dotgraph import * 
  28  from epydoc import log 
  29  from epydoc.util import plaintext_to_html, is_src_filename 
  30  from epydoc.compat import * # Backwards compatibility 
  31   
  32  ###################################################################### 
  33  ## Template Compiler 
  34  ###################################################################### 
  35  # The compile_template() method defined in this section is used to 
  36  # define several of HTMLWriter's methods. 
  37   
38 -def compile_template(docstring, template_string, 39 output_function='out', debug=epydoc.DEBUG):
40 """ 41 Given a template string containing inline python source code, 42 return a python function that will fill in the template, and 43 output the result. The signature for this function is taken from 44 the first line of C{docstring}. Output is generated by making 45 repeated calls to the output function with the given name (which 46 is typically one of the function's parameters). 47 48 The templating language used by this function passes through all 49 text as-is, with three exceptions: 50 51 - If every line in the template string is indented by at least 52 M{x} spaces, then the first M{x} spaces are stripped from each 53 line. 54 55 - Any line that begins with '>>>' (with no indentation) 56 should contain python code, and will be inserted as-is into 57 the template-filling function. If the line begins a control 58 block (such as 'if' or 'for'), then the control block will 59 be closed by the first '>>>'-marked line whose indentation is 60 less than or equal to the line's own indentation (including 61 lines that only contain comments.) 62 63 - In any other line, any expression between two '$' signs will 64 be evaluated and inserted into the line (using C{str()} to 65 convert the result to a string). 66 67 Here is a simple example: 68 69 >>> TEMPLATE = ''' 70 ... <book> 71 ... <title>$book.title$</title> 72 ... <pages>$book.count_pages()$</pages> 73 ... >>> for chapter in book.chapters: 74 ... <chaptername>$chapter.name$</chaptername> 75 ... >>> #endfor 76 ... </book> 77 >>> write_book = compile_template('write_book(out, book)', TEMPLATE) 78 79 @newfield acknowledgements: Acknowledgements 80 @acknowledgements: The syntax used by C{compile_template} is 81 loosely based on Cheetah. 82 """ 83 # Extract signature from the docstring: 84 signature = docstring.lstrip().split('\n',1)[0].strip() 85 func_name = signature.split('(',1)[0].strip() 86 87 # Regexp to search for inline substitutions: 88 INLINE = re.compile(r'\$([^\$]+)\$') 89 # Regexp to search for python statements in the template: 90 COMMAND = re.compile(r'(^>>>.*)\n?', re.MULTILINE) 91 92 # Strip indentation from the template. 93 template_string = strip_indent(template_string) 94 95 # If we're debugging, then we'll store the generated function, 96 # so we can print it along with any tracebacks that depend on it. 97 if debug: 98 signature = re.sub(r'\)\s*$', ', __debug=__debug)', signature) 99 100 # Funciton declaration line 101 pysrc_lines = ['def %s:' % signature] 102 indents = [-1] 103 104 if debug: 105 pysrc_lines.append(' try:') 106 indents.append(-1) 107 108 commands = COMMAND.split(template_string.strip()+'\n') 109 for i, command in enumerate(commands): 110 if command == '': continue 111 112 # String literal segment: 113 if i%2 == 0: 114 pieces = INLINE.split(command) 115 for j, piece in enumerate(pieces): 116 if j%2 == 0: 117 # String piece 118 pysrc_lines.append(' '*len(indents)+ 119 '%s(%r)' % (output_function, piece)) 120 else: 121 # Variable piece 122 pysrc_lines.append(' '*len(indents)+ 123 '%s(unicode(%s))' % (output_function, piece)) 124 125 # Python command: 126 else: 127 srcline = command[3:].lstrip() 128 # Update indentation 129 indent = len(command)-len(srcline) 130 while indent <= indents[-1]: indents.pop() 131 # Add on the line. 132 srcline = srcline.rstrip() 133 pysrc_lines.append(' '*len(indents)+srcline) 134 if srcline.endswith(':'): 135 indents.append(indent) 136 137 if debug: 138 pysrc_lines.append(' except Exception,e:') 139 pysrc_lines.append(' pysrc, func_name = __debug ') 140 pysrc_lines.append(' lineno = sys.exc_info()[2].tb_lineno') 141 pysrc_lines.append(' print ("Exception in template %s() on "') 142 pysrc_lines.append(' "line %d:" % (func_name, lineno))') 143 pysrc_lines.append(' print pysrc[lineno-1]') 144 pysrc_lines.append(' raise') 145 146 pysrc = '\n'.join(pysrc_lines)+'\n' 147 #log.debug(pysrc) 148 if debug: localdict = {'__debug': (pysrc_lines, func_name)} 149 else: localdict = {} 150 try: exec pysrc in globals(), localdict 151 except SyntaxError: 152 log.error('Error in script:\n' + pysrc + '\n') 153 raise 154 template_func = localdict[func_name] 155 template_func.__doc__ = docstring 156 return template_func
157
158 -def strip_indent(s):
159 """ 160 Given a multiline string C{s}, find the minimum indentation for 161 all non-blank lines, and return a new string formed by stripping 162 that amount of indentation from all lines in C{s}. 163 """ 164 # Strip indentation from the template. 165 minindent = sys.maxint 166 lines = s.split('\n') 167 for line in lines: 168 stripline = line.lstrip() 169 if stripline: 170 minindent = min(minindent, len(line)-len(stripline)) 171 return '\n'.join([l[minindent:] for l in lines])
172 173 ###################################################################### 174 ## HTML Writer 175 ###################################################################### 176
177 -class HTMLWriter:
178 #//////////////////////////////////////////////////////////// 179 # Table of Contents 180 #//////////////////////////////////////////////////////////// 181 # 182 # 1. Interface Methods 183 # 184 # 2. Page Generation -- write complete web page files 185 # 2.1. Module Pages 186 # 2.2. Class Pages 187 # 2.3. Trees Page 188 # 2.4. Indices Page 189 # 2.5. Help Page 190 # 2.6. Frames-based table of contents pages 191 # 2.7. Homepage (index.html) 192 # 2.8. CSS Stylesheet 193 # 2.9. Javascript file 194 # 2.10. Graphs 195 # 2.11. Images 196 # 197 # 3. Page Element Generation -- write pieces of a web page file 198 # 3.1. Page Header 199 # 3.2. Page Footer 200 # 3.3. Navigation Bar 201 # 3.4. Breadcrumbs 202 # 3.5. Summary Tables 203 # 204 # 4. Helper functions 205
206 - def __init__(self, docindex, **kwargs):
207 """ 208 Construct a new HTML writer, using the given documentation 209 index. 210 211 @param docindex: The documentation index. 212 213 @type prj_name: C{string} 214 @keyword prj_name: The name of the project. Defaults to 215 none. 216 @type prj_url: C{string} 217 @keyword prj_url: The target for the project hopeage link on 218 the navigation bar. If C{prj_url} is not specified, 219 then no hyperlink is created. 220 @type prj_link: C{string} 221 @keyword prj_link: The label for the project link on the 222 navigation bar. This link can contain arbitrary HTML 223 code (e.g. images). By default, a label is constructed 224 from C{prj_name}. 225 @type top_page: C{string} 226 @keyword top_page: The top page for the documentation. This 227 is the default page shown main frame, when frames are 228 enabled. C{top} can be a URL, the name of a 229 module, the name of a class, or one of the special 230 strings C{"trees.html"}, C{"indices.html"}, or 231 C{"help.html"}. By default, the top-level package or 232 module is used, if there is one; otherwise, C{"trees"} 233 is used. 234 @type css: C{string} 235 @keyword css: The CSS stylesheet file. If C{css} is a file 236 name, then the specified file's conents will be used. 237 Otherwise, if C{css} is the name of a CSS stylesheet in 238 L{epydoc.docwriter.html_css}, then that stylesheet will 239 be used. Otherwise, an error is reported. If no stylesheet 240 is specified, then the default stylesheet is used. 241 @type help_file: C{string} 242 @keyword help_file: The name of the help file. If no help file is 243 specified, then the default help file will be used. 244 @type show_private: C{boolean} 245 @keyword show_private: Whether to create documentation for 246 private objects. By default, private objects are documented. 247 @type show_frames: C{boolean}) 248 @keyword show_frames: Whether to create a frames-based table of 249 contents. By default, it is produced. 250 @type show_imports: C{boolean} 251 @keyword show_imports: Whether or not to display lists of 252 imported functions and classes. By default, they are 253 not shown. 254 @type variable_maxlines: C{int} 255 @keyword variable_maxlines: The maximum number of lines that 256 should be displayed for the value of a variable in the 257 variable details section. By default, 8 lines are 258 displayed. 259 @type variable_linelength: C{int} 260 @keyword variable_linelength: The maximum line length used for 261 displaying the values of variables in the variable 262 details sections. If a line is longer than this length, 263 then it will be wrapped to the next line. The default 264 line length is 70 characters. 265 @type variable_summary_linelength: C{int} 266 @keyword variable_summary_linelength: The maximum line length 267 used for displaying the values of variables in the summary 268 section. If a line is longer than this length, then it 269 will be truncated. The default is 40 characters. 270 @type variable_tooltip_linelength: C{int} 271 @keyword variable_tooltip_linelength: The maximum line length 272 used for tooltips for the values of variables. If a 273 line is longer than this length, then it will be 274 truncated. The default is 600 characters. 275 @type property_function_linelength: C{int} 276 @keyword property_function_linelength: The maximum line length 277 used to dispaly property functions (C{fget}, C{fset}, and 278 C{fdel}) that contain something other than a function 279 object. The default length is 40 characters. 280 @type inheritance: C{string} 281 @keyword inheritance: How inherited objects should be displayed. 282 If C{inheritance='grouped'}, then inherited objects are 283 gathered into groups; if C{inheritance='listed'}, then 284 inherited objects are listed in a short list at the 285 end of their group; if C{inheritance='included'}, then 286 inherited objects are mixed in with non-inherited 287 objects. The default is 'grouped'. 288 @type include_source_code: C{boolean} 289 @keyword include_source_code: If true, then generate colorized 290 source code files for each python module. 291 @type include_log: C{boolean} 292 @keyword include_log: If true, the the footer will include an 293 href to the page 'epydoc-log.html'. 294 @type include_timestamp: C{boolean} 295 @keyword include_timestamp: If true, then include a timestamp in 296 the footer. 297 @type src_code_tab_width: C{int} 298 @keyword src_code_tab_width: Number of spaces to replace each tab 299 with in source code listings. 300 """ 301 self.docindex = docindex 302 303 # Process keyword arguments. 304 self._show_private = kwargs.get('show_private', 1) 305 """Should private docs be included?""" 306 307 self._prj_name = kwargs.get('prj_name', None) 308 """The project's name (for the project link in the navbar)""" 309 310 self._prj_url = kwargs.get('prj_url', None) 311 """URL for the project link in the navbar""" 312 313 self._prj_link = kwargs.get('prj_link', None) 314 """HTML code for the project link in the navbar""" 315 316 self._top_page = kwargs.get('top_page', None) 317 """The 'main' page""" 318 319 self._css = kwargs.get('css') 320 """CSS stylesheet to use""" 321 322 self._helpfile = kwargs.get('help_file', None) 323 """Filename of file to extract help contents from""" 324 325 self._frames_index = kwargs.get('show_frames', 1) 326 """Should a frames index be created?""" 327 328 self._show_imports = kwargs.get('show_imports', False) 329 """Should imports be listed?""" 330 331 self._propfunc_linelen = kwargs.get('property_function_linelength', 40) 332 """[XXX] Not used!""" 333 334 self._variable_maxlines = kwargs.get('variable_maxlines', 8) 335 """Max lines for variable values""" 336 337 self._variable_linelen = kwargs.get('variable_linelength', 70) 338 """Max line length for variable values""" 339 340 self._variable_summary_linelen = \ 341 kwargs.get('variable_summary_linelength', 65) 342 """Max length for variable value summaries""" 343 344 self._variable_tooltip_linelen = \ 345 kwargs.get('variable_tooltip_linelength', 600) 346 """Max length for variable tooltips""" 347 348 self._inheritance = kwargs.get('inheritance', 'listed') 349 """How should inheritance be displayed? 'listed', 'included', 350 or 'grouped'""" 351 352 self._incl_sourcecode = kwargs.get('include_source_code', True) 353 """Should pages be generated for source code of modules?""" 354 355 self._mark_docstrings = kwargs.get('mark_docstrings', False) 356 """Wrap <span class='docstring'>...</span> around docstrings?""" 357 358 self._graph_types = kwargs.get('graphs', ()) or () 359 """Graphs that we should include in our output.""" 360 361 self._include_log = kwargs.get('include_log', False) 362 """Are we generating an HTML log page?""" 363 364 self._include_timestamp = kwargs.get('include_timestamp', True) 365 """Include a timestamp on the generated docs?""" 366 367 self._src_code_tab_width = kwargs.get('src_code_tab_width', 8) 368 """Number of spaces to replace each tab with in source code 369 listings.""" 370 371 self._callgraph_cache = {} 372 """Map the callgraph L{uid<DotGraph.uid>} to their HTML 373 representation.""" 374 375 self._redundant_details = kwargs.get('redundant_details', False) 376 """If true, then include objects in the details list even if all 377 info about them is already provided by the summary table.""" 378 379 self._show_submodule_list = kwargs.get('show_submodule_list', True) 380 """If true, the include a list of submodules on the package 381 documentation page.""" 382 383 self._google_analytics = kwargs.get('google_analytics', None) 384 """A tracker code for google analytics; or None""" 385 386 # For use with select_variables(): 387 if self._show_private: 388 self._public_filter = None 389 else: 390 self._public_filter = True 391 392 # Make sure inheritance has a sane value. 393 if self._inheritance not in ('listed', 'included', 394 'grouped', 'hidden'): 395 raise ValueError, 'Bad value for inheritance' 396 397 # Create the project homepage link, if it was not specified. 398 if (self._prj_name or self._prj_url) and not self._prj_link: 399 self._prj_link = plaintext_to_html(self._prj_name or 400 'Project Homepage') 401 402 # Add a hyperlink to _prj_url, if _prj_link doesn't already 403 # contain any hyperlinks. 404 if (self._prj_link and self._prj_url and 405 not re.search(r'<a[^>]*\shref', self._prj_link)): 406 self._prj_link = ('<a class="navbar" target="_top" href="'+ 407 self._prj_url+'">'+self._prj_link+'</a>') 408 409 # Precompute lists & sets of APIDoc objects that we're 410 # interested in. 411 self.valdocs = valdocs = sorted(docindex.reachable_valdocs( 412 imports=False, packages=False, bases=False, submodules=False, 413 subclasses=False, private=self._show_private)) 414 self.module_list = [d for d in valdocs if isinstance(d, ModuleDoc)] 415 """The list of L{ModuleDoc}s for the documented modules.""" 416 self.module_set = set(self.module_list) 417 """The set of L{ModuleDoc}s for the documented modules.""" 418 self.class_list = [d for d in valdocs if isinstance(d, ClassDoc)] 419 """The list of L{ClassDoc}s for the documented classes.""" 420 self.class_set = set(self.class_list) 421 """The set of L{ClassDoc}s for the documented classes.""" 422 self.routine_list = [d for d in valdocs if isinstance(d, RoutineDoc)] 423 """The list of L{RoutineDoc}s for the documented routines.""" 424 self.indexed_docs = [] 425 """The list of L{APIDoc}s for variables and values that should 426 be included in the index.""" 427 428 # URL for 'trees' page 429 if self.module_list: self._trees_url = 'module-tree.html' 430 else: self._trees_url = 'class-tree.html' 431 432 # Construct the value for self.indexed_docs. 433 self.indexed_docs += [d for d in valdocs 434 if not isinstance(d, GenericValueDoc)] 435 for doc in valdocs: 436 if isinstance(doc, NamespaceDoc): 437 # add any vars with generic values; but don't include 438 # inherited vars. 439 self.indexed_docs += [d for d in doc.variables.values() if 440 isinstance(d.value, GenericValueDoc) 441 and d.container == doc] 442 self.indexed_docs.sort() 443 444 # Figure out the url for the top page. 445 self._top_page_url = self._find_top_page(self._top_page) 446 447 # Decide whether or not to split the identifier index. 448 self._split_ident_index = (len(self.indexed_docs) >= 449 self.SPLIT_IDENT_INDEX_SIZE) 450 451 # Figure out how many output files there will be (for progress 452 # reporting). 453 self.modules_with_sourcecode = set() 454 for doc in self.module_list: 455 if isinstance(doc, ModuleDoc) and is_src_filename(doc.filename): 456 self.modules_with_sourcecode.add(doc) 457 self._num_files = (len(self.class_list) + len(self.module_list) + 458 10 + len(self.METADATA_INDICES)) 459 if self._frames_index: 460 self._num_files += len(self.module_list) + 3 461 462 if self._incl_sourcecode: 463 self._num_files += len(self.modules_with_sourcecode) 464 if self._split_ident_index: 465 self._num_files += len(self.LETTERS)
466
467 - def _find_top_page(self, pagename):
468 """ 469 Find the top page for the API documentation. This page is 470 used as the default page shown in the main frame, when frames 471 are used. When frames are not used, this page is copied to 472 C{index.html}. 473 474 @param pagename: The name of the page, as specified by the 475 keyword argument C{top} to the constructor. 476 @type pagename: C{string} 477 @return: The URL of the top page. 478 @rtype: C{string} 479 """ 480 # If a page name was specified, then we need to figure out 481 # what it points to. 482 if pagename: 483 # If it's a URL, then use it directly. 484 if pagename.lower().startswith('http:'): 485 return pagename 486 487 # If it's an object, then use that object's page. 488 try: 489 doc = self.docindex.get_valdoc(pagename) 490 return self.url(doc) 491 except: 492 pass 493 494 # Otherwise, give up. 495 log.warning('Could not find top page %r; using %s ' 496 'instead' % (pagename, self._trees_url)) 497 return self._trees_url 498 499 # If no page name was specified, then try to choose one 500 # automatically. 501 else: 502 root = [val_doc for val_doc in self.docindex.root 503 if isinstance(val_doc, (ClassDoc, ModuleDoc))] 504 if len(root) == 0: 505 # No docs?? Try the trees page. 506 return self._trees_url 507 elif len(root) == 1: 508 # One item in the root; use that. 509 return self.url(root[0]) 510 else: 511 # Multiple root items; if they're all in one package, 512 # then use that. Otherwise, use self._trees_url 513 root = sorted(root, key=lambda v:len(v.canonical_name)) 514 top = root[0] 515 for doc in root[1:]: 516 if not top.canonical_name.dominates(doc.canonical_name): 517 return self._trees_url 518 else: 519 return self.url(top)
520 521 #//////////////////////////////////////////////////////////// 522 #{ 1. Interface Methods 523 #//////////////////////////////////////////////////////////// 524
525 - def write(self, directory=None):
526 """ 527 Write the documentation to the given directory. 528 529 @type directory: C{string} 530 @param directory: The directory to which output should be 531 written. If no directory is specified, output will be 532 written to the current directory. If the directory does 533 not exist, it will be created. 534 @rtype: C{None} 535 @raise OSError: If C{directory} cannot be created. 536 @raise OSError: If any file cannot be created or written to. 537 """ 538 # For progress reporting: 539 self._files_written = 0. 540 541 # Set the default values for ValueDoc formatted representations. 542 orig_valdoc_defaults = (ValueDoc.SUMMARY_REPR_LINELEN, 543 ValueDoc.REPR_LINELEN, 544 ValueDoc.REPR_MAXLINES) 545 ValueDoc.SUMMARY_REPR_LINELEN = self._variable_summary_linelen 546 ValueDoc.REPR_LINELEN = self._variable_linelen 547 ValueDoc.REPR_MAXLINES = self._variable_maxlines 548 549 # Use an image for the crarr symbol. 550 from epydoc.markup.epytext import ParsedEpytextDocstring 551 orig_crarr_html = ParsedEpytextDocstring.SYMBOL_TO_HTML['crarr'] 552 ParsedEpytextDocstring.SYMBOL_TO_HTML['crarr'] = ( 553 r'<span class="variable-linewrap">' 554 r'<img src="crarr.png" alt="\" /></span>') 555 556 # Keep track of failed xrefs, and report them at the end. 557 self._failed_xrefs = {} 558 559 # Create destination directories, if necessary 560 if not directory: directory = os.curdir 561 self._mkdir(directory) 562 self._directory = directory 563 564 # Write the CSS file. 565 self._files_written += 1 566 log.progress(self._files_written/self._num_files, 'epydoc.css') 567 self.write_css(directory, self._css) 568 569 # Write the Javascript file. 570 self._files_written += 1 571 log.progress(self._files_written/self._num_files, 'epydoc.js') 572 self.write_javascript(directory) 573 574 # Write images 575 self.write_images(directory) 576 577 # Build the indices. 578 indices = {'ident': self.build_identifier_index(), 579 'term': self.build_term_index()} 580 for (name, label, label2) in self.METADATA_INDICES: 581 indices[name] = self.build_metadata_index(name) 582 583 # Write the identifier index. If requested, split it into 584 # separate pages for each letter. 585 ident_by_letter = self._group_by_letter(indices['ident']) 586 if not self._split_ident_index: 587 self._write(self.write_link_index, directory, 588 'identifier-index.html', indices, 589 'Identifier Index', 'identifier-index.html', 590 ident_by_letter) 591 else: 592 # Write a page for each section. 593 for letter in self.LETTERS: 594 filename = 'identifier-index-%s.html' % letter 595 self._write(self.write_link_index, directory, filename, 596 indices, 'Identifier Index', filename, 597 ident_by_letter, [letter], 598 'identifier-index-%s.html') 599 # Use the first non-empty section as the main index page. 600 for letter in self.LETTERS: 601 if letter in ident_by_letter: 602 filename = 'identifier-index.html' 603 self._write(self.write_link_index, directory, filename, 604 indices, 'Identifier Index', filename, 605 ident_by_letter, [letter], 606 'identifier-index-%s.html') 607 break 608 609 # Write the term index. 610 if indices['term']: 611 term_by_letter = self._group_by_letter(indices['term']) 612 self._write(self.write_link_index, directory, 'term-index.html', 613 indices, 'Term Definition Index', 614 'term-index.html', term_by_letter) 615 else: 616 self._files_written += 1 # (skipped) 617 618 # Write the metadata indices. 619 for (name, label, label2) in self.METADATA_INDICES: 620 if indices[name]: 621 self._write(self.write_metadata_index, directory, 622 '%s-index.html' % name, indices, name, 623 label, label2) 624 else: 625 self._files_written += 1 # (skipped) 626 627 # Write the trees file (package & class hierarchies) 628 if self.module_list: 629 self._write(self.write_module_tree, directory, 'module-tree.html') 630 else: 631 self._files_written += 1 # (skipped) 632 if self.class_list: 633 self._write(self.write_class_tree, directory, 'class-tree.html') 634 else: 635 self._files_written += 1 # (skipped) 636 637 # Write the help file. 638 self._write(self.write_help, directory,'help.html') 639 640 # Write the frames-based table of contents. 641 if self._frames_index: 642 self._write(self.write_frames_index, directory, 'frames.html') 643 self._write(self.write_toc, directory, 'toc.html') 644 self._write(self.write_project_toc, directory, 'toc-everything.html') 645 for doc in self.module_list: 646 filename = 'toc-%s' % urllib.unquote(self.url(doc)) 647 self._write(self.write_module_toc, directory, filename, doc) 648 649 # Write the object documentation. 650 for doc in self.module_list: 651 filename = urllib.unquote(self.url(doc)) 652 self._write(self.write_module, directory, filename, doc) 653 for doc in self.class_list: 654 filename = urllib.unquote(self.url(doc)) 655 self._write(self.write_class, directory, filename, doc) 656 657 # Write source code files. 658 if self._incl_sourcecode: 659 # Build a map from short names to APIDocs, used when 660 # linking names in the source code. 661 name_to_docs = {} 662 for api_doc in self.indexed_docs: 663 if (api_doc.canonical_name is not None and 664 self.url(api_doc) is not None): 665 name = api_doc.canonical_name[-1] 666 name_to_docs.setdefault(name, []).append(api_doc) 667 # Sort each entry of the name_to_docs list. 668 for doc_list in name_to_docs.values(): 669 doc_list.sort() 670 # Write the source code for each module. 671 for doc in self.modules_with_sourcecode: 672 filename = urllib.unquote(self.pysrc_url(doc)) 673 self._write(self.write_sourcecode, directory, filename, doc, 674 name_to_docs) 675 676 # Write the auto-redirect page. 677 self._write(self.write_redirect_page, directory, 'redirect.html') 678 679 # Write the mapping object name -> URL 680 self._write(self.write_api_list, directory, 'api-objects.txt') 681 682 # Write the index.html files. 683 # (this must be done last, since it might copy another file) 684 self._files_written += 1 685 log.progress(self._files_written/self._num_files, 'index.html') 686 self.write_homepage(directory) 687 688 # Don't report references to builtins as missing 689 for k in self._failed_xrefs.keys(): # have a copy of keys 690 if hasattr(__builtin__, k): 691 del self._failed_xrefs[k] 692 693 # Report any failed crossreferences 694 if self._failed_xrefs: 695 estr = 'Failed identifier crossreference targets:\n' 696 failed_identifiers = self._failed_xrefs.keys() 697 failed_identifiers.sort() 698 for identifier in failed_identifiers: 699 names = self._failed_xrefs[identifier].keys() 700 names.sort() 701 estr += '- %s' % identifier 702 estr += '\n' 703 for name in names: 704 estr += ' (from %s)\n' % name 705 log.docstring_warning(estr) 706 707 # [xx] testing: 708 if self._num_files != int(self._files_written): 709 log.debug("Expected to write %d files, but actually " 710 "wrote %d files" % 711 (self._num_files, int(self._files_written))) 712 713 # Restore defaults that we changed. 714 (ValueDoc.SUMMARY_REPR_LINELEN, ValueDoc.REPR_LINELEN, 715 ValueDoc.REPR_MAXLINES) = orig_valdoc_defaults 716 ParsedEpytextDocstring.SYMBOL_TO_HTML['crarr'] = orig_crarr_html
717
718 - def _write(self, write_func, directory, filename, *args):
719 # Display our progress. 720 self._files_written += 1 721 log.progress(self._files_written/self._num_files, filename) 722 723 path = os.path.join(directory, filename) 724 f = codecs.open(path, 'w', 'ascii', errors='xmlcharrefreplace') 725 write_func(f.write, *args) 726 f.close()
727
728 - def _mkdir(self, directory):
729 """ 730 If the given directory does not exist, then attempt to create it. 731 @rtype: C{None} 732 """ 733 if not os.path.isdir(directory): 734 if os.path.exists(directory): 735 raise OSError('%r is not a directory' % directory) 736 os.mkdir(directory)
737 738 #//////////////////////////////////////////////////////////// 739 #{ 2.1. Module Pages 740 #//////////////////////////////////////////////////////////// 741
742 - def write_module(self, out, doc):
743 """ 744 Write an HTML page containing the API documentation for the 745 given module to C{out}. 746 747 @param doc: A L{ModuleDoc} containing the API documentation 748 for the module that should be described. 749 """ 750 longname = doc.canonical_name 751 shortname = doc.canonical_name[-1] 752 753 # Write the page header (incl. navigation bar & breadcrumbs) 754 self.write_header(out, str(longname)) 755 self.write_navbar(out, doc) 756 self.write_breadcrumbs(out, doc, self.url(doc)) 757 758 # Write the name of the module we're describing. 759 if doc.is_package is True: typ = 'Package' 760 else: typ = 'Module' 761 if longname[0].startswith('script-'): 762 shortname = str(longname)[7:] 763 typ = 'Script' 764 out('<!-- ==================== %s ' % typ.upper() + 765 'DESCRIPTION ==================== -->\n') 766 out('<h1 class="epydoc">%s %s</h1>' % (typ, shortname)) 767 out('<p class="nomargin-top">%s</p>\n' % self.pysrc_link(doc)) 768 769 # If the module has a description, then list it. 770 if doc.descr not in (None, UNKNOWN): 771 out(self.descr(doc, 2)+'\n\n') 772 773 # Write any standarad metadata (todo, author, etc.) 774 if doc.metadata is not UNKNOWN and doc.metadata: 775 out('<hr />\n') 776 self.write_standard_fields(out, doc) 777 778 # If it's a package, then list the modules it contains. 779 if doc.is_package is True and self._show_submodule_list: 780 self.write_module_list(out, doc) 781 782 # Write summary tables describing the variables that the 783 # module defines. 784 self.write_summary_table(out, "Classes", doc, "class") 785 self.write_summary_table(out, "Functions", doc, "function") 786 self.write_summary_table(out, "Variables", doc, "other") 787 788 # Write a list of all imported objects. 789 if self._show_imports: 790 self.write_imports(out, doc) 791 792 # Write detailed descriptions of functions & variables defined 793 # in this module. 794 self.write_details_list(out, "Function Details", doc, "function") 795 self.write_details_list(out, "Variables Details", doc, "other") 796 797 # Write the page footer (including navigation bar) 798 self.write_navbar(out, doc) 799 self.write_footer(out)
800 801 #//////////////////////////////////////////////////////////// 802 #{ 2.??. Source Code Pages 803 #//////////////////////////////////////////////////////////// 804
805 - def write_sourcecode(self, out, doc, name_to_docs):
806 #t0 = time.time() 807 808 filename = doc.filename 809 name = str(doc.canonical_name) 810 811 # Header 812 self.write_header(out, name) 813 self.write_navbar(out, doc) 814 self.write_breadcrumbs(out, doc, self.pysrc_url(doc)) 815 816 # Source code listing 817 out('<h1 class="epydoc">Source Code for %s</h1>\n' % 818 self.href(doc, label='%s %s' % (self.doc_kind(doc), name))) 819 out('<pre class="py-src">\n') 820 out(PythonSourceColorizer(filename, name, self.docindex, 821 self.url, name_to_docs, 822 self._src_code_tab_width).colorize()) 823 out('</pre>\n<br />\n') 824 825 # Footer 826 self.write_navbar(out, doc) 827 self.write_footer(out)
828 829 #log.debug('[%6.2f sec] Wrote pysrc for %s' % 830 # (time.time()-t0, name)) 831 832 #//////////////////////////////////////////////////////////// 833 #{ 2.2. Class Pages 834 #//////////////////////////////////////////////////////////// 835
836 - def write_class(self, out, doc):
837 """ 838 Write an HTML page containing the API documentation for the 839 given class to C{out}. 840 841 @param doc: A L{ClassDoc} containing the API documentation 842 for the class that should be described. 843 """ 844 longname = doc.canonical_name 845 shortname = doc.canonical_name[-1] 846 847 # Write the page header (incl. navigation bar & breadcrumbs) 848 self.write_header(out, str(longname)) 849 self.write_navbar(out, doc) 850 self.write_breadcrumbs(out, doc, self.url(doc)) 851 852 # Write the name of the class we're describing. 853 if (doc.metaclass is not UNKNOWN and 854 doc.metaclass.canonical_name not in (None, UNKNOWN) and 855 doc.metaclass.canonical_name != 'type'): 856 typ = self.href(doc.metaclass, doc.metaclass.canonical_name[-1]) 857 elif doc.is_type(): typ = 'Type' 858 elif doc.is_exception(): typ = 'Exception' 859 else: typ = 'Class' 860 out('<!-- ==================== %s ' % typ.upper() + 861 'DESCRIPTION ==================== -->\n') 862 out('<h1 class="epydoc">%s %s</h1>' % (typ, shortname)) 863 out('<p class="nomargin-top">%s</p>\n' % self.pysrc_link(doc)) 864 865 if ((doc.bases not in (UNKNOWN, None) and len(doc.bases) > 0) or 866 (doc.subclasses not in (UNKNOWN,None) and len(doc.subclasses)>0)): 867 # Display bases graphically, if requested. 868 if 'umlclasstree' in self._graph_types: 869 self.write_class_tree_graph(out, doc, uml_class_tree_graph) 870 elif 'classtree' in self._graph_types: 871 self.write_class_tree_graph(out, doc, class_tree_graph) 872 873 # Otherwise, use ascii-art. 874 else: 875 # Write the base class tree. 876 if doc.bases not in (UNKNOWN, None) and len(doc.bases) > 0: 877 out('<pre class="base-tree">\n%s</pre>\n\n' % 878 self.base_tree(doc)) 879 880 # Write the known subclasses 881 if (doc.subclasses not in (UNKNOWN, None) and 882 len(doc.subclasses) > 0): 883 out('<dl><dt>Known Subclasses:</dt>\n<dd>\n ') 884 out(' <ul class="subclass-list">\n') 885 for i, subclass in enumerate(doc.subclasses): 886 href = self.href(subclass, context=doc) 887 if self._val_is_public(subclass): css = '' 888 else: css = ' class="private"' 889 if i > 0: href = ', '+href 890 out('<li%s>%s</li>' % (css, href)) 891 out(' </ul>\n') 892 out('</dd></dl>\n\n') 893 894 out('<hr />\n') 895 896 # If the class has a description, then list it. 897 if doc.descr not in (None, UNKNOWN): 898 out(self.descr(doc, 2)+'\n\n') 899 900 # Write any standarad metadata (todo, author, etc.) 901 if doc.metadata is not UNKNOWN and doc.metadata: 902 out('<hr />\n') 903 self.write_standard_fields(out, doc) 904 905 # Write summary tables describing the variables that the 906 # class defines. 907 self.write_summary_table(out, "Nested Classes", doc, "class") 908 self.write_summary_table(out, "Instance Methods", doc, 909 "instancemethod") 910 self.write_summary_table(out, "Class Methods", doc, "classmethod") 911 self.write_summary_table(out, "Static Methods", doc, "staticmethod") 912 self.write_summary_table(out, "Class Variables", doc, 913 "classvariable") 914 self.write_summary_table(out, "Instance Variables", doc, 915 "instancevariable") 916 self.write_summary_table(out, "Properties", doc, "property") 917 918 # Write a list of all imported objects. 919 if self._show_imports: 920 self.write_imports(out, doc) 921 922 # Write detailed descriptions of functions & variables defined 923 # in this class. 924 # [xx] why group methods into one section but split vars into two? 925 # seems like we should either group in both cases or split in both 926 # cases. 927 self.write_details_list(out, "Method Details", doc, "method") 928 self.write_details_list(out, "Class Variable Details", doc, 929 "classvariable") 930 self.write_details_list(out, "Instance Variable Details", doc, 931 "instancevariable") 932 self.write_details_list(out, "Property Details", doc, "property") 933 934 # Write the page footer (including navigation bar) 935 self.write_navbar(out, doc) 936 self.write_footer(out)
937
938 - def write_class_tree_graph(self, out, doc, graphmaker):
939 """ 940 Write HTML code for a class tree graph of C{doc} (a classdoc), 941 using C{graphmaker} to draw the actual graph. C{graphmaker} 942 should be L{class_tree_graph()}, or L{uml_class_tree_graph()}, 943 or any other function with a compatible signature. 944 945 If the given class has any private sublcasses (including 946 recursive subclasses), then two graph images will be generated 947 -- one to display when private values are shown, and the other 948 to display when private values are hidden. 949 """ 950 linker = _HTMLDocstringLinker(self, doc) 951 private_subcls = self._private_subclasses(doc) 952 if private_subcls: 953 out('<center>\n' 954 ' <div class="private">%s</div>\n' 955 ' <div class="public" style="display:none">%s</div>\n' 956 '</center>\n' % 957 (self.render_graph(graphmaker(doc, linker, doc)), 958 self.render_graph(graphmaker(doc, linker, doc, 959 exclude=private_subcls)))) 960 else: 961 out('<center>\n%s\n</center>\n' % 962 self.render_graph(graphmaker(doc, linker, doc)))
963 964 #//////////////////////////////////////////////////////////// 965 #{ 2.3. Trees pages 966 #//////////////////////////////////////////////////////////// 967
968 - def write_module_tree(self, out):
969 # Header material 970 self.write_treepage_header(out, 'Module Hierarchy', 'module-tree.html') 971 out('<h1 class="epydoc">Module Hierarchy</h1>\n') 972 973 # Write entries for all top-level modules/packages. 974 out('<ul class="nomargin-top">\n') 975 for doc in self.module_list: 976 if (doc.package in (None, UNKNOWN) or 977 doc.package not in self.module_set): 978 self.write_module_tree_item(out, doc) 979 out('</ul>\n') 980 981 # Footer material 982 self.write_navbar(out, 'trees') 983 self.write_footer(out)
984
985 - def write_class_tree(self, out):
986 """ 987 Write HTML code for a nested list showing the base/subclass 988 relationships between all documented classes. Each element of 989 the top-level list is a class with no (documented) bases; and 990 under each class is listed all of its subclasses. Note that 991 in the case of multiple inheritance, a class may appear 992 multiple times. 993 994 @todo: For multiple inheritance, don't repeat subclasses the 995 second time a class is mentioned; instead, link to the 996 first mention. 997 """ 998 # [XX] backref for multiple inheritance? 999 # Header material 1000 self.write_treepage_header(out, 'Class Hierarchy', 'class-tree.html') 1001 out('<h1 class="epydoc">Class Hierarchy</h1>\n') 1002 1003 # Build a set containing all classes that we should list. 1004 # This includes everything in class_list, plus any of those 1005 # class' bases, but not undocumented subclasses. 1006 class_set = set() 1007 for doc in self.class_list: 1008 class_set.update([base for base in doc.mro() 1009 if isinstance(base, ClassDoc) and 1010 base.canonical_name != DottedName('object')]) 1011 1012 1013 out('<ul class="nomargin-top">\n') 1014 for doc in sorted(class_set, key=lambda c:c.canonical_name[-1]): 1015 # If doc is a subclass of anything that's documented, then 1016 # we don't need to list it separately; it will be listed 1017 # under that base. 1018 for base in doc.mro()[1:]: 1019 if base in class_set: break 1020 else: 1021 # It's not a subclass of anything documented: 1022 self.write_class_tree_item(out, doc, class_set) 1023 out('</ul>\n') 1024 1025 # Footer material 1026 self.write_navbar(out, 'trees') 1027 self.write_footer(out)
1028
1029 - def write_treepage_header(self, out, title, url):
1030 # Header material. 1031 self.write_header(out, title) 1032 self.write_navbar(out, 'trees') 1033 self.write_breadcrumbs(out, 'trees', url) 1034 if self.class_list and self.module_list: 1035 out('<center><b>\n') 1036 out(' [ <a href="module-tree.html">Module Hierarchy</a>\n') 1037 out(' | <a href="class-tree.html">Class Hierarchy</a> ]\n') 1038 out('</b></center><br />\n')
1039 1040 1041 #//////////////////////////////////////////////////////////// 1042 #{ 2.4. Index pages 1043 #//////////////////////////////////////////////////////////// 1044 1045 SPLIT_IDENT_INDEX_SIZE = 3000 1046 """If the identifier index has more than this number of entries, 1047 then it will be split into separate pages, one for each 1048 alphabetical section.""" 1049 1050 LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_' 1051 """The alphabetical sections that are used for link index pages.""" 1052 1088 1089
1090 - def write_metadata_index(self, out, indices, field, title, typ):
1091 """ 1092 Write an HTML page containing a metadata index. 1093 """ 1094 index = indices[field] 1095 1096 # Header material. 1097 self.write_indexpage_header(out, indices, title, 1098 '%s-index.html' % field) 1099 1100 # Page title. 1101 out('<h1 class="epydoc"><a name="%s">%s</a></h1>\n<br />\n' % 1102 (field, title)) 1103 1104 # Index (one section per arg) 1105 for arg in sorted(index): 1106 # Write a section title. 1107 if arg is not None: 1108 if len([1 for (doc, descrs) in index[arg] if 1109 not self._doc_or_ancestor_is_private(doc)]) == 0: 1110 out('<div class="private">') 1111 else: 1112 out('<div>') 1113 self.write_table_header(out, 'metadata-index', arg) 1114 out('</table>') 1115 # List every descr for this arg. 1116 for (doc, descrs) in index[arg]: 1117 if self._doc_or_ancestor_is_private(doc): 1118 out('<div class="private">\n') 1119 else: 1120 out('<div>\n') 1121 out('<table width="100%" class="metadata-index" ' 1122 'bgcolor="#e0e0e0"><tr><td class="metadata-index">') 1123 out('<b>%s in %s</b>' % 1124 (typ, self.href(doc, label=doc.canonical_name))) 1125 out(' <ul class="nomargin">\n') 1126 for descr in descrs: 1127 out(' <li>%s</li>\n' % 1128 self.docstring_to_html(descr,doc,4)) 1129 out(' </ul>\n') 1130 out('</table></div>\n') 1131 1132 # Footer material. 1133 out('<br />') 1134 self.write_navbar(out, 'indices') 1135 self.write_footer(out)
1136
1137 - def write_indexpage_header(self, out, indices, title, url):
1138 """ 1139 A helper for the index page generation functions, which 1140 generates a header that can be used to navigate between the 1141 different indices. 1142 """ 1143 self.write_header(out, title) 1144 self.write_navbar(out, 'indices') 1145 self.write_breadcrumbs(out, 'indices', url) 1146 1147 if (indices['term'] or 1148 [1 for (name,l,l2) in self.METADATA_INDICES if indices[name]]): 1149 out('<center><b>[\n') 1150 out(' <a href="identifier-index.html">Identifiers</a>\n') 1151 if indices['term']: 1152 out('| <a href="term-index.html">Term Definitions</a>\n') 1153 for (name, label, label2) in self.METADATA_INDICES: 1154 if indices[name]: 1155 out('| <a href="%s-index.html">%s</a>\n' % 1156 (name, label2)) 1157 out(']</b></center><br />\n')
1158
1159 - def write_index_section(self, out, items, add_blankline=False):
1160 out('<table class="link-index" width="100%" border="1">\n') 1161 num_rows = (len(items)+2)/3 1162 for row in range(num_rows): 1163 out('<tr>\n') 1164 for col in range(3): 1165 out('<td width="33%" class="link-index">') 1166 i = col*num_rows+row 1167 if i < len(items): 1168 name, url, container = items[col*num_rows+row] 1169 out('<a href="%s">%s</a>' % (url, name)) 1170 if container is not None: 1171 out('<br />\n') 1172 if isinstance(container, ModuleDoc): 1173 label = container.canonical_name 1174 else: 1175 label = container.canonical_name[-1] 1176 out('<span class="index-where">(in&nbsp;%s)' 1177 '</span>' % self.href(container, label)) 1178 else: 1179 out('&nbsp;') 1180 out('</td>\n') 1181 out('</tr>\n') 1182 if add_blankline and num_rows == 1: 1183 blank_cell = '<td class="link-index">&nbsp;</td>' 1184 out('<tr>'+3*blank_cell+'</tr>\n') 1185 out('</table>\n')
1186 1187 #//////////////////////////////////////////////////////////// 1188 #{ 2.5. Help Page 1189 #//////////////////////////////////////////////////////////// 1190
1191 - def write_help(self, out):
1192 """ 1193 Write an HTML help file to the given stream. If 1194 C{self._helpfile} contains a help file, then use it; 1195 otherwise, use the default helpfile from 1196 L{epydoc.docwriter.html_help}. 1197 """ 1198 # todo: optionally parse .rst etc help files? 1199 1200 # Get the contents of the help file. 1201 if self._helpfile: 1202 if os.path.exists(self._helpfile): 1203 try: help = open(self._helpfile).read() 1204 except: raise IOError("Can't open help file: %r" % 1205 self._helpfile) 1206 else: 1207 raise IOError("Can't find help file: %r" % self._helpfile) 1208 else: 1209 if self._prj_name: thisprj = self._prj_name 1210 else: thisprj = 'this project' 1211 help = HTML_HELP % {'this_project':thisprj} 1212 1213 # Insert the help contents into a webpage. 1214 self.write_header(out, 'Help') 1215 self.write_navbar(out, 'help') 1216 self.write_breadcrumbs(out, 'help', 'help.html') 1217 out(help) 1218 self.write_navbar(out, 'help') 1219 self.write_footer(out)
1220 1221 #//////////////////////////////////////////////////////////// 1222 #{ 2.6. Frames-based Table of Contents 1223 #//////////////////////////////////////////////////////////// 1224 1225 write_frames_index = compile_template( 1226 """ 1227 write_frames_index(self, out) 1228 1229 Write the frames index file for the frames-based table of 1230 contents to the given streams. 1231 """, 1232 # /------------------------- Template -------------------------\ 1233 ''' 1234 <?xml version="1.0" encoding="iso-8859-1"?> 1235 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" 1236 "DTD/xhtml1-frameset.dtd"> 1237 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 1238 <head> 1239 <title> $self._prj_name or "API Documentation"$ </title> 1240 </head> 1241 <frameset cols="20%,80%"> 1242 <frameset rows="30%,70%"> 1243 <frame src="toc.html" name="moduleListFrame" 1244 id="moduleListFrame" /> 1245 <frame src="toc-everything.html" name="moduleFrame" 1246 id="moduleFrame" /> 1247 </frameset> 1248 <frame src="$self._top_page_url$" name="mainFrame" id="mainFrame" /> 1249 </frameset> 1250 </html> 1251 ''') 1252 # \------------------------------------------------------------/ 1253 1254 write_toc = compile_template( 1255 """ 1256 write_toc(self, out) 1257 """, 1258 # /------------------------- Template -------------------------\ 1259 ''' 1260 >>> self.write_header(out, "Table of Contents") 1261 <h1 class="toc">Table&nbsp;of&nbsp;Contents</h1> 1262 <hr /> 1263 <a target="moduleFrame" href="toc-everything.html">Everything</a> 1264 <br /> 1265 >>> self.write_toc_section(out, "Modules", self.module_list) 1266 <hr /> 1267 >>> if self._show_private: 1268 $self.PRIVATE_LINK$ 1269 >>> #endif 1270 >>> self.write_footer(out, short=True) 1271 ''') 1272 # \------------------------------------------------------------/ 1273
1274 - def write_toc_section(self, out, name, docs, fullname=True):
1275 if not docs: return 1276 1277 # Assign names to each item, and sort by name. 1278 if fullname: 1279 docs = [(str(d.canonical_name), d) for d in docs] 1280 else: 1281 docs = [(str(d.canonical_name[-1]), d) for d in docs] 1282 docs.sort() 1283 1284 out(' <h2 class="toc">%s</h2>\n' % name) 1285 for label, doc in docs: 1286 doc_url = self.url(doc) 1287 toc_url = 'toc-%s' % doc_url 1288 is_private = self._doc_or_ancestor_is_private(doc) 1289 if is_private: 1290 if not self._show_private: continue 1291 out(' <div class="private">\n') 1292 1293 if isinstance(doc, ModuleDoc): 1294 out(' <a target="moduleFrame" href="%s"\n' 1295 ' onclick="setFrame(\'%s\',\'%s\');"' 1296 ' >%s</a><br />' % (toc_url, toc_url, doc_url, label)) 1297 else: 1298 out(' <a target="mainFrame" href="%s"\n' 1299 ' >%s</a><br />' % (doc_url, label)) 1300 if is_private: 1301 out(' </div>\n')
1302
1303 - def write_project_toc(self, out):
1304 self.write_header(out, "Everything") 1305 out('<h1 class="toc">Everything</h1>\n') 1306 out('<hr />\n') 1307 1308 # List the classes. 1309 self.write_toc_section(out, "All Classes", self.class_list) 1310 1311 # List the functions. 1312 funcs = [d for d in self.routine_list 1313 if not isinstance(self.docindex.container(d), 1314 (ClassDoc, types.NoneType))] 1315 self.write_toc_section(out, "All Functions", funcs) 1316 1317 # List the variables. 1318 vars = [] 1319 for doc in self.module_list: 1320 vars += doc.select_variables(value_type='other', 1321 imported=False, 1322 public=self._public_filter) 1323 self.write_toc_section(out, "All Variables", vars) 1324 1325 # Footer material. 1326 out('<hr />\n') 1327 if self._show_private: 1328 out(self.PRIVATE_LINK+'\n') 1329 self.write_footer(out, short=True)
1330
1331 - def write_module_toc(self, out, doc):
1332 """ 1333 Write an HTML page containing the table of contents page for 1334 the given module to the given streams. This page lists the 1335 modules, classes, exceptions, functions, and variables defined 1336 by the module. 1337 """ 1338 name = doc.canonical_name[-1] 1339 self.write_header(out, name) 1340 out('<h1 class="toc">Module %s</h1>\n' % name) 1341 out('<hr />\n') 1342 1343 1344 # List the classes. 1345 classes = doc.select_variables(value_type='class', imported=False, 1346 public=self._public_filter) 1347 self.write_toc_section(out, "Classes", classes, fullname=False) 1348 1349 # List the functions. 1350 funcs = doc.select_variables(value_type='function', imported=False, 1351 public=self._public_filter) 1352 self.write_toc_section(out, "Functions", funcs, fullname=False) 1353 1354 # List the variables. 1355 variables = doc.select_variables(value_type='other', imported=False, 1356 public=self._public_filter) 1357 self.write_toc_section(out, "Variables", variables, fullname=False) 1358 1359 # Footer material. 1360 out('<hr />\n') 1361 if self._show_private: 1362 out(self.PRIVATE_LINK+'\n') 1363 self.write_footer(out, short=True)
1364 1365 #//////////////////////////////////////////////////////////// 1366 #{ 2.7. Project homepage (index.html) 1367 #//////////////////////////////////////////////////////////// 1368
1369 - def write_homepage(self, directory):
1370 """ 1371 Write an C{index.html} file in the given directory. The 1372 contents of this file are copied or linked from an existing 1373 page, so this method must be called after all pages have been 1374 written. The page used is determined by L{_frames_index} and 1375 L{_top_page}: 1376 - If L{_frames_index} is true, then C{frames.html} is 1377 copied. 1378 - Otherwise, the page specified by L{_top_page} is 1379 copied. 1380 """ 1381 filename = os.path.join(directory, 'index.html') 1382 if self._frames_index: top = 'frames.html' 1383 else: top = self._top_page_url 1384 1385 # Copy the non-frames index file from top, if it's internal. 1386 if top[:5] != 'http:' and '/' not in top: 1387 try: 1388 # Read top into `s`. 1389 topfile = os.path.join(directory, top) 1390 s = open(topfile, 'r').read() 1391 1392 # Write the output file. 1393 open(filename, 'w').write(s) 1394 return 1395 except: 1396 log.error('Warning: error copying index; ' 1397 'using a redirect page') 1398 1399 # Use a redirect if top is external, or if we faild to copy. 1400 name = self._prj_name or 'this project' 1401 f = open(filename, 'w') 1402 self.write_redirect_index(f.write, top, name) 1403 f.close()
1404 1405 write_redirect_index = compile_template( 1406 """ 1407 write_redirect_index(self, out, top, name) 1408 """, 1409 # /------------------------- Template -------------------------\ 1410 ''' 1411 <?xml version="1.0" encoding="iso-8859-1"?> 1412 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 1413 "DTD/xhtml1-strict.dtd"> 1414 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 1415 <head> 1416 <title> Redirect </title> 1417 <meta http-equiv="refresh" content="1;url=$top$" /> 1418 <link rel="stylesheet" href="epydoc.css" type="text/css"></link> 1419 </head> 1420 <body> 1421 <p>Redirecting to the API documentation for 1422 <a href="$top$">$self._prj_name or "this project"$</a>...</p> 1423 </body> 1424 </html> 1425 ''') 1426 # \------------------------------------------------------------/ 1427 1428 #//////////////////////////////////////////////////////////// 1429 #{ 2.8. Stylesheet (epydoc.css) 1430 #//////////////////////////////////////////////////////////// 1431
1432 - def write_css(self, directory, cssname):
1433 """ 1434 Write the CSS stylesheet in the given directory. If 1435 C{cssname} contains a stylesheet file or name (from 1436 L{epydoc.docwriter.html_css}), then use that stylesheet; 1437 otherwise, use the default stylesheet. 1438 1439 @rtype: C{None} 1440 """ 1441 filename = os.path.join(directory, 'epydoc.css') 1442 1443 # Get the contents for the stylesheet file. 1444 if cssname is None: 1445 css = STYLESHEETS['default'][0] 1446 else: 1447 if os.path.exists(cssname): 1448 try: css = open(cssname).read() 1449 except: raise IOError("Can't open CSS file: %r" % cssname) 1450 elif cssname in STYLESHEETS: 1451 css = STYLESHEETS[cssname][0] 1452 else: 1453 raise IOError("Can't find CSS file: %r" % cssname) 1454 1455 # Write the stylesheet. 1456 cssfile = open(filename, 'w') 1457 cssfile.write(css) 1458 cssfile.close()
1459 1460 #//////////////////////////////////////////////////////////// 1461 #{ 2.9. Javascript (epydoc.js) 1462 #//////////////////////////////////////////////////////////// 1463
1464 - def write_javascript(self, directory):
1465 jsfile = open(os.path.join(directory, 'epydoc.js'), 'w') 1466 print >> jsfile, self.TOGGLE_PRIVATE_JS 1467 print >> jsfile, self.SHOW_PRIVATE_JS 1468 print >> jsfile, self.GET_COOKIE_JS 1469 print >> jsfile, self.SET_FRAME_JS 1470 print >> jsfile, self.HIDE_PRIVATE_JS 1471 print >> jsfile, self.TOGGLE_CALLGRAPH_JS 1472 print >> jsfile, html_colorize.PYSRC_JAVASCRIPTS 1473 print >> jsfile, self.GET_ANCHOR_JS 1474 print >> jsfile, self.REDIRECT_URL_JS 1475 jsfile.close()
1476 1477 #: A javascript that is used to show or hide the API documentation 1478 #: for private objects. In order for this to work correctly, all 1479 #: documentation for private objects should be enclosed in 1480 #: C{<div class="private">...</div>} elements. 1481 TOGGLE_PRIVATE_JS = ''' 1482 function toggle_private() { 1483 // Search for any private/public links on this page. Store 1484 // their old text in "cmd," so we will know what action to 1485 // take; and change their text to the opposite action. 1486 var cmd = "?"; 1487 var elts = document.getElementsByTagName("a"); 1488 for(var i=0; i<elts.length; i++) { 1489 if (elts[i].className == "privatelink") { 1490 cmd = elts[i].innerHTML; 1491 elts[i].innerHTML = ((cmd && cmd.substr(0,4)=="show")? 1492 "hide&nbsp;private":"show&nbsp;private"); 1493 } 1494 } 1495 // Update all DIVs containing private objects. 1496 var elts = document.getElementsByTagName("div"); 1497 for(var i=0; i<elts.length; i++) { 1498 if (elts[i].className == "private") { 1499 elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"none":"block"); 1500 } 1501 else if (elts[i].className == "public") { 1502 elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"block":"none"); 1503 } 1504 } 1505 // Update all table rows containing private objects. Note, we 1506 // use "" instead of "block" becaue IE & firefox disagree on what 1507 // this should be (block vs table-row), and "" just gives the 1508 // default for both browsers. 1509 var elts = document.getElementsByTagName("tr"); 1510 for(var i=0; i<elts.length; i++) { 1511 if (elts[i].className == "private") { 1512 elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"none":""); 1513 } 1514 } 1515 // Update all list items containing private objects. 1516 var elts = document.getElementsByTagName("li"); 1517 for(var i=0; i<elts.length; i++) { 1518 if (elts[i].className == "private") { 1519 elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")? 1520 "none":""); 1521 } 1522 } 1523 // Update all list items containing private objects. 1524 var elts = document.getElementsByTagName("ul"); 1525 for(var i=0; i<elts.length; i++) { 1526 if (elts[i].className == "private") { 1527 elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"none":"block"); 1528 } 1529 } 1530 // Set a cookie to remember the current option. 1531 document.cookie = "EpydocPrivate="+cmd; 1532 } 1533 '''.strip() 1534 1535 #: A javascript that is used to read the value of a cookie. This 1536 #: is used to remember whether private variables should be shown or 1537 #: hidden. 1538 GET_COOKIE_JS = ''' 1539 function getCookie(name) { 1540 var dc = document.cookie; 1541 var prefix = name + "="; 1542 var begin = dc.indexOf("; " + prefix); 1543 if (begin == -1) { 1544 begin = dc.indexOf(prefix); 1545 if (begin != 0) return null; 1546 } else 1547 { begin += 2; } 1548 var end = document.cookie.indexOf(";", begin); 1549 if (end == -1) 1550 { end = dc.length; } 1551 return unescape(dc.substring(begin + prefix.length, end)); 1552 } 1553 '''.strip() 1554 1555 #: A javascript that is used to set the contents of two frames at 1556 #: once. This is used by the project table-of-contents frame to 1557 #: set both the module table-of-contents frame and the main frame 1558 #: when the user clicks on a module. 1559 SET_FRAME_JS = ''' 1560 function setFrame(url1, url2) { 1561 parent.frames[1].location.href = url1; 1562 parent.frames[2].location.href = url2; 1563 } 1564 '''.strip() 1565 1566 #: A javascript that is used to hide private variables, unless 1567 #: either: (a) the cookie says not to; or (b) we appear to be 1568 #: linking to a private variable. 1569 HIDE_PRIVATE_JS = ''' 1570 function checkCookie() { 1571 var cmd=getCookie("EpydocPrivate"); 1572 if (cmd && cmd.substr(0,4)!="show" && location.href.indexOf("#_") < 0) 1573 toggle_private(); 1574 } 1575 '''.strip() 1576 1577 TOGGLE_CALLGRAPH_JS = ''' 1578 function toggleCallGraph(id) { 1579 var elt = document.getElementById(id); 1580 if (elt.style.display == "none") 1581 elt.style.display = "block"; 1582 else 1583 elt.style.display = "none"; 1584 } 1585 '''.strip() 1586 1587 SHOW_PRIVATE_JS = ''' 1588 function show_private() { 1589 var elts = document.getElementsByTagName("a"); 1590 for(var i=0; i<elts.length; i++) { 1591 if (elts[i].className == "privatelink") { 1592 cmd = elts[i].innerHTML; 1593 if (cmd && cmd.substr(0,4)=="show") 1594 toggle_private(); 1595 } 1596 } 1597 } 1598 '''.strip() 1599 1600 GET_ANCHOR_JS = ''' 1601 function get_anchor() { 1602 var href = location.href; 1603 var start = href.indexOf("#")+1; 1604 if ((start != 0) && (start != href.length)) 1605 return href.substring(start, href.length); 1606 } 1607 '''.strip() 1608 1609 #: A javascript that is used to implement the auto-redirect page. 1610 #: When the user visits <redirect.html#dotted.name>, they will 1611 #: automatically get redirected to the page for the object with 1612 #: the given fully-qualified dotted name. E.g., for epydoc, 1613 #: <redirect.html#epydoc.apidoc.UNKNOWN> redirects the user to 1614 #: <epydoc.apidoc-module.html#UNKNOWN>. 1615 REDIRECT_URL_JS = ''' 1616 function redirect_url(dottedName) { 1617 // Scan through each element of the "pages" list, and check 1618 // if "name" matches with any of them. 1619 for (var i=0; i<pages.length; i++) { 1620 1621 // Each page has the form "<pagename>-m" or "<pagename>-c"; 1622 // extract the <pagename> portion & compare it to dottedName. 1623 var pagename = pages[i].substring(0, pages[i].length-2); 1624 if (pagename == dottedName.substring(0,pagename.length)) { 1625 1626 // We\'ve found a page that matches `dottedName`; 1627 // construct its URL, using leftover `dottedName` 1628 // content to form an anchor. 1629 var pagetype = pages[i].charAt(pages[i].length-1); 1630 var url = pagename + ((pagetype=="m")?"-module.html": 1631 "-class.html"); 1632 if (dottedName.length > pagename.length) 1633 url += "#" + dottedName.substring(pagename.length+1, 1634 dottedName.length); 1635 return url; 1636 } 1637 } 1638 } 1639 '''.strip() 1640 1641 1642 #//////////////////////////////////////////////////////////// 1643 #{ 2.10. Graphs 1644 #//////////////////////////////////////////////////////////// 1645
1646 - def render_graph(self, graph):
1647 if graph is None: return '' 1648 graph.caption = graph.title = None 1649 return graph.to_html(self._directory) or ''
1650 1651 RE_CALLGRAPH_ID = re.compile(r"""["'](.+-div)['"]""") 1652
1653 - def render_callgraph(self, callgraph, token=""):
1654 """Render the HTML chunk of a callgraph. 1655 1656 If C{callgraph} is a string, use the L{_callgraph_cache} to return 1657 a pre-rendered HTML chunk. This mostly avoids to run C{dot} twice for 1658 the same callgraph. Else, run the graph and store its HTML output in 1659 the cache. 1660 1661 @param callgraph: The graph to render or its L{uid<DotGraph.uid>}. 1662 @type callgraph: L{DotGraph} or C{str} 1663 @param token: A string that can be used to make the C{<div>} id 1664 unambiguous, if the callgraph is used more than once in a page. 1665 @type token: C{str} 1666 @return: The HTML representation of the graph. 1667 @rtype: C{str} 1668 """ 1669 if callgraph is None: return "" 1670 1671 if isinstance(callgraph, basestring): 1672 uid = callgraph 1673 graph_html = self._callgraph_cache.get(callgraph, "") 1674 elif callgraph.uid in self._callgraph_cache: 1675 uid = callgraph.uid 1676 graph_html = self._callgraph_cache.get(callgraph, "") 1677 else: 1678 uid = callgraph.uid 1679 graph_html = self.render_graph(callgraph) 1680 self._callgraph_cache[uid] = graph_html 1681 1682 if graph_html: 1683 return ('<div style="display:none" id="%s-div"><center>\n' 1684 '<table border="0" cellpadding="0" cellspacing="0">\n' 1685 ' <tr><td>%s</td></tr>\n' 1686 ' <tr><th>Call Graph</th></tr>\n' 1687 '</table><br />\n</center></div>\n' % 1688 (uid+token, graph_html)) 1689 else: 1690 return ''
1691 1717 1718 #//////////////////////////////////////////////////////////// 1719 #{ 2.11. Images 1720 #//////////////////////////////////////////////////////////// 1721 1722 IMAGES = {'crarr.png': # Carriage-return arrow, used for LINEWRAP. 1723 'iVBORw0KGgoAAAANSUhEUgAAABEAAAAKCAMAAABlokWQAAAALHRFWHRD' 1724 'cmVhdGlvbiBUaW1lAFR1\nZSAyMiBBdWcgMjAwNiAwMDo0MzoxMCAtMD' 1725 'UwMGAMEFgAAAAHdElNRQfWCBYFASkQ033WAAAACXBI\nWXMAAB7CAAAe' 1726 'wgFu0HU+AAAABGdBTUEAALGPC/xhBQAAAEVQTFRF////zcOw18/AgGY0' 1727 'c1cg4dvQ\ninJEYEAAYkME3NXI6eTcloFYe2Asr5+AbE4Uh29A9fPwqp' 1728 'l4ZEUI8O3onopk0Ma0lH5U1nfFdgAA\nAAF0Uk5TAEDm2GYAAABNSURB' 1729 'VHjaY2BAAbzsvDAmK5oIlxgfioiwCAe7KJKIgKAQOzsLLwTwA0VY\n+d' 1730 'iRAT8T0AxuIIMHqoaXCWIPGzsHJ6orGJiYWRjQASOcBQAocgMSPKMTIg' 1731 'AAAABJRU5ErkJggg==\n', 1732 } 1733
1734 - def write_images(self, directory):
1735 for (name, data) in self.IMAGES.items(): 1736 f = open(os.path.join(directory, name), 'wb') 1737 f.write(base64.decodestring(data)) 1738 f.close()
1739 1740 #//////////////////////////////////////////////////////////// 1741 #{ 3.1. Page Header 1742 #//////////////////////////////////////////////////////////// 1743 1744 write_header = compile_template( 1745 """ 1746 write_header(self, out, title) 1747 1748 Generate HTML code for the standard page header, and write it 1749 to C{out}. C{title} is a string containing the page title. 1750 It should be appropriately escaped/encoded. 1751 """, 1752 # /------------------------- Template -------------------------\ 1753 ''' 1754 <?xml version="1.0" encoding="ascii"?> 1755 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 1756 "DTD/xhtml1-transitional.dtd"> 1757 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 1758 <head> 1759 <title>$title$</title> 1760 <link rel="stylesheet" href="epydoc.css" type="text/css" /> 1761 <script type="text/javascript" src="epydoc.js"></script> 1762 </head> 1763 1764 <body bgcolor="white" text="black" link="blue" vlink="#204080" 1765 alink="#204080"> 1766 ''') 1767 # \------------------------------------------------------------/ 1768 1769 #//////////////////////////////////////////////////////////// 1770 #{ 3.2. Page Footer 1771 #//////////////////////////////////////////////////////////// 1772 1773 write_footer = compile_template( 1774 """ 1775 write_footer(self, out, short=False) 1776 1777 Generate HTML code for the standard page footer, and write it 1778 to C{out}. 1779 """, 1780 # /------------------------- Template -------------------------\ 1781 ''' 1782 >>> if not short: 1783 <table border="0" cellpadding="0" cellspacing="0" width="100%%"> 1784 <tr> 1785 <td align="left" class="footer"> 1786 >>> if self._include_log: 1787 <a href="epydoc-log.html">Generated by Epydoc 1788 $epydoc.__version__$ 1789 >>> if self._include_timestamp: 1790 on $time.asctime()$</a> 1791 >>> else: 1792 Generated by Epydoc $epydoc.__version__$ 1793 >>> if self._include_timestamp: 1794 on $time.asctime()$ 1795 >>> #endif 1796 </td> 1797 <td align="right" class="footer">