1#
2# epydoc -- HTML output generator
3# Edward Loper
4#
5# Created [01/30/01 05:18 PM]
6# $Id: html.py 1674 2008-01-29 06:03:36Z edloper $
7#
8
9"""
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
3492