| Tor Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame^] | 1 | # |
| 2 | # epydoc -- HTML output generator |
| 3 | # Edward Loper |
| 4 | # |
| 5 | # Created [01/30/01 05:18 PM] |
| 6 | # $Id: html.py 1674 2008-01-29 06:03:36Z 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 src_code_tab_width: C{int} |
| 295 | @keyword src_code_tab_width: Number of spaces to replace each tab |
| 296 | with in source code listings. |
| 297 | """ |
| 298 | self.docindex = docindex |
| 299 | |
| 300 | # Process keyword arguments. |
| 301 | self._show_private = kwargs.get('show_private', 1) |
| 302 | """Should private docs be included?""" |
| 303 | |
| 304 | self._prj_name = kwargs.get('prj_name', None) |
| 305 | """The project's name (for the project link in the navbar)""" |
| 306 | |
| 307 | self._prj_url = kwargs.get('prj_url', None) |
| 308 | """URL for the project link in the navbar""" |
| 309 | |
| 310 | self._prj_link = kwargs.get('prj_link', None) |
| 311 | """HTML code for the project link in the navbar""" |
| 312 | |
| 313 | self._top_page = kwargs.get('top_page', None) |
| 314 | """The 'main' page""" |
| 315 | |
| 316 | self._css = kwargs.get('css') |
| 317 | """CSS stylesheet to use""" |
| 318 | |
| 319 | self._helpfile = kwargs.get('help_file', None) |
| 320 | """Filename of file to extract help contents from""" |
| 321 | |
| 322 | self._frames_index = kwargs.get('show_frames', 1) |
| 323 | """Should a frames index be created?""" |
| 324 | |
| 325 | self._show_imports = kwargs.get('show_imports', False) |
| 326 | """Should imports be listed?""" |
| 327 | |
| 328 | self._propfunc_linelen = kwargs.get('property_function_linelength', 40) |
| 329 | """[XXX] Not used!""" |
| 330 | |
| 331 | self._variable_maxlines = kwargs.get('variable_maxlines', 8) |
| 332 | """Max lines for variable values""" |
| 333 | |
| 334 | self._variable_linelen = kwargs.get('variable_linelength', 70) |
| 335 | """Max line length for variable values""" |
| 336 | |
| 337 | self._variable_summary_linelen = \ |
| 338 | kwargs.get('variable_summary_linelength', 65) |
| 339 | """Max length for variable value summaries""" |
| 340 | |
| 341 | self._variable_tooltip_linelen = \ |
| 342 | kwargs.get('variable_tooltip_linelength', 600) |
| 343 | """Max length for variable tooltips""" |
| 344 | |
| 345 | self._inheritance = kwargs.get('inheritance', 'listed') |
| 346 | """How should inheritance be displayed? 'listed', 'included', |
| 347 | or 'grouped'""" |
| 348 | |
| 349 | self._incl_sourcecode = kwargs.get('include_source_code', True) |
| 350 | """Should pages be generated for source code of modules?""" |
| 351 | |
| 352 | self._mark_docstrings = kwargs.get('mark_docstrings', False) |
| 353 | """Wrap <span class='docstring'>...</span> around docstrings?""" |
| 354 | |
| 355 | self._graph_types = kwargs.get('graphs', ()) or () |
| 356 | """Graphs that we should include in our output.""" |
| 357 | |
| 358 | self._include_log = kwargs.get('include_log', False) |
| 359 | """Are we generating an HTML log page?""" |
| 360 | |
| 361 | self._src_code_tab_width = kwargs.get('src_code_tab_width', 8) |
| 362 | """Number of spaces to replace each tab with in source code |
| 363 | listings.""" |
| 364 | |
| 365 | self._callgraph_cache = {} |
| 366 | """Map the callgraph L{uid<DotGraph.uid>} to their HTML |
| 367 | representation.""" |
| 368 | |
| 369 | self._redundant_details = kwargs.get('redundant_details', False) |
| 370 | """If true, then include objects in the details list even if all |
| 371 | info about them is already provided by the summary table.""" |
| 372 | |
| 373 | # For use with select_variables(): |
| 374 | if self._show_private: |
| 375 | self._public_filter = None |
| 376 | else: |
| 377 | self._public_filter = True |
| 378 | |
| 379 | # Make sure inheritance has a sane value. |
| 380 | if self._inheritance not in ('listed', 'included', 'grouped'): |
| 381 | raise ValueError, 'Bad value for inheritance' |
| 382 | |
| 383 | # Create the project homepage link, if it was not specified. |
| 384 | if (self._prj_name or self._prj_url) and not self._prj_link: |
| 385 | self._prj_link = plaintext_to_html(self._prj_name or |
| 386 | 'Project Homepage') |
| 387 | |
| 388 | # Add a hyperlink to _prj_url, if _prj_link doesn't already |
| 389 | # contain any hyperlinks. |
| 390 | if (self._prj_link and self._prj_url and |
| 391 | not re.search(r'<a[^>]*\shref', self._prj_link)): |
| 392 | self._prj_link = ('<a class="navbar" target="_top" href="'+ |
| 393 | self._prj_url+'">'+self._prj_link+'</a>') |
| 394 | |
| 395 | # Precompute lists & sets of APIDoc objects that we're |
| 396 | # interested in. |
| 397 | self.valdocs = valdocs = sorted(docindex.reachable_valdocs( |
| 398 | imports=False, packages=False, bases=False, submodules=False, |
| 399 | subclasses=False, private=self._show_private)) |
| 400 | self.module_list = [d for d in valdocs if isinstance(d, ModuleDoc)] |
| 401 | """The list of L{ModuleDoc}s for the documented modules.""" |
| 402 | self.module_set = set(self.module_list) |
| 403 | """The set of L{ModuleDoc}s for the documented modules.""" |
| 404 | self.class_list = [d for d in valdocs if isinstance(d, ClassDoc)] |
| 405 | """The list of L{ClassDoc}s for the documented classes.""" |
| 406 | self.class_set = set(self.class_list) |
| 407 | """The set of L{ClassDoc}s for the documented classes.""" |
| 408 | self.routine_list = [d for d in valdocs if isinstance(d, RoutineDoc)] |
| 409 | """The list of L{RoutineDoc}s for the documented routines.""" |
| 410 | self.indexed_docs = [] |
| 411 | """The list of L{APIDoc}s for variables and values that should |
| 412 | be included in the index.""" |
| 413 | |
| 414 | # URL for 'trees' page |
| 415 | if self.module_list: self._trees_url = 'module-tree.html' |
| 416 | else: self._trees_url = 'class-tree.html' |
| 417 | |
| 418 | # Construct the value for self.indexed_docs. |
| 419 | self.indexed_docs += [d for d in valdocs |
| 420 | if not isinstance(d, GenericValueDoc)] |
| 421 | for doc in valdocs: |
| 422 | if isinstance(doc, NamespaceDoc): |
| 423 | # add any vars with generic values; but don't include |
| 424 | # inherited vars. |
| 425 | self.indexed_docs += [d for d in doc.variables.values() if |
| 426 | isinstance(d.value, GenericValueDoc) |
| 427 | and d.container == doc] |
| 428 | self.indexed_docs.sort() |
| 429 | |
| 430 | # Figure out the url for the top page. |
| 431 | self._top_page_url = self._find_top_page(self._top_page) |
| 432 | |
| 433 | # Decide whether or not to split the identifier index. |
| 434 | self._split_ident_index = (len(self.indexed_docs) >= |
| 435 | self.SPLIT_IDENT_INDEX_SIZE) |
| 436 | |
| 437 | # Figure out how many output files there will be (for progress |
| 438 | # reporting). |
| 439 | self.modules_with_sourcecode = set() |
| 440 | for doc in self.module_list: |
| 441 | if isinstance(doc, ModuleDoc) and is_src_filename(doc.filename): |
| 442 | self.modules_with_sourcecode.add(doc) |
| 443 | self._num_files = (len(self.class_list) + len(self.module_list) + |
| 444 | 10 + len(self.METADATA_INDICES)) |
| 445 | if self._frames_index: |
| 446 | self._num_files += len(self.module_list) + 3 |
| 447 | |
| 448 | if self._incl_sourcecode: |
| 449 | self._num_files += len(self.modules_with_sourcecode) |
| 450 | if self._split_ident_index: |
| 451 | self._num_files += len(self.LETTERS) |
| 452 | |
| 453 | def _find_top_page(self, pagename): |
| 454 | """ |
| 455 | Find the top page for the API documentation. This page is |
| 456 | used as the default page shown in the main frame, when frames |
| 457 | are used. When frames are not used, this page is copied to |
| 458 | C{index.html}. |
| 459 | |
| 460 | @param pagename: The name of the page, as specified by the |
| 461 | keyword argument C{top} to the constructor. |
| 462 | @type pagename: C{string} |
| 463 | @return: The URL of the top page. |
| 464 | @rtype: C{string} |
| 465 | """ |
| 466 | # If a page name was specified, then we need to figure out |
| 467 | # what it points to. |
| 468 | if pagename: |
| 469 | # If it's a URL, then use it directly. |
| 470 | if pagename.lower().startswith('http:'): |
| 471 | return pagename |
| 472 | |
| 473 | # If it's an object, then use that object's page. |
| 474 | try: |
| 475 | doc = self.docindex.get_valdoc(pagename) |
| 476 | return self.url(doc) |
| 477 | except: |
| 478 | pass |
| 479 | |
| 480 | # Otherwise, give up. |
| 481 | log.warning('Could not find top page %r; using %s ' |
| 482 | 'instead' % (pagename, self._trees_url)) |
| 483 | return self._trees_url |
| 484 | |
| 485 | # If no page name was specified, then try to choose one |
| 486 | # automatically. |
| 487 | else: |
| 488 | root = [val_doc for val_doc in self.docindex.root |
| 489 | if isinstance(val_doc, (ClassDoc, ModuleDoc))] |
| 490 | if len(root) == 0: |
| 491 | # No docs?? Try the trees page. |
| 492 | return self._trees_url |
| 493 | elif len(root) == 1: |
| 494 | # One item in the root; use that. |
| 495 | return self.url(root[0]) |
| 496 | else: |
| 497 | # Multiple root items; if they're all in one package, |
| 498 | # then use that. Otherwise, use self._trees_url |
| 499 | root = sorted(root, key=lambda v:len(v.canonical_name)) |
| 500 | top = root[0] |
| 501 | for doc in root[1:]: |
| 502 | if not top.canonical_name.dominates(doc.canonical_name): |
| 503 | return self._trees_url |
| 504 | else: |
| 505 | return self.url(top) |
| 506 | |
| 507 | #//////////////////////////////////////////////////////////// |
| 508 | #{ 1. Interface Methods |
| 509 | #//////////////////////////////////////////////////////////// |
| 510 | |
| 511 | def write(self, directory=None): |
| 512 | """ |
| 513 | Write the documentation to the given directory. |
| 514 | |
| 515 | @type directory: C{string} |
| 516 | @param directory: The directory to which output should be |
| 517 | written. If no directory is specified, output will be |
| 518 | written to the current directory. If the directory does |
| 519 | not exist, it will be created. |
| 520 | @rtype: C{None} |
| 521 | @raise OSError: If C{directory} cannot be created. |
| 522 | @raise OSError: If any file cannot be created or written to. |
| 523 | """ |
| 524 | # For progress reporting: |
| 525 | self._files_written = 0. |
| 526 | |
| 527 | # Set the default values for ValueDoc formatted representations. |
| 528 | orig_valdoc_defaults = (ValueDoc.SUMMARY_REPR_LINELEN, |
| 529 | ValueDoc.REPR_LINELEN, |
| 530 | ValueDoc.REPR_MAXLINES) |
| 531 | ValueDoc.SUMMARY_REPR_LINELEN = self._variable_summary_linelen |
| 532 | ValueDoc.REPR_LINELEN = self._variable_linelen |
| 533 | ValueDoc.REPR_MAXLINES = self._variable_maxlines |
| 534 | |
| 535 | # Use an image for the crarr symbol. |
| 536 | from epydoc.markup.epytext import ParsedEpytextDocstring |
| 537 | orig_crarr_html = ParsedEpytextDocstring.SYMBOL_TO_HTML['crarr'] |
| 538 | ParsedEpytextDocstring.SYMBOL_TO_HTML['crarr'] = ( |
| 539 | r'<span class="variable-linewrap">' |
| 540 | r'<img src="crarr.png" alt="\" /></span>') |
| 541 | |
| 542 | # Keep track of failed xrefs, and report them at the end. |
| 543 | self._failed_xrefs = {} |
| 544 | |
| 545 | # Create destination directories, if necessary |
| 546 | if not directory: directory = os.curdir |
| 547 | self._mkdir(directory) |
| 548 | self._directory = directory |
| 549 | |
| 550 | # Write the CSS file. |
| 551 | self._files_written += 1 |
| 552 | log.progress(self._files_written/self._num_files, 'epydoc.css') |
| 553 | self.write_css(directory, self._css) |
| 554 | |
| 555 | # Write the Javascript file. |
| 556 | self._files_written += 1 |
| 557 | log.progress(self._files_written/self._num_files, 'epydoc.js') |
| 558 | self.write_javascript(directory) |
| 559 | |
| 560 | # Write images |
| 561 | self.write_images(directory) |
| 562 | |
| 563 | # Build the indices. |
| 564 | indices = {'ident': self.build_identifier_index(), |
| 565 | 'term': self.build_term_index()} |
| 566 | for (name, label, label2) in self.METADATA_INDICES: |
| 567 | indices[name] = self.build_metadata_index(name) |
| 568 | |
| 569 | # Write the identifier index. If requested, split it into |
| 570 | # separate pages for each letter. |
| 571 | ident_by_letter = self._group_by_letter(indices['ident']) |
| 572 | if not self._split_ident_index: |
| 573 | self._write(self.write_link_index, directory, |
| 574 | 'identifier-index.html', indices, |
| 575 | 'Identifier Index', 'identifier-index.html', |
| 576 | ident_by_letter) |
| 577 | else: |
| 578 | # Write a page for each section. |
| 579 | for letter in self.LETTERS: |
| 580 | filename = 'identifier-index-%s.html' % letter |
| 581 | self._write(self.write_link_index, directory, filename, |
| 582 | indices, 'Identifier Index', filename, |
| 583 | ident_by_letter, [letter], |
| 584 | 'identifier-index-%s.html') |
| 585 | # Use the first non-empty section as the main index page. |
| 586 | for letter in self.LETTERS: |
| 587 | if letter in ident_by_letter: |
| 588 | filename = 'identifier-index.html' |
| 589 | self._write(self.write_link_index, directory, filename, |
| 590 | indices, 'Identifier Index', filename, |
| 591 | ident_by_letter, [letter], |
| 592 | 'identifier-index-%s.html') |
| 593 | break |
| 594 | |
| 595 | # Write the term index. |
| 596 | if indices['term']: |
| 597 | term_by_letter = self._group_by_letter(indices['term']) |
| 598 | self._write(self.write_link_index, directory, 'term-index.html', |
| 599 | indices, 'Term Definition Index', |
| 600 | 'term-index.html', term_by_letter) |
| 601 | else: |
| 602 | self._files_written += 1 # (skipped) |
| 603 | |
| 604 | # Write the metadata indices. |
| 605 | for (name, label, label2) in self.METADATA_INDICES: |
| 606 | if indices[name]: |
| 607 | self._write(self.write_metadata_index, directory, |
| 608 | '%s-index.html' % name, indices, name, |
| 609 | label, label2) |
| 610 | else: |
| 611 | self._files_written += 1 # (skipped) |
| 612 | |
| 613 | # Write the trees file (package & class hierarchies) |
| 614 | if self.module_list: |
| 615 | self._write(self.write_module_tree, directory, 'module-tree.html') |
| 616 | else: |
| 617 | self._files_written += 1 # (skipped) |
| 618 | if self.class_list: |
| 619 | self._write(self.write_class_tree, directory, 'class-tree.html') |
| 620 | else: |
| 621 | self._files_written += 1 # (skipped) |
| 622 | |
| 623 | # Write the help file. |
| 624 | self._write(self.write_help, directory,'help.html') |
| 625 | |
| 626 | # Write the frames-based table of contents. |
| 627 | if self._frames_index: |
| 628 | self._write(self.write_frames_index, directory, 'frames.html') |
| 629 | self._write(self.write_toc, directory, 'toc.html') |
| 630 | self._write(self.write_project_toc, directory, 'toc-everything.html') |
| 631 | for doc in self.module_list: |
| 632 | filename = 'toc-%s' % urllib.unquote(self.url(doc)) |
| 633 | self._write(self.write_module_toc, directory, filename, doc) |
| 634 | |
| 635 | # Write the object documentation. |
| 636 | for doc in self.module_list: |
| 637 | filename = urllib.unquote(self.url(doc)) |
| 638 | self._write(self.write_module, directory, filename, doc) |
| 639 | for doc in self.class_list: |
| 640 | filename = urllib.unquote(self.url(doc)) |
| 641 | self._write(self.write_class, directory, filename, doc) |
| 642 | |
| 643 | # Write source code files. |
| 644 | if self._incl_sourcecode: |
| 645 | # Build a map from short names to APIDocs, used when |
| 646 | # linking names in the source code. |
| 647 | name_to_docs = {} |
| 648 | for api_doc in self.indexed_docs: |
| 649 | if (api_doc.canonical_name is not None and |
| 650 | self.url(api_doc) is not None): |
| 651 | name = api_doc.canonical_name[-1] |
| 652 | name_to_docs.setdefault(name, []).append(api_doc) |
| 653 | # Sort each entry of the name_to_docs list. |
| 654 | for doc_list in name_to_docs.values(): |
| 655 | doc_list.sort() |
| 656 | # Write the source code for each module. |
| 657 | for doc in self.modules_with_sourcecode: |
| 658 | filename = urllib.unquote(self.pysrc_url(doc)) |
| 659 | self._write(self.write_sourcecode, directory, filename, doc, |
| 660 | name_to_docs) |
| 661 | |
| 662 | # Write the auto-redirect page. |
| 663 | self._write(self.write_redirect_page, directory, 'redirect.html') |
| 664 | |
| 665 | # Write the mapping object name -> URL |
| 666 | self._write(self.write_api_list, directory, 'api-objects.txt') |
| 667 | |
| 668 | # Write the index.html files. |
| 669 | # (this must be done last, since it might copy another file) |
| 670 | self._files_written += 1 |
| 671 | log.progress(self._files_written/self._num_files, 'index.html') |
| 672 | self.write_homepage(directory) |
| 673 | |
| 674 | # Don't report references to builtins as missing |
| 675 | for k in self._failed_xrefs.keys(): # have a copy of keys |
| 676 | if hasattr(__builtin__, k): |
| 677 | del self._failed_xrefs[k] |
| 678 | |
| 679 | # Report any failed crossreferences |
| 680 | if self._failed_xrefs: |
| 681 | estr = 'Failed identifier crossreference targets:\n' |
| 682 | failed_identifiers = self._failed_xrefs.keys() |
| 683 | failed_identifiers.sort() |
| 684 | for identifier in failed_identifiers: |
| 685 | names = self._failed_xrefs[identifier].keys() |
| 686 | names.sort() |
| 687 | estr += '- %s' % identifier |
| 688 | estr += '\n' |
| 689 | for name in names: |
| 690 | estr += ' (from %s)\n' % name |
| 691 | log.docstring_warning(estr) |
| 692 | |
| 693 | # [xx] testing: |
| 694 | if self._num_files != int(self._files_written): |
| 695 | log.debug("Expected to write %d files, but actually " |
| 696 | "wrote %d files" % |
| 697 | (self._num_files, int(self._files_written))) |
| 698 | |
| 699 | # Restore defaults that we changed. |
| 700 | (ValueDoc.SUMMARY_REPR_LINELEN, ValueDoc.REPR_LINELEN, |
| 701 | ValueDoc.REPR_MAXLINES) = orig_valdoc_defaults |
| 702 | ParsedEpytextDocstring.SYMBOL_TO_HTML['crarr'] = orig_crarr_html |
| 703 | |
| 704 | def _write(self, write_func, directory, filename, *args): |
| 705 | # Display our progress. |
| 706 | self._files_written += 1 |
| 707 | log.progress(self._files_written/self._num_files, filename) |
| 708 | |
| 709 | path = os.path.join(directory, filename) |
| 710 | f = codecs.open(path, 'w', 'ascii', errors='xmlcharrefreplace') |
| 711 | write_func(f.write, *args) |
| 712 | f.close() |
| 713 | |
| 714 | def _mkdir(self, directory): |
| 715 | """ |
| 716 | If the given directory does not exist, then attempt to create it. |
| 717 | @rtype: C{None} |
| 718 | """ |
| 719 | if not os.path.isdir(directory): |
| 720 | if os.path.exists(directory): |
| 721 | raise OSError('%r is not a directory' % directory) |
| 722 | os.mkdir(directory) |
| 723 | |
| 724 | #//////////////////////////////////////////////////////////// |
| 725 | #{ 2.1. Module Pages |
| 726 | #//////////////////////////////////////////////////////////// |
| 727 | |
| 728 | def write_module(self, out, doc): |
| 729 | """ |
| 730 | Write an HTML page containing the API documentation for the |
| 731 | given module to C{out}. |
| 732 | |
| 733 | @param doc: A L{ModuleDoc} containing the API documentation |
| 734 | for the module that should be described. |
| 735 | """ |
| 736 | longname = doc.canonical_name |
| 737 | shortname = doc.canonical_name[-1] |
| 738 | |
| 739 | # Write the page header (incl. navigation bar & breadcrumbs) |
| 740 | self.write_header(out, str(longname)) |
| 741 | self.write_navbar(out, doc) |
| 742 | self.write_breadcrumbs(out, doc, self.url(doc)) |
| 743 | |
| 744 | # Write the name of the module we're describing. |
| 745 | if doc.is_package is True: typ = 'Package' |
| 746 | else: typ = 'Module' |
| 747 | if longname[0].startswith('script-'): |
| 748 | shortname = str(longname)[7:] |
| 749 | typ = 'Script' |
| 750 | out('<!-- ==================== %s ' % typ.upper() + |
| 751 | 'DESCRIPTION ==================== -->\n') |
| 752 | out('<h1 class="epydoc">%s %s</h1>' % (typ, shortname)) |
| 753 | out('<p class="nomargin-top">%s</p>\n' % self.pysrc_link(doc)) |
| 754 | |
| 755 | # If the module has a description, then list it. |
| 756 | if doc.descr not in (None, UNKNOWN): |
| 757 | out(self.descr(doc, 2)+'\n\n') |
| 758 | |
| 759 | # Write any standarad metadata (todo, author, etc.) |
| 760 | if doc.metadata is not UNKNOWN and doc.metadata: |
| 761 | out('<hr />\n') |
| 762 | self.write_standard_fields(out, doc) |
| 763 | |
| 764 | # If it's a package, then list the modules it contains. |
| 765 | if doc.is_package is True: |
| 766 | self.write_module_list(out, doc) |
| 767 | |
| 768 | # Write summary tables describing the variables that the |
| 769 | # module defines. |
| 770 | self.write_summary_table(out, "Classes", doc, "class") |
| 771 | self.write_summary_table(out, "Functions", doc, "function") |
| 772 | self.write_summary_table(out, "Variables", doc, "other") |
| 773 | |
| 774 | # Write a list of all imported objects. |
| 775 | if self._show_imports: |
| 776 | self.write_imports(out, doc) |
| 777 | |
| 778 | # Write detailed descriptions of functions & variables defined |
| 779 | # in this module. |
| 780 | self.write_details_list(out, "Function Details", doc, "function") |
| 781 | self.write_details_list(out, "Variables Details", doc, "other") |
| 782 | |
| 783 | # Write the page footer (including navigation bar) |
| 784 | self.write_navbar(out, doc) |
| 785 | self.write_footer(out) |
| 786 | |
| 787 | #//////////////////////////////////////////////////////////// |
| 788 | #{ 2.??. Source Code Pages |
| 789 | #//////////////////////////////////////////////////////////// |
| 790 | |
| 791 | def write_sourcecode(self, out, doc, name_to_docs): |
| 792 | #t0 = time.time() |
| 793 | |
| 794 | filename = doc.filename |
| 795 | name = str(doc.canonical_name) |
| 796 | |
| 797 | # Header |
| 798 | self.write_header(out, name) |
| 799 | self.write_navbar(out, doc) |
| 800 | self.write_breadcrumbs(out, doc, self.pysrc_url(doc)) |
| 801 | |
| 802 | # Source code listing |
| 803 | out('<h1 class="epydoc">Source Code for %s</h1>\n' % |
| 804 | self.href(doc, label='%s %s' % (self.doc_kind(doc), name))) |
| 805 | out('<pre class="py-src">\n') |
| 806 | out(PythonSourceColorizer(filename, name, self.docindex, |
| 807 | self.url, name_to_docs, |
| 808 | self._src_code_tab_width).colorize()) |
| 809 | out('</pre>\n<br />\n') |
| 810 | |
| 811 | # Footer |
| 812 | self.write_navbar(out, doc) |
| 813 | self.write_footer(out) |
| 814 | |
| 815 | #log.debug('[%6.2f sec] Wrote pysrc for %s' % |
| 816 | # (time.time()-t0, name)) |
| 817 | |
| 818 | #//////////////////////////////////////////////////////////// |
| 819 | #{ 2.2. Class Pages |
| 820 | #//////////////////////////////////////////////////////////// |
| 821 | |
| 822 | def write_class(self, out, doc): |
| 823 | """ |
| 824 | Write an HTML page containing the API documentation for the |
| 825 | given class to C{out}. |
| 826 | |
| 827 | @param doc: A L{ClassDoc} containing the API documentation |
| 828 | for the class that should be described. |
| 829 | """ |
| 830 | longname = doc.canonical_name |
| 831 | shortname = doc.canonical_name[-1] |
| 832 | |
| 833 | # Write the page header (incl. navigation bar & breadcrumbs) |
| 834 | self.write_header(out, str(longname)) |
| 835 | self.write_navbar(out, doc) |
| 836 | self.write_breadcrumbs(out, doc, self.url(doc)) |
| 837 | |
| 838 | # Write the name of the class we're describing. |
| 839 | if doc.is_type(): typ = 'Type' |
| 840 | elif doc.is_exception(): typ = 'Exception' |
| 841 | else: typ = 'Class' |
| 842 | out('<!-- ==================== %s ' % typ.upper() + |
| 843 | 'DESCRIPTION ==================== -->\n') |
| 844 | out('<h1 class="epydoc">%s %s</h1>' % (typ, shortname)) |
| 845 | out('<p class="nomargin-top">%s</p>\n' % self.pysrc_link(doc)) |
| 846 | |
| 847 | if ((doc.bases not in (UNKNOWN, None) and len(doc.bases) > 0) or |
| 848 | (doc.subclasses not in (UNKNOWN,None) and len(doc.subclasses)>0)): |
| 849 | # Display bases graphically, if requested. |
| 850 | if 'umlclasstree' in self._graph_types: |
| 851 | self.write_class_tree_graph(out, doc, uml_class_tree_graph) |
| 852 | elif 'classtree' in self._graph_types: |
| 853 | self.write_class_tree_graph(out, doc, class_tree_graph) |
| 854 | |
| 855 | # Otherwise, use ascii-art. |
| 856 | else: |
| 857 | # Write the base class tree. |
| 858 | if doc.bases not in (UNKNOWN, None) and len(doc.bases) > 0: |
| 859 | out('<pre class="base-tree">\n%s</pre>\n\n' % |
| 860 | self.base_tree(doc)) |
| 861 | |
| 862 | # Write the known subclasses |
| 863 | if (doc.subclasses not in (UNKNOWN, None) and |
| 864 | len(doc.subclasses) > 0): |
| 865 | out('<dl><dt>Known Subclasses:</dt>\n<dd>\n ') |
| 866 | out(' <ul class="subclass-list">\n') |
| 867 | for i, subclass in enumerate(doc.subclasses): |
| 868 | href = self.href(subclass, context=doc) |
| 869 | if self._val_is_public(subclass): css = '' |
| 870 | else: css = ' class="private"' |
| 871 | if i > 0: href = ', '+href |
| 872 | out('<li%s>%s</li>' % (css, href)) |
| 873 | out(' </ul>\n') |
| 874 | out('</dd></dl>\n\n') |
| 875 | |
| 876 | out('<hr />\n') |
| 877 | |
| 878 | # If the class has a description, then list it. |
| 879 | if doc.descr not in (None, UNKNOWN): |
| 880 | out(self.descr(doc, 2)+'\n\n') |
| 881 | |
| 882 | # Write any standarad metadata (todo, author, etc.) |
| 883 | if doc.metadata is not UNKNOWN and doc.metadata: |
| 884 | out('<hr />\n') |
| 885 | self.write_standard_fields(out, doc) |
| 886 | |
| 887 | # Write summary tables describing the variables that the |
| 888 | # class defines. |
| 889 | self.write_summary_table(out, "Nested Classes", doc, "class") |
| 890 | self.write_summary_table(out, "Instance Methods", doc, |
| 891 | "instancemethod") |
| 892 | self.write_summary_table(out, "Class Methods", doc, "classmethod") |
| 893 | self.write_summary_table(out, "Static Methods", doc, "staticmethod") |
| 894 | self.write_summary_table(out, "Class Variables", doc, |
| 895 | "classvariable") |
| 896 | self.write_summary_table(out, "Instance Variables", doc, |
| 897 | "instancevariable") |
| 898 | self.write_summary_table(out, "Properties", doc, "property") |
| 899 | |
| 900 | # Write a list of all imported objects. |
| 901 | if self._show_imports: |
| 902 | self.write_imports(out, doc) |
| 903 | |
| 904 | # Write detailed descriptions of functions & variables defined |
| 905 | # in this class. |
| 906 | # [xx] why group methods into one section but split vars into two? |
| 907 | # seems like we should either group in both cases or split in both |
| 908 | # cases. |
| 909 | self.write_details_list(out, "Method Details", doc, "method") |
| 910 | self.write_details_list(out, "Class Variable Details", doc, |
| 911 | "classvariable") |
| 912 | self.write_details_list(out, "Instance Variable Details", doc, |
| 913 | "instancevariable") |
| 914 | self.write_details_list(out, "Property Details", doc, "property") |
| 915 | |
| 916 | # Write the page footer (including navigation bar) |
| 917 | self.write_navbar(out, doc) |
| 918 | self.write_footer(out) |
| 919 | |
| 920 | def write_class_tree_graph(self, out, doc, graphmaker): |
| 921 | """ |
| 922 | Write HTML code for a class tree graph of C{doc} (a classdoc), |
| 923 | using C{graphmaker} to draw the actual graph. C{graphmaker} |
| 924 | should be L{class_tree_graph()}, or L{uml_class_tree_graph()}, |
| 925 | or any other function with a compatible signature. |
| 926 | |
| 927 | If the given class has any private sublcasses (including |
| 928 | recursive subclasses), then two graph images will be generated |
| 929 | -- one to display when private values are shown, and the other |
| 930 | to display when private values are hidden. |
| 931 | """ |
| 932 | linker = _HTMLDocstringLinker(self, doc) |
| 933 | private_subcls = self._private_subclasses(doc) |
| 934 | if private_subcls: |
| 935 | out('<center>\n' |
| 936 | ' <div class="private">%s</div>\n' |
| 937 | ' <div class="public" style="display:none">%s</div>\n' |
| 938 | '</center>\n' % |
| 939 | (self.render_graph(graphmaker(doc, linker, doc)), |
| 940 | self.render_graph(graphmaker(doc, linker, doc, |
| 941 | exclude=private_subcls)))) |
| 942 | else: |
| 943 | out('<center>\n%s\n</center>\n' % |
| 944 | self.render_graph(graphmaker(doc, linker, doc))) |
| 945 | |
| 946 | #//////////////////////////////////////////////////////////// |
| 947 | #{ 2.3. Trees pages |
| 948 | #//////////////////////////////////////////////////////////// |
| 949 | |
| 950 | def write_module_tree(self, out): |
| 951 | # Header material |
| 952 | self.write_treepage_header(out, 'Module Hierarchy', 'module-tree.html') |
| 953 | out('<h1 class="epydoc">Module Hierarchy</h1>\n') |
| 954 | |
| 955 | # Write entries for all top-level modules/packages. |
| 956 | out('<ul class="nomargin-top">\n') |
| 957 | for doc in self.module_list: |
| 958 | if (doc.package in (None, UNKNOWN) or |
| 959 | doc.package not in self.module_set): |
| 960 | self.write_module_tree_item(out, doc) |
| 961 | out('</ul>\n') |
| 962 | |
| 963 | # Footer material |
| 964 | self.write_navbar(out, 'trees') |
| 965 | self.write_footer(out) |
| 966 | |
| 967 | def write_class_tree(self, out): |
| 968 | """ |
| 969 | Write HTML code for a nested list showing the base/subclass |
| 970 | relationships between all documented classes. Each element of |
| 971 | the top-level list is a class with no (documented) bases; and |
| 972 | under each class is listed all of its subclasses. Note that |
| 973 | in the case of multiple inheritance, a class may appear |
| 974 | multiple times. |
| 975 | |
| 976 | @todo: For multiple inheritance, don't repeat subclasses the |
| 977 | second time a class is mentioned; instead, link to the |
| 978 | first mention. |
| 979 | """ |
| 980 | # [XX] backref for multiple inheritance? |
| 981 | # Header material |
| 982 | self.write_treepage_header(out, 'Class Hierarchy', 'class-tree.html') |
| 983 | out('<h1 class="epydoc">Class Hierarchy</h1>\n') |
| 984 | |
| 985 | # Build a set containing all classes that we should list. |
| 986 | # This includes everything in class_list, plus any of those |
| 987 | # class' bases, but not undocumented subclasses. |
| 988 | class_set = self.class_set.copy() |
| 989 | for doc in self.class_list: |
| 990 | if doc.bases != UNKNOWN: |
| 991 | for base in doc.bases: |
| 992 | if base not in class_set: |
| 993 | if isinstance(base, ClassDoc): |
| 994 | class_set.update(base.mro()) |
| 995 | else: |
| 996 | # [XX] need to deal with this -- how? |
| 997 | pass |
| 998 | #class_set.add(base) |
| 999 | |
| 1000 | out('<ul class="nomargin-top">\n') |
| 1001 | for doc in sorted(class_set, key=lambda c:c.canonical_name[-1]): |
| 1002 | if doc.bases != UNKNOWN and len(doc.bases)==0: |
| 1003 | self.write_class_tree_item(out, doc, class_set) |
| 1004 | out('</ul>\n') |
| 1005 | |
| 1006 | # Footer material |
| 1007 | self.write_navbar(out, 'trees') |
| 1008 | self.write_footer(out) |
| 1009 | |
| 1010 | def write_treepage_header(self, out, title, url): |
| 1011 | # Header material. |
| 1012 | self.write_header(out, title) |
| 1013 | self.write_navbar(out, 'trees') |
| 1014 | self.write_breadcrumbs(out, 'trees', url) |
| 1015 | if self.class_list and self.module_list: |
| 1016 | out('<center><b>\n') |
| 1017 | out(' [ <a href="module-tree.html">Module Hierarchy</a>\n') |
| 1018 | out(' | <a href="class-tree.html">Class Hierarchy</a> ]\n') |
| 1019 | out('</b></center><br />\n') |
| 1020 | |
| 1021 | |
| 1022 | #//////////////////////////////////////////////////////////// |
| 1023 | #{ 2.4. Index pages |
| 1024 | #//////////////////////////////////////////////////////////// |
| 1025 | |
| 1026 | SPLIT_IDENT_INDEX_SIZE = 3000 |
| 1027 | """If the identifier index has more than this number of entries, |
| 1028 | then it will be split into separate pages, one for each |
| 1029 | alphabetical section.""" |
| 1030 | |
| 1031 | LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_' |
| 1032 | """The alphabetical sections that are used for link index pages.""" |
| 1033 | |
| 1034 | def write_link_index(self, out, indices, title, url, index_by_section, |
| 1035 | sections=LETTERS, section_url='#%s'): |
| 1036 | |
| 1037 | # Header |
| 1038 | self.write_indexpage_header(out, indices, title, url) |
| 1039 | |
| 1040 | # Index title & links to alphabetical sections. |
| 1041 | out('<table border="0" width="100%">\n' |
| 1042 | '<tr valign="bottom"><td>\n') |
| 1043 | out('<h1 class="epydoc">%s</h1>\n</td><td>\n[\n' % title) |
| 1044 | for sec in self.LETTERS: |
| 1045 | if sec in index_by_section: |
| 1046 | out(' <a href="%s">%s</a>\n' % (section_url % sec, sec)) |
| 1047 | else: |
| 1048 | out(' %s\n' % sec) |
| 1049 | out(']\n') |
| 1050 | out('</td></table>\n') |
| 1051 | |
| 1052 | # Alphabetical sections. |
| 1053 | sections = [s for s in sections if s in index_by_section] |
| 1054 | if sections: |
| 1055 | out('<table border="0" width="100%">\n') |
| 1056 | for section in sorted(sections): |
| 1057 | out('<tr valign="top"><td valign="top" width="1%">') |
| 1058 | out('<h2 class="epydoc"><a name="%s">%s</a></h2></td>\n' % |
| 1059 | (section, section)) |
| 1060 | out('<td valign="top">\n') |
| 1061 | self.write_index_section(out, index_by_section[section], True) |
| 1062 | out('</td></tr>\n') |
| 1063 | out('</table>\n<br />') |
| 1064 | |
| 1065 | # Footer material. |
| 1066 | out('<br />') |
| 1067 | self.write_navbar(out, 'indices') |
| 1068 | self.write_footer(out) |
| 1069 | |
| 1070 | |
| 1071 | def write_metadata_index(self, out, indices, field, title, typ): |
| 1072 | """ |
| 1073 | Write an HTML page containing a metadata index. |
| 1074 | """ |
| 1075 | index = indices[field] |
| 1076 | |
| 1077 | # Header material. |
| 1078 | self.write_indexpage_header(out, indices, title, |
| 1079 | '%s-index.html' % field) |
| 1080 | |
| 1081 | # Page title. |
| 1082 | out('<h1 class="epydoc"><a name="%s">%s</a></h1>\n<br />\n' % |
| 1083 | (field, title)) |
| 1084 | |
| 1085 | # Index (one section per arg) |
| 1086 | for arg in sorted(index): |
| 1087 | # Write a section title. |
| 1088 | if arg is not None: |
| 1089 | if len([1 for (doc, descrs) in index[arg] if |
| 1090 | not self._doc_or_ancestor_is_private(doc)]) == 0: |
| 1091 | out('<div class="private">') |
| 1092 | else: |
| 1093 | out('<div>') |
| 1094 | self.write_table_header(out, 'metadata-index', arg) |
| 1095 | out('</table>') |
| 1096 | # List every descr for this arg. |
| 1097 | for (doc, descrs) in index[arg]: |
| 1098 | if self._doc_or_ancestor_is_private(doc): |
| 1099 | out('<div class="private">\n') |
| 1100 | else: |
| 1101 | out('<div>\n') |
| 1102 | out('<table width="100%" class="metadata-index" ' |
| 1103 | 'bgcolor="#e0e0e0"><tr><td class="metadata-index">') |
| 1104 | out('<b>%s in %s</b>' % |
| 1105 | (typ, self.href(doc, label=doc.canonical_name))) |
| 1106 | out(' <ul class="nomargin">\n') |
| 1107 | for descr in descrs: |
| 1108 | out(' <li>%s</li>\n' % |
| 1109 | self.docstring_to_html(descr,doc,4)) |
| 1110 | out(' </ul>\n') |
| 1111 | out('</table></div>\n') |
| 1112 | |
| 1113 | # Footer material. |
| 1114 | out('<br />') |
| 1115 | self.write_navbar(out, 'indices') |
| 1116 | self.write_footer(out) |
| 1117 | |
| 1118 | def write_indexpage_header(self, out, indices, title, url): |
| 1119 | """ |
| 1120 | A helper for the index page generation functions, which |
| 1121 | generates a header that can be used to navigate between the |
| 1122 | different indices. |
| 1123 | """ |
| 1124 | self.write_header(out, title) |
| 1125 | self.write_navbar(out, 'indices') |
| 1126 | self.write_breadcrumbs(out, 'indices', url) |
| 1127 | |
| 1128 | if (indices['term'] or |
| 1129 | [1 for (name,l,l2) in self.METADATA_INDICES if indices[name]]): |
| 1130 | out('<center><b>[\n') |
| 1131 | out(' <a href="identifier-index.html">Identifiers</a>\n') |
| 1132 | if indices['term']: |
| 1133 | out('| <a href="term-index.html">Term Definitions</a>\n') |
| 1134 | for (name, label, label2) in self.METADATA_INDICES: |
| 1135 | if indices[name]: |
| 1136 | out('| <a href="%s-index.html">%s</a>\n' % |
| 1137 | (name, label2)) |
| 1138 | out(']</b></center><br />\n') |
| 1139 | |
| 1140 | def write_index_section(self, out, items, add_blankline=False): |
| 1141 | out('<table class="link-index" width="100%" border="1">\n') |
| 1142 | num_rows = (len(items)+2)/3 |
| 1143 | for row in range(num_rows): |
| 1144 | out('<tr>\n') |
| 1145 | for col in range(3): |
| 1146 | out('<td width="33%" class="link-index">') |
| 1147 | i = col*num_rows+row |
| 1148 | if i < len(items): |
| 1149 | name, url, container = items[col*num_rows+row] |
| 1150 | out('<a href="%s">%s</a>' % (url, name)) |
| 1151 | if container is not None: |
| 1152 | out('<br />\n') |
| 1153 | if isinstance(container, ModuleDoc): |
| 1154 | label = container.canonical_name |
| 1155 | else: |
| 1156 | label = container.canonical_name[-1] |
| 1157 | out('<span class="index-where">(in %s)' |
| 1158 | '</span>' % self.href(container, label)) |
| 1159 | else: |
| 1160 | out(' ') |
| 1161 | out('</td>\n') |
| 1162 | out('</tr>\n') |
| 1163 | if add_blankline and num_rows == 1: |
| 1164 | blank_cell = '<td class="link-index"> </td>' |
| 1165 | out('<tr>'+3*blank_cell+'</tr>\n') |
| 1166 | out('</table>\n') |
| 1167 | |
| 1168 | #//////////////////////////////////////////////////////////// |
| 1169 | #{ 2.5. Help Page |
| 1170 | #//////////////////////////////////////////////////////////// |
| 1171 | |
| 1172 | def write_help(self, out): |
| 1173 | """ |
| 1174 | Write an HTML help file to the given stream. If |
| 1175 | C{self._helpfile} contains a help file, then use it; |
| 1176 | otherwise, use the default helpfile from |
| 1177 | L{epydoc.docwriter.html_help}. |
| 1178 | """ |
| 1179 | # todo: optionally parse .rst etc help files? |
| 1180 | |
| 1181 | # Get the contents of the help file. |
| 1182 | if self._helpfile: |
| 1183 | if os.path.exists(self._helpfile): |
| 1184 | try: help = open(self._helpfile).read() |
| 1185 | except: raise IOError("Can't open help file: %r" % |
| 1186 | self._helpfile) |
| 1187 | else: |
| 1188 | raise IOError("Can't find help file: %r" % self._helpfile) |
| 1189 | else: |
| 1190 | if self._prj_name: thisprj = self._prj_name |
| 1191 | else: thisprj = 'this project' |
| 1192 | help = HTML_HELP % {'this_project':thisprj} |
| 1193 | |
| 1194 | # Insert the help contents into a webpage. |
| 1195 | self.write_header(out, 'Help') |
| 1196 | self.write_navbar(out, 'help') |
| 1197 | self.write_breadcrumbs(out, 'help', 'help.html') |
| 1198 | out(help) |
| 1199 | self.write_navbar(out, 'help') |
| 1200 | self.write_footer(out) |
| 1201 | |
| 1202 | #//////////////////////////////////////////////////////////// |
| 1203 | #{ 2.6. Frames-based Table of Contents |
| 1204 | #//////////////////////////////////////////////////////////// |
| 1205 | |
| 1206 | write_frames_index = compile_template( |
| 1207 | """ |
| 1208 | write_frames_index(self, out) |
| 1209 | |
| 1210 | Write the frames index file for the frames-based table of |
| 1211 | contents to the given streams. |
| 1212 | """, |
| 1213 | # /------------------------- Template -------------------------\ |
| 1214 | ''' |
| 1215 | <?xml version="1.0" encoding="iso-8859-1"?> |
| 1216 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" |
| 1217 | "DTD/xhtml1-frameset.dtd"> |
| 1218 | <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> |
| 1219 | <head> |
| 1220 | <title> $self._prj_name or "API Documentation"$ </title> |
| 1221 | </head> |
| 1222 | <frameset cols="20%,80%"> |
| 1223 | <frameset rows="30%,70%"> |
| 1224 | <frame src="toc.html" name="moduleListFrame" |
| 1225 | id="moduleListFrame" /> |
| 1226 | <frame src="toc-everything.html" name="moduleFrame" |
| 1227 | id="moduleFrame" /> |
| 1228 | </frameset> |
| 1229 | <frame src="$self._top_page_url$" name="mainFrame" id="mainFrame" /> |
| 1230 | </frameset> |
| 1231 | </html> |
| 1232 | ''') |
| 1233 | # \------------------------------------------------------------/ |
| 1234 | |
| 1235 | write_toc = compile_template( |
| 1236 | """ |
| 1237 | write_toc(self, out) |
| 1238 | """, |
| 1239 | # /------------------------- Template -------------------------\ |
| 1240 | ''' |
| 1241 | >>> self.write_header(out, "Table of Contents") |
| 1242 | <h1 class="toc">Table of Contents</h1> |
| 1243 | <hr /> |
| 1244 | <a target="moduleFrame" href="toc-everything.html">Everything</a> |
| 1245 | <br /> |
| 1246 | >>> self.write_toc_section(out, "Modules", self.module_list) |
| 1247 | <hr /> |
| 1248 | >>> if self._show_private: |
| 1249 | $self.PRIVATE_LINK$ |
| 1250 | >>> #endif |
| 1251 | >>> self.write_footer(out, short=True) |
| 1252 | ''') |
| 1253 | # \------------------------------------------------------------/ |
| 1254 | |
| 1255 | def write_toc_section(self, out, name, docs, fullname=True): |
| 1256 | if not docs: return |
| 1257 | |
| 1258 | # Assign names to each item, and sort by name. |
| 1259 | if fullname: |
| 1260 | docs = [(str(d.canonical_name), d) for d in docs] |
| 1261 | else: |
| 1262 | docs = [(str(d.canonical_name[-1]), d) for d in docs] |
| 1263 | docs.sort() |
| 1264 | |
| 1265 | out(' <h2 class="toc">%s</h2>\n' % name) |
| 1266 | for label, doc in docs: |
| 1267 | doc_url = self.url(doc) |
| 1268 | toc_url = 'toc-%s' % doc_url |
| 1269 | is_private = self._doc_or_ancestor_is_private(doc) |
| 1270 | if is_private: |
| 1271 | if not self._show_private: continue |
| 1272 | out(' <div class="private">\n') |
| 1273 | |
| 1274 | if isinstance(doc, ModuleDoc): |
| 1275 | out(' <a target="moduleFrame" href="%s"\n' |
| 1276 | ' onclick="setFrame(\'%s\',\'%s\');"' |
| 1277 | ' >%s</a><br />' % (toc_url, toc_url, doc_url, label)) |
| 1278 | else: |
| 1279 | out(' <a target="mainFrame" href="%s"\n' |
| 1280 | ' >%s</a><br />' % (doc_url, label)) |
| 1281 | if is_private: |
| 1282 | out(' </div>\n') |
| 1283 | |
| 1284 | def write_project_toc(self, out): |
| 1285 | self.write_header(out, "Everything") |
| 1286 | out('<h1 class="toc">Everything</h1>\n') |
| 1287 | out('<hr />\n') |
| 1288 | |
| 1289 | # List the classes. |
| 1290 | self.write_toc_section(out, "All Classes", self.class_list) |
| 1291 | |
| 1292 | # List the functions. |
| 1293 | funcs = [d for d in self.routine_list |
| 1294 | if not isinstance(self.docindex.container(d), |
| 1295 | (ClassDoc, types.NoneType))] |
| 1296 | self.write_toc_section(out, "All Functions", funcs) |
| 1297 | |
| 1298 | # List the variables. |
| 1299 | vars = [] |
| 1300 | for doc in self.module_list: |
| 1301 | vars += doc.select_variables(value_type='other', |
| 1302 | imported=False, |
| 1303 | public=self._public_filter) |
| 1304 | self.write_toc_section(out, "All Variables", vars) |
| 1305 | |
| 1306 | # Footer material. |
| 1307 | out('<hr />\n') |
| 1308 | if self._show_private: |
| 1309 | out(self.PRIVATE_LINK+'\n') |
| 1310 | self.write_footer(out, short=True) |
| 1311 | |
| 1312 | def write_module_toc(self, out, doc): |
| 1313 | """ |
| 1314 | Write an HTML page containing the table of contents page for |
| 1315 | the given module to the given streams. This page lists the |
| 1316 | modules, classes, exceptions, functions, and variables defined |
| 1317 | by the module. |
| 1318 | """ |
| 1319 | name = doc.canonical_name[-1] |
| 1320 | self.write_header(out, name) |
| 1321 | out('<h1 class="toc">Module %s</h1>\n' % name) |
| 1322 | out('<hr />\n') |
| 1323 | |
| 1324 | |
| 1325 | # List the classes. |
| 1326 | classes = doc.select_variables(value_type='class', imported=False, |
| 1327 | public=self._public_filter) |
| 1328 | self.write_toc_section(out, "Classes", classes, fullname=False) |
| 1329 | |
| 1330 | # List the functions. |
| 1331 | funcs = doc.select_variables(value_type='function', imported=False, |
| 1332 | public=self._public_filter) |
| 1333 | self.write_toc_section(out, "Functions", funcs, fullname=False) |
| 1334 | |
| 1335 | # List the variables. |
| 1336 | variables = doc.select_variables(value_type='other', imported=False, |
| 1337 | public=self._public_filter) |
| 1338 | self.write_toc_section(out, "Variables", variables, fullname=False) |
| 1339 | |
| 1340 | # Footer material. |
| 1341 | out('<hr />\n') |
| 1342 | if self._show_private: |
| 1343 | out(self.PRIVATE_LINK+'\n') |
| 1344 | self.write_footer(out, short=True) |
| 1345 | |
| 1346 | #//////////////////////////////////////////////////////////// |
| 1347 | #{ 2.7. Project homepage (index.html) |
| 1348 | #//////////////////////////////////////////////////////////// |
| 1349 | |
| 1350 | def write_homepage(self, directory): |
| 1351 | """ |
| 1352 | Write an C{index.html} file in the given directory. The |
| 1353 | contents of this file are copied or linked from an existing |
| 1354 | page, so this method must be called after all pages have been |
| 1355 | written. The page used is determined by L{_frames_index} and |
| 1356 | L{_top_page}: |
| 1357 | - If L{_frames_index} is true, then C{frames.html} is |
| 1358 | copied. |
| 1359 | - Otherwise, the page specified by L{_top_page} is |
| 1360 | copied. |
| 1361 | """ |
| 1362 | filename = os.path.join(directory, 'index.html') |
| 1363 | if self._frames_index: top = 'frames.html' |
| 1364 | else: top = self._top_page_url |
| 1365 | |
| 1366 | # Copy the non-frames index file from top, if it's internal. |
| 1367 | if top[:5] != 'http:' and '/' not in top: |
| 1368 | try: |
| 1369 | # Read top into `s`. |
| 1370 | topfile = os.path.join(directory, top) |
| 1371 | s = open(topfile, 'r').read() |
| 1372 | |
| 1373 | # Write the output file. |
| 1374 | open(filename, 'w').write(s) |
| 1375 | return |
| 1376 | except: |
| 1377 | log.error('Warning: error copying index; ' |
| 1378 | 'using a redirect page') |
| 1379 | |
| 1380 | # Use a redirect if top is external, or if we faild to copy. |
| 1381 | name = self._prj_name or 'this project' |
| 1382 | f = open(filename, 'w') |
| 1383 | self.write_redirect_index(f.write, top, name) |
| 1384 | f.close() |
| 1385 | |
| 1386 | write_redirect_index = compile_template( |
| 1387 | """ |
| 1388 | write_redirect_index(self, out, top, name) |
| 1389 | """, |
| 1390 | # /------------------------- Template -------------------------\ |
| 1391 | ''' |
| 1392 | <?xml version="1.0" encoding="iso-8859-1"?> |
| 1393 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" |
| 1394 | "DTD/xhtml1-strict.dtd"> |
| 1395 | <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> |
| 1396 | <head> |
| 1397 | <title> Redirect </title> |
| 1398 | <meta http-equiv="refresh" content="1;url=$top$" /> |
| 1399 | <link rel="stylesheet" href="epydoc.css" type="text/css"></link> |
| 1400 | </head> |
| 1401 | <body> |
| 1402 | <p>Redirecting to the API documentation for |
| 1403 | <a href="$top$">$self._prj_name or "this project"$</a>...</p> |
| 1404 | </body> |
| 1405 | </html> |
| 1406 | ''') |
| 1407 | # \------------------------------------------------------------/ |
| 1408 | |
| 1409 | #//////////////////////////////////////////////////////////// |
| 1410 | #{ 2.8. Stylesheet (epydoc.css) |
| 1411 | #//////////////////////////////////////////////////////////// |
| 1412 | |
| 1413 | def write_css(self, directory, cssname): |
| 1414 | """ |
| 1415 | Write the CSS stylesheet in the given directory. If |
| 1416 | C{cssname} contains a stylesheet file or name (from |
| 1417 | L{epydoc.docwriter.html_css}), then use that stylesheet; |
| 1418 | otherwise, use the default stylesheet. |
| 1419 | |
| 1420 | @rtype: C{None} |
| 1421 | """ |
| 1422 | filename = os.path.join(directory, 'epydoc.css') |
| 1423 | |
| 1424 | # Get the contents for the stylesheet file. |
| 1425 | if cssname is None: |
| 1426 | css = STYLESHEETS['default'][0] |
| 1427 | else: |
| 1428 | if os.path.exists(cssname): |
| 1429 | try: css = open(cssname).read() |
| 1430 | except: raise IOError("Can't open CSS file: %r" % cssname) |
| 1431 | elif cssname in STYLESHEETS: |
| 1432 | css = STYLESHEETS[cssname][0] |
| 1433 | else: |
| 1434 | raise IOError("Can't find CSS file: %r" % cssname) |
| 1435 | |
| 1436 | # Write the stylesheet. |
| 1437 | cssfile = open(filename, 'w') |
| 1438 | cssfile.write(css) |
| 1439 | cssfile.close() |
| 1440 | |
| 1441 | #//////////////////////////////////////////////////////////// |
| 1442 | #{ 2.9. Javascript (epydoc.js) |
| 1443 | #//////////////////////////////////////////////////////////// |
| 1444 | |
| 1445 | def write_javascript(self, directory): |
| 1446 | jsfile = open(os.path.join(directory, 'epydoc.js'), 'w') |
| 1447 | print >> jsfile, self.TOGGLE_PRIVATE_JS |
| 1448 | print >> jsfile, self.SHOW_PRIVATE_JS |
| 1449 | print >> jsfile, self.GET_COOKIE_JS |
| 1450 | print >> jsfile, self.SET_FRAME_JS |
| 1451 | print >> jsfile, self.HIDE_PRIVATE_JS |
| 1452 | print >> jsfile, self.TOGGLE_CALLGRAPH_JS |
| 1453 | print >> jsfile, html_colorize.PYSRC_JAVASCRIPTS |
| 1454 | print >> jsfile, self.GET_ANCHOR_JS |
| 1455 | print >> jsfile, self.REDIRECT_URL_JS |
| 1456 | jsfile.close() |
| 1457 | |
| 1458 | #: A javascript that is used to show or hide the API documentation |
| 1459 | #: for private objects. In order for this to work correctly, all |
| 1460 | #: documentation for private objects should be enclosed in |
| 1461 | #: C{<div class="private">...</div>} elements. |
| 1462 | TOGGLE_PRIVATE_JS = ''' |
| 1463 | function toggle_private() { |
| 1464 | // Search for any private/public links on this page. Store |
| 1465 | // their old text in "cmd," so we will know what action to |
| 1466 | // take; and change their text to the opposite action. |
| 1467 | var cmd = "?"; |
| 1468 | var elts = document.getElementsByTagName("a"); |
| 1469 | for(var i=0; i<elts.length; i++) { |
| 1470 | if (elts[i].className == "privatelink") { |
| 1471 | cmd = elts[i].innerHTML; |
| 1472 | elts[i].innerHTML = ((cmd && cmd.substr(0,4)=="show")? |
| 1473 | "hide private":"show private"); |
| 1474 | } |
| 1475 | } |
| 1476 | // Update all DIVs containing private objects. |
| 1477 | var elts = document.getElementsByTagName("div"); |
| 1478 | for(var i=0; i<elts.length; i++) { |
| 1479 | if (elts[i].className == "private") { |
| 1480 | elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"none":"block"); |
| 1481 | } |
| 1482 | else if (elts[i].className == "public") { |
| 1483 | elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"block":"none"); |
| 1484 | } |
| 1485 | } |
| 1486 | // Update all table rows containing private objects. Note, we |
| 1487 | // use "" instead of "block" becaue IE & firefox disagree on what |
| 1488 | // this should be (block vs table-row), and "" just gives the |
| 1489 | // default for both browsers. |
| 1490 | var elts = document.getElementsByTagName("tr"); |
| 1491 | for(var i=0; i<elts.length; i++) { |
| 1492 | if (elts[i].className == "private") { |
| 1493 | elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"none":""); |
| 1494 | } |
| 1495 | } |
| 1496 | // Update all list items containing private objects. |
| 1497 | var elts = document.getElementsByTagName("li"); |
| 1498 | for(var i=0; i<elts.length; i++) { |
| 1499 | if (elts[i].className == "private") { |
| 1500 | elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")? |
| 1501 | "none":""); |
| 1502 | } |
| 1503 | } |
| 1504 | // Update all list items containing private objects. |
| 1505 | var elts = document.getElementsByTagName("ul"); |
| 1506 | for(var i=0; i<elts.length; i++) { |
| 1507 | if (elts[i].className == "private") { |
| 1508 | elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"none":"block"); |
| 1509 | } |
| 1510 | } |
| 1511 | // Set a cookie to remember the current option. |
| 1512 | document.cookie = "EpydocPrivate="+cmd; |
| 1513 | } |
| 1514 | '''.strip() |
| 1515 | |
| 1516 | #: A javascript that is used to read the value of a cookie. This |
| 1517 | #: is used to remember whether private variables should be shown or |
| 1518 | #: hidden. |
| 1519 | GET_COOKIE_JS = ''' |
| 1520 | function getCookie(name) { |
| 1521 | var dc = document.cookie; |
| 1522 | var prefix = name + "="; |
| 1523 | var begin = dc.indexOf("; " + prefix); |
| 1524 | if (begin == -1) { |
| 1525 | begin = dc.indexOf(prefix); |
| 1526 | if (begin != 0) return null; |
| 1527 | } else |
| 1528 | { begin += 2; } |
| 1529 | var end = document.cookie.indexOf(";", begin); |
| 1530 | if (end == -1) |
| 1531 | { end = dc.length; } |
| 1532 | return unescape(dc.substring(begin + prefix.length, end)); |
| 1533 | } |
| 1534 | '''.strip() |
| 1535 | |
| 1536 | #: A javascript that is used to set the contents of two frames at |
| 1537 | #: once. This is used by the project table-of-contents frame to |
| 1538 | #: set both the module table-of-contents frame and the main frame |
| 1539 | #: when the user clicks on a module. |
| 1540 | SET_FRAME_JS = ''' |
| 1541 | function setFrame(url1, url2) { |
| 1542 | parent.frames[1].location.href = url1; |
| 1543 | parent.frames[2].location.href = url2; |
| 1544 | } |
| 1545 | '''.strip() |
| 1546 | |
| 1547 | #: A javascript that is used to hide private variables, unless |
| 1548 | #: either: (a) the cookie says not to; or (b) we appear to be |
| 1549 | #: linking to a private variable. |
| 1550 | HIDE_PRIVATE_JS = ''' |
| 1551 | function checkCookie() { |
| 1552 | var cmd=getCookie("EpydocPrivate"); |
| 1553 | if (cmd && cmd.substr(0,4)!="show" && location.href.indexOf("#_") < 0) |
| 1554 | toggle_private(); |
| 1555 | } |
| 1556 | '''.strip() |
| 1557 | |
| 1558 | TOGGLE_CALLGRAPH_JS = ''' |
| 1559 | function toggleCallGraph(id) { |
| 1560 | var elt = document.getElementById(id); |
| 1561 | if (elt.style.display == "none") |
| 1562 | elt.style.display = "block"; |
| 1563 | else |
| 1564 | elt.style.display = "none"; |
| 1565 | } |
| 1566 | '''.strip() |
| 1567 | |
| 1568 | SHOW_PRIVATE_JS = ''' |
| 1569 | function show_private() { |
| 1570 | var elts = document.getElementsByTagName("a"); |
| 1571 | for(var i=0; i<elts.length; i++) { |
| 1572 | if (elts[i].className == "privatelink") { |
| 1573 | cmd = elts[i].innerHTML; |
| 1574 | if (cmd && cmd.substr(0,4)=="show") |
| 1575 | toggle_private(); |
| 1576 | } |
| 1577 | } |
| 1578 | } |
| 1579 | '''.strip() |
| 1580 | |
| 1581 | GET_ANCHOR_JS = ''' |
| 1582 | function get_anchor() { |
| 1583 | var href = location.href; |
| 1584 | var start = href.indexOf("#")+1; |
| 1585 | if ((start != 0) && (start != href.length)) |
| 1586 | return href.substring(start, href.length); |
| 1587 | } |
| 1588 | '''.strip() |
| 1589 | |
| 1590 | #: A javascript that is used to implement the auto-redirect page. |
| 1591 | #: When the user visits <redirect.html#dotted.name>, they will |
| 1592 | #: automatically get redirected to the page for the object with |
| 1593 | #: the given fully-qualified dotted name. E.g., for epydoc, |
| 1594 | #: <redirect.html#epydoc.apidoc.UNKNOWN> redirects the user to |
| 1595 | #: <epydoc.apidoc-module.html#UNKNOWN>. |
| 1596 | REDIRECT_URL_JS = ''' |
| 1597 | function redirect_url(dottedName) { |
| 1598 | // Scan through each element of the "pages" list, and check |
| 1599 | // if "name" matches with any of them. |
| 1600 | for (var i=0; i<pages.length; i++) { |
| 1601 | |
| 1602 | // Each page has the form "<pagename>-m" or "<pagename>-c"; |
| 1603 | // extract the <pagename> portion & compare it to dottedName. |
| 1604 | var pagename = pages[i].substring(0, pages[i].length-2); |
| 1605 | if (pagename == dottedName.substring(0,pagename.length)) { |
| 1606 | |
| 1607 | // We\'ve found a page that matches `dottedName`; |
| 1608 | // construct its URL, using leftover `dottedName` |
| 1609 | // content to form an anchor. |
| 1610 | var pagetype = pages[i].charAt(pages[i].length-1); |
| 1611 | var url = pagename + ((pagetype=="m")?"-module.html": |
| 1612 | "-class.html"); |
| 1613 | if (dottedName.length > pagename.length) |
| 1614 | url += "#" + dottedName.substring(pagename.length+1, |
| 1615 | dottedName.length); |
| 1616 | return url; |
| 1617 | } |
| 1618 | } |
| 1619 | } |
| 1620 | '''.strip() |
| 1621 | |
| 1622 | |
| 1623 | #//////////////////////////////////////////////////////////// |
| 1624 | #{ 2.10. Graphs |
| 1625 | #//////////////////////////////////////////////////////////// |
| 1626 | |
| 1627 | def render_graph(self, graph): |
| 1628 | if graph is None: return '' |
| 1629 | graph.caption = graph.title = None |
| 1630 | image_url = '%s.gif' % graph.uid |
| 1631 | image_file = os.path.join(self._directory, image_url) |
| 1632 | return graph.to_html(image_file, image_url) |
| 1633 | |
| 1634 | RE_CALLGRAPH_ID = re.compile(r"""["'](.+-div)['"]""") |
| 1635 | |
| 1636 | def render_callgraph(self, callgraph, token=""): |
| 1637 | """Render the HTML chunk of a callgraph. |
| 1638 | |
| 1639 | If C{callgraph} is a string, use the L{_callgraph_cache} to return |
| 1640 | a pre-rendered HTML chunk. This mostly avoids to run C{dot} twice for |
| 1641 | the same callgraph. Else, run the graph and store its HTML output in |
| 1642 | the cache. |
| 1643 | |
| 1644 | @param callgraph: The graph to render or its L{uid<DotGraph.uid>}. |
| 1645 | @type callgraph: L{DotGraph} or C{str} |
| 1646 | @param token: A string that can be used to make the C{<div>} id |
| 1647 | unambiguous, if the callgraph is used more than once in a page. |
| 1648 | @type token: C{str} |
| 1649 | @return: The HTML representation of the graph. |
| 1650 | @rtype: C{str} |
| 1651 | """ |
| 1652 | if callgraph is None: return "" |
| 1653 | |
| 1654 | if isinstance(callgraph, basestring): |
| 1655 | uid = callgraph |
| 1656 | rv = self._callgraph_cache.get(callgraph, "") |
| 1657 | |
| 1658 | else: |
| 1659 | uid = callgraph.uid |
| 1660 | graph_html = self.render_graph(callgraph) |
| 1661 | if graph_html == '': |
| 1662 | rv = "" |
| 1663 | else: |
| 1664 | rv = ('<div style="display:none" id="%%s-div"><center>\n' |
| 1665 | '<table border="0" cellpadding="0" cellspacing="0">\n' |
| 1666 | ' <tr><td>%s</td></tr>\n' |
| 1667 | ' <tr><th>Call Graph</th></tr>\n' |
| 1668 | '</table><br />\n</center></div>\n' % graph_html) |
| 1669 | |
| 1670 | # Store in the cache the complete HTML chunk without the |
| 1671 | # div id, which may be made unambiguous by the token |
| 1672 | self._callgraph_cache[uid] = rv |
| 1673 | |
| 1674 | # Mangle with the graph |
| 1675 | if rv: rv = rv % (uid + token) |
| 1676 | return rv |
| 1677 | |
| 1678 | def callgraph_link(self, callgraph, token=""): |
| 1679 | """Render the HTML chunk of a callgraph link. |
| 1680 | |
| 1681 | The link can toggles the visibility of the callgraph rendered using |
| 1682 | L{render_callgraph} with matching parameters. |
| 1683 | |
| 1684 | @param callgraph: The graph to render or its L{uid<DotGraph.uid>}. |
| 1685 | @type callgraph: L{DotGraph} or C{str} |
| 1686 | @param token: A string that can be used to make the C{<div>} id |
| 1687 | unambiguous, if the callgraph is used more than once in a page. |
| 1688 | @type token: C{str} |
| 1689 | @return: The HTML representation of the graph link. |
| 1690 | @rtype: C{str} |
| 1691 | """ |
| 1692 | # Use class=codelink, to match style w/ the source code link. |
| 1693 | if callgraph is None: return '' |
| 1694 | |
| 1695 | if isinstance(callgraph, basestring): |
| 1696 | uid = callgraph |
| 1697 | else: |
| 1698 | uid = callgraph.uid |
| 1699 | |
| 1700 | return ('<br /><span class="codelink"><a href="javascript:void(0);" ' |
| 1701 | 'onclick="toggleCallGraph(\'%s-div\');return false;">' |
| 1702 | 'call graph</a></span> ' % (uid + token)) |
| 1703 | |
| 1704 | #//////////////////////////////////////////////////////////// |
| 1705 | #{ 2.11. Images |
| 1706 | #//////////////////////////////////////////////////////////// |
| 1707 | |
| 1708 | IMAGES = {'crarr.png': # Carriage-return arrow, used for LINEWRAP. |
| 1709 | 'iVBORw0KGgoAAAANSUhEUgAAABEAAAAKCAMAAABlokWQAAAALHRFWHRD' |
| 1710 | 'cmVhdGlvbiBUaW1lAFR1\nZSAyMiBBdWcgMjAwNiAwMDo0MzoxMCAtMD' |
| 1711 | 'UwMGAMEFgAAAAHdElNRQfWCBYFASkQ033WAAAACXBI\nWXMAAB7CAAAe' |
| 1712 | 'wgFu0HU+AAAABGdBTUEAALGPC/xhBQAAAEVQTFRF////zcOw18/AgGY0' |
| 1713 | 'c1cg4dvQ\ninJEYEAAYkME3NXI6eTcloFYe2Asr5+AbE4Uh29A9fPwqp' |
| 1714 | 'l4ZEUI8O3onopk0Ma0lH5U1nfFdgAA\nAAF0Uk5TAEDm2GYAAABNSURB' |
| 1715 | 'VHjaY2BAAbzsvDAmK5oIlxgfioiwCAe7KJKIgKAQOzsLLwTwA0VY\n+d' |
| 1716 | 'iRAT8T0AxuIIMHqoaXCWIPGzsHJ6orGJiYWRjQASOcBQAocgMSPKMTIg' |
| 1717 | 'AAAABJRU5ErkJggg==\n', |
| 1718 | } |
| 1719 | |
| 1720 | def write_images(self, directory): |
| 1721 | for (name, data) in self.IMAGES.items(): |
| 1722 | f = open(os.path.join(directory, name), 'wb') |
| 1723 | f.write(base64.decodestring(data)) |
| 1724 | f.close() |
| 1725 | |
| 1726 | #//////////////////////////////////////////////////////////// |
| 1727 | #{ 3.1. Page Header |
| 1728 | #//////////////////////////////////////////////////////////// |
| 1729 | |
| 1730 | write_header = compile_template( |
| 1731 | """ |
| 1732 | write_header(self, out, title) |
| 1733 | |
| 1734 | Generate HTML code for the standard page header, and write it |
| 1735 | to C{out}. C{title} is a string containing the page title. |
| 1736 | It should be appropriately escaped/encoded. |
| 1737 | """, |
| 1738 | # /------------------------- Template -------------------------\ |
| 1739 | ''' |
| 1740 | <?xml version="1.0" encoding="ascii"?> |
| 1741 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" |
| 1742 | "DTD/xhtml1-transitional.dtd"> |
| 1743 | <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> |
| 1744 | <head> |
| 1745 | <title>$title$</title> |
| 1746 | <link rel="stylesheet" href="epydoc.css" type="text/css" /> |
| 1747 | <script type="text/javascript" src="epydoc.js"></script> |
| 1748 | </head> |
| 1749 | |
| 1750 | <body bgcolor="white" text="black" link="blue" vlink="#204080" |
| 1751 | alink="#204080"> |
| 1752 | ''') |
| 1753 | # \------------------------------------------------------------/ |
| 1754 | |
| 1755 | #//////////////////////////////////////////////////////////// |
| 1756 | #{ 3.2. Page Footer |
| 1757 | #//////////////////////////////////////////////////////////// |
| 1758 | |
| 1759 | write_footer = compile_template( |
| 1760 | """ |
| 1761 | write_footer(self, out, short=False) |
| 1762 | |
| 1763 | Generate HTML code for the standard page footer, and write it |
| 1764 | to C{out}. |
| 1765 | """, |
| 1766 | # /------------------------- Template -------------------------\ |
| 1767 | ''' |
| 1768 | >>> if not short: |
| 1769 | <table border="0" cellpadding="0" cellspacing="0" width="100%%"> |
| 1770 | <tr> |
| 1771 | <td align="left" class="footer"> |
| 1772 | >>> if self._include_log: |
| 1773 | <a href="epydoc-log.html">Generated by Epydoc |
| 1774 | $epydoc.__version__$ on $time.asctime()$</a> |
| 1775 | >>> else: |
| 1776 | Generated by Epydoc $epydoc.__version__$ on $time.asctime()$ |
| 1777 | >>> #endif |
| 1778 | </td> |
| 1779 | <td align="right" class="footer"> |
| 1780 | <a target="mainFrame" href="http://epydoc.sourceforge.net" |
| 1781 | >http://epydoc.sourceforge.net</a> |
| 1782 | </td> |
| 1783 | </tr> |
| 1784 | </table> |
| 1785 | >>> #endif |
| 1786 | |
| 1787 | <script type="text/javascript"> |
| 1788 | <!-- |
| 1789 | // Private objects are initially displayed (because if |
| 1790 | // javascript is turned off then we want them to be |
| 1791 | // visible); but by default, we want to hide them. So hide |
| 1792 | // them unless we have a cookie that says to show them. |
| 1793 | checkCookie(); |
| 1794 | // --> |
| 1795 | </script> |
| 1796 | </body> |
| 1797 | </html> |
| 1798 | ''') |
| 1799 | # \------------------------------------------------------------/ |
| 1800 | |
| 1801 | #//////////////////////////////////////////////////////////// |
| 1802 | #{ 3.3. Navigation Bar |
| 1803 | #//////////////////////////////////////////////////////////// |
| 1804 | |
| 1805 | write_navbar = compile_template( |
| 1806 | """ |
| 1807 | write_navbar(self, out, context) |
| 1808 | |
| 1809 | Generate HTML code for the navigation bar, and write it to |
| 1810 | C{out}. The navigation bar typically looks like:: |
| 1811 | |
| 1812 | [ Home Trees Index Help Project ] |
| 1813 | |
| 1814 | @param context: A value indicating what page we're generating |
| 1815 | a navigation bar for. If we're generating an API |
| 1816 | documentation page for an object, then C{context} is a |
| 1817 | L{ValueDoc} containing the documentation for that object; |
| 1818 | otherwise, C{context} is a string name for the page. The |
| 1819 | following string names are recognized: C{'tree'}, C{'index'}, |
| 1820 | and C{'help'}. |
| 1821 | """, |
| 1822 | # /------------------------- Template -------------------------\ |
| 1823 | ''' |
| 1824 | <!-- ==================== NAVIGATION BAR ==================== --> |
| 1825 | <table class="navbar" border="0" width="100%" cellpadding="0" |
| 1826 | bgcolor="#a0c0ff" cellspacing="0"> |
| 1827 | <tr valign="middle"> |
| 1828 | >>> if self._top_page_url not in (self._trees_url, "identifier-index.html", "help.html"): |
| 1829 | <!-- Home link --> |
| 1830 | >>> if (isinstance(context, ValueDoc) and |
| 1831 | >>> self._top_page_url == self.url(context.canonical_name)): |
| 1832 | <th bgcolor="#70b0f0" class="navbar-select" |
| 1833 | > Home </th> |
| 1834 | >>> else: |
| 1835 | <th> <a |
| 1836 | href="$self._top_page_url$">Home</a> </th> |
| 1837 | >>> #endif |
| 1838 | |
| 1839 | <!-- Tree link --> |
| 1840 | >>> if context == "trees": |
| 1841 | <th bgcolor="#70b0f0" class="navbar-select" |
| 1842 | > Trees </th> |
| 1843 | >>> else: |
| 1844 | <th> <a |
| 1845 | href="$self._trees_url$">Trees</a> </th> |
| 1846 | >>> #endif |
| 1847 | |
| 1848 | <!-- Index link --> |
| 1849 | >>> if context == "indices": |
| 1850 | <th bgcolor="#70b0f0" class="navbar-select" |
| 1851 | > Indices </th> |
| 1852 | >>> else: |
| 1853 | <th> <a |
| 1854 | href="identifier-index.html">Indices</a> </th> |
| 1855 | >>> #endif |
| 1856 | |
| 1857 | <!-- Help link --> |
| 1858 | >>> if context == "help": |
| 1859 | <th bgcolor="#70b0f0" class="navbar-select" |
| 1860 | > Help </th> |
| 1861 | >>> else: |
| 1862 | <th> <a |
| 1863 | href="help.html">Help</a> </th> |
| 1864 | >>> #endif |
| 1865 | |
| 1866 | >>> if self._prj_link: |
| 1867 | <!-- Project homepage --> |
| 1868 | <th class="navbar" align="right" width="100%"> |
| 1869 | <table border="0" cellpadding="0" cellspacing="0"> |
| 1870 | <tr><th class="navbar" align="center" |
| 1871 | >$self._prj_link.strip()$</th> |
| 1872 | </tr></table></th> |
| 1873 | >>> else: |
| 1874 | <th class="navbar" width="100%"></th> |
| 1875 | >>> #endif |
| 1876 | </tr> |
| 1877 | </table> |
| 1878 | ''') |
| 1879 | # \------------------------------------------------------------/ |
| 1880 | |
| 1881 | #//////////////////////////////////////////////////////////// |
| 1882 | #{ 3.4. Breadcrumbs |
| 1883 | #//////////////////////////////////////////////////////////// |
| 1884 | |
| 1885 | write_breadcrumbs = compile_template( |
| 1886 | """ |
| 1887 | write_breadcrumbs(self, out, context, context_url) |
| 1888 | |
| 1889 | Generate HTML for the breadcrumbs line, and write it to |
| 1890 | C{out}. The breadcrumbs line is an invisible table with a |
| 1891 | list of pointers to the current object's ancestors on the |
| 1892 | left; and the show/hide private selector and the |
| 1893 | frames/noframes selector on the right. |
| 1894 | |
| 1895 | @param context: The API documentation for the object whose |
| 1896 | breadcrumbs we should generate. |
| 1897 | @type context: L{ValueDoc} |
| 1898 | """, |
| 1899 | # /------------------------- Template -------------------------\ |
| 1900 | ''' |
| 1901 | <table width="100%" cellpadding="0" cellspacing="0"> |
| 1902 | <tr valign="top"> |
| 1903 | >>> if isinstance(context, APIDoc): |
| 1904 | <td width="100%"> |
| 1905 | <span class="breadcrumbs"> |
| 1906 | >>> crumbs = self.breadcrumbs(context) |
| 1907 | >>> for crumb in crumbs[:-1]: |
| 1908 | $crumb$ :: |
| 1909 | >>> #endfor |
| 1910 | $crumbs[-1]$ |
| 1911 | </span> |
| 1912 | </td> |
| 1913 | >>> else: |
| 1914 | <td width="100%"> </td> |
| 1915 | >>> #endif |
| 1916 | <td> |
| 1917 | <table cellpadding="0" cellspacing="0"> |
| 1918 | <!-- hide/show private --> |
| 1919 | >>> if self._show_private: |
| 1920 | <tr><td align="right">$self.PRIVATE_LINK$</td></tr> |
| 1921 | >>> #endif |
| 1922 | >>> if self._frames_index: |
| 1923 | <tr><td align="right"><span class="options" |
| 1924 | >[<a href="frames.html" target="_top">frames</a |
| 1925 | >] | <a href="$context_url$" |
| 1926 | target="_top">no frames</a>]</span></td></tr> |
| 1927 | >>> #endif |
| 1928 | </table> |
| 1929 | </td> |
| 1930 | </tr> |
| 1931 | </table> |
| 1932 | ''') |
| 1933 | # \------------------------------------------------------------/ |
| 1934 | |
| 1935 | def breadcrumbs(self, doc): |
| 1936 | crumbs = [self._crumb(doc)] |
| 1937 | |
| 1938 | # Generate the crumbs for uid's ancestors. |
| 1939 | while True: |
| 1940 | container = self.docindex.container(doc) |
| 1941 | assert doc != container, 'object is its own container?' |
| 1942 | if container is None: |
| 1943 | if doc.canonical_name is UNKNOWN: |
| 1944 | return ['??']+crumbs |
| 1945 | elif isinstance(doc, ModuleDoc): |
| 1946 | return ['Package %s' % ident |
| 1947 | for ident in doc.canonical_name[:-1]]+crumbs |
| 1948 | else: |
| 1949 | return list(doc.canonical_name)+crumbs |
| 1950 | else: |
| 1951 | label = self._crumb(container) |
| 1952 | name = container.canonical_name |
| 1953 | crumbs.insert(0, self.href(container, label)) # [xx] code=0?? |
| 1954 | doc = container |
| 1955 | |
| 1956 | def _crumb(self, doc): |
| 1957 | if (len(doc.canonical_name)==1 and |
| 1958 | doc.canonical_name[0].startswith('script-')): |
| 1959 | return 'Script %s' % doc.canonical_name[0][7:] |
| 1960 | return '%s %s' % (self.doc_kind(doc), doc.canonical_name[-1]) |
| 1961 | |
| 1962 | #//////////////////////////////////////////////////////////// |
| 1963 | #{ 3.5. Summary Tables |
| 1964 | #//////////////////////////////////////////////////////////// |
| 1965 | |
| 1966 | def write_summary_table(self, out, heading, doc, value_type): |
| 1967 | """ |
| 1968 | Generate HTML code for a summary table, and write it to |
| 1969 | C{out}. A summary table is a table that includes a one-row |
| 1970 | description for each variable (of a given type) in a module |
| 1971 | or class. |
| 1972 | |
| 1973 | @param heading: The heading for the summary table; typically, |
| 1974 | this indicates what kind of value the table describes |
| 1975 | (e.g., functions or classes). |
| 1976 | @param doc: A L{ValueDoc} object containing the API |
| 1977 | documentation for the module or class whose variables |
| 1978 | we should summarize. |
| 1979 | @param value_type: A string indicating what type of value |
| 1980 | should be listed in this summary table. This value |
| 1981 | is passed on to C{doc}'s C{select_variables()} method. |
| 1982 | """ |
| 1983 | # inh_var_groups is a dictionary used to hold "inheritance |
| 1984 | # pseudo-groups", which are created when inheritance is |
| 1985 | # 'grouped'. It maps each base to a list of vars inherited |
| 1986 | # from that base. |
| 1987 | grouped_inh_vars = {} |
| 1988 | |
| 1989 | # Divide all public variables of the given type into groups. |
| 1990 | groups = [(plaintext_to_html(group_name), |
| 1991 | doc.select_variables(group=group_name, imported=False, |
| 1992 | value_type=value_type, |
| 1993 | public=self._public_filter)) |
| 1994 | for group_name in doc.group_names()] |
| 1995 | |
| 1996 | # Discard any empty groups; and return if they're all empty. |
| 1997 | groups = [(g,vars) for (g,vars) in groups if vars] |
| 1998 | if not groups: return |
| 1999 | |
| 2000 | # Write a header |
| 2001 | self.write_table_header(out, "summary", heading) |
| 2002 | |
| 2003 | # Write a section for each group. |
| 2004 | for name, var_docs in groups: |
| 2005 | self.write_summary_group(out, doc, name, |
| 2006 | var_docs, grouped_inh_vars) |
| 2007 | |
| 2008 | # Write a section for each inheritance pseudo-group (used if |
| 2009 | # inheritance=='grouped') |
| 2010 | if grouped_inh_vars: |
| 2011 | for base in doc.mro(): |
| 2012 | if base in grouped_inh_vars: |
| 2013 | hdr = 'Inherited from %s' % self.href(base, context=doc) |
| 2014 | tr_class = '' |
| 2015 | if len([v for v in grouped_inh_vars[base] |
| 2016 | if v.is_public]) == 0: |
| 2017 | tr_class = ' class="private"' |
| 2018 | self.write_group_header(out, hdr, tr_class) |
| 2019 | for var_doc in grouped_inh_vars[base]: |
| 2020 | self.write_summary_line(out, var_doc, doc) |
| 2021 | |
| 2022 | # Write a footer for the table. |
| 2023 | out(self.TABLE_FOOTER) |
| 2024 | |
| 2025 | def write_summary_group(self, out, doc, name, var_docs, grouped_inh_vars): |
| 2026 | # Split up the var_docs list, according to the way each var |
| 2027 | # should be displayed: |
| 2028 | # - listed_inh_vars -- for listed inherited variables. |
| 2029 | # - grouped_inh_vars -- for grouped inherited variables. |
| 2030 | # - normal_vars -- for all other variables. |
| 2031 | listed_inh_vars = {} |
| 2032 | normal_vars = [] |
| 2033 | for var_doc in var_docs: |
| 2034 | if var_doc.container != doc: |
| 2035 | base = var_doc.container |
| 2036 | if not isinstance(base, ClassDoc): |
| 2037 | # This *should* never happen: |
| 2038 | log.warning("%s's container is not a class!" % var_doc) |
| 2039 | normal_vars.append(var_doc) |
| 2040 | elif (base not in self.class_set or |
| 2041 | self._inheritance == 'listed'): |
| 2042 | listed_inh_vars.setdefault(base,[]).append(var_doc) |
| 2043 | elif self._inheritance == 'grouped': |
| 2044 | grouped_inh_vars.setdefault(base,[]).append(var_doc) |
| 2045 | else: |
| 2046 | normal_vars.append(var_doc) |
| 2047 | else: |
| 2048 | normal_vars.append(var_doc) |
| 2049 | |
| 2050 | # Write a header for the group. |
| 2051 | if name != '': |
| 2052 | tr_class = '' |
| 2053 | if len([v for v in var_docs if v.is_public]) == 0: |
| 2054 | tr_class = ' class="private"' |
| 2055 | self.write_group_header(out, name, tr_class) |
| 2056 | |
| 2057 | # Write a line for each normal var: |
| 2058 | for var_doc in normal_vars: |
| 2059 | self.write_summary_line(out, var_doc, doc) |
| 2060 | # Write a subsection for inherited vars: |
| 2061 | if listed_inh_vars: |
| 2062 | self.write_inheritance_list(out, doc, listed_inh_vars) |
| 2063 | |
| 2064 | def write_inheritance_list(self, out, doc, listed_inh_vars): |
| 2065 | out(' <tr>\n <td colspan="2" class="summary">\n') |
| 2066 | for base in doc.mro(): |
| 2067 | if base not in listed_inh_vars: continue |
| 2068 | public_vars = [v for v in listed_inh_vars[base] |
| 2069 | if v.is_public] |
| 2070 | private_vars = [v for v in listed_inh_vars[base] |
| 2071 | if not v.is_public] |
| 2072 | if public_vars: |
| 2073 | out(' <p class="indent-wrapped-lines">' |
| 2074 | '<b>Inherited from <code>%s</code></b>:\n' % |
| 2075 | self.href(base, context=doc)) |
| 2076 | self.write_var_list(out, public_vars) |
| 2077 | out(' </p>\n') |
| 2078 | if private_vars and self._show_private: |
| 2079 | out(' <div class="private">') |
| 2080 | out(' <p class="indent-wrapped-lines">' |
| 2081 | '<b>Inherited from <code>%s</code></b> (private):\n' % |
| 2082 | self.href(base, context=doc)) |
| 2083 | self.write_var_list(out, private_vars) |
| 2084 | out(' </p></div>\n') |
| 2085 | out(' </td>\n </tr>\n') |
| 2086 | |
| 2087 | def write_var_list(self, out, vardocs): |
| 2088 | out(' ') |
| 2089 | out(',\n '.join(['<code>%s</code>' % self.href(v,v.name) |
| 2090 | for v in vardocs])+'\n') |
| 2091 | |
| 2092 | def write_summary_line(self, out, var_doc, container): |
| 2093 | """ |
| 2094 | Generate HTML code for a single line of a summary table, and |
| 2095 | write it to C{out}. See L{write_summary_table} for more |
| 2096 | information. |
| 2097 | |
| 2098 | @param var_doc: The API documentation for the variable that |
| 2099 | should be described by this line of the summary table. |
| 2100 | @param container: The API documentation for the class or |
| 2101 | module whose summary table we're writing. |
| 2102 | """ |
| 2103 | pysrc_link = None |
| 2104 | callgraph = None |
| 2105 | |
| 2106 | # If it's a private variable, then mark its <tr>. |
| 2107 | if var_doc.is_public: tr_class = '' |
| 2108 | else: tr_class = ' class="private"' |
| 2109 | |
| 2110 | # Decide an anchor or a link is to be generated. |
| 2111 | link_name = self._redundant_details or var_doc.is_detailed() |
| 2112 | anchor = not link_name |
| 2113 | |
| 2114 | # Construct the HTML code for the type (cell 1) & description |
| 2115 | # (cell 2). |
| 2116 | if isinstance(var_doc.value, RoutineDoc): |
| 2117 | typ = self.return_type(var_doc, indent=6) |
| 2118 | description = self.function_signature(var_doc, is_summary=True, |
| 2119 | link_name=link_name, anchor=anchor) |
| 2120 | pysrc_link = self.pysrc_link(var_doc.value) |
| 2121 | |
| 2122 | # Perpare the call-graph, if requested |
| 2123 | if 'callgraph' in self._graph_types: |
| 2124 | linker = _HTMLDocstringLinker(self, var_doc.value) |
| 2125 | callgraph = call_graph([var_doc.value], self.docindex, |
| 2126 | linker, var_doc, add_callers=True, |
| 2127 | add_callees=True) |
| 2128 | if callgraph and callgraph.nodes: |
| 2129 | var_doc.value.callgraph_uid = callgraph.uid |
| 2130 | else: |
| 2131 | callgraph = None |
| 2132 | else: |
| 2133 | typ = self.type_descr(var_doc, indent=6) |
| 2134 | description = self.summary_name(var_doc, |
| 2135 | link_name=link_name, anchor=anchor) |
| 2136 | if isinstance(var_doc.value, GenericValueDoc): |
| 2137 | # The summary max length has been chosen setting |
| 2138 | # L{ValueDoc.SUMMARY_REPR_LINELEN} in the constructor |
| 2139 | max_len=self._variable_summary_linelen-3-len(var_doc.name) |
| 2140 | val_repr = var_doc.value.summary_pyval_repr(max_len) |
| 2141 | tooltip = self.variable_tooltip(var_doc) |
| 2142 | description += (' = <code%s>%s</code>' % |
| 2143 | (tooltip, val_repr.to_html(None))) |
| 2144 | |
| 2145 | # Add the summary to the description (if there is one). |
| 2146 | summary = self.summary(var_doc, indent=6) |
| 2147 | if summary: description += '<br />\n %s' % summary |
| 2148 | |
| 2149 | # If it's inherited, then add a note to the description. |
| 2150 | if var_doc.container != container and self._inheritance=="included": |
| 2151 | description += ("\n <em>(Inherited from " + |
| 2152 | self.href(var_doc.container) + ")</em>") |
| 2153 | |
| 2154 | # Write the summary line. |
| 2155 | self._write_summary_line(out, typ, description, tr_class, pysrc_link, |
| 2156 | callgraph) |
| 2157 | |
| 2158 | _write_summary_line = compile_template( |
| 2159 | "_write_summary_line(self, out, typ, description, tr_class, " |
| 2160 | "pysrc_link, callgraph)", |
| 2161 | # /------------------------- Template -------------------------\ |
| 2162 | ''' |
| 2163 | <tr$tr_class$> |
| 2164 | <td width="15%" align="right" valign="top" class="summary"> |
| 2165 | <span class="summary-type">$typ or " "$</span> |
| 2166 | </td><td class="summary"> |
| 2167 | >>> if pysrc_link is not None or callgraph is not None: |
| 2168 | <table width="100%" cellpadding="0" cellspacing="0" border="0"> |
| 2169 | <tr> |
| 2170 | <td>$description$</td> |
| 2171 | <td align="right" valign="top"> |
| 2172 | $pysrc_link$ |
| 2173 | $self.callgraph_link(callgraph, token='-summary')$ |
| 2174 | </td> |
| 2175 | </tr> |
| 2176 | </table> |
| 2177 | $self.render_callgraph(callgraph, token='-summary')$ |
| 2178 | >>> #endif |
| 2179 | >>> if pysrc_link is None and callgraph is None: |
| 2180 | $description$ |
| 2181 | >>> #endif |
| 2182 | </td> |
| 2183 | </tr> |
| 2184 | ''') |
| 2185 | # \------------------------------------------------------------/ |
| 2186 | |
| 2187 | #//////////////////////////////////////////////////////////// |
| 2188 | #{ 3.6. Details Lists |
| 2189 | #//////////////////////////////////////////////////////////// |
| 2190 | |
| 2191 | def write_details_list(self, out, heading, doc, value_type): |
| 2192 | # Get a list of the VarDocs we should describe. |
| 2193 | if self._redundant_details: |
| 2194 | detailed = None |
| 2195 | else: |
| 2196 | detailed = True |
| 2197 | if isinstance(doc, ClassDoc): |
| 2198 | var_docs = doc.select_variables(value_type=value_type, |
| 2199 | imported=False, inherited=False, |
| 2200 | public=self._public_filter, |
| 2201 | detailed=detailed) |
| 2202 | else: |
| 2203 | var_docs = doc.select_variables(value_type=value_type, |
| 2204 | imported=False, |
| 2205 | public=self._public_filter, |
| 2206 | detailed=detailed) |
| 2207 | if not var_docs: return |
| 2208 | |
| 2209 | # Write a header |
| 2210 | self.write_table_header(out, "details", heading) |
| 2211 | out(self.TABLE_FOOTER) |
| 2212 | |
| 2213 | for var_doc in var_docs: |
| 2214 | self.write_details_entry(out, var_doc) |
| 2215 | |
| 2216 | out('<br />\n') |
| 2217 | |
| 2218 | def write_details_entry(self, out, var_doc): |
| 2219 | descr = self.descr(var_doc, indent=2) or '' |
| 2220 | if var_doc.is_public: div_class = '' |
| 2221 | else: div_class = ' class="private"' |
| 2222 | |
| 2223 | # Functions |
| 2224 | if isinstance(var_doc.value, RoutineDoc): |
| 2225 | rtype = self.return_type(var_doc, indent=10) |
| 2226 | rdescr = self.return_descr(var_doc, indent=10) |
| 2227 | arg_descrs = [] |
| 2228 | args = set() |
| 2229 | # Find the description for each arg. (Leave them in the |
| 2230 | # same order that they're listed in the docstring.) |
| 2231 | for (arg_names, arg_descr) in var_doc.value.arg_descrs: |
| 2232 | args.update(arg_names) |
| 2233 | lhs = ', '.join([self.arg_name_to_html(var_doc.value, n) |
| 2234 | for n in arg_names]) |
| 2235 | rhs = self.docstring_to_html(arg_descr, var_doc.value, 10) |
| 2236 | arg_descrs.append( (lhs, rhs) ) |
| 2237 | # Check for arguments for which we have @type but not @param; |
| 2238 | # and add them to the arg_descrs list. |
| 2239 | for arg in var_doc.value.arg_types: |
| 2240 | if arg not in args: |
| 2241 | argname = self.arg_name_to_html(var_doc.value, arg) |
| 2242 | arg_descrs.append( (argname,'') ) |
| 2243 | |
| 2244 | self.write_function_details_entry(out, var_doc, descr, |
| 2245 | var_doc.value.callgraph_uid, |
| 2246 | rtype, rdescr, arg_descrs, |
| 2247 | div_class) |
| 2248 | |
| 2249 | # Properties |
| 2250 | elif isinstance(var_doc.value, PropertyDoc): |
| 2251 | prop_doc = var_doc.value |
| 2252 | accessors = [ (name, |
| 2253 | self.property_accessor_to_html(val_doc, prop_doc), |
| 2254 | self.summary(val_doc)) |
| 2255 | for (name, val_doc) in |
| 2256 | [('Get', prop_doc.fget), ('Set', prop_doc.fset), |
| 2257 | ('Delete', prop_doc.fdel)] |
| 2258 | if val_doc not in (None, UNKNOWN) |
| 2259 | and val_doc.pyval is not None ] |
| 2260 | |
| 2261 | self.write_property_details_entry(out, var_doc, descr, |
| 2262 | accessors, div_class) |
| 2263 | |
| 2264 | # Variables |
| 2265 | else: |
| 2266 | self.write_variable_details_entry(out, var_doc, descr, div_class) |
| 2267 | |
| 2268 | def labelled_list_item(self, lhs, rhs): |
| 2269 | # If the RHS starts with a paragraph, then move the |
| 2270 | # paragraph-start tag to the beginning of the lhs instead (so |
| 2271 | # there won't be a line break after the '-'). |
| 2272 | m = re.match(r'^<p( [^>]+)?>', rhs) |
| 2273 | if m: |
| 2274 | lhs = m.group() + lhs |
| 2275 | rhs = rhs[m.end():] |
| 2276 | |
| 2277 | if rhs: |
| 2278 | return '<li>%s - %s</li>' % (lhs, rhs) |
| 2279 | else: |
| 2280 | return '<li>%s</li>' % (lhs,) |
| 2281 | |
| 2282 | def property_accessor_to_html(self, val_doc, context=None): |
| 2283 | if val_doc not in (None, UNKNOWN): |
| 2284 | if isinstance(val_doc, RoutineDoc): |
| 2285 | return self.function_signature(val_doc, is_summary=True, |
| 2286 | link_name=True, context=context) |
| 2287 | elif isinstance(val_doc, GenericValueDoc): |
| 2288 | return self.pprint_value(val_doc) |
| 2289 | else: |
| 2290 | return self.href(val_doc, context=context) |
| 2291 | else: |
| 2292 | return '??' |
| 2293 | |
| 2294 | def arg_name_to_html(self, func_doc, arg_name): |
| 2295 | """ |
| 2296 | A helper function used to format an argument name, for use in |
| 2297 | the argument description list under a routine's details entry. |
| 2298 | This just wraps strong & code tags around the arg name; and if |
| 2299 | the arg name is associated with a type, then adds it |
| 2300 | parenthetically after the name. |
| 2301 | """ |
| 2302 | s = '<strong class="pname"><code>%s</code></strong>' % arg_name |
| 2303 | if arg_name in func_doc.arg_types: |
| 2304 | typ = func_doc.arg_types[arg_name] |
| 2305 | typ_html = self.docstring_to_html(typ, func_doc, 10) |
| 2306 | s += " (%s)" % typ_html |
| 2307 | return s |
| 2308 | |
| 2309 | write_function_details_entry = compile_template( |
| 2310 | ''' |
| 2311 | write_function_details_entry(self, out, var_doc, descr, callgraph, \ |
| 2312 | rtype, rdescr, arg_descrs, div_class) |
| 2313 | ''', |
| 2314 | # /------------------------- Template -------------------------\ |
| 2315 | ''' |
| 2316 | >>> func_doc = var_doc.value |
| 2317 | <a name="$var_doc.name$"></a> |
| 2318 | <div$div_class$> |
| 2319 | >>> self.write_table_header(out, "details") |
| 2320 | <tr><td> |
| 2321 | <table width="100%" cellpadding="0" cellspacing="0" border="0"> |
| 2322 | <tr valign="top"><td> |
| 2323 | <h3 class="epydoc">$self.function_signature(var_doc)$ |
| 2324 | >>> if var_doc.name in self.SPECIAL_METHODS: |
| 2325 | <br /><em class="fname">($self.SPECIAL_METHODS[var_doc.name]$)</em> |
| 2326 | >>> #endif |
| 2327 | >>> if isinstance(func_doc, ClassMethodDoc): |
| 2328 | <br /><em class="fname">Class Method</em> |
| 2329 | >>> #endif |
| 2330 | >>> if isinstance(func_doc, StaticMethodDoc): |
| 2331 | <br /><em class="fname">Static Method</em> |
| 2332 | >>> #endif |
| 2333 | </h3> |
| 2334 | </td><td align="right" valign="top" |
| 2335 | >$self.pysrc_link(func_doc)$ |
| 2336 | $self.callgraph_link(callgraph)$</td> |
| 2337 | </tr></table> |
| 2338 | $self.render_callgraph(callgraph)$ |
| 2339 | $descr$ |
| 2340 | <dl class="fields"> |
| 2341 | >>> # === parameters === |
| 2342 | >>> if arg_descrs: |
| 2343 | <dt>Parameters:</dt> |
| 2344 | <dd><ul class="nomargin-top"> |
| 2345 | >>> for lhs, rhs in arg_descrs: |
| 2346 | $self.labelled_list_item(lhs, rhs)$ |
| 2347 | >>> #endfor |
| 2348 | </ul></dd> |
| 2349 | >>> #endif |
| 2350 | >>> # === return type === |
| 2351 | >>> if rdescr and rtype: |
| 2352 | <dt>Returns: $rtype$</dt> |
| 2353 | <dd>$rdescr$</dd> |
| 2354 | >>> elif rdescr: |
| 2355 | <dt>Returns:</dt> |
| 2356 | <dd>$rdescr$</dd> |
| 2357 | >>> elif rtype: |
| 2358 | <dt>Returns: $rtype$</dt> |
| 2359 | >>> #endif |
| 2360 | >>> # === decorators === |
| 2361 | >>> if func_doc.decorators not in (None, UNKNOWN): |
| 2362 | >>> # (staticmethod & classmethod are already shown, above) |
| 2363 | >>> decos = filter(lambda deco: |
| 2364 | >>> not ((deco=="staticmethod" and |
| 2365 | >>> isinstance(func_doc, StaticMethodDoc)) or |
| 2366 | >>> (deco=="classmethod" and |
| 2367 | >>> isinstance(func_doc, ClassMethodDoc))), |
| 2368 | >>> func_doc.decorators) |
| 2369 | >>> else: |
| 2370 | >>> decos = None |
| 2371 | >>> #endif |
| 2372 | >>> if decos: |
| 2373 | <dt>Decorators:</dt> |
| 2374 | <dd><ul class="nomargin-top"> |
| 2375 | >>> for deco in decos: |
| 2376 | <li><code>@$deco$</code></li> |
| 2377 | >>> #endfor |
| 2378 | </ul></dd> |
| 2379 | >>> #endif |
| 2380 | >>> # === exceptions === |
| 2381 | >>> if func_doc.exception_descrs not in (None, UNKNOWN, (), []): |
| 2382 | <dt>Raises:</dt> |
| 2383 | <dd><ul class="nomargin-top"> |
| 2384 | >>> for name, descr in func_doc.exception_descrs: |
| 2385 | >>> exc_name = self.docindex.find(name, func_doc) |
| 2386 | >>> if exc_name is not None: |
| 2387 | >>> name = self.href(exc_name, label=str(name)) |
| 2388 | >>> #endif |
| 2389 | $self.labelled_list_item( |
| 2390 | "<code><strong class=\'fraise\'>" + |
| 2391 | str(name) + "</strong></code>", |
| 2392 | self.docstring_to_html(descr, func_doc, 8))$ |
| 2393 | >>> #endfor |
| 2394 | </ul></dd> |
| 2395 | >>> #endif |
| 2396 | >>> # === overrides === |
| 2397 | >>> if var_doc.overrides not in (None, UNKNOWN): |
| 2398 | <dt>Overrides: |
| 2399 | >>> # Avoid passing GenericValueDoc to href() |
| 2400 | >>> if isinstance(var_doc.overrides.value, RoutineDoc): |
| 2401 | $self.href(var_doc.overrides.value, context=var_doc)$ |
| 2402 | >>> else: |
| 2403 | >>> # In this case, a less interesting label is generated. |
| 2404 | $self.href(var_doc.overrides, context=var_doc)$ |
| 2405 | >>> #endif |
| 2406 | >>> if (func_doc.docstring in (None, UNKNOWN) and |
| 2407 | >>> var_doc.overrides.value.docstring not in (None, UNKNOWN)): |
| 2408 | <dd><em class="note">(inherited documentation)</em></dd> |
| 2409 | >>> #endif |
| 2410 | </dt> |
| 2411 | >>> #endif |
| 2412 | </dl> |
| 2413 | >>> # === metadata === |
| 2414 | >>> self.write_standard_fields(out, func_doc) |
| 2415 | </td></tr></table> |
| 2416 | </div> |
| 2417 | ''') |
| 2418 | # \------------------------------------------------------------/ |
| 2419 | |
| 2420 | # Names for the __special__ methods. |
| 2421 | SPECIAL_METHODS ={ |
| 2422 | '__init__': 'Constructor', |
| 2423 | '__del__': 'Destructor', |
| 2424 | '__add__': 'Addition operator', |
| 2425 | '__sub__': 'Subtraction operator', |
| 2426 | '__and__': 'And operator', |
| 2427 | '__or__': 'Or operator', |
| 2428 | '__xor__': 'Exclusive-Or operator', |
| 2429 | '__repr__': 'Representation operator', |
| 2430 | '__call__': 'Call operator', |
| 2431 | '__getattr__': 'Qualification operator', |
| 2432 | '__getitem__': 'Indexing operator', |
| 2433 | '__setitem__': 'Index assignment operator', |
| 2434 | '__delitem__': 'Index deletion operator', |
| 2435 | '__delslice__': 'Slice deletion operator', |
| 2436 | '__setslice__': 'Slice assignment operator', |
| 2437 | '__getslice__': 'Slicling operator', |
| 2438 | '__len__': 'Length operator', |
| 2439 | '__cmp__': 'Comparison operator', |
| 2440 | '__eq__': 'Equality operator', |
| 2441 | '__in__': 'Containership operator', |
| 2442 | '__gt__': 'Greater-than operator', |
| 2443 | '__lt__': 'Less-than operator', |
| 2444 | '__ge__': 'Greater-than-or-equals operator', |
| 2445 | '__le__': 'Less-than-or-equals operator', |
| 2446 | '__radd__': 'Right-side addition operator', |
| 2447 | '__hash__': 'Hashing function', |
| 2448 | '__contains__': 'In operator', |
| 2449 | '__nonzero__': 'Boolean test operator', |
| 2450 | '__str__': 'Informal representation operator', |
| 2451 | } |
| 2452 | |
| 2453 | write_property_details_entry = compile_template( |
| 2454 | ''' |
| 2455 | write_property_details_entry(self, out, var_doc, descr, \ |
| 2456 | accessors, div_class) |
| 2457 | ''', |
| 2458 | # /------------------------- Template -------------------------\ |
| 2459 | ''' |
| 2460 | >>> prop_doc = var_doc.value |
| 2461 | <a name="$var_doc.name$"></a> |
| 2462 | <div$div_class$> |
| 2463 | >>> self.write_table_header(out, "details") |
| 2464 | <tr><td> |
| 2465 | <h3 class="epydoc">$var_doc.name$</h3> |
| 2466 | $descr$ |
| 2467 | <dl class="fields"> |
| 2468 | >>> for (name, val, summary) in accessors: |
| 2469 | <dt>$name$ Method:</dt> |
| 2470 | <dd class="value">$val$ |
| 2471 | >>> if summary: |
| 2472 | - $summary$ |
| 2473 | >>> #endif |
| 2474 | </dd> |
| 2475 | >>> #endfor |
| 2476 | >>> if prop_doc.type_descr not in (None, UNKNOWN): |
| 2477 | <dt>Type:</dt> |
| 2478 | <dd>$self.type_descr(var_doc, indent=6)$</dd> |
| 2479 | >>> #endif |
| 2480 | </dl> |
| 2481 | >>> self.write_standard_fields(out, prop_doc) |
| 2482 | </td></tr></table> |
| 2483 | </div> |
| 2484 | ''') |
| 2485 | # \------------------------------------------------------------/ |
| 2486 | |
| 2487 | write_variable_details_entry = compile_template( |
| 2488 | ''' |
| 2489 | write_variable_details_entry(self, out, var_doc, descr, div_class) |
| 2490 | ''', |
| 2491 | # /------------------------- Template -------------------------\ |
| 2492 | ''' |
| 2493 | <a name="$var_doc.name$"></a> |
| 2494 | <div$div_class$> |
| 2495 | >>> self.write_table_header(out, "details") |
| 2496 | <tr><td> |
| 2497 | <h3 class="epydoc">$var_doc.name$</h3> |
| 2498 | $descr$ |
| 2499 | <dl class="fields"> |
| 2500 | >>> if var_doc.type_descr not in (None, UNKNOWN): |
| 2501 | <dt>Type:</dt> |
| 2502 | <dd>$self.type_descr(var_doc, indent=6)$</dd> |
| 2503 | >>> #endif |
| 2504 | </dl> |
| 2505 | >>> self.write_standard_fields(out, var_doc) |
| 2506 | >>> if var_doc.value is not UNKNOWN: |
| 2507 | <dl class="fields"> |
| 2508 | <dt>Value:</dt> |
| 2509 | <dd>$self.pprint_value(var_doc.value)$</dd> |
| 2510 | </dl> |
| 2511 | >>> #endif |
| 2512 | </td></tr></table> |
| 2513 | </div> |
| 2514 | ''') |
| 2515 | # \------------------------------------------------------------/ |
| 2516 | |
| 2517 | def variable_tooltip(self, var_doc): |
| 2518 | if var_doc.value in (None, UNKNOWN): |
| 2519 | return '' |
| 2520 | s = var_doc.value.pyval_repr().to_plaintext(None) |
| 2521 | if len(s) > self._variable_tooltip_linelen: |
| 2522 | s = s[:self._variable_tooltip_linelen-3]+'...' |
| 2523 | return ' title="%s"' % plaintext_to_html(s) |
| 2524 | |
| 2525 | def pprint_value(self, val_doc): |
| 2526 | if val_doc is UNKNOWN: |
| 2527 | return '??' |
| 2528 | elif isinstance(val_doc, GenericValueDoc): |
| 2529 | return ('<table><tr><td><pre class="variable">\n' + |
| 2530 | val_doc.pyval_repr().to_html(None) + |
| 2531 | '\n</pre></td></tr></table>\n') |
| 2532 | else: |
| 2533 | return self.href(val_doc) |
| 2534 | |
| 2535 | #//////////////////////////////////////////////////////////// |
| 2536 | #{ Base Tree |
| 2537 | #//////////////////////////////////////////////////////////// |
| 2538 | |
| 2539 | def base_tree(self, doc, width=None, postfix='', context=None): |
| 2540 | """ |
| 2541 | @return: The HTML code for a class's base tree. The tree is |
| 2542 | drawn 'upside-down' and right justified, to allow for |
| 2543 | multiple inheritance. |
| 2544 | @rtype: C{string} |
| 2545 | """ |
| 2546 | if context is None: |
| 2547 | context = doc.defining_module |
| 2548 | if width == None: width = self.find_tree_width(doc, context) |
| 2549 | if isinstance(doc, ClassDoc) and doc.bases != UNKNOWN: |
| 2550 | bases = doc.bases |
| 2551 | else: |
| 2552 | bases = [] |
| 2553 | |
| 2554 | if postfix == '': |
| 2555 | # [XX] use var name instead of canonical name? |
| 2556 | s = (' '*(width-2) + '<strong class="uidshort">'+ |
| 2557 | self.contextual_label(doc, context)+'</strong>\n') |
| 2558 | else: s = '' |
| 2559 | for i in range(len(bases)-1, -1, -1): |
| 2560 | base = bases[i] |
| 2561 | label = self.contextual_label(base, context) |
| 2562 | s = (' '*(width-4-len(label)) + self.href(base, label) |
| 2563 | +' --+'+postfix+'\n' + |
| 2564 | ' '*(width-4) + |
| 2565 | ' |'+postfix+'\n' + |
| 2566 | s) |
| 2567 | if i != 0: |
| 2568 | s = (self.base_tree(base, width-4, ' |'+postfix, context)+s) |
| 2569 | else: |
| 2570 | s = (self.base_tree(base, width-4, ' '+postfix, context)+s) |
| 2571 | return s |
| 2572 | |
| 2573 | def find_tree_width(self, doc, context): |
| 2574 | """ |
| 2575 | Helper function for L{base_tree}. |
| 2576 | @return: The width of a base tree, when drawn |
| 2577 | right-justified. This is used by L{base_tree} to |
| 2578 | determine how far to indent lines of the base tree. |
| 2579 | @rtype: C{int} |
| 2580 | """ |
| 2581 | if not isinstance(doc, ClassDoc): return 2 |
| 2582 | if doc.bases == UNKNOWN: return 2 |
| 2583 | width = 2 |
| 2584 | for base in doc.bases: |
| 2585 | width = max(width, len(self.contextual_label(base, context))+4, |
| 2586 | self.find_tree_width(base, context)+4) |
| 2587 | return width |
| 2588 | |
| 2589 | def contextual_label(self, doc, context): |
| 2590 | """ |
| 2591 | Return the label for C{doc} to be shown in C{context}. |
| 2592 | """ |
| 2593 | if doc.canonical_name is None: |
| 2594 | if doc.parse_repr is not None: |
| 2595 | return doc.parse_repr |
| 2596 | else: |
| 2597 | return '??' |
| 2598 | else: |
| 2599 | if context is UNKNOWN: |
| 2600 | return str(doc.canonical_name) |
| 2601 | else: |
| 2602 | context_name = context.canonical_name |
| 2603 | return str(doc.canonical_name.contextualize(context_name)) |
| 2604 | |
| 2605 | #//////////////////////////////////////////////////////////// |
| 2606 | #{ Function Signatures |
| 2607 | #//////////////////////////////////////////////////////////// |
| 2608 | |
| 2609 | def function_signature(self, api_doc, is_summary=False, |
| 2610 | link_name=False, anchor=False, context=None): |
| 2611 | """Render a function signature in HTML. |
| 2612 | |
| 2613 | @param api_doc: The object whose name is to be rendered. If a |
| 2614 | C{VariableDoc}, its C{value} should be a C{RoutineDoc} |
| 2615 | @type api_doc: L{VariableDoc} or L{RoutineDoc} |
| 2616 | @param is_summary: True if the fuction is to be rendered in the summary. |
| 2617 | type css_class: C{bool} |
| 2618 | @param link_name: If True, the name is a link to the object anchor. |
| 2619 | @type link_name: C{bool} |
| 2620 | @param anchor: If True, the name is the object anchor. |
| 2621 | @type anchor: C{bool} |
| 2622 | @param context: If set, represent the function name from this context. |
| 2623 | Only useful when C{api_doc} is a L{RoutineDoc}. |
| 2624 | @type context: L{DottedName} |
| 2625 | |
| 2626 | @return: The HTML code for the object. |
| 2627 | @rtype: C{str} |
| 2628 | """ |
| 2629 | if is_summary: css_class = 'summary-sig' |
| 2630 | else: css_class = 'sig' |
| 2631 | |
| 2632 | # [XX] clean this up! |
| 2633 | if isinstance(api_doc, VariableDoc): |
| 2634 | func_doc = api_doc.value |
| 2635 | # This should never happen, but just in case: |
| 2636 | if api_doc.value in (None, UNKNOWN): |
| 2637 | return (('<span class="%s"><span class="%s-name">%s'+ |
| 2638 | '</span>(...)</span>') % |
| 2639 | (css_class, css_class, api_doc.name)) |
| 2640 | # Get the function's name. |
| 2641 | name = self.summary_name(api_doc, css_class=css_class+'-name', |
| 2642 | link_name=link_name, anchor=anchor) |
| 2643 | else: |
| 2644 | func_doc = api_doc |
| 2645 | name = self.href(api_doc, css_class=css_class+'-name', |
| 2646 | context=context) |
| 2647 | |
| 2648 | if func_doc.posargs == UNKNOWN: |
| 2649 | args = ['...'] |
| 2650 | else: |
| 2651 | args = [self.func_arg(n, d, css_class) for (n, d) |
| 2652 | in zip(func_doc.posargs, func_doc.posarg_defaults)] |
| 2653 | if func_doc.vararg not in (None, UNKNOWN): |
| 2654 | if func_doc.vararg == '...': |
| 2655 | args.append('<span class="%s-arg">...</span>' % css_class) |
| 2656 | else: |
| 2657 | args.append('<span class="%s-arg">*%s</span>' % |
| 2658 | (css_class, func_doc.vararg)) |
| 2659 | if func_doc.kwarg not in (None, UNKNOWN): |
| 2660 | args.append('<span class="%s-arg">**%s</span>' % |
| 2661 | (css_class, func_doc.kwarg)) |
| 2662 | |
| 2663 | return ('<span class="%s">%s(%s)</span>' % |
| 2664 | (css_class, name, ',\n '.join(args))) |
| 2665 | |
| 2666 | def summary_name(self, api_doc, css_class='summary-name', |
| 2667 | link_name=False, anchor=False): |
| 2668 | """Render an object name in HTML. |
| 2669 | |
| 2670 | @param api_doc: The object whose name is to be rendered |
| 2671 | @type api_doc: L{APIDoc} |
| 2672 | @param css_class: The CSS class to assign to the rendered name |
| 2673 | type css_class: C{str} |
| 2674 | @param link_name: If True, the name is a link to the object anchor. |
| 2675 | @type link_name: C{bool} |
| 2676 | @param anchor: If True, the name is the object anchor. |
| 2677 | @type anchor: C{bool} |
| 2678 | |
| 2679 | @return: The HTML code for the object. |
| 2680 | @rtype: C{str} |
| 2681 | """ |
| 2682 | if anchor: |
| 2683 | rv = '<a name="%s"></a>' % api_doc.name |
| 2684 | else: |
| 2685 | rv = '' |
| 2686 | |
| 2687 | if link_name: |
| 2688 | rv += self.href(api_doc, css_class=css_class) |
| 2689 | else: |
| 2690 | rv += '<span class="%s">%s</span>' % (css_class, api_doc.name) |
| 2691 | |
| 2692 | return rv |
| 2693 | |
| 2694 | # [xx] tuple args??? |
| 2695 | def func_arg(self, name, default, css_class): |
| 2696 | name = self._arg_name(name) |
| 2697 | s = '<span class="%s-arg">%s</span>' % (css_class, name) |
| 2698 | if default is not None: |
| 2699 | s += ('=<span class="%s-default">%s</span>' % |
| 2700 | (css_class, default.summary_pyval_repr().to_html(None))) |
| 2701 | return s |
| 2702 | |
| 2703 | def _arg_name(self, arg): |
| 2704 | if isinstance(arg, basestring): |
| 2705 | return arg |
| 2706 | elif len(arg) == 1: |
| 2707 | return '(%s,)' % self._arg_name(arg[0]) |
| 2708 | else: |
| 2709 | return '(%s)' % (', '.join([self._arg_name(a) for a in arg])) |
| 2710 | |
| 2711 | |
| 2712 | |
| 2713 | |
| 2714 | #//////////////////////////////////////////////////////////// |
| 2715 | #{ Import Lists |
| 2716 | #//////////////////////////////////////////////////////////// |
| 2717 | |
| 2718 | def write_imports(self, out, doc): |
| 2719 | assert isinstance(doc, NamespaceDoc) |
| 2720 | imports = doc.select_variables(imported=True, |
| 2721 | public=self._public_filter) |
| 2722 | if not imports: return |
| 2723 | |
| 2724 | out('<p class="indent-wrapped-lines">') |
| 2725 | out('<b>Imports:</b>\n ') |
| 2726 | out(',\n '.join([self._import(v, doc) for v in imports])) |
| 2727 | out('\n</p><br />\n') |
| 2728 | |
| 2729 | def _import(self, var_doc, context): |
| 2730 | if var_doc.imported_from not in (None, UNKNOWN): |
| 2731 | return self.href(var_doc.imported_from, |
| 2732 | var_doc.name, context=context, |
| 2733 | tooltip='%s' % var_doc.imported_from) |
| 2734 | elif (var_doc.value not in (None, UNKNOWN) and not |
| 2735 | isinstance(var_doc.value, GenericValueDoc)): |
| 2736 | return self.href(var_doc.value, |
| 2737 | var_doc.name, context=context, |
| 2738 | tooltip='%s' % var_doc.value.canonical_name) |
| 2739 | else: |
| 2740 | return plaintext_to_html(var_doc.name) |
| 2741 | |
| 2742 | #//////////////////////////////////////////////////////////// |
| 2743 | #{ Function Attributes |
| 2744 | #//////////////////////////////////////////////////////////// |
| 2745 | |
| 2746 | #//////////////////////////////////////////////////////////// |
| 2747 | #{ Module Trees |
| 2748 | #//////////////////////////////////////////////////////////// |
| 2749 | |
| 2750 | def write_module_list(self, out, doc): |
| 2751 | if len(doc.submodules) == 0: return |
| 2752 | self.write_table_header(out, "summary", "Submodules") |
| 2753 | |
| 2754 | for group_name in doc.group_names(): |
| 2755 | if not doc.submodule_groups[group_name]: continue |
| 2756 | if group_name: |
| 2757 | self.write_group_header(out, group_name) |
| 2758 | out(' <tr><td class="summary">\n' |
| 2759 | ' <ul class="nomargin">\n') |
| 2760 | for submodule in doc.submodule_groups[group_name]: |
| 2761 | self.write_module_tree_item(out, submodule, package=doc) |
| 2762 | out(' </ul></td></tr>\n') |
| 2763 | |
| 2764 | out(self.TABLE_FOOTER+'\n<br />\n') |
| 2765 | |
| 2766 | def write_module_tree_item(self, out, doc, package=None): |
| 2767 | # If it's a private variable, then mark its <li>. |
| 2768 | var = package and package.variables.get(doc.canonical_name[-1]) |
| 2769 | priv = ((var is not None and var.is_public is False) or |
| 2770 | (var is None and doc.canonical_name[-1].startswith('_'))) |
| 2771 | out(' <li%s> <strong class="uidlink">%s</strong>' |
| 2772 | % (priv and ' class="private"' or '', self.href(doc))) |
| 2773 | if doc.summary not in (None, UNKNOWN): |
| 2774 | out(': <em class="summary">'+ |
| 2775 | self.description(doc.summary, doc, 8)+'</em>') |
| 2776 | if doc.submodules != UNKNOWN and doc.submodules: |
| 2777 | if priv: out('\n <ul class="private">\n') |
| 2778 | else: out('\n <ul>\n') |
| 2779 | for submodule in doc.submodules: |
| 2780 | self.write_module_tree_item(out, submodule, package=doc) |
| 2781 | out(' </ul>\n') |
| 2782 | out(' </li>\n') |
| 2783 | |
| 2784 | #//////////////////////////////////////////////////////////// |
| 2785 | #{ Class trees |
| 2786 | #//////////////////////////////////////////////////////////// |
| 2787 | |
| 2788 | write_class_tree_item = compile_template( |
| 2789 | ''' |
| 2790 | write_class_tree_item(self, out, doc, class_set) |
| 2791 | ''', |
| 2792 | # /------------------------- Template -------------------------\ |
| 2793 | ''' |
| 2794 | >>> if doc.summary in (None, UNKNOWN): |
| 2795 | <li> <strong class="uidlink">$self.href(doc)$</strong> |
| 2796 | >>> else: |
| 2797 | <li> <strong class="uidlink">$self.href(doc)$</strong>: |
| 2798 | <em class="summary">$self.description(doc.summary, doc, 8)$</em> |
| 2799 | >>> # endif |
| 2800 | >>> if doc.subclasses: |
| 2801 | <ul> |
| 2802 | >>> for subclass in sorted(set(doc.subclasses), key=lambda c:c.canonical_name[-1]): |
| 2803 | >>> if subclass in class_set: |
| 2804 | >>> self.write_class_tree_item(out, subclass, class_set) |
| 2805 | >>> #endif |
| 2806 | >>> #endfor |
| 2807 | </ul> |
| 2808 | >>> #endif |
| 2809 | </li> |
| 2810 | ''') |
| 2811 | # \------------------------------------------------------------/ |
| 2812 | |
| 2813 | #//////////////////////////////////////////////////////////// |
| 2814 | #{ Standard Fields |
| 2815 | #//////////////////////////////////////////////////////////// |
| 2816 | |
| 2817 | def write_standard_fields(self, out, doc): |
| 2818 | """ |
| 2819 | Write HTML code containing descriptions of any standard markup |
| 2820 | fields that are defined by the given L{APIDoc} object (such as |
| 2821 | C{@author} and C{@todo} fields). |
| 2822 | |
| 2823 | @param doc: The L{APIDoc} object containing the API documentation |
| 2824 | for the object whose standard markup fields should be |
| 2825 | described. |
| 2826 | """ |
| 2827 | fields = [] |
| 2828 | field_values = {} |
| 2829 | |
| 2830 | for (field, arg, descr) in doc.metadata: |
| 2831 | if field not in field_values: |
| 2832 | fields.append(field) |
| 2833 | if field.takes_arg: |
| 2834 | subfields = field_values.setdefault(field,{}) |
| 2835 | subfields.setdefault(arg,[]).append(descr) |
| 2836 | else: |
| 2837 | field_values.setdefault(field,[]).append(descr) |
| 2838 | |
| 2839 | if not fields: return |
| 2840 | |
| 2841 | out('<div class="fields">') |
| 2842 | for field in fields: |
| 2843 | if field.takes_arg: |
| 2844 | for arg, descrs in field_values[field].items(): |
| 2845 | self.write_standard_field(out, doc, field, descrs, arg) |
| 2846 | |
| 2847 | else: |
| 2848 | self.write_standard_field(out, doc, field, field_values[field]) |
| 2849 | |
| 2850 | out('</div>') |
| 2851 | |
| 2852 | write_standard_field = compile_template( |
| 2853 | """ |
| 2854 | write_standard_field(self, out, doc, field, descrs, arg='') |
| 2855 | |
| 2856 | """, |
| 2857 | # /------------------------- Template -------------------------\ |
| 2858 | ''' |
| 2859 | >>> if arg: arglabel = " (%s)" % arg |
| 2860 | >>> else: arglabel = "" |
| 2861 | >>> if len(descrs) == 1: |
| 2862 | <p><strong>$field.singular+arglabel$:</strong> |
| 2863 | $self.description(descrs[0], doc, 8)$ |
| 2864 | </p> |
| 2865 | >>> elif field.short: |
| 2866 | <dl><dt>$field.plural+arglabel$:</dt> |
| 2867 | <dd> |
| 2868 | >>> for descr in descrs[:-1]: |
| 2869 | $self.description(descr, doc, 10)$, |
| 2870 | >>> # end for |
| 2871 | $self.description(descrs[-1], doc, 10)$ |
| 2872 | </dd> |
| 2873 | </dl> |
| 2874 | >>> else: |
| 2875 | <strong>$field.plural+arglabel$:</strong> |
| 2876 | <ul class="nomargin-top"> |
| 2877 | >>> for descr in descrs: |
| 2878 | <li> |
| 2879 | $self.description(descr, doc, 8)$ |
| 2880 | </li> |
| 2881 | >>> # end for |
| 2882 | </ul> |
| 2883 | >>> # end else |
| 2884 | >>> # end for |
| 2885 | ''') |
| 2886 | # \------------------------------------------------------------/ |
| 2887 | |
| 2888 | #//////////////////////////////////////////////////////////// |
| 2889 | #{ Index generation |
| 2890 | #//////////////////////////////////////////////////////////// |
| 2891 | |
| 2892 | #: A list of metadata indices that should be generated. Each |
| 2893 | #: entry in this list is a tuple C{(tag, label, short_label)}, |
| 2894 | #: where C{tag} is the cannonical tag of a metadata field; |
| 2895 | #: C{label} is a label for the index page; and C{short_label} |
| 2896 | #: is a shorter label, used in the index selector. |
| 2897 | METADATA_INDICES = [('bug', 'Bug List', 'Bugs'), |
| 2898 | ('todo', 'To Do List', 'To Do'), |
| 2899 | ('change', 'Change Log', 'Changes'), |
| 2900 | ('deprecated', 'Deprecation List', 'Deprecations'), |
| 2901 | ('since', 'Introductions List', 'Introductions'), |
| 2902 | ] |
| 2903 | |
| 2904 | def build_identifier_index(self): |
| 2905 | items = [] |
| 2906 | for doc in self.indexed_docs: |
| 2907 | name = plaintext_to_html(doc.canonical_name[-1]) |
| 2908 | if isinstance(doc, RoutineDoc): name += '()' |
| 2909 | url = self.url(doc) |
| 2910 | if not url: continue |
| 2911 | container = self.docindex.container(doc) |
| 2912 | items.append( (name, url, container) ) |
| 2913 | return sorted(items, key=lambda v:v[0].lower()) |
| 2914 | |
| 2915 | def _group_by_letter(self, items): |
| 2916 | """Preserves sort order of the input.""" |
| 2917 | index = {} |
| 2918 | for item in items: |
| 2919 | first_letter = item[0][0].upper() |
| 2920 | if not ("A" <= first_letter <= "Z"): |
| 2921 | first_letter = '_' |
| 2922 | index.setdefault(first_letter, []).append(item) |
| 2923 | return index |
| 2924 | |
| 2925 | def build_term_index(self): |
| 2926 | items = [] |
| 2927 | for doc in self.indexed_docs: |
| 2928 | url = self.url(doc) |
| 2929 | items += self._terms_from_docstring(url, doc, doc.descr) |
| 2930 | for (field, arg, descr) in doc.metadata: |
| 2931 | items += self._terms_from_docstring(url, doc, descr) |
| 2932 | if hasattr(doc, 'type_descr'): |
| 2933 | items += self._terms_from_docstring(url, doc, |
| 2934 | doc.type_descr) |
| 2935 | if hasattr(doc, 'return_descr'): |
| 2936 | items += self._terms_from_docstring(url, doc, |
| 2937 | doc.return_descr) |
| 2938 | if hasattr(doc, 'return_type'): |
| 2939 | items += self._terms_from_docstring(url, doc, |
| 2940 | doc.return_type) |
| 2941 | return sorted(items, key=lambda v:v[0].lower()) |
| 2942 | |
| 2943 | def _terms_from_docstring(self, base_url, container, parsed_docstring): |
| 2944 | if parsed_docstring in (None, UNKNOWN): return [] |
| 2945 | terms = [] |
| 2946 | # Strip any existing anchor off: |
| 2947 | base_url = re.sub('#.*', '', '%s' % (base_url,)) |
| 2948 | for term in parsed_docstring.index_terms(): |
| 2949 | anchor = self._term_index_to_anchor(term) |
| 2950 | url = '%s#%s' % (base_url, anchor) |
| 2951 | terms.append( (term.to_plaintext(None), url, container) ) |
| 2952 | return terms |
| 2953 | |
| 2954 | def build_metadata_index(self, field_name): |
| 2955 | # Build the index. |
| 2956 | index = {} |
| 2957 | for doc in self.indexed_docs: |
| 2958 | if (not self._show_private and |
| 2959 | self._doc_or_ancestor_is_private(doc)): |
| 2960 | continue |
| 2961 | descrs = {} |
| 2962 | if doc.metadata is not UNKNOWN: |
| 2963 | for (field, arg, descr) in doc.metadata: |
| 2964 | if field.tags[0] == field_name: |
| 2965 | descrs.setdefault(arg, []).append(descr) |
| 2966 | for (arg, descr_list) in descrs.iteritems(): |
| 2967 | index.setdefault(arg, []).append( (doc, descr_list) ) |
| 2968 | return index |
| 2969 | |
| 2970 | def _term_index_to_anchor(self, term): |
| 2971 | """ |
| 2972 | Given the name of an inline index item, construct a URI anchor. |
| 2973 | These anchors are used to create links from the index page to each |
| 2974 | index item. |
| 2975 | """ |
| 2976 | # Include "-" so we don't accidentally collide with the name |
| 2977 | # of a python identifier. |
| 2978 | s = re.sub(r'\s\s+', '-', term.to_plaintext(None)) |
| 2979 | return "index-"+re.sub("[^a-zA-Z0-9]", "_", s) |
| 2980 | |
| 2981 | #//////////////////////////////////////////////////////////// |
| 2982 | #{ Redirect page |
| 2983 | #//////////////////////////////////////////////////////////// |
| 2984 | |
| 2985 | def write_redirect_page(self, out): |
| 2986 | """ |
| 2987 | Build the auto-redirect page, which translates dotted names to |
| 2988 | URLs using javascript. When the user visits |
| 2989 | <redirect.html#dotted.name>, they will automatically get |
| 2990 | redirected to the page for the object with the given |
| 2991 | fully-qualified dotted name. E.g., for epydoc, |
| 2992 | <redirect.html#epydoc.apidoc.UNKNOWN> redirects the user to |
| 2993 | <epydoc.apidoc-module.html#UNKNOWN>. |
| 2994 | """ |
| 2995 | # Construct a list of all the module & class pages that we're |
| 2996 | # documenting. The redirect_url javascript will scan through |
| 2997 | # this list, looking for a page name that matches the |
| 2998 | # requested dotted name. |
| 2999 | pages = (['%s-m' % val_doc.canonical_name |
| 3000 | for val_doc in self.module_list] + |
| 3001 | ['%s-c' % val_doc.canonical_name |
| 3002 | for val_doc in self.class_list]) |
| 3003 | # Sort the pages from longest to shortest. This ensures that |
| 3004 | # we find e.g. "x.y.z" in the list before "x.y". |
| 3005 | pages = sorted(pages, key=lambda p:-len(p)) |
| 3006 | |
| 3007 | # Write the redirect page. |
| 3008 | self._write_redirect_page(out, pages) |
| 3009 | |
| 3010 | _write_redirect_page = compile_template( |
| 3011 | ''' |
| 3012 | _write_redirect_page(self, out, pages) |
| 3013 | ''', |
| 3014 | # /------------------------- Template -------------------------\ |
| 3015 | ''' |
| 3016 | <html><head><title>Epydoc Redirect Page</title> |
| 3017 | <meta http-equiv="cache-control" content="no-cache" /> |
| 3018 | <meta http-equiv="expires" content="0" /> |
| 3019 | <meta http-equiv="pragma" content="no-cache" /> |
| 3020 | <script type="text/javascript" src="epydoc.js"></script> |
| 3021 | </head> |
| 3022 | <body> |
| 3023 | <script type="text/javascript"> |
| 3024 | <!-- |
| 3025 | var pages = $"[%s]" % ", ".join(['"%s"' % v for v in pages])$; |
| 3026 | var dottedName = get_anchor(); |
| 3027 | if (dottedName) { |
| 3028 | var target = redirect_url(dottedName); |
| 3029 | if (target) window.location.replace(target); |
| 3030 | } |
| 3031 | // --> |
| 3032 | </script> |
| 3033 | |
| 3034 | <h3>Epydoc Auto-redirect page</h3> |
| 3035 | |
| 3036 | <p>When javascript is enabled, this page will redirect URLs of |
| 3037 | the form <tt>redirect.html#<i>dotted.name</i></tt> to the |
| 3038 | documentation for the object with the given fully-qualified |
| 3039 | dotted name.</p> |
| 3040 | <p><a id="message"> </a></p> |
| 3041 | |
| 3042 | <script type="text/javascript"> |
| 3043 | <!-- |
| 3044 | if (dottedName) { |
| 3045 | var msg = document.getElementById("message"); |
| 3046 | msg.innerHTML = "No documentation found for <tt>"+ |
| 3047 | dottedName+"</tt>"; |
| 3048 | } |
| 3049 | // --> |
| 3050 | </script> |
| 3051 | |
| 3052 | </body> |
| 3053 | </html> |
| 3054 | ''') |
| 3055 | # \------------------------------------------------------------/ |
| 3056 | |
| 3057 | #//////////////////////////////////////////////////////////// |
| 3058 | #{ URLs list |
| 3059 | #//////////////////////////////////////////////////////////// |
| 3060 | |
| 3061 | def write_api_list(self, out): |
| 3062 | """ |
| 3063 | Write a list of mapping name->url for all the documented objects. |
| 3064 | """ |
| 3065 | # Construct a list of all the module & class pages that we're |
| 3066 | # documenting. The redirect_url javascript will scan through |
| 3067 | # this list, looking for a page name that matches the |
| 3068 | # requested dotted name. |
| 3069 | skip = (ModuleDoc, ClassDoc, type(UNKNOWN)) |
| 3070 | for val_doc in self.module_list: |
| 3071 | self.write_url_record(out, val_doc) |
| 3072 | for var in val_doc.variables.itervalues(): |
| 3073 | if not isinstance(var.value, skip): |
| 3074 | self.write_url_record(out, var) |
| 3075 | |
| 3076 | for val_doc in self.class_list: |
| 3077 | self.write_url_record(out, val_doc) |
| 3078 | for var in val_doc.variables.itervalues(): |
| 3079 | self.write_url_record(out, var) |
| 3080 | |
| 3081 | def write_url_record(self, out, obj): |
| 3082 | url = self.url(obj) |
| 3083 | if url is not None: |
| 3084 | out("%s\t%s\n" % (obj.canonical_name, url)) |
| 3085 | |
| 3086 | #//////////////////////////////////////////////////////////// |
| 3087 | #{ Helper functions |
| 3088 | #//////////////////////////////////////////////////////////// |
| 3089 | |
| 3090 | def _val_is_public(self, valdoc): |
| 3091 | """Make a best-guess as to whether the given class is public.""" |
| 3092 | container = self.docindex.container(valdoc) |
| 3093 | if isinstance(container, NamespaceDoc): |
| 3094 | for vardoc in container.variables.values(): |
| 3095 | if vardoc in (UNKNOWN, None): continue |
| 3096 | if vardoc.value is valdoc: |
| 3097 | return vardoc.is_public |
| 3098 | return True |
| 3099 | |
| 3100 | # [XX] Is it worth-while to pull the anchor tricks that I do here? |
| 3101 | # Or should I just live with the fact that show/hide private moves |
| 3102 | # stuff around? |
| 3103 | write_table_header = compile_template( |
| 3104 | ''' |
| 3105 | write_table_header(self, out, css_class, heading=None, \ |
| 3106 | private_link=True, colspan=2) |
| 3107 | ''', |
| 3108 | # /------------------------- Template -------------------------\ |
| 3109 | ''' |
| 3110 | >>> if heading is not None: |
| 3111 | >>> anchor = "section-%s" % re.sub("\W", "", heading) |
| 3112 | <!-- ==================== $heading.upper()$ ==================== --> |
| 3113 | <a name="$anchor$"></a> |
| 3114 | >>> #endif |
| 3115 | <table class="$css_class$" border="1" cellpadding="3" |
| 3116 | cellspacing="0" width="100%" bgcolor="white"> |
| 3117 | >>> if heading is not None: |
| 3118 | <tr bgcolor="#70b0f0" class="table-header"> |
| 3119 | >>> if private_link and self._show_private: |
| 3120 | <td colspan="$colspan$" class="table-header"> |
| 3121 | <table border="0" cellpadding="0" cellspacing="0" width="100%"> |
| 3122 | <tr valign="top"> |
| 3123 | <td align="left"><span class="table-header">$heading$</span></td> |
| 3124 | <td align="right" valign="top" |
| 3125 | ><span class="options">[<a href="#$anchor$" |
| 3126 | class="privatelink" onclick="toggle_private();" |
| 3127 | >hide private</a>]</span></td> |
| 3128 | </tr> |
| 3129 | </table> |
| 3130 | </td> |
| 3131 | >>> else: |
| 3132 | <td align="left" colspan="2" class="table-header"> |
| 3133 | <span class="table-header">$heading$</span></td> |
| 3134 | >>> #endif |
| 3135 | </tr> |
| 3136 | >>> #endif |
| 3137 | ''') |
| 3138 | # \------------------------------------------------------------/ |
| 3139 | |
| 3140 | TABLE_FOOTER = '</table>\n' |
| 3141 | |
| 3142 | PRIVATE_LINK = ''' |
| 3143 | <span class="options">[<a href="javascript:void(0);" class="privatelink" |
| 3144 | onclick="toggle_private();">hide private</a>]</span> |
| 3145 | '''.strip() |
| 3146 | |
| 3147 | write_group_header = compile_template( |
| 3148 | ''' |
| 3149 | write_group_header(self, out, group, tr_class='') |
| 3150 | ''', |
| 3151 | # /------------------------- Template -------------------------\ |
| 3152 | ''' |
| 3153 | <tr bgcolor="#e8f0f8" $tr_class$> |
| 3154 | <th colspan="2" class="group-header" |
| 3155 | > $group$</th></tr> |
| 3156 | ''') |
| 3157 | # \------------------------------------------------------------/ |
| 3158 | |
| 3159 | _url_cache = {} |
| 3160 | def url(self, obj): |
| 3161 | """ |
| 3162 | Return the URL for the given object, which can be a |
| 3163 | C{VariableDoc}, a C{ValueDoc}, or a C{DottedName}. |
| 3164 | """ |
| 3165 | cached_url = self._url_cache.get(id(obj)) |
| 3166 | if cached_url is not None: |
| 3167 | return cached_url |
| 3168 | else: |
| 3169 | url = self._url_cache[id(obj)] = self._url(obj) |
| 3170 | return url |
| 3171 | |
| 3172 | def _url(self, obj): |
| 3173 | """ |
| 3174 | Internal helper for L{url}. |
| 3175 | """ |
| 3176 | # Module: <canonical_name>-module.html |
| 3177 | if isinstance(obj, ModuleDoc): |
| 3178 | if obj not in self.module_set: return None |
| 3179 | return urllib.quote('%s'%obj.canonical_name) + '-module.html' |
| 3180 | # Class: <canonical_name>-class.html |
| 3181 | elif isinstance(obj, ClassDoc): |
| 3182 | if obj not in self.class_set: return None |
| 3183 | return urllib.quote('%s'%obj.canonical_name) + '-class.html' |
| 3184 | # Variable |
| 3185 | elif isinstance(obj, VariableDoc): |
| 3186 | val_doc = obj.value |
| 3187 | if isinstance(val_doc, (ModuleDoc, ClassDoc)): |
| 3188 | return self.url(val_doc) |
| 3189 | elif obj.container in (None, UNKNOWN): |
| 3190 | if val_doc in (None, UNKNOWN): return None |
| 3191 | return self.url(val_doc) |
| 3192 | elif obj.is_imported == True: |
| 3193 | if obj.imported_from is not UNKNOWN: |
| 3194 | return self.url(obj.imported_from) |
| 3195 | else: |
| 3196 | return None |
| 3197 | else: |
| 3198 | container_url = self.url(obj.container) |
| 3199 | if container_url is None: return None |
| 3200 | return '%s#%s' % (container_url, urllib.quote('%s'%obj.name)) |
| 3201 | # Value (other than module or class) |
| 3202 | elif isinstance(obj, ValueDoc): |
| 3203 | container = self.docindex.container(obj) |
| 3204 | if container is None: |
| 3205 | return None # We couldn't find it! |
| 3206 | else: |
| 3207 | container_url = self.url(container) |
| 3208 | if container_url is None: return None |
| 3209 | anchor = urllib.quote('%s'%obj.canonical_name[-1]) |
| 3210 | return '%s#%s' % (container_url, anchor) |
| 3211 | # Dotted name: look up the corresponding APIDoc |
| 3212 | elif isinstance(obj, DottedName): |
| 3213 | val_doc = self.docindex.get_valdoc(obj) |
| 3214 | if val_doc is None: return None |
| 3215 | return self.url(val_doc) |
| 3216 | # Special pages: |
| 3217 | elif obj == 'indices': |
| 3218 | return 'identifier-index.html' |
| 3219 | elif obj == 'help': |
| 3220 | return 'help.html' |
| 3221 | elif obj == 'trees': |
| 3222 | return self._trees_url |
| 3223 | else: |
| 3224 | raise ValueError, "Don't know what to do with %r" % obj |
| 3225 | |
| 3226 | def pysrc_link(self, api_doc): |
| 3227 | if not self._incl_sourcecode: |
| 3228 | return '' |
| 3229 | url = self.pysrc_url(api_doc) |
| 3230 | if url is not None: |
| 3231 | return ('<span class="codelink"><a href="%s">source ' |
| 3232 | 'code</a></span>' % url) |
| 3233 | else: |
| 3234 | return '' |
| 3235 | |
| 3236 | def pysrc_url(self, api_doc): |
| 3237 | if isinstance(api_doc, VariableDoc): |
| 3238 | if api_doc.value not in (None, UNKNOWN): |
| 3239 | return pysrc_url(api_doc.value) |
| 3240 | else: |
| 3241 | return None |
| 3242 | elif isinstance(api_doc, ModuleDoc): |
| 3243 | if api_doc in self.modules_with_sourcecode: |
| 3244 | return ('%s-pysrc.html' % |
| 3245 | urllib.quote('%s' % api_doc.canonical_name)) |
| 3246 | else: |
| 3247 | return None |
| 3248 | else: |
| 3249 | module = api_doc.defining_module |
| 3250 | if module == UNKNOWN: return None |
| 3251 | module_pysrc_url = self.pysrc_url(module) |
| 3252 | if module_pysrc_url is None: return None |
| 3253 | module_name = module.canonical_name |
| 3254 | if not module_name.dominates(api_doc.canonical_name, True): |
| 3255 | log.debug('%r is in %r but name does not dominate' % |
| 3256 | (api_doc, module)) |
| 3257 | return module_pysrc_url |
| 3258 | mname_len = len(module.canonical_name) |
| 3259 | anchor = '%s' % api_doc.canonical_name[mname_len:] |
| 3260 | return '%s#%s' % (module_pysrc_url, urllib.quote(anchor)) |
| 3261 | |
| 3262 | # We didn't find it: |
| 3263 | return None |
| 3264 | |
| 3265 | # [xx] add code to automatically do <code> wrapping or the like? |
| 3266 | def href(self, target, label=None, css_class=None, context=None, |
| 3267 | tooltip=None): |
| 3268 | """ |
| 3269 | Return the HTML code for an HREF link to the given target |
| 3270 | (which can be a C{VariableDoc}, a C{ValueDoc}, or a |
| 3271 | C{DottedName}. |
| 3272 | If a C{NamespaceDoc} C{context} is specified, the target label is |
| 3273 | contextualized to it. |
| 3274 | """ |
| 3275 | assert isinstance(target, (APIDoc, DottedName)) |
| 3276 | |
| 3277 | # Pick a label, if none was given. |
| 3278 | if label is None: |
| 3279 | if isinstance(target, VariableDoc): |
| 3280 | label = target.name |
| 3281 | elif (isinstance(target, ValueDoc) and |
| 3282 | target.canonical_name is not UNKNOWN): |
| 3283 | label = target.canonical_name |
| 3284 | elif isinstance(target, DottedName): |
| 3285 | label = target |
| 3286 | elif isinstance(target, GenericValueDoc): |
| 3287 | raise ValueError("href() should not be called with " |
| 3288 | "GenericValueDoc objects (perhaps you " |
| 3289 | "meant to use the containing variable?)") |
| 3290 | else: |
| 3291 | raise ValueError("Unable to find a label for %r" % target) |
| 3292 | |
| 3293 | if context is not None and isinstance(label, DottedName): |
| 3294 | label = label.contextualize(context.canonical_name.container()) |
| 3295 | |
| 3296 | label = plaintext_to_html(str(label)) |
| 3297 | |
| 3298 | # Munge names for scripts & unreachable values |
| 3299 | if label.startswith('script-'): |
| 3300 | label = label[7:] + ' (script)' |
| 3301 | if label.startswith('??'): |
| 3302 | label = '<i>unreachable</i>' + label[2:] |
| 3303 | label = re.sub(r'-\d+$', '', label) |
| 3304 | |
| 3305 | # Get the url for the target. |
| 3306 | url = self.url(target) |
| 3307 | if url is None: |
| 3308 | if tooltip: return '<span title="%s">%s</span>' % (tooltip, label) |
| 3309 | else: return label |
| 3310 | |
| 3311 | # Construct a string for the class attribute. |
| 3312 | if css_class is None: |
| 3313 | css = '' |
| 3314 | else: |
| 3315 | css = ' class="%s"' % css_class |
| 3316 | |
| 3317 | onclick = '' |
| 3318 | if ((isinstance(target, VariableDoc) and not target.is_public) or |
| 3319 | (isinstance(target, ValueDoc) and |
| 3320 | not isinstance(target, GenericValueDoc) and |
| 3321 | not self._val_is_public(target))): |
| 3322 | onclick = ' onclick="show_private();"' |
| 3323 | |
| 3324 | if tooltip: |
| 3325 | tooltip = ' title="%s"' % tooltip |
| 3326 | else: |
| 3327 | tooltip = '' |
| 3328 | |
| 3329 | return '<a href="%s"%s%s%s>%s</a>' % (url, css, onclick, tooltip, label) |
| 3330 | |
| 3331 | def _attr_to_html(self, attr, api_doc, indent): |
| 3332 | if api_doc in (None, UNKNOWN): |
| 3333 | return '' |
| 3334 | pds = getattr(api_doc, attr, None) # pds = ParsedDocstring. |
| 3335 | if pds not in (None, UNKNOWN): |
| 3336 | return self.docstring_to_html(pds, api_doc, indent) |
| 3337 | elif isinstance(api_doc, VariableDoc): |
| 3338 | return self._attr_to_html(attr, api_doc.value, indent) |
| 3339 | |
| 3340 | def summary(self, api_doc, indent=0): |
| 3341 | return self._attr_to_html('summary', api_doc, indent) |
| 3342 | |
| 3343 | def descr(self, api_doc, indent=0): |
| 3344 | return self._attr_to_html('descr', api_doc, indent) |
| 3345 | |
| 3346 | def type_descr(self, api_doc, indent=0): |
| 3347 | return self._attr_to_html('type_descr', api_doc, indent) |
| 3348 | |
| 3349 | def return_type(self, api_doc, indent=0): |
| 3350 | return self._attr_to_html('return_type', api_doc, indent) |
| 3351 | |
| 3352 | def return_descr(self, api_doc, indent=0): |
| 3353 | return self._attr_to_html('return_descr', api_doc, indent) |
| 3354 | |
| 3355 | def docstring_to_html(self, parsed_docstring, where=None, indent=0): |
| 3356 | if parsed_docstring in (None, UNKNOWN): return '' |
| 3357 | linker = _HTMLDocstringLinker(self, where) |
| 3358 | s = parsed_docstring.to_html(linker, indent=indent, |
| 3359 | directory=self._directory, |
| 3360 | docindex=self.docindex, |
| 3361 | context=where).strip() |
| 3362 | if self._mark_docstrings: |
| 3363 | s = '<span class="docstring">%s</span><!--end docstring-->' % s |
| 3364 | return s |
| 3365 | |
| 3366 | def description(self, parsed_docstring, where=None, indent=0): |
| 3367 | assert isinstance(where, (APIDoc, type(None))) |
| 3368 | if parsed_docstring in (None, UNKNOWN): return '' |
| 3369 | linker = _HTMLDocstringLinker(self, where) |
| 3370 | descr = parsed_docstring.to_html(linker, indent=indent, |
| 3371 | directory=self._directory, |
| 3372 | docindex=self.docindex, |
| 3373 | context=where).strip() |
| 3374 | if descr == '': return ' ' |
| 3375 | return descr |
| 3376 | |
| 3377 | # [xx] Should this be defined by the APIDoc classes themselves?? |
| 3378 | def doc_kind(self, doc): |
| 3379 | if isinstance(doc, ModuleDoc) and doc.is_package == True: |
| 3380 | return 'Package' |
| 3381 | elif (isinstance(doc, ModuleDoc) and |
| 3382 | doc.canonical_name[0].startswith('script')): |
| 3383 | return 'Script' |
| 3384 | elif isinstance(doc, ModuleDoc): |
| 3385 | return 'Module' |
| 3386 | elif isinstance(doc, ClassDoc): |
| 3387 | return 'Class' |
| 3388 | elif isinstance(doc, ClassMethodDoc): |
| 3389 | return 'Class Method' |
| 3390 | elif isinstance(doc, StaticMethodDoc): |
| 3391 | return 'Static Method' |
| 3392 | elif isinstance(doc, RoutineDoc): |
| 3393 | if isinstance(self.docindex.container(doc), ClassDoc): |
| 3394 | return 'Method' |
| 3395 | else: |
| 3396 | return 'Function' |
| 3397 | else: |
| 3398 | return 'Variable' |
| 3399 | |
| 3400 | def _doc_or_ancestor_is_private(self, api_doc): |
| 3401 | name = api_doc.canonical_name |
| 3402 | for i in range(len(name), 0, -1): |
| 3403 | # Is it (or an ancestor) a private var? |
| 3404 | var_doc = self.docindex.get_vardoc(name[:i]) |
| 3405 | if var_doc is not None and var_doc.is_public == False: |
| 3406 | return True |
| 3407 | # Is it (or an ancestor) a private module? |
| 3408 | val_doc = self.docindex.get_valdoc(name[:i]) |
| 3409 | if (val_doc is not None and isinstance(val_doc, ModuleDoc) and |
| 3410 | val_doc.canonical_name[-1].startswith('_')): |
| 3411 | return True |
| 3412 | return False |
| 3413 | |
| 3414 | def _private_subclasses(self, class_doc): |
| 3415 | """Return a list of all subclasses of the given class that are |
| 3416 | private, as determined by L{_val_is_private}. Recursive |
| 3417 | subclasses are included in this list.""" |
| 3418 | queue = [class_doc] |
| 3419 | private = set() |
| 3420 | for cls in queue: |
| 3421 | if (isinstance(cls, ClassDoc) and |
| 3422 | cls.subclasses not in (None, UNKNOWN)): |
| 3423 | queue.extend(cls.subclasses) |
| 3424 | private.update([c for c in cls.subclasses if |
| 3425 | not self._val_is_public(c)]) |
| 3426 | return private |
| 3427 | |
| 3428 | class _HTMLDocstringLinker(epydoc.markup.DocstringLinker): |
| 3429 | def __init__(self, htmlwriter, container): |
| 3430 | self.htmlwriter = htmlwriter |
| 3431 | self.docindex = htmlwriter.docindex |
| 3432 | self.container = container |
| 3433 | |
| 3434 | def translate_indexterm(self, indexterm): |
| 3435 | key = self.htmlwriter._term_index_to_anchor(indexterm) |
| 3436 | return ('<a name="%s"></a><i class="indexterm">%s</i>' % |
| 3437 | (key, indexterm.to_html(self))) |
| 3438 | |
| 3439 | def translate_identifier_xref(self, identifier, label=None): |
| 3440 | # Pick a label for this xref. |
| 3441 | if label is None: label = plaintext_to_html(identifier) |
| 3442 | |
| 3443 | # Find the APIDoc for it (if it's available). |
| 3444 | doc = self.docindex.find(identifier, self.container) |
| 3445 | |
| 3446 | # If we didn't find a target, then try checking in the contexts |
| 3447 | # of the ancestor classes. |
| 3448 | if doc is None and isinstance(self.container, RoutineDoc): |
| 3449 | container = self.docindex.get_vardoc( |
| 3450 | self.container.canonical_name) |
| 3451 | while (doc is None and container not in (None, UNKNOWN) |
| 3452 | and container.overrides not in (None, UNKNOWN)): |
| 3453 | container = container.overrides |
| 3454 | doc = self.docindex.find(identifier, container) |
| 3455 | |
| 3456 | # Translate it into HTML. |
| 3457 | if doc is None: |
| 3458 | self._failed_xref(identifier) |
| 3459 | return '<code class="link">%s</code>' % label |
| 3460 | else: |
| 3461 | return self.htmlwriter.href(doc, label, 'link') |
| 3462 | |
| 3463 | # [xx] Should this be added to the DocstringLinker interface??? |
| 3464 | # Currently, this is *only* used by dotgraph. |
| 3465 | def url_for(self, identifier): |
| 3466 | if isinstance(identifier, (basestring, DottedName)): |
| 3467 | doc = self.docindex.find(identifier, self.container) |
| 3468 | if doc: |
| 3469 | return self.htmlwriter.url(doc) |
| 3470 | else: |
| 3471 | return None |
| 3472 | |
| 3473 | elif isinstance(identifier, APIDoc): |
| 3474 | return self.htmlwriter.url(identifier) |
| 3475 | doc = identifier |
| 3476 | |
| 3477 | else: |
| 3478 | raise TypeError('Expected string or APIDoc') |
| 3479 | |
| 3480 | def _failed_xref(self, identifier): |
| 3481 | """Add an identifier to the htmlwriter's failed crossreference |
| 3482 | list.""" |
| 3483 | # Don't count it as a failed xref if it's a parameter of the |
| 3484 | # current function. |
| 3485 | if (isinstance(self.container, RoutineDoc) and |
| 3486 | identifier in self.container.all_args()): |
| 3487 | return |
| 3488 | |
| 3489 | failed_xrefs = self.htmlwriter._failed_xrefs |
| 3490 | context = self.container.canonical_name |
| 3491 | failed_xrefs.setdefault(identifier,{})[context] = 1 |