blob: e7791ef902b3578c34c595739b49557dd7246467 [file] [log] [blame]
Tor Norbye3a2425a2013-11-04 10:16:08 -08001#
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"""
10The HTML output generator for epydoc. The main interface provided by
11this module is the L{HTMLWriter} class.
12
13@todo: Add a cache to L{HTMLWriter.url()}?
14"""
15__docformat__ = 'epytext en'
16
17import re, os, sys, codecs, sre_constants, pprint, base64
18import urllib
19import __builtin__
20from epydoc.apidoc import *
21import epydoc.docstringparser
22import time, epydoc, epydoc.markup, epydoc.markup.epytext
23from epydoc.docwriter.html_colorize import PythonSourceColorizer
24from epydoc.docwriter import html_colorize
25from epydoc.docwriter.html_css import STYLESHEETS
26from epydoc.docwriter.html_help import HTML_HELP
27from epydoc.docwriter.dotgraph import *
28from epydoc import log
29from epydoc.util import plaintext_to_html, is_src_filename
30from 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
38def 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
158def 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
177class 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&nbsp;%s)'
1158 '</span>' % self.href(container, label))
1159 else:
1160 out('&nbsp;')
1161 out('</td>\n')
1162 out('</tr>\n')
1163 if add_blankline and num_rows == 1:
1164 blank_cell = '<td class="link-index">&nbsp;</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&nbsp;of&nbsp;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&nbsp;private":"show&nbsp;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&nbsp;graph</a></span>&nbsp;' % (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 >&nbsp;&nbsp;&nbsp;Home&nbsp;&nbsp;&nbsp;</th>
1834 >>> else:
1835 <th>&nbsp;&nbsp;&nbsp;<a
1836 href="$self._top_page_url$">Home</a>&nbsp;&nbsp;&nbsp;</th>
1837 >>> #endif
1838
1839 <!-- Tree link -->
1840 >>> if context == "trees":
1841 <th bgcolor="#70b0f0" class="navbar-select"
1842 >&nbsp;&nbsp;&nbsp;Trees&nbsp;&nbsp;&nbsp;</th>
1843 >>> else:
1844 <th>&nbsp;&nbsp;&nbsp;<a
1845 href="$self._trees_url$">Trees</a>&nbsp;&nbsp;&nbsp;</th>
1846 >>> #endif
1847
1848 <!-- Index link -->
1849 >>> if context == "indices":
1850 <th bgcolor="#70b0f0" class="navbar-select"
1851 >&nbsp;&nbsp;&nbsp;Indices&nbsp;&nbsp;&nbsp;</th>
1852 >>> else:
1853 <th>&nbsp;&nbsp;&nbsp;<a
1854 href="identifier-index.html">Indices</a>&nbsp;&nbsp;&nbsp;</th>
1855 >>> #endif
1856
1857 <!-- Help link -->
1858 >>> if context == "help":
1859 <th bgcolor="#70b0f0" class="navbar-select"
1860 >&nbsp;&nbsp;&nbsp;Help&nbsp;&nbsp;&nbsp;</th>
1861 >>> else:
1862 <th>&nbsp;&nbsp;&nbsp;<a
1863 href="help.html">Help</a>&nbsp;&nbsp;&nbsp;</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%">&nbsp;</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 >]&nbsp;|&nbsp;<a href="$context_url$"
1926 target="_top">no&nbsp;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&nbsp;%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&nbsp;%s' % doc.canonical_name[0][7:]
1960 return '%s&nbsp;%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 "&nbsp;"$</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)$&nbsp;
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"> &nbsp; </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&nbsp;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 >&nbsp;&nbsp;&nbsp;&nbsp;$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&nbsp;'
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 '&nbsp;'
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
3428class _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