| # |
| # epydoc -- HTML output generator |
| # Edward Loper |
| # |
| # Created [01/30/01 05:18 PM] |
| # $Id: html.py 1674 2008-01-29 06:03:36Z edloper $ |
| # |
| |
| """ |
| The HTML output generator for epydoc. The main interface provided by |
| this module is the L{HTMLWriter} class. |
| |
| @todo: Add a cache to L{HTMLWriter.url()}? |
| """ |
| __docformat__ = 'epytext en' |
| |
| import re, os, sys, codecs, sre_constants, pprint, base64 |
| import urllib |
| import __builtin__ |
| from epydoc.apidoc import * |
| import epydoc.docstringparser |
| import time, epydoc, epydoc.markup, epydoc.markup.epytext |
| from epydoc.docwriter.html_colorize import PythonSourceColorizer |
| from epydoc.docwriter import html_colorize |
| from epydoc.docwriter.html_css import STYLESHEETS |
| from epydoc.docwriter.html_help import HTML_HELP |
| from epydoc.docwriter.dotgraph import * |
| from epydoc import log |
| from epydoc.util import plaintext_to_html, is_src_filename |
| from epydoc.compat import * # Backwards compatibility |
| |
| ###################################################################### |
| ## Template Compiler |
| ###################################################################### |
| # The compile_template() method defined in this section is used to |
| # define several of HTMLWriter's methods. |
| |
| def compile_template(docstring, template_string, |
| output_function='out', debug=epydoc.DEBUG): |
| """ |
| Given a template string containing inline python source code, |
| return a python function that will fill in the template, and |
| output the result. The signature for this function is taken from |
| the first line of C{docstring}. Output is generated by making |
| repeated calls to the output function with the given name (which |
| is typically one of the function's parameters). |
| |
| The templating language used by this function passes through all |
| text as-is, with three exceptions: |
| |
| - If every line in the template string is indented by at least |
| M{x} spaces, then the first M{x} spaces are stripped from each |
| line. |
| |
| - Any line that begins with '>>>' (with no indentation) |
| should contain python code, and will be inserted as-is into |
| the template-filling function. If the line begins a control |
| block (such as 'if' or 'for'), then the control block will |
| be closed by the first '>>>'-marked line whose indentation is |
| less than or equal to the line's own indentation (including |
| lines that only contain comments.) |
| |
| - In any other line, any expression between two '$' signs will |
| be evaluated and inserted into the line (using C{str()} to |
| convert the result to a string). |
| |
| Here is a simple example: |
| |
| >>> TEMPLATE = ''' |
| ... <book> |
| ... <title>$book.title$</title> |
| ... <pages>$book.count_pages()$</pages> |
| ... >>> for chapter in book.chapters: |
| ... <chaptername>$chapter.name$</chaptername> |
| ... >>> #endfor |
| ... </book> |
| >>> write_book = compile_template('write_book(out, book)', TEMPLATE) |
| |
| @newfield acknowledgements: Acknowledgements |
| @acknowledgements: The syntax used by C{compile_template} is |
| loosely based on Cheetah. |
| """ |
| # Extract signature from the docstring: |
| signature = docstring.lstrip().split('\n',1)[0].strip() |
| func_name = signature.split('(',1)[0].strip() |
| |
| # Regexp to search for inline substitutions: |
| INLINE = re.compile(r'\$([^\$]+)\$') |
| # Regexp to search for python statements in the template: |
| COMMAND = re.compile(r'(^>>>.*)\n?', re.MULTILINE) |
| |
| # Strip indentation from the template. |
| template_string = strip_indent(template_string) |
| |
| # If we're debugging, then we'll store the generated function, |
| # so we can print it along with any tracebacks that depend on it. |
| if debug: |
| signature = re.sub(r'\)\s*$', ', __debug=__debug)', signature) |
| |
| # Funciton declaration line |
| pysrc_lines = ['def %s:' % signature] |
| indents = [-1] |
| |
| if debug: |
| pysrc_lines.append(' try:') |
| indents.append(-1) |
| |
| commands = COMMAND.split(template_string.strip()+'\n') |
| for i, command in enumerate(commands): |
| if command == '': continue |
| |
| # String literal segment: |
| if i%2 == 0: |
| pieces = INLINE.split(command) |
| for j, piece in enumerate(pieces): |
| if j%2 == 0: |
| # String piece |
| pysrc_lines.append(' '*len(indents)+ |
| '%s(%r)' % (output_function, piece)) |
| else: |
| # Variable piece |
| pysrc_lines.append(' '*len(indents)+ |
| '%s(unicode(%s))' % (output_function, piece)) |
| |
| # Python command: |
| else: |
| srcline = command[3:].lstrip() |
| # Update indentation |
| indent = len(command)-len(srcline) |
| while indent <= indents[-1]: indents.pop() |
| # Add on the line. |
| srcline = srcline.rstrip() |
| pysrc_lines.append(' '*len(indents)+srcline) |
| if srcline.endswith(':'): |
| indents.append(indent) |
| |
| if debug: |
| pysrc_lines.append(' except Exception,e:') |
| pysrc_lines.append(' pysrc, func_name = __debug ') |
| pysrc_lines.append(' lineno = sys.exc_info()[2].tb_lineno') |
| pysrc_lines.append(' print ("Exception in template %s() on "') |
| pysrc_lines.append(' "line %d:" % (func_name, lineno))') |
| pysrc_lines.append(' print pysrc[lineno-1]') |
| pysrc_lines.append(' raise') |
| |
| pysrc = '\n'.join(pysrc_lines)+'\n' |
| #log.debug(pysrc) |
| if debug: localdict = {'__debug': (pysrc_lines, func_name)} |
| else: localdict = {} |
| try: exec pysrc in globals(), localdict |
| except SyntaxError: |
| log.error('Error in script:\n' + pysrc + '\n') |
| raise |
| template_func = localdict[func_name] |
| template_func.__doc__ = docstring |
| return template_func |
| |
| def strip_indent(s): |
| """ |
| Given a multiline string C{s}, find the minimum indentation for |
| all non-blank lines, and return a new string formed by stripping |
| that amount of indentation from all lines in C{s}. |
| """ |
| # Strip indentation from the template. |
| minindent = sys.maxint |
| lines = s.split('\n') |
| for line in lines: |
| stripline = line.lstrip() |
| if stripline: |
| minindent = min(minindent, len(line)-len(stripline)) |
| return '\n'.join([l[minindent:] for l in lines]) |
| |
| ###################################################################### |
| ## HTML Writer |
| ###################################################################### |
| |
| class HTMLWriter: |
| #//////////////////////////////////////////////////////////// |
| # Table of Contents |
| #//////////////////////////////////////////////////////////// |
| # |
| # 1. Interface Methods |
| # |
| # 2. Page Generation -- write complete web page files |
| # 2.1. Module Pages |
| # 2.2. Class Pages |
| # 2.3. Trees Page |
| # 2.4. Indices Page |
| # 2.5. Help Page |
| # 2.6. Frames-based table of contents pages |
| # 2.7. Homepage (index.html) |
| # 2.8. CSS Stylesheet |
| # 2.9. Javascript file |
| # 2.10. Graphs |
| # 2.11. Images |
| # |
| # 3. Page Element Generation -- write pieces of a web page file |
| # 3.1. Page Header |
| # 3.2. Page Footer |
| # 3.3. Navigation Bar |
| # 3.4. Breadcrumbs |
| # 3.5. Summary Tables |
| # |
| # 4. Helper functions |
| |
| def __init__(self, docindex, **kwargs): |
| """ |
| Construct a new HTML writer, using the given documentation |
| index. |
| |
| @param docindex: The documentation index. |
| |
| @type prj_name: C{string} |
| @keyword prj_name: The name of the project. Defaults to |
| none. |
| @type prj_url: C{string} |
| @keyword prj_url: The target for the project hopeage link on |
| the navigation bar. If C{prj_url} is not specified, |
| then no hyperlink is created. |
| @type prj_link: C{string} |
| @keyword prj_link: The label for the project link on the |
| navigation bar. This link can contain arbitrary HTML |
| code (e.g. images). By default, a label is constructed |
| from C{prj_name}. |
| @type top_page: C{string} |
| @keyword top_page: The top page for the documentation. This |
| is the default page shown main frame, when frames are |
| enabled. C{top} can be a URL, the name of a |
| module, the name of a class, or one of the special |
| strings C{"trees.html"}, C{"indices.html"}, or |
| C{"help.html"}. By default, the top-level package or |
| module is used, if there is one; otherwise, C{"trees"} |
| is used. |
| @type css: C{string} |
| @keyword css: The CSS stylesheet file. If C{css} is a file |
| name, then the specified file's conents will be used. |
| Otherwise, if C{css} is the name of a CSS stylesheet in |
| L{epydoc.docwriter.html_css}, then that stylesheet will |
| be used. Otherwise, an error is reported. If no stylesheet |
| is specified, then the default stylesheet is used. |
| @type help_file: C{string} |
| @keyword help_file: The name of the help file. If no help file is |
| specified, then the default help file will be used. |
| @type show_private: C{boolean} |
| @keyword show_private: Whether to create documentation for |
| private objects. By default, private objects are documented. |
| @type show_frames: C{boolean}) |
| @keyword show_frames: Whether to create a frames-based table of |
| contents. By default, it is produced. |
| @type show_imports: C{boolean} |
| @keyword show_imports: Whether or not to display lists of |
| imported functions and classes. By default, they are |
| not shown. |
| @type variable_maxlines: C{int} |
| @keyword variable_maxlines: The maximum number of lines that |
| should be displayed for the value of a variable in the |
| variable details section. By default, 8 lines are |
| displayed. |
| @type variable_linelength: C{int} |
| @keyword variable_linelength: The maximum line length used for |
| displaying the values of variables in the variable |
| details sections. If a line is longer than this length, |
| then it will be wrapped to the next line. The default |
| line length is 70 characters. |
| @type variable_summary_linelength: C{int} |
| @keyword variable_summary_linelength: The maximum line length |
| used for displaying the values of variables in the summary |
| section. If a line is longer than this length, then it |
| will be truncated. The default is 40 characters. |
| @type variable_tooltip_linelength: C{int} |
| @keyword variable_tooltip_linelength: The maximum line length |
| used for tooltips for the values of variables. If a |
| line is longer than this length, then it will be |
| truncated. The default is 600 characters. |
| @type property_function_linelength: C{int} |
| @keyword property_function_linelength: The maximum line length |
| used to dispaly property functions (C{fget}, C{fset}, and |
| C{fdel}) that contain something other than a function |
| object. The default length is 40 characters. |
| @type inheritance: C{string} |
| @keyword inheritance: How inherited objects should be displayed. |
| If C{inheritance='grouped'}, then inherited objects are |
| gathered into groups; if C{inheritance='listed'}, then |
| inherited objects are listed in a short list at the |
| end of their group; if C{inheritance='included'}, then |
| inherited objects are mixed in with non-inherited |
| objects. The default is 'grouped'. |
| @type include_source_code: C{boolean} |
| @keyword include_source_code: If true, then generate colorized |
| source code files for each python module. |
| @type include_log: C{boolean} |
| @keyword include_log: If true, the the footer will include an |
| href to the page 'epydoc-log.html'. |
| @type src_code_tab_width: C{int} |
| @keyword src_code_tab_width: Number of spaces to replace each tab |
| with in source code listings. |
| """ |
| self.docindex = docindex |
| |
| # Process keyword arguments. |
| self._show_private = kwargs.get('show_private', 1) |
| """Should private docs be included?""" |
| |
| self._prj_name = kwargs.get('prj_name', None) |
| """The project's name (for the project link in the navbar)""" |
| |
| self._prj_url = kwargs.get('prj_url', None) |
| """URL for the project link in the navbar""" |
| |
| self._prj_link = kwargs.get('prj_link', None) |
| """HTML code for the project link in the navbar""" |
| |
| self._top_page = kwargs.get('top_page', None) |
| """The 'main' page""" |
| |
| self._css = kwargs.get('css') |
| """CSS stylesheet to use""" |
| |
| self._helpfile = kwargs.get('help_file', None) |
| """Filename of file to extract help contents from""" |
| |
| self._frames_index = kwargs.get('show_frames', 1) |
| """Should a frames index be created?""" |
| |
| self._show_imports = kwargs.get('show_imports', False) |
| """Should imports be listed?""" |
| |
| self._propfunc_linelen = kwargs.get('property_function_linelength', 40) |
| """[XXX] Not used!""" |
| |
| self._variable_maxlines = kwargs.get('variable_maxlines', 8) |
| """Max lines for variable values""" |
| |
| self._variable_linelen = kwargs.get('variable_linelength', 70) |
| """Max line length for variable values""" |
| |
| self._variable_summary_linelen = \ |
| kwargs.get('variable_summary_linelength', 65) |
| """Max length for variable value summaries""" |
| |
| self._variable_tooltip_linelen = \ |
| kwargs.get('variable_tooltip_linelength', 600) |
| """Max length for variable tooltips""" |
| |
| self._inheritance = kwargs.get('inheritance', 'listed') |
| """How should inheritance be displayed? 'listed', 'included', |
| or 'grouped'""" |
| |
| self._incl_sourcecode = kwargs.get('include_source_code', True) |
| """Should pages be generated for source code of modules?""" |
| |
| self._mark_docstrings = kwargs.get('mark_docstrings', False) |
| """Wrap <span class='docstring'>...</span> around docstrings?""" |
| |
| self._graph_types = kwargs.get('graphs', ()) or () |
| """Graphs that we should include in our output.""" |
| |
| self._include_log = kwargs.get('include_log', False) |
| """Are we generating an HTML log page?""" |
| |
| self._src_code_tab_width = kwargs.get('src_code_tab_width', 8) |
| """Number of spaces to replace each tab with in source code |
| listings.""" |
| |
| self._callgraph_cache = {} |
| """Map the callgraph L{uid<DotGraph.uid>} to their HTML |
| representation.""" |
| |
| self._redundant_details = kwargs.get('redundant_details', False) |
| """If true, then include objects in the details list even if all |
| info about them is already provided by the summary table.""" |
| |
| # For use with select_variables(): |
| if self._show_private: |
| self._public_filter = None |
| else: |
| self._public_filter = True |
| |
| # Make sure inheritance has a sane value. |
| if self._inheritance not in ('listed', 'included', 'grouped'): |
| raise ValueError, 'Bad value for inheritance' |
| |
| # Create the project homepage link, if it was not specified. |
| if (self._prj_name or self._prj_url) and not self._prj_link: |
| self._prj_link = plaintext_to_html(self._prj_name or |
| 'Project Homepage') |
| |
| # Add a hyperlink to _prj_url, if _prj_link doesn't already |
| # contain any hyperlinks. |
| if (self._prj_link and self._prj_url and |
| not re.search(r'<a[^>]*\shref', self._prj_link)): |
| self._prj_link = ('<a class="navbar" target="_top" href="'+ |
| self._prj_url+'">'+self._prj_link+'</a>') |
| |
| # Precompute lists & sets of APIDoc objects that we're |
| # interested in. |
| self.valdocs = valdocs = sorted(docindex.reachable_valdocs( |
| imports=False, packages=False, bases=False, submodules=False, |
| subclasses=False, private=self._show_private)) |
| self.module_list = [d for d in valdocs if isinstance(d, ModuleDoc)] |
| """The list of L{ModuleDoc}s for the documented modules.""" |
| self.module_set = set(self.module_list) |
| """The set of L{ModuleDoc}s for the documented modules.""" |
| self.class_list = [d for d in valdocs if isinstance(d, ClassDoc)] |
| """The list of L{ClassDoc}s for the documented classes.""" |
| self.class_set = set(self.class_list) |
| """The set of L{ClassDoc}s for the documented classes.""" |
| self.routine_list = [d for d in valdocs if isinstance(d, RoutineDoc)] |
| """The list of L{RoutineDoc}s for the documented routines.""" |
| self.indexed_docs = [] |
| """The list of L{APIDoc}s for variables and values that should |
| be included in the index.""" |
| |
| # URL for 'trees' page |
| if self.module_list: self._trees_url = 'module-tree.html' |
| else: self._trees_url = 'class-tree.html' |
| |
| # Construct the value for self.indexed_docs. |
| self.indexed_docs += [d for d in valdocs |
| if not isinstance(d, GenericValueDoc)] |
| for doc in valdocs: |
| if isinstance(doc, NamespaceDoc): |
| # add any vars with generic values; but don't include |
| # inherited vars. |
| self.indexed_docs += [d for d in doc.variables.values() if |
| isinstance(d.value, GenericValueDoc) |
| and d.container == doc] |
| self.indexed_docs.sort() |
| |
| # Figure out the url for the top page. |
| self._top_page_url = self._find_top_page(self._top_page) |
| |
| # Decide whether or not to split the identifier index. |
| self._split_ident_index = (len(self.indexed_docs) >= |
| self.SPLIT_IDENT_INDEX_SIZE) |
| |
| # Figure out how many output files there will be (for progress |
| # reporting). |
| self.modules_with_sourcecode = set() |
| for doc in self.module_list: |
| if isinstance(doc, ModuleDoc) and is_src_filename(doc.filename): |
| self.modules_with_sourcecode.add(doc) |
| self._num_files = (len(self.class_list) + len(self.module_list) + |
| 10 + len(self.METADATA_INDICES)) |
| if self._frames_index: |
| self._num_files += len(self.module_list) + 3 |
| |
| if self._incl_sourcecode: |
| self._num_files += len(self.modules_with_sourcecode) |
| if self._split_ident_index: |
| self._num_files += len(self.LETTERS) |
| |
| def _find_top_page(self, pagename): |
| """ |
| Find the top page for the API documentation. This page is |
| used as the default page shown in the main frame, when frames |
| are used. When frames are not used, this page is copied to |
| C{index.html}. |
| |
| @param pagename: The name of the page, as specified by the |
| keyword argument C{top} to the constructor. |
| @type pagename: C{string} |
| @return: The URL of the top page. |
| @rtype: C{string} |
| """ |
| # If a page name was specified, then we need to figure out |
| # what it points to. |
| if pagename: |
| # If it's a URL, then use it directly. |
| if pagename.lower().startswith('http:'): |
| return pagename |
| |
| # If it's an object, then use that object's page. |
| try: |
| doc = self.docindex.get_valdoc(pagename) |
| return self.url(doc) |
| except: |
| pass |
| |
| # Otherwise, give up. |
| log.warning('Could not find top page %r; using %s ' |
| 'instead' % (pagename, self._trees_url)) |
| return self._trees_url |
| |
| # If no page name was specified, then try to choose one |
| # automatically. |
| else: |
| root = [val_doc for val_doc in self.docindex.root |
| if isinstance(val_doc, (ClassDoc, ModuleDoc))] |
| if len(root) == 0: |
| # No docs?? Try the trees page. |
| return self._trees_url |
| elif len(root) == 1: |
| # One item in the root; use that. |
| return self.url(root[0]) |
| else: |
| # Multiple root items; if they're all in one package, |
| # then use that. Otherwise, use self._trees_url |
| root = sorted(root, key=lambda v:len(v.canonical_name)) |
| top = root[0] |
| for doc in root[1:]: |
| if not top.canonical_name.dominates(doc.canonical_name): |
| return self._trees_url |
| else: |
| return self.url(top) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 1. Interface Methods |
| #//////////////////////////////////////////////////////////// |
| |
| def write(self, directory=None): |
| """ |
| Write the documentation to the given directory. |
| |
| @type directory: C{string} |
| @param directory: The directory to which output should be |
| written. If no directory is specified, output will be |
| written to the current directory. If the directory does |
| not exist, it will be created. |
| @rtype: C{None} |
| @raise OSError: If C{directory} cannot be created. |
| @raise OSError: If any file cannot be created or written to. |
| """ |
| # For progress reporting: |
| self._files_written = 0. |
| |
| # Set the default values for ValueDoc formatted representations. |
| orig_valdoc_defaults = (ValueDoc.SUMMARY_REPR_LINELEN, |
| ValueDoc.REPR_LINELEN, |
| ValueDoc.REPR_MAXLINES) |
| ValueDoc.SUMMARY_REPR_LINELEN = self._variable_summary_linelen |
| ValueDoc.REPR_LINELEN = self._variable_linelen |
| ValueDoc.REPR_MAXLINES = self._variable_maxlines |
| |
| # Use an image for the crarr symbol. |
| from epydoc.markup.epytext import ParsedEpytextDocstring |
| orig_crarr_html = ParsedEpytextDocstring.SYMBOL_TO_HTML['crarr'] |
| ParsedEpytextDocstring.SYMBOL_TO_HTML['crarr'] = ( |
| r'<span class="variable-linewrap">' |
| r'<img src="crarr.png" alt="\" /></span>') |
| |
| # Keep track of failed xrefs, and report them at the end. |
| self._failed_xrefs = {} |
| |
| # Create destination directories, if necessary |
| if not directory: directory = os.curdir |
| self._mkdir(directory) |
| self._directory = directory |
| |
| # Write the CSS file. |
| self._files_written += 1 |
| log.progress(self._files_written/self._num_files, 'epydoc.css') |
| self.write_css(directory, self._css) |
| |
| # Write the Javascript file. |
| self._files_written += 1 |
| log.progress(self._files_written/self._num_files, 'epydoc.js') |
| self.write_javascript(directory) |
| |
| # Write images |
| self.write_images(directory) |
| |
| # Build the indices. |
| indices = {'ident': self.build_identifier_index(), |
| 'term': self.build_term_index()} |
| for (name, label, label2) in self.METADATA_INDICES: |
| indices[name] = self.build_metadata_index(name) |
| |
| # Write the identifier index. If requested, split it into |
| # separate pages for each letter. |
| ident_by_letter = self._group_by_letter(indices['ident']) |
| if not self._split_ident_index: |
| self._write(self.write_link_index, directory, |
| 'identifier-index.html', indices, |
| 'Identifier Index', 'identifier-index.html', |
| ident_by_letter) |
| else: |
| # Write a page for each section. |
| for letter in self.LETTERS: |
| filename = 'identifier-index-%s.html' % letter |
| self._write(self.write_link_index, directory, filename, |
| indices, 'Identifier Index', filename, |
| ident_by_letter, [letter], |
| 'identifier-index-%s.html') |
| # Use the first non-empty section as the main index page. |
| for letter in self.LETTERS: |
| if letter in ident_by_letter: |
| filename = 'identifier-index.html' |
| self._write(self.write_link_index, directory, filename, |
| indices, 'Identifier Index', filename, |
| ident_by_letter, [letter], |
| 'identifier-index-%s.html') |
| break |
| |
| # Write the term index. |
| if indices['term']: |
| term_by_letter = self._group_by_letter(indices['term']) |
| self._write(self.write_link_index, directory, 'term-index.html', |
| indices, 'Term Definition Index', |
| 'term-index.html', term_by_letter) |
| else: |
| self._files_written += 1 # (skipped) |
| |
| # Write the metadata indices. |
| for (name, label, label2) in self.METADATA_INDICES: |
| if indices[name]: |
| self._write(self.write_metadata_index, directory, |
| '%s-index.html' % name, indices, name, |
| label, label2) |
| else: |
| self._files_written += 1 # (skipped) |
| |
| # Write the trees file (package & class hierarchies) |
| if self.module_list: |
| self._write(self.write_module_tree, directory, 'module-tree.html') |
| else: |
| self._files_written += 1 # (skipped) |
| if self.class_list: |
| self._write(self.write_class_tree, directory, 'class-tree.html') |
| else: |
| self._files_written += 1 # (skipped) |
| |
| # Write the help file. |
| self._write(self.write_help, directory,'help.html') |
| |
| # Write the frames-based table of contents. |
| if self._frames_index: |
| self._write(self.write_frames_index, directory, 'frames.html') |
| self._write(self.write_toc, directory, 'toc.html') |
| self._write(self.write_project_toc, directory, 'toc-everything.html') |
| for doc in self.module_list: |
| filename = 'toc-%s' % urllib.unquote(self.url(doc)) |
| self._write(self.write_module_toc, directory, filename, doc) |
| |
| # Write the object documentation. |
| for doc in self.module_list: |
| filename = urllib.unquote(self.url(doc)) |
| self._write(self.write_module, directory, filename, doc) |
| for doc in self.class_list: |
| filename = urllib.unquote(self.url(doc)) |
| self._write(self.write_class, directory, filename, doc) |
| |
| # Write source code files. |
| if self._incl_sourcecode: |
| # Build a map from short names to APIDocs, used when |
| # linking names in the source code. |
| name_to_docs = {} |
| for api_doc in self.indexed_docs: |
| if (api_doc.canonical_name is not None and |
| self.url(api_doc) is not None): |
| name = api_doc.canonical_name[-1] |
| name_to_docs.setdefault(name, []).append(api_doc) |
| # Sort each entry of the name_to_docs list. |
| for doc_list in name_to_docs.values(): |
| doc_list.sort() |
| # Write the source code for each module. |
| for doc in self.modules_with_sourcecode: |
| filename = urllib.unquote(self.pysrc_url(doc)) |
| self._write(self.write_sourcecode, directory, filename, doc, |
| name_to_docs) |
| |
| # Write the auto-redirect page. |
| self._write(self.write_redirect_page, directory, 'redirect.html') |
| |
| # Write the mapping object name -> URL |
| self._write(self.write_api_list, directory, 'api-objects.txt') |
| |
| # Write the index.html files. |
| # (this must be done last, since it might copy another file) |
| self._files_written += 1 |
| log.progress(self._files_written/self._num_files, 'index.html') |
| self.write_homepage(directory) |
| |
| # Don't report references to builtins as missing |
| for k in self._failed_xrefs.keys(): # have a copy of keys |
| if hasattr(__builtin__, k): |
| del self._failed_xrefs[k] |
| |
| # Report any failed crossreferences |
| if self._failed_xrefs: |
| estr = 'Failed identifier crossreference targets:\n' |
| failed_identifiers = self._failed_xrefs.keys() |
| failed_identifiers.sort() |
| for identifier in failed_identifiers: |
| names = self._failed_xrefs[identifier].keys() |
| names.sort() |
| estr += '- %s' % identifier |
| estr += '\n' |
| for name in names: |
| estr += ' (from %s)\n' % name |
| log.docstring_warning(estr) |
| |
| # [xx] testing: |
| if self._num_files != int(self._files_written): |
| log.debug("Expected to write %d files, but actually " |
| "wrote %d files" % |
| (self._num_files, int(self._files_written))) |
| |
| # Restore defaults that we changed. |
| (ValueDoc.SUMMARY_REPR_LINELEN, ValueDoc.REPR_LINELEN, |
| ValueDoc.REPR_MAXLINES) = orig_valdoc_defaults |
| ParsedEpytextDocstring.SYMBOL_TO_HTML['crarr'] = orig_crarr_html |
| |
| def _write(self, write_func, directory, filename, *args): |
| # Display our progress. |
| self._files_written += 1 |
| log.progress(self._files_written/self._num_files, filename) |
| |
| path = os.path.join(directory, filename) |
| f = codecs.open(path, 'w', 'ascii', errors='xmlcharrefreplace') |
| write_func(f.write, *args) |
| f.close() |
| |
| def _mkdir(self, directory): |
| """ |
| If the given directory does not exist, then attempt to create it. |
| @rtype: C{None} |
| """ |
| if not os.path.isdir(directory): |
| if os.path.exists(directory): |
| raise OSError('%r is not a directory' % directory) |
| os.mkdir(directory) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 2.1. Module Pages |
| #//////////////////////////////////////////////////////////// |
| |
| def write_module(self, out, doc): |
| """ |
| Write an HTML page containing the API documentation for the |
| given module to C{out}. |
| |
| @param doc: A L{ModuleDoc} containing the API documentation |
| for the module that should be described. |
| """ |
| longname = doc.canonical_name |
| shortname = doc.canonical_name[-1] |
| |
| # Write the page header (incl. navigation bar & breadcrumbs) |
| self.write_header(out, str(longname)) |
| self.write_navbar(out, doc) |
| self.write_breadcrumbs(out, doc, self.url(doc)) |
| |
| # Write the name of the module we're describing. |
| if doc.is_package is True: typ = 'Package' |
| else: typ = 'Module' |
| if longname[0].startswith('script-'): |
| shortname = str(longname)[7:] |
| typ = 'Script' |
| out('<!-- ==================== %s ' % typ.upper() + |
| 'DESCRIPTION ==================== -->\n') |
| out('<h1 class="epydoc">%s %s</h1>' % (typ, shortname)) |
| out('<p class="nomargin-top">%s</p>\n' % self.pysrc_link(doc)) |
| |
| # If the module has a description, then list it. |
| if doc.descr not in (None, UNKNOWN): |
| out(self.descr(doc, 2)+'\n\n') |
| |
| # Write any standarad metadata (todo, author, etc.) |
| if doc.metadata is not UNKNOWN and doc.metadata: |
| out('<hr />\n') |
| self.write_standard_fields(out, doc) |
| |
| # If it's a package, then list the modules it contains. |
| if doc.is_package is True: |
| self.write_module_list(out, doc) |
| |
| # Write summary tables describing the variables that the |
| # module defines. |
| self.write_summary_table(out, "Classes", doc, "class") |
| self.write_summary_table(out, "Functions", doc, "function") |
| self.write_summary_table(out, "Variables", doc, "other") |
| |
| # Write a list of all imported objects. |
| if self._show_imports: |
| self.write_imports(out, doc) |
| |
| # Write detailed descriptions of functions & variables defined |
| # in this module. |
| self.write_details_list(out, "Function Details", doc, "function") |
| self.write_details_list(out, "Variables Details", doc, "other") |
| |
| # Write the page footer (including navigation bar) |
| self.write_navbar(out, doc) |
| self.write_footer(out) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 2.??. Source Code Pages |
| #//////////////////////////////////////////////////////////// |
| |
| def write_sourcecode(self, out, doc, name_to_docs): |
| #t0 = time.time() |
| |
| filename = doc.filename |
| name = str(doc.canonical_name) |
| |
| # Header |
| self.write_header(out, name) |
| self.write_navbar(out, doc) |
| self.write_breadcrumbs(out, doc, self.pysrc_url(doc)) |
| |
| # Source code listing |
| out('<h1 class="epydoc">Source Code for %s</h1>\n' % |
| self.href(doc, label='%s %s' % (self.doc_kind(doc), name))) |
| out('<pre class="py-src">\n') |
| out(PythonSourceColorizer(filename, name, self.docindex, |
| self.url, name_to_docs, |
| self._src_code_tab_width).colorize()) |
| out('</pre>\n<br />\n') |
| |
| # Footer |
| self.write_navbar(out, doc) |
| self.write_footer(out) |
| |
| #log.debug('[%6.2f sec] Wrote pysrc for %s' % |
| # (time.time()-t0, name)) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 2.2. Class Pages |
| #//////////////////////////////////////////////////////////// |
| |
| def write_class(self, out, doc): |
| """ |
| Write an HTML page containing the API documentation for the |
| given class to C{out}. |
| |
| @param doc: A L{ClassDoc} containing the API documentation |
| for the class that should be described. |
| """ |
| longname = doc.canonical_name |
| shortname = doc.canonical_name[-1] |
| |
| # Write the page header (incl. navigation bar & breadcrumbs) |
| self.write_header(out, str(longname)) |
| self.write_navbar(out, doc) |
| self.write_breadcrumbs(out, doc, self.url(doc)) |
| |
| # Write the name of the class we're describing. |
| if doc.is_type(): typ = 'Type' |
| elif doc.is_exception(): typ = 'Exception' |
| else: typ = 'Class' |
| out('<!-- ==================== %s ' % typ.upper() + |
| 'DESCRIPTION ==================== -->\n') |
| out('<h1 class="epydoc">%s %s</h1>' % (typ, shortname)) |
| out('<p class="nomargin-top">%s</p>\n' % self.pysrc_link(doc)) |
| |
| if ((doc.bases not in (UNKNOWN, None) and len(doc.bases) > 0) or |
| (doc.subclasses not in (UNKNOWN,None) and len(doc.subclasses)>0)): |
| # Display bases graphically, if requested. |
| if 'umlclasstree' in self._graph_types: |
| self.write_class_tree_graph(out, doc, uml_class_tree_graph) |
| elif 'classtree' in self._graph_types: |
| self.write_class_tree_graph(out, doc, class_tree_graph) |
| |
| # Otherwise, use ascii-art. |
| else: |
| # Write the base class tree. |
| if doc.bases not in (UNKNOWN, None) and len(doc.bases) > 0: |
| out('<pre class="base-tree">\n%s</pre>\n\n' % |
| self.base_tree(doc)) |
| |
| # Write the known subclasses |
| if (doc.subclasses not in (UNKNOWN, None) and |
| len(doc.subclasses) > 0): |
| out('<dl><dt>Known Subclasses:</dt>\n<dd>\n ') |
| out(' <ul class="subclass-list">\n') |
| for i, subclass in enumerate(doc.subclasses): |
| href = self.href(subclass, context=doc) |
| if self._val_is_public(subclass): css = '' |
| else: css = ' class="private"' |
| if i > 0: href = ', '+href |
| out('<li%s>%s</li>' % (css, href)) |
| out(' </ul>\n') |
| out('</dd></dl>\n\n') |
| |
| out('<hr />\n') |
| |
| # If the class has a description, then list it. |
| if doc.descr not in (None, UNKNOWN): |
| out(self.descr(doc, 2)+'\n\n') |
| |
| # Write any standarad metadata (todo, author, etc.) |
| if doc.metadata is not UNKNOWN and doc.metadata: |
| out('<hr />\n') |
| self.write_standard_fields(out, doc) |
| |
| # Write summary tables describing the variables that the |
| # class defines. |
| self.write_summary_table(out, "Nested Classes", doc, "class") |
| self.write_summary_table(out, "Instance Methods", doc, |
| "instancemethod") |
| self.write_summary_table(out, "Class Methods", doc, "classmethod") |
| self.write_summary_table(out, "Static Methods", doc, "staticmethod") |
| self.write_summary_table(out, "Class Variables", doc, |
| "classvariable") |
| self.write_summary_table(out, "Instance Variables", doc, |
| "instancevariable") |
| self.write_summary_table(out, "Properties", doc, "property") |
| |
| # Write a list of all imported objects. |
| if self._show_imports: |
| self.write_imports(out, doc) |
| |
| # Write detailed descriptions of functions & variables defined |
| # in this class. |
| # [xx] why group methods into one section but split vars into two? |
| # seems like we should either group in both cases or split in both |
| # cases. |
| self.write_details_list(out, "Method Details", doc, "method") |
| self.write_details_list(out, "Class Variable Details", doc, |
| "classvariable") |
| self.write_details_list(out, "Instance Variable Details", doc, |
| "instancevariable") |
| self.write_details_list(out, "Property Details", doc, "property") |
| |
| # Write the page footer (including navigation bar) |
| self.write_navbar(out, doc) |
| self.write_footer(out) |
| |
| def write_class_tree_graph(self, out, doc, graphmaker): |
| """ |
| Write HTML code for a class tree graph of C{doc} (a classdoc), |
| using C{graphmaker} to draw the actual graph. C{graphmaker} |
| should be L{class_tree_graph()}, or L{uml_class_tree_graph()}, |
| or any other function with a compatible signature. |
| |
| If the given class has any private sublcasses (including |
| recursive subclasses), then two graph images will be generated |
| -- one to display when private values are shown, and the other |
| to display when private values are hidden. |
| """ |
| linker = _HTMLDocstringLinker(self, doc) |
| private_subcls = self._private_subclasses(doc) |
| if private_subcls: |
| out('<center>\n' |
| ' <div class="private">%s</div>\n' |
| ' <div class="public" style="display:none">%s</div>\n' |
| '</center>\n' % |
| (self.render_graph(graphmaker(doc, linker, doc)), |
| self.render_graph(graphmaker(doc, linker, doc, |
| exclude=private_subcls)))) |
| else: |
| out('<center>\n%s\n</center>\n' % |
| self.render_graph(graphmaker(doc, linker, doc))) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 2.3. Trees pages |
| #//////////////////////////////////////////////////////////// |
| |
| def write_module_tree(self, out): |
| # Header material |
| self.write_treepage_header(out, 'Module Hierarchy', 'module-tree.html') |
| out('<h1 class="epydoc">Module Hierarchy</h1>\n') |
| |
| # Write entries for all top-level modules/packages. |
| out('<ul class="nomargin-top">\n') |
| for doc in self.module_list: |
| if (doc.package in (None, UNKNOWN) or |
| doc.package not in self.module_set): |
| self.write_module_tree_item(out, doc) |
| out('</ul>\n') |
| |
| # Footer material |
| self.write_navbar(out, 'trees') |
| self.write_footer(out) |
| |
| def write_class_tree(self, out): |
| """ |
| Write HTML code for a nested list showing the base/subclass |
| relationships between all documented classes. Each element of |
| the top-level list is a class with no (documented) bases; and |
| under each class is listed all of its subclasses. Note that |
| in the case of multiple inheritance, a class may appear |
| multiple times. |
| |
| @todo: For multiple inheritance, don't repeat subclasses the |
| second time a class is mentioned; instead, link to the |
| first mention. |
| """ |
| # [XX] backref for multiple inheritance? |
| # Header material |
| self.write_treepage_header(out, 'Class Hierarchy', 'class-tree.html') |
| out('<h1 class="epydoc">Class Hierarchy</h1>\n') |
| |
| # Build a set containing all classes that we should list. |
| # This includes everything in class_list, plus any of those |
| # class' bases, but not undocumented subclasses. |
| class_set = self.class_set.copy() |
| for doc in self.class_list: |
| if doc.bases != UNKNOWN: |
| for base in doc.bases: |
| if base not in class_set: |
| if isinstance(base, ClassDoc): |
| class_set.update(base.mro()) |
| else: |
| # [XX] need to deal with this -- how? |
| pass |
| #class_set.add(base) |
| |
| out('<ul class="nomargin-top">\n') |
| for doc in sorted(class_set, key=lambda c:c.canonical_name[-1]): |
| if doc.bases != UNKNOWN and len(doc.bases)==0: |
| self.write_class_tree_item(out, doc, class_set) |
| out('</ul>\n') |
| |
| # Footer material |
| self.write_navbar(out, 'trees') |
| self.write_footer(out) |
| |
| def write_treepage_header(self, out, title, url): |
| # Header material. |
| self.write_header(out, title) |
| self.write_navbar(out, 'trees') |
| self.write_breadcrumbs(out, 'trees', url) |
| if self.class_list and self.module_list: |
| out('<center><b>\n') |
| out(' [ <a href="module-tree.html">Module Hierarchy</a>\n') |
| out(' | <a href="class-tree.html">Class Hierarchy</a> ]\n') |
| out('</b></center><br />\n') |
| |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 2.4. Index pages |
| #//////////////////////////////////////////////////////////// |
| |
| SPLIT_IDENT_INDEX_SIZE = 3000 |
| """If the identifier index has more than this number of entries, |
| then it will be split into separate pages, one for each |
| alphabetical section.""" |
| |
| LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_' |
| """The alphabetical sections that are used for link index pages.""" |
| |
| def write_link_index(self, out, indices, title, url, index_by_section, |
| sections=LETTERS, section_url='#%s'): |
| |
| # Header |
| self.write_indexpage_header(out, indices, title, url) |
| |
| # Index title & links to alphabetical sections. |
| out('<table border="0" width="100%">\n' |
| '<tr valign="bottom"><td>\n') |
| out('<h1 class="epydoc">%s</h1>\n</td><td>\n[\n' % title) |
| for sec in self.LETTERS: |
| if sec in index_by_section: |
| out(' <a href="%s">%s</a>\n' % (section_url % sec, sec)) |
| else: |
| out(' %s\n' % sec) |
| out(']\n') |
| out('</td></table>\n') |
| |
| # Alphabetical sections. |
| sections = [s for s in sections if s in index_by_section] |
| if sections: |
| out('<table border="0" width="100%">\n') |
| for section in sorted(sections): |
| out('<tr valign="top"><td valign="top" width="1%">') |
| out('<h2 class="epydoc"><a name="%s">%s</a></h2></td>\n' % |
| (section, section)) |
| out('<td valign="top">\n') |
| self.write_index_section(out, index_by_section[section], True) |
| out('</td></tr>\n') |
| out('</table>\n<br />') |
| |
| # Footer material. |
| out('<br />') |
| self.write_navbar(out, 'indices') |
| self.write_footer(out) |
| |
| |
| def write_metadata_index(self, out, indices, field, title, typ): |
| """ |
| Write an HTML page containing a metadata index. |
| """ |
| index = indices[field] |
| |
| # Header material. |
| self.write_indexpage_header(out, indices, title, |
| '%s-index.html' % field) |
| |
| # Page title. |
| out('<h1 class="epydoc"><a name="%s">%s</a></h1>\n<br />\n' % |
| (field, title)) |
| |
| # Index (one section per arg) |
| for arg in sorted(index): |
| # Write a section title. |
| if arg is not None: |
| if len([1 for (doc, descrs) in index[arg] if |
| not self._doc_or_ancestor_is_private(doc)]) == 0: |
| out('<div class="private">') |
| else: |
| out('<div>') |
| self.write_table_header(out, 'metadata-index', arg) |
| out('</table>') |
| # List every descr for this arg. |
| for (doc, descrs) in index[arg]: |
| if self._doc_or_ancestor_is_private(doc): |
| out('<div class="private">\n') |
| else: |
| out('<div>\n') |
| out('<table width="100%" class="metadata-index" ' |
| 'bgcolor="#e0e0e0"><tr><td class="metadata-index">') |
| out('<b>%s in %s</b>' % |
| (typ, self.href(doc, label=doc.canonical_name))) |
| out(' <ul class="nomargin">\n') |
| for descr in descrs: |
| out(' <li>%s</li>\n' % |
| self.docstring_to_html(descr,doc,4)) |
| out(' </ul>\n') |
| out('</table></div>\n') |
| |
| # Footer material. |
| out('<br />') |
| self.write_navbar(out, 'indices') |
| self.write_footer(out) |
| |
| def write_indexpage_header(self, out, indices, title, url): |
| """ |
| A helper for the index page generation functions, which |
| generates a header that can be used to navigate between the |
| different indices. |
| """ |
| self.write_header(out, title) |
| self.write_navbar(out, 'indices') |
| self.write_breadcrumbs(out, 'indices', url) |
| |
| if (indices['term'] or |
| [1 for (name,l,l2) in self.METADATA_INDICES if indices[name]]): |
| out('<center><b>[\n') |
| out(' <a href="identifier-index.html">Identifiers</a>\n') |
| if indices['term']: |
| out('| <a href="term-index.html">Term Definitions</a>\n') |
| for (name, label, label2) in self.METADATA_INDICES: |
| if indices[name]: |
| out('| <a href="%s-index.html">%s</a>\n' % |
| (name, label2)) |
| out(']</b></center><br />\n') |
| |
| def write_index_section(self, out, items, add_blankline=False): |
| out('<table class="link-index" width="100%" border="1">\n') |
| num_rows = (len(items)+2)/3 |
| for row in range(num_rows): |
| out('<tr>\n') |
| for col in range(3): |
| out('<td width="33%" class="link-index">') |
| i = col*num_rows+row |
| if i < len(items): |
| name, url, container = items[col*num_rows+row] |
| out('<a href="%s">%s</a>' % (url, name)) |
| if container is not None: |
| out('<br />\n') |
| if isinstance(container, ModuleDoc): |
| label = container.canonical_name |
| else: |
| label = container.canonical_name[-1] |
| out('<span class="index-where">(in %s)' |
| '</span>' % self.href(container, label)) |
| else: |
| out(' ') |
| out('</td>\n') |
| out('</tr>\n') |
| if add_blankline and num_rows == 1: |
| blank_cell = '<td class="link-index"> </td>' |
| out('<tr>'+3*blank_cell+'</tr>\n') |
| out('</table>\n') |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 2.5. Help Page |
| #//////////////////////////////////////////////////////////// |
| |
| def write_help(self, out): |
| """ |
| Write an HTML help file to the given stream. If |
| C{self._helpfile} contains a help file, then use it; |
| otherwise, use the default helpfile from |
| L{epydoc.docwriter.html_help}. |
| """ |
| # todo: optionally parse .rst etc help files? |
| |
| # Get the contents of the help file. |
| if self._helpfile: |
| if os.path.exists(self._helpfile): |
| try: help = open(self._helpfile).read() |
| except: raise IOError("Can't open help file: %r" % |
| self._helpfile) |
| else: |
| raise IOError("Can't find help file: %r" % self._helpfile) |
| else: |
| if self._prj_name: thisprj = self._prj_name |
| else: thisprj = 'this project' |
| help = HTML_HELP % {'this_project':thisprj} |
| |
| # Insert the help contents into a webpage. |
| self.write_header(out, 'Help') |
| self.write_navbar(out, 'help') |
| self.write_breadcrumbs(out, 'help', 'help.html') |
| out(help) |
| self.write_navbar(out, 'help') |
| self.write_footer(out) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 2.6. Frames-based Table of Contents |
| #//////////////////////////////////////////////////////////// |
| |
| write_frames_index = compile_template( |
| """ |
| write_frames_index(self, out) |
| |
| Write the frames index file for the frames-based table of |
| contents to the given streams. |
| """, |
| # /------------------------- Template -------------------------\ |
| ''' |
| <?xml version="1.0" encoding="iso-8859-1"?> |
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" |
| "DTD/xhtml1-frameset.dtd"> |
| <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> |
| <head> |
| <title> $self._prj_name or "API Documentation"$ </title> |
| </head> |
| <frameset cols="20%,80%"> |
| <frameset rows="30%,70%"> |
| <frame src="toc.html" name="moduleListFrame" |
| id="moduleListFrame" /> |
| <frame src="toc-everything.html" name="moduleFrame" |
| id="moduleFrame" /> |
| </frameset> |
| <frame src="$self._top_page_url$" name="mainFrame" id="mainFrame" /> |
| </frameset> |
| </html> |
| ''') |
| # \------------------------------------------------------------/ |
| |
| write_toc = compile_template( |
| """ |
| write_toc(self, out) |
| """, |
| # /------------------------- Template -------------------------\ |
| ''' |
| >>> self.write_header(out, "Table of Contents") |
| <h1 class="toc">Table of Contents</h1> |
| <hr /> |
| <a target="moduleFrame" href="toc-everything.html">Everything</a> |
| <br /> |
| >>> self.write_toc_section(out, "Modules", self.module_list) |
| <hr /> |
| >>> if self._show_private: |
| $self.PRIVATE_LINK$ |
| >>> #endif |
| >>> self.write_footer(out, short=True) |
| ''') |
| # \------------------------------------------------------------/ |
| |
| def write_toc_section(self, out, name, docs, fullname=True): |
| if not docs: return |
| |
| # Assign names to each item, and sort by name. |
| if fullname: |
| docs = [(str(d.canonical_name), d) for d in docs] |
| else: |
| docs = [(str(d.canonical_name[-1]), d) for d in docs] |
| docs.sort() |
| |
| out(' <h2 class="toc">%s</h2>\n' % name) |
| for label, doc in docs: |
| doc_url = self.url(doc) |
| toc_url = 'toc-%s' % doc_url |
| is_private = self._doc_or_ancestor_is_private(doc) |
| if is_private: |
| if not self._show_private: continue |
| out(' <div class="private">\n') |
| |
| if isinstance(doc, ModuleDoc): |
| out(' <a target="moduleFrame" href="%s"\n' |
| ' onclick="setFrame(\'%s\',\'%s\');"' |
| ' >%s</a><br />' % (toc_url, toc_url, doc_url, label)) |
| else: |
| out(' <a target="mainFrame" href="%s"\n' |
| ' >%s</a><br />' % (doc_url, label)) |
| if is_private: |
| out(' </div>\n') |
| |
| def write_project_toc(self, out): |
| self.write_header(out, "Everything") |
| out('<h1 class="toc">Everything</h1>\n') |
| out('<hr />\n') |
| |
| # List the classes. |
| self.write_toc_section(out, "All Classes", self.class_list) |
| |
| # List the functions. |
| funcs = [d for d in self.routine_list |
| if not isinstance(self.docindex.container(d), |
| (ClassDoc, types.NoneType))] |
| self.write_toc_section(out, "All Functions", funcs) |
| |
| # List the variables. |
| vars = [] |
| for doc in self.module_list: |
| vars += doc.select_variables(value_type='other', |
| imported=False, |
| public=self._public_filter) |
| self.write_toc_section(out, "All Variables", vars) |
| |
| # Footer material. |
| out('<hr />\n') |
| if self._show_private: |
| out(self.PRIVATE_LINK+'\n') |
| self.write_footer(out, short=True) |
| |
| def write_module_toc(self, out, doc): |
| """ |
| Write an HTML page containing the table of contents page for |
| the given module to the given streams. This page lists the |
| modules, classes, exceptions, functions, and variables defined |
| by the module. |
| """ |
| name = doc.canonical_name[-1] |
| self.write_header(out, name) |
| out('<h1 class="toc">Module %s</h1>\n' % name) |
| out('<hr />\n') |
| |
| |
| # List the classes. |
| classes = doc.select_variables(value_type='class', imported=False, |
| public=self._public_filter) |
| self.write_toc_section(out, "Classes", classes, fullname=False) |
| |
| # List the functions. |
| funcs = doc.select_variables(value_type='function', imported=False, |
| public=self._public_filter) |
| self.write_toc_section(out, "Functions", funcs, fullname=False) |
| |
| # List the variables. |
| variables = doc.select_variables(value_type='other', imported=False, |
| public=self._public_filter) |
| self.write_toc_section(out, "Variables", variables, fullname=False) |
| |
| # Footer material. |
| out('<hr />\n') |
| if self._show_private: |
| out(self.PRIVATE_LINK+'\n') |
| self.write_footer(out, short=True) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 2.7. Project homepage (index.html) |
| #//////////////////////////////////////////////////////////// |
| |
| def write_homepage(self, directory): |
| """ |
| Write an C{index.html} file in the given directory. The |
| contents of this file are copied or linked from an existing |
| page, so this method must be called after all pages have been |
| written. The page used is determined by L{_frames_index} and |
| L{_top_page}: |
| - If L{_frames_index} is true, then C{frames.html} is |
| copied. |
| - Otherwise, the page specified by L{_top_page} is |
| copied. |
| """ |
| filename = os.path.join(directory, 'index.html') |
| if self._frames_index: top = 'frames.html' |
| else: top = self._top_page_url |
| |
| # Copy the non-frames index file from top, if it's internal. |
| if top[:5] != 'http:' and '/' not in top: |
| try: |
| # Read top into `s`. |
| topfile = os.path.join(directory, top) |
| s = open(topfile, 'r').read() |
| |
| # Write the output file. |
| open(filename, 'w').write(s) |
| return |
| except: |
| log.error('Warning: error copying index; ' |
| 'using a redirect page') |
| |
| # Use a redirect if top is external, or if we faild to copy. |
| name = self._prj_name or 'this project' |
| f = open(filename, 'w') |
| self.write_redirect_index(f.write, top, name) |
| f.close() |
| |
| write_redirect_index = compile_template( |
| """ |
| write_redirect_index(self, out, top, name) |
| """, |
| # /------------------------- Template -------------------------\ |
| ''' |
| <?xml version="1.0" encoding="iso-8859-1"?> |
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" |
| "DTD/xhtml1-strict.dtd"> |
| <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> |
| <head> |
| <title> Redirect </title> |
| <meta http-equiv="refresh" content="1;url=$top$" /> |
| <link rel="stylesheet" href="epydoc.css" type="text/css"></link> |
| </head> |
| <body> |
| <p>Redirecting to the API documentation for |
| <a href="$top$">$self._prj_name or "this project"$</a>...</p> |
| </body> |
| </html> |
| ''') |
| # \------------------------------------------------------------/ |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 2.8. Stylesheet (epydoc.css) |
| #//////////////////////////////////////////////////////////// |
| |
| def write_css(self, directory, cssname): |
| """ |
| Write the CSS stylesheet in the given directory. If |
| C{cssname} contains a stylesheet file or name (from |
| L{epydoc.docwriter.html_css}), then use that stylesheet; |
| otherwise, use the default stylesheet. |
| |
| @rtype: C{None} |
| """ |
| filename = os.path.join(directory, 'epydoc.css') |
| |
| # Get the contents for the stylesheet file. |
| if cssname is None: |
| css = STYLESHEETS['default'][0] |
| else: |
| if os.path.exists(cssname): |
| try: css = open(cssname).read() |
| except: raise IOError("Can't open CSS file: %r" % cssname) |
| elif cssname in STYLESHEETS: |
| css = STYLESHEETS[cssname][0] |
| else: |
| raise IOError("Can't find CSS file: %r" % cssname) |
| |
| # Write the stylesheet. |
| cssfile = open(filename, 'w') |
| cssfile.write(css) |
| cssfile.close() |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 2.9. Javascript (epydoc.js) |
| #//////////////////////////////////////////////////////////// |
| |
| def write_javascript(self, directory): |
| jsfile = open(os.path.join(directory, 'epydoc.js'), 'w') |
| print >> jsfile, self.TOGGLE_PRIVATE_JS |
| print >> jsfile, self.SHOW_PRIVATE_JS |
| print >> jsfile, self.GET_COOKIE_JS |
| print >> jsfile, self.SET_FRAME_JS |
| print >> jsfile, self.HIDE_PRIVATE_JS |
| print >> jsfile, self.TOGGLE_CALLGRAPH_JS |
| print >> jsfile, html_colorize.PYSRC_JAVASCRIPTS |
| print >> jsfile, self.GET_ANCHOR_JS |
| print >> jsfile, self.REDIRECT_URL_JS |
| jsfile.close() |
| |
| #: A javascript that is used to show or hide the API documentation |
| #: for private objects. In order for this to work correctly, all |
| #: documentation for private objects should be enclosed in |
| #: C{<div class="private">...</div>} elements. |
| TOGGLE_PRIVATE_JS = ''' |
| function toggle_private() { |
| // Search for any private/public links on this page. Store |
| // their old text in "cmd," so we will know what action to |
| // take; and change their text to the opposite action. |
| var cmd = "?"; |
| var elts = document.getElementsByTagName("a"); |
| for(var i=0; i<elts.length; i++) { |
| if (elts[i].className == "privatelink") { |
| cmd = elts[i].innerHTML; |
| elts[i].innerHTML = ((cmd && cmd.substr(0,4)=="show")? |
| "hide private":"show private"); |
| } |
| } |
| // Update all DIVs containing private objects. |
| var elts = document.getElementsByTagName("div"); |
| for(var i=0; i<elts.length; i++) { |
| if (elts[i].className == "private") { |
| elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"none":"block"); |
| } |
| else if (elts[i].className == "public") { |
| elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"block":"none"); |
| } |
| } |
| // Update all table rows containing private objects. Note, we |
| // use "" instead of "block" becaue IE & firefox disagree on what |
| // this should be (block vs table-row), and "" just gives the |
| // default for both browsers. |
| var elts = document.getElementsByTagName("tr"); |
| for(var i=0; i<elts.length; i++) { |
| if (elts[i].className == "private") { |
| elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"none":""); |
| } |
| } |
| // Update all list items containing private objects. |
| var elts = document.getElementsByTagName("li"); |
| for(var i=0; i<elts.length; i++) { |
| if (elts[i].className == "private") { |
| elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")? |
| "none":""); |
| } |
| } |
| // Update all list items containing private objects. |
| var elts = document.getElementsByTagName("ul"); |
| for(var i=0; i<elts.length; i++) { |
| if (elts[i].className == "private") { |
| elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"none":"block"); |
| } |
| } |
| // Set a cookie to remember the current option. |
| document.cookie = "EpydocPrivate="+cmd; |
| } |
| '''.strip() |
| |
| #: A javascript that is used to read the value of a cookie. This |
| #: is used to remember whether private variables should be shown or |
| #: hidden. |
| GET_COOKIE_JS = ''' |
| function getCookie(name) { |
| var dc = document.cookie; |
| var prefix = name + "="; |
| var begin = dc.indexOf("; " + prefix); |
| if (begin == -1) { |
| begin = dc.indexOf(prefix); |
| if (begin != 0) return null; |
| } else |
| { begin += 2; } |
| var end = document.cookie.indexOf(";", begin); |
| if (end == -1) |
| { end = dc.length; } |
| return unescape(dc.substring(begin + prefix.length, end)); |
| } |
| '''.strip() |
| |
| #: A javascript that is used to set the contents of two frames at |
| #: once. This is used by the project table-of-contents frame to |
| #: set both the module table-of-contents frame and the main frame |
| #: when the user clicks on a module. |
| SET_FRAME_JS = ''' |
| function setFrame(url1, url2) { |
| parent.frames[1].location.href = url1; |
| parent.frames[2].location.href = url2; |
| } |
| '''.strip() |
| |
| #: A javascript that is used to hide private variables, unless |
| #: either: (a) the cookie says not to; or (b) we appear to be |
| #: linking to a private variable. |
| HIDE_PRIVATE_JS = ''' |
| function checkCookie() { |
| var cmd=getCookie("EpydocPrivate"); |
| if (cmd && cmd.substr(0,4)!="show" && location.href.indexOf("#_") < 0) |
| toggle_private(); |
| } |
| '''.strip() |
| |
| TOGGLE_CALLGRAPH_JS = ''' |
| function toggleCallGraph(id) { |
| var elt = document.getElementById(id); |
| if (elt.style.display == "none") |
| elt.style.display = "block"; |
| else |
| elt.style.display = "none"; |
| } |
| '''.strip() |
| |
| SHOW_PRIVATE_JS = ''' |
| function show_private() { |
| var elts = document.getElementsByTagName("a"); |
| for(var i=0; i<elts.length; i++) { |
| if (elts[i].className == "privatelink") { |
| cmd = elts[i].innerHTML; |
| if (cmd && cmd.substr(0,4)=="show") |
| toggle_private(); |
| } |
| } |
| } |
| '''.strip() |
| |
| GET_ANCHOR_JS = ''' |
| function get_anchor() { |
| var href = location.href; |
| var start = href.indexOf("#")+1; |
| if ((start != 0) && (start != href.length)) |
| return href.substring(start, href.length); |
| } |
| '''.strip() |
| |
| #: A javascript that is used to implement the auto-redirect page. |
| #: When the user visits <redirect.html#dotted.name>, they will |
| #: automatically get redirected to the page for the object with |
| #: the given fully-qualified dotted name. E.g., for epydoc, |
| #: <redirect.html#epydoc.apidoc.UNKNOWN> redirects the user to |
| #: <epydoc.apidoc-module.html#UNKNOWN>. |
| REDIRECT_URL_JS = ''' |
| function redirect_url(dottedName) { |
| // Scan through each element of the "pages" list, and check |
| // if "name" matches with any of them. |
| for (var i=0; i<pages.length; i++) { |
| |
| // Each page has the form "<pagename>-m" or "<pagename>-c"; |
| // extract the <pagename> portion & compare it to dottedName. |
| var pagename = pages[i].substring(0, pages[i].length-2); |
| if (pagename == dottedName.substring(0,pagename.length)) { |
| |
| // We\'ve found a page that matches `dottedName`; |
| // construct its URL, using leftover `dottedName` |
| // content to form an anchor. |
| var pagetype = pages[i].charAt(pages[i].length-1); |
| var url = pagename + ((pagetype=="m")?"-module.html": |
| "-class.html"); |
| if (dottedName.length > pagename.length) |
| url += "#" + dottedName.substring(pagename.length+1, |
| dottedName.length); |
| return url; |
| } |
| } |
| } |
| '''.strip() |
| |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 2.10. Graphs |
| #//////////////////////////////////////////////////////////// |
| |
| def render_graph(self, graph): |
| if graph is None: return '' |
| graph.caption = graph.title = None |
| image_url = '%s.gif' % graph.uid |
| image_file = os.path.join(self._directory, image_url) |
| return graph.to_html(image_file, image_url) |
| |
| RE_CALLGRAPH_ID = re.compile(r"""["'](.+-div)['"]""") |
| |
| def render_callgraph(self, callgraph, token=""): |
| """Render the HTML chunk of a callgraph. |
| |
| If C{callgraph} is a string, use the L{_callgraph_cache} to return |
| a pre-rendered HTML chunk. This mostly avoids to run C{dot} twice for |
| the same callgraph. Else, run the graph and store its HTML output in |
| the cache. |
| |
| @param callgraph: The graph to render or its L{uid<DotGraph.uid>}. |
| @type callgraph: L{DotGraph} or C{str} |
| @param token: A string that can be used to make the C{<div>} id |
| unambiguous, if the callgraph is used more than once in a page. |
| @type token: C{str} |
| @return: The HTML representation of the graph. |
| @rtype: C{str} |
| """ |
| if callgraph is None: return "" |
| |
| if isinstance(callgraph, basestring): |
| uid = callgraph |
| rv = self._callgraph_cache.get(callgraph, "") |
| |
| else: |
| uid = callgraph.uid |
| graph_html = self.render_graph(callgraph) |
| if graph_html == '': |
| rv = "" |
| else: |
| rv = ('<div style="display:none" id="%%s-div"><center>\n' |
| '<table border="0" cellpadding="0" cellspacing="0">\n' |
| ' <tr><td>%s</td></tr>\n' |
| ' <tr><th>Call Graph</th></tr>\n' |
| '</table><br />\n</center></div>\n' % graph_html) |
| |
| # Store in the cache the complete HTML chunk without the |
| # div id, which may be made unambiguous by the token |
| self._callgraph_cache[uid] = rv |
| |
| # Mangle with the graph |
| if rv: rv = rv % (uid + token) |
| return rv |
| |
| def callgraph_link(self, callgraph, token=""): |
| """Render the HTML chunk of a callgraph link. |
| |
| The link can toggles the visibility of the callgraph rendered using |
| L{render_callgraph} with matching parameters. |
| |
| @param callgraph: The graph to render or its L{uid<DotGraph.uid>}. |
| @type callgraph: L{DotGraph} or C{str} |
| @param token: A string that can be used to make the C{<div>} id |
| unambiguous, if the callgraph is used more than once in a page. |
| @type token: C{str} |
| @return: The HTML representation of the graph link. |
| @rtype: C{str} |
| """ |
| # Use class=codelink, to match style w/ the source code link. |
| if callgraph is None: return '' |
| |
| if isinstance(callgraph, basestring): |
| uid = callgraph |
| else: |
| uid = callgraph.uid |
| |
| return ('<br /><span class="codelink"><a href="javascript:void(0);" ' |
| 'onclick="toggleCallGraph(\'%s-div\');return false;">' |
| 'call graph</a></span> ' % (uid + token)) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 2.11. Images |
| #//////////////////////////////////////////////////////////// |
| |
| IMAGES = {'crarr.png': # Carriage-return arrow, used for LINEWRAP. |
| 'iVBORw0KGgoAAAANSUhEUgAAABEAAAAKCAMAAABlokWQAAAALHRFWHRD' |
| 'cmVhdGlvbiBUaW1lAFR1\nZSAyMiBBdWcgMjAwNiAwMDo0MzoxMCAtMD' |
| 'UwMGAMEFgAAAAHdElNRQfWCBYFASkQ033WAAAACXBI\nWXMAAB7CAAAe' |
| 'wgFu0HU+AAAABGdBTUEAALGPC/xhBQAAAEVQTFRF////zcOw18/AgGY0' |
| 'c1cg4dvQ\ninJEYEAAYkME3NXI6eTcloFYe2Asr5+AbE4Uh29A9fPwqp' |
| 'l4ZEUI8O3onopk0Ma0lH5U1nfFdgAA\nAAF0Uk5TAEDm2GYAAABNSURB' |
| 'VHjaY2BAAbzsvDAmK5oIlxgfioiwCAe7KJKIgKAQOzsLLwTwA0VY\n+d' |
| 'iRAT8T0AxuIIMHqoaXCWIPGzsHJ6orGJiYWRjQASOcBQAocgMSPKMTIg' |
| 'AAAABJRU5ErkJggg==\n', |
| } |
| |
| def write_images(self, directory): |
| for (name, data) in self.IMAGES.items(): |
| f = open(os.path.join(directory, name), 'wb') |
| f.write(base64.decodestring(data)) |
| f.close() |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 3.1. Page Header |
| #//////////////////////////////////////////////////////////// |
| |
| write_header = compile_template( |
| """ |
| write_header(self, out, title) |
| |
| Generate HTML code for the standard page header, and write it |
| to C{out}. C{title} is a string containing the page title. |
| It should be appropriately escaped/encoded. |
| """, |
| # /------------------------- Template -------------------------\ |
| ''' |
| <?xml version="1.0" encoding="ascii"?> |
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" |
| "DTD/xhtml1-transitional.dtd"> |
| <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> |
| <head> |
| <title>$title$</title> |
| <link rel="stylesheet" href="epydoc.css" type="text/css" /> |
| <script type="text/javascript" src="epydoc.js"></script> |
| </head> |
| |
| <body bgcolor="white" text="black" link="blue" vlink="#204080" |
| alink="#204080"> |
| ''') |
| # \------------------------------------------------------------/ |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 3.2. Page Footer |
| #//////////////////////////////////////////////////////////// |
| |
| write_footer = compile_template( |
| """ |
| write_footer(self, out, short=False) |
| |
| Generate HTML code for the standard page footer, and write it |
| to C{out}. |
| """, |
| # /------------------------- Template -------------------------\ |
| ''' |
| >>> if not short: |
| <table border="0" cellpadding="0" cellspacing="0" width="100%%"> |
| <tr> |
| <td align="left" class="footer"> |
| >>> if self._include_log: |
| <a href="epydoc-log.html">Generated by Epydoc |
| $epydoc.__version__$ on $time.asctime()$</a> |
| >>> else: |
| Generated by Epydoc $epydoc.__version__$ on $time.asctime()$ |
| >>> #endif |
| </td> |
| <td align="right" class="footer"> |
| <a target="mainFrame" href="http://epydoc.sourceforge.net" |
| >http://epydoc.sourceforge.net</a> |
| </td> |
| </tr> |
| </table> |
| >>> #endif |
| |
| <script type="text/javascript"> |
| <!-- |
| // Private objects are initially displayed (because if |
| // javascript is turned off then we want them to be |
| // visible); but by default, we want to hide them. So hide |
| // them unless we have a cookie that says to show them. |
| checkCookie(); |
| // --> |
| </script> |
| </body> |
| </html> |
| ''') |
| # \------------------------------------------------------------/ |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 3.3. Navigation Bar |
| #//////////////////////////////////////////////////////////// |
| |
| write_navbar = compile_template( |
| """ |
| write_navbar(self, out, context) |
| |
| Generate HTML code for the navigation bar, and write it to |
| C{out}. The navigation bar typically looks like:: |
| |
| [ Home Trees Index Help Project ] |
| |
| @param context: A value indicating what page we're generating |
| a navigation bar for. If we're generating an API |
| documentation page for an object, then C{context} is a |
| L{ValueDoc} containing the documentation for that object; |
| otherwise, C{context} is a string name for the page. The |
| following string names are recognized: C{'tree'}, C{'index'}, |
| and C{'help'}. |
| """, |
| # /------------------------- Template -------------------------\ |
| ''' |
| <!-- ==================== NAVIGATION BAR ==================== --> |
| <table class="navbar" border="0" width="100%" cellpadding="0" |
| bgcolor="#a0c0ff" cellspacing="0"> |
| <tr valign="middle"> |
| >>> if self._top_page_url not in (self._trees_url, "identifier-index.html", "help.html"): |
| <!-- Home link --> |
| >>> if (isinstance(context, ValueDoc) and |
| >>> self._top_page_url == self.url(context.canonical_name)): |
| <th bgcolor="#70b0f0" class="navbar-select" |
| > Home </th> |
| >>> else: |
| <th> <a |
| href="$self._top_page_url$">Home</a> </th> |
| >>> #endif |
| |
| <!-- Tree link --> |
| >>> if context == "trees": |
| <th bgcolor="#70b0f0" class="navbar-select" |
| > Trees </th> |
| >>> else: |
| <th> <a |
| href="$self._trees_url$">Trees</a> </th> |
| >>> #endif |
| |
| <!-- Index link --> |
| >>> if context == "indices": |
| <th bgcolor="#70b0f0" class="navbar-select" |
| > Indices </th> |
| >>> else: |
| <th> <a |
| href="identifier-index.html">Indices</a> </th> |
| >>> #endif |
| |
| <!-- Help link --> |
| >>> if context == "help": |
| <th bgcolor="#70b0f0" class="navbar-select" |
| > Help </th> |
| >>> else: |
| <th> <a |
| href="help.html">Help</a> </th> |
| >>> #endif |
| |
| >>> if self._prj_link: |
| <!-- Project homepage --> |
| <th class="navbar" align="right" width="100%"> |
| <table border="0" cellpadding="0" cellspacing="0"> |
| <tr><th class="navbar" align="center" |
| >$self._prj_link.strip()$</th> |
| </tr></table></th> |
| >>> else: |
| <th class="navbar" width="100%"></th> |
| >>> #endif |
| </tr> |
| </table> |
| ''') |
| # \------------------------------------------------------------/ |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 3.4. Breadcrumbs |
| #//////////////////////////////////////////////////////////// |
| |
| write_breadcrumbs = compile_template( |
| """ |
| write_breadcrumbs(self, out, context, context_url) |
| |
| Generate HTML for the breadcrumbs line, and write it to |
| C{out}. The breadcrumbs line is an invisible table with a |
| list of pointers to the current object's ancestors on the |
| left; and the show/hide private selector and the |
| frames/noframes selector on the right. |
| |
| @param context: The API documentation for the object whose |
| breadcrumbs we should generate. |
| @type context: L{ValueDoc} |
| """, |
| # /------------------------- Template -------------------------\ |
| ''' |
| <table width="100%" cellpadding="0" cellspacing="0"> |
| <tr valign="top"> |
| >>> if isinstance(context, APIDoc): |
| <td width="100%"> |
| <span class="breadcrumbs"> |
| >>> crumbs = self.breadcrumbs(context) |
| >>> for crumb in crumbs[:-1]: |
| $crumb$ :: |
| >>> #endfor |
| $crumbs[-1]$ |
| </span> |
| </td> |
| >>> else: |
| <td width="100%"> </td> |
| >>> #endif |
| <td> |
| <table cellpadding="0" cellspacing="0"> |
| <!-- hide/show private --> |
| >>> if self._show_private: |
| <tr><td align="right">$self.PRIVATE_LINK$</td></tr> |
| >>> #endif |
| >>> if self._frames_index: |
| <tr><td align="right"><span class="options" |
| >[<a href="frames.html" target="_top">frames</a |
| >] | <a href="$context_url$" |
| target="_top">no frames</a>]</span></td></tr> |
| >>> #endif |
| </table> |
| </td> |
| </tr> |
| </table> |
| ''') |
| # \------------------------------------------------------------/ |
| |
| def breadcrumbs(self, doc): |
| crumbs = [self._crumb(doc)] |
| |
| # Generate the crumbs for uid's ancestors. |
| while True: |
| container = self.docindex.container(doc) |
| assert doc != container, 'object is its own container?' |
| if container is None: |
| if doc.canonical_name is UNKNOWN: |
| return ['??']+crumbs |
| elif isinstance(doc, ModuleDoc): |
| return ['Package %s' % ident |
| for ident in doc.canonical_name[:-1]]+crumbs |
| else: |
| return list(doc.canonical_name)+crumbs |
| else: |
| label = self._crumb(container) |
| name = container.canonical_name |
| crumbs.insert(0, self.href(container, label)) # [xx] code=0?? |
| doc = container |
| |
| def _crumb(self, doc): |
| if (len(doc.canonical_name)==1 and |
| doc.canonical_name[0].startswith('script-')): |
| return 'Script %s' % doc.canonical_name[0][7:] |
| return '%s %s' % (self.doc_kind(doc), doc.canonical_name[-1]) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 3.5. Summary Tables |
| #//////////////////////////////////////////////////////////// |
| |
| def write_summary_table(self, out, heading, doc, value_type): |
| """ |
| Generate HTML code for a summary table, and write it to |
| C{out}. A summary table is a table that includes a one-row |
| description for each variable (of a given type) in a module |
| or class. |
| |
| @param heading: The heading for the summary table; typically, |
| this indicates what kind of value the table describes |
| (e.g., functions or classes). |
| @param doc: A L{ValueDoc} object containing the API |
| documentation for the module or class whose variables |
| we should summarize. |
| @param value_type: A string indicating what type of value |
| should be listed in this summary table. This value |
| is passed on to C{doc}'s C{select_variables()} method. |
| """ |
| # inh_var_groups is a dictionary used to hold "inheritance |
| # pseudo-groups", which are created when inheritance is |
| # 'grouped'. It maps each base to a list of vars inherited |
| # from that base. |
| grouped_inh_vars = {} |
| |
| # Divide all public variables of the given type into groups. |
| groups = [(plaintext_to_html(group_name), |
| doc.select_variables(group=group_name, imported=False, |
| value_type=value_type, |
| public=self._public_filter)) |
| for group_name in doc.group_names()] |
| |
| # Discard any empty groups; and return if they're all empty. |
| groups = [(g,vars) for (g,vars) in groups if vars] |
| if not groups: return |
| |
| # Write a header |
| self.write_table_header(out, "summary", heading) |
| |
| # Write a section for each group. |
| for name, var_docs in groups: |
| self.write_summary_group(out, doc, name, |
| var_docs, grouped_inh_vars) |
| |
| # Write a section for each inheritance pseudo-group (used if |
| # inheritance=='grouped') |
| if grouped_inh_vars: |
| for base in doc.mro(): |
| if base in grouped_inh_vars: |
| hdr = 'Inherited from %s' % self.href(base, context=doc) |
| tr_class = '' |
| if len([v for v in grouped_inh_vars[base] |
| if v.is_public]) == 0: |
| tr_class = ' class="private"' |
| self.write_group_header(out, hdr, tr_class) |
| for var_doc in grouped_inh_vars[base]: |
| self.write_summary_line(out, var_doc, doc) |
| |
| # Write a footer for the table. |
| out(self.TABLE_FOOTER) |
| |
| def write_summary_group(self, out, doc, name, var_docs, grouped_inh_vars): |
| # Split up the var_docs list, according to the way each var |
| # should be displayed: |
| # - listed_inh_vars -- for listed inherited variables. |
| # - grouped_inh_vars -- for grouped inherited variables. |
| # - normal_vars -- for all other variables. |
| listed_inh_vars = {} |
| normal_vars = [] |
| for var_doc in var_docs: |
| if var_doc.container != doc: |
| base = var_doc.container |
| if not isinstance(base, ClassDoc): |
| # This *should* never happen: |
| log.warning("%s's container is not a class!" % var_doc) |
| normal_vars.append(var_doc) |
| elif (base not in self.class_set or |
| self._inheritance == 'listed'): |
| listed_inh_vars.setdefault(base,[]).append(var_doc) |
| elif self._inheritance == 'grouped': |
| grouped_inh_vars.setdefault(base,[]).append(var_doc) |
| else: |
| normal_vars.append(var_doc) |
| else: |
| normal_vars.append(var_doc) |
| |
| # Write a header for the group. |
| if name != '': |
| tr_class = '' |
| if len([v for v in var_docs if v.is_public]) == 0: |
| tr_class = ' class="private"' |
| self.write_group_header(out, name, tr_class) |
| |
| # Write a line for each normal var: |
| for var_doc in normal_vars: |
| self.write_summary_line(out, var_doc, doc) |
| # Write a subsection for inherited vars: |
| if listed_inh_vars: |
| self.write_inheritance_list(out, doc, listed_inh_vars) |
| |
| def write_inheritance_list(self, out, doc, listed_inh_vars): |
| out(' <tr>\n <td colspan="2" class="summary">\n') |
| for base in doc.mro(): |
| if base not in listed_inh_vars: continue |
| public_vars = [v for v in listed_inh_vars[base] |
| if v.is_public] |
| private_vars = [v for v in listed_inh_vars[base] |
| if not v.is_public] |
| if public_vars: |
| out(' <p class="indent-wrapped-lines">' |
| '<b>Inherited from <code>%s</code></b>:\n' % |
| self.href(base, context=doc)) |
| self.write_var_list(out, public_vars) |
| out(' </p>\n') |
| if private_vars and self._show_private: |
| out(' <div class="private">') |
| out(' <p class="indent-wrapped-lines">' |
| '<b>Inherited from <code>%s</code></b> (private):\n' % |
| self.href(base, context=doc)) |
| self.write_var_list(out, private_vars) |
| out(' </p></div>\n') |
| out(' </td>\n </tr>\n') |
| |
| def write_var_list(self, out, vardocs): |
| out(' ') |
| out(',\n '.join(['<code>%s</code>' % self.href(v,v.name) |
| for v in vardocs])+'\n') |
| |
| def write_summary_line(self, out, var_doc, container): |
| """ |
| Generate HTML code for a single line of a summary table, and |
| write it to C{out}. See L{write_summary_table} for more |
| information. |
| |
| @param var_doc: The API documentation for the variable that |
| should be described by this line of the summary table. |
| @param container: The API documentation for the class or |
| module whose summary table we're writing. |
| """ |
| pysrc_link = None |
| callgraph = None |
| |
| # If it's a private variable, then mark its <tr>. |
| if var_doc.is_public: tr_class = '' |
| else: tr_class = ' class="private"' |
| |
| # Decide an anchor or a link is to be generated. |
| link_name = self._redundant_details or var_doc.is_detailed() |
| anchor = not link_name |
| |
| # Construct the HTML code for the type (cell 1) & description |
| # (cell 2). |
| if isinstance(var_doc.value, RoutineDoc): |
| typ = self.return_type(var_doc, indent=6) |
| description = self.function_signature(var_doc, is_summary=True, |
| link_name=link_name, anchor=anchor) |
| pysrc_link = self.pysrc_link(var_doc.value) |
| |
| # Perpare the call-graph, if requested |
| if 'callgraph' in self._graph_types: |
| linker = _HTMLDocstringLinker(self, var_doc.value) |
| callgraph = call_graph([var_doc.value], self.docindex, |
| linker, var_doc, add_callers=True, |
| add_callees=True) |
| if callgraph and callgraph.nodes: |
| var_doc.value.callgraph_uid = callgraph.uid |
| else: |
| callgraph = None |
| else: |
| typ = self.type_descr(var_doc, indent=6) |
| description = self.summary_name(var_doc, |
| link_name=link_name, anchor=anchor) |
| if isinstance(var_doc.value, GenericValueDoc): |
| # The summary max length has been chosen setting |
| # L{ValueDoc.SUMMARY_REPR_LINELEN} in the constructor |
| max_len=self._variable_summary_linelen-3-len(var_doc.name) |
| val_repr = var_doc.value.summary_pyval_repr(max_len) |
| tooltip = self.variable_tooltip(var_doc) |
| description += (' = <code%s>%s</code>' % |
| (tooltip, val_repr.to_html(None))) |
| |
| # Add the summary to the description (if there is one). |
| summary = self.summary(var_doc, indent=6) |
| if summary: description += '<br />\n %s' % summary |
| |
| # If it's inherited, then add a note to the description. |
| if var_doc.container != container and self._inheritance=="included": |
| description += ("\n <em>(Inherited from " + |
| self.href(var_doc.container) + ")</em>") |
| |
| # Write the summary line. |
| self._write_summary_line(out, typ, description, tr_class, pysrc_link, |
| callgraph) |
| |
| _write_summary_line = compile_template( |
| "_write_summary_line(self, out, typ, description, tr_class, " |
| "pysrc_link, callgraph)", |
| # /------------------------- Template -------------------------\ |
| ''' |
| <tr$tr_class$> |
| <td width="15%" align="right" valign="top" class="summary"> |
| <span class="summary-type">$typ or " "$</span> |
| </td><td class="summary"> |
| >>> if pysrc_link is not None or callgraph is not None: |
| <table width="100%" cellpadding="0" cellspacing="0" border="0"> |
| <tr> |
| <td>$description$</td> |
| <td align="right" valign="top"> |
| $pysrc_link$ |
| $self.callgraph_link(callgraph, token='-summary')$ |
| </td> |
| </tr> |
| </table> |
| $self.render_callgraph(callgraph, token='-summary')$ |
| >>> #endif |
| >>> if pysrc_link is None and callgraph is None: |
| $description$ |
| >>> #endif |
| </td> |
| </tr> |
| ''') |
| # \------------------------------------------------------------/ |
| |
| #//////////////////////////////////////////////////////////// |
| #{ 3.6. Details Lists |
| #//////////////////////////////////////////////////////////// |
| |
| def write_details_list(self, out, heading, doc, value_type): |
| # Get a list of the VarDocs we should describe. |
| if self._redundant_details: |
| detailed = None |
| else: |
| detailed = True |
| if isinstance(doc, ClassDoc): |
| var_docs = doc.select_variables(value_type=value_type, |
| imported=False, inherited=False, |
| public=self._public_filter, |
| detailed=detailed) |
| else: |
| var_docs = doc.select_variables(value_type=value_type, |
| imported=False, |
| public=self._public_filter, |
| detailed=detailed) |
| if not var_docs: return |
| |
| # Write a header |
| self.write_table_header(out, "details", heading) |
| out(self.TABLE_FOOTER) |
| |
| for var_doc in var_docs: |
| self.write_details_entry(out, var_doc) |
| |
| out('<br />\n') |
| |
| def write_details_entry(self, out, var_doc): |
| descr = self.descr(var_doc, indent=2) or '' |
| if var_doc.is_public: div_class = '' |
| else: div_class = ' class="private"' |
| |
| # Functions |
| if isinstance(var_doc.value, RoutineDoc): |
| rtype = self.return_type(var_doc, indent=10) |
| rdescr = self.return_descr(var_doc, indent=10) |
| arg_descrs = [] |
| args = set() |
| # Find the description for each arg. (Leave them in the |
| # same order that they're listed in the docstring.) |
| for (arg_names, arg_descr) in var_doc.value.arg_descrs: |
| args.update(arg_names) |
| lhs = ', '.join([self.arg_name_to_html(var_doc.value, n) |
| for n in arg_names]) |
| rhs = self.docstring_to_html(arg_descr, var_doc.value, 10) |
| arg_descrs.append( (lhs, rhs) ) |
| # Check for arguments for which we have @type but not @param; |
| # and add them to the arg_descrs list. |
| for arg in var_doc.value.arg_types: |
| if arg not in args: |
| argname = self.arg_name_to_html(var_doc.value, arg) |
| arg_descrs.append( (argname,'') ) |
| |
| self.write_function_details_entry(out, var_doc, descr, |
| var_doc.value.callgraph_uid, |
| rtype, rdescr, arg_descrs, |
| div_class) |
| |
| # Properties |
| elif isinstance(var_doc.value, PropertyDoc): |
| prop_doc = var_doc.value |
| accessors = [ (name, |
| self.property_accessor_to_html(val_doc, prop_doc), |
| self.summary(val_doc)) |
| for (name, val_doc) in |
| [('Get', prop_doc.fget), ('Set', prop_doc.fset), |
| ('Delete', prop_doc.fdel)] |
| if val_doc not in (None, UNKNOWN) |
| and val_doc.pyval is not None ] |
| |
| self.write_property_details_entry(out, var_doc, descr, |
| accessors, div_class) |
| |
| # Variables |
| else: |
| self.write_variable_details_entry(out, var_doc, descr, div_class) |
| |
| def labelled_list_item(self, lhs, rhs): |
| # If the RHS starts with a paragraph, then move the |
| # paragraph-start tag to the beginning of the lhs instead (so |
| # there won't be a line break after the '-'). |
| m = re.match(r'^<p( [^>]+)?>', rhs) |
| if m: |
| lhs = m.group() + lhs |
| rhs = rhs[m.end():] |
| |
| if rhs: |
| return '<li>%s - %s</li>' % (lhs, rhs) |
| else: |
| return '<li>%s</li>' % (lhs,) |
| |
| def property_accessor_to_html(self, val_doc, context=None): |
| if val_doc not in (None, UNKNOWN): |
| if isinstance(val_doc, RoutineDoc): |
| return self.function_signature(val_doc, is_summary=True, |
| link_name=True, context=context) |
| elif isinstance(val_doc, GenericValueDoc): |
| return self.pprint_value(val_doc) |
| else: |
| return self.href(val_doc, context=context) |
| else: |
| return '??' |
| |
| def arg_name_to_html(self, func_doc, arg_name): |
| """ |
| A helper function used to format an argument name, for use in |
| the argument description list under a routine's details entry. |
| This just wraps strong & code tags around the arg name; and if |
| the arg name is associated with a type, then adds it |
| parenthetically after the name. |
| """ |
| s = '<strong class="pname"><code>%s</code></strong>' % arg_name |
| if arg_name in func_doc.arg_types: |
| typ = func_doc.arg_types[arg_name] |
| typ_html = self.docstring_to_html(typ, func_doc, 10) |
| s += " (%s)" % typ_html |
| return s |
| |
| write_function_details_entry = compile_template( |
| ''' |
| write_function_details_entry(self, out, var_doc, descr, callgraph, \ |
| rtype, rdescr, arg_descrs, div_class) |
| ''', |
| # /------------------------- Template -------------------------\ |
| ''' |
| >>> func_doc = var_doc.value |
| <a name="$var_doc.name$"></a> |
| <div$div_class$> |
| >>> self.write_table_header(out, "details") |
| <tr><td> |
| <table width="100%" cellpadding="0" cellspacing="0" border="0"> |
| <tr valign="top"><td> |
| <h3 class="epydoc">$self.function_signature(var_doc)$ |
| >>> if var_doc.name in self.SPECIAL_METHODS: |
| <br /><em class="fname">($self.SPECIAL_METHODS[var_doc.name]$)</em> |
| >>> #endif |
| >>> if isinstance(func_doc, ClassMethodDoc): |
| <br /><em class="fname">Class Method</em> |
| >>> #endif |
| >>> if isinstance(func_doc, StaticMethodDoc): |
| <br /><em class="fname">Static Method</em> |
| >>> #endif |
| </h3> |
| </td><td align="right" valign="top" |
| >$self.pysrc_link(func_doc)$ |
| $self.callgraph_link(callgraph)$</td> |
| </tr></table> |
| $self.render_callgraph(callgraph)$ |
| $descr$ |
| <dl class="fields"> |
| >>> # === parameters === |
| >>> if arg_descrs: |
| <dt>Parameters:</dt> |
| <dd><ul class="nomargin-top"> |
| >>> for lhs, rhs in arg_descrs: |
| $self.labelled_list_item(lhs, rhs)$ |
| >>> #endfor |
| </ul></dd> |
| >>> #endif |
| >>> # === return type === |
| >>> if rdescr and rtype: |
| <dt>Returns: $rtype$</dt> |
| <dd>$rdescr$</dd> |
| >>> elif rdescr: |
| <dt>Returns:</dt> |
| <dd>$rdescr$</dd> |
| >>> elif rtype: |
| <dt>Returns: $rtype$</dt> |
| >>> #endif |
| >>> # === decorators === |
| >>> if func_doc.decorators not in (None, UNKNOWN): |
| >>> # (staticmethod & classmethod are already shown, above) |
| >>> decos = filter(lambda deco: |
| >>> not ((deco=="staticmethod" and |
| >>> isinstance(func_doc, StaticMethodDoc)) or |
| >>> (deco=="classmethod" and |
| >>> isinstance(func_doc, ClassMethodDoc))), |
| >>> func_doc.decorators) |
| >>> else: |
| >>> decos = None |
| >>> #endif |
| >>> if decos: |
| <dt>Decorators:</dt> |
| <dd><ul class="nomargin-top"> |
| >>> for deco in decos: |
| <li><code>@$deco$</code></li> |
| >>> #endfor |
| </ul></dd> |
| >>> #endif |
| >>> # === exceptions === |
| >>> if func_doc.exception_descrs not in (None, UNKNOWN, (), []): |
| <dt>Raises:</dt> |
| <dd><ul class="nomargin-top"> |
| >>> for name, descr in func_doc.exception_descrs: |
| >>> exc_name = self.docindex.find(name, func_doc) |
| >>> if exc_name is not None: |
| >>> name = self.href(exc_name, label=str(name)) |
| >>> #endif |
| $self.labelled_list_item( |
| "<code><strong class=\'fraise\'>" + |
| str(name) + "</strong></code>", |
| self.docstring_to_html(descr, func_doc, 8))$ |
| >>> #endfor |
| </ul></dd> |
| >>> #endif |
| >>> # === overrides === |
| >>> if var_doc.overrides not in (None, UNKNOWN): |
| <dt>Overrides: |
| >>> # Avoid passing GenericValueDoc to href() |
| >>> if isinstance(var_doc.overrides.value, RoutineDoc): |
| $self.href(var_doc.overrides.value, context=var_doc)$ |
| >>> else: |
| >>> # In this case, a less interesting label is generated. |
| $self.href(var_doc.overrides, context=var_doc)$ |
| >>> #endif |
| >>> if (func_doc.docstring in (None, UNKNOWN) and |
| >>> var_doc.overrides.value.docstring not in (None, UNKNOWN)): |
| <dd><em class="note">(inherited documentation)</em></dd> |
| >>> #endif |
| </dt> |
| >>> #endif |
| </dl> |
| >>> # === metadata === |
| >>> self.write_standard_fields(out, func_doc) |
| </td></tr></table> |
| </div> |
| ''') |
| # \------------------------------------------------------------/ |
| |
| # Names for the __special__ methods. |
| SPECIAL_METHODS ={ |
| '__init__': 'Constructor', |
| '__del__': 'Destructor', |
| '__add__': 'Addition operator', |
| '__sub__': 'Subtraction operator', |
| '__and__': 'And operator', |
| '__or__': 'Or operator', |
| '__xor__': 'Exclusive-Or operator', |
| '__repr__': 'Representation operator', |
| '__call__': 'Call operator', |
| '__getattr__': 'Qualification operator', |
| '__getitem__': 'Indexing operator', |
| '__setitem__': 'Index assignment operator', |
| '__delitem__': 'Index deletion operator', |
| '__delslice__': 'Slice deletion operator', |
| '__setslice__': 'Slice assignment operator', |
| '__getslice__': 'Slicling operator', |
| '__len__': 'Length operator', |
| '__cmp__': 'Comparison operator', |
| '__eq__': 'Equality operator', |
| '__in__': 'Containership operator', |
| '__gt__': 'Greater-than operator', |
| '__lt__': 'Less-than operator', |
| '__ge__': 'Greater-than-or-equals operator', |
| '__le__': 'Less-than-or-equals operator', |
| '__radd__': 'Right-side addition operator', |
| '__hash__': 'Hashing function', |
| '__contains__': 'In operator', |
| '__nonzero__': 'Boolean test operator', |
| '__str__': 'Informal representation operator', |
| } |
| |
| write_property_details_entry = compile_template( |
| ''' |
| write_property_details_entry(self, out, var_doc, descr, \ |
| accessors, div_class) |
| ''', |
| # /------------------------- Template -------------------------\ |
| ''' |
| >>> prop_doc = var_doc.value |
| <a name="$var_doc.name$"></a> |
| <div$div_class$> |
| >>> self.write_table_header(out, "details") |
| <tr><td> |
| <h3 class="epydoc">$var_doc.name$</h3> |
| $descr$ |
| <dl class="fields"> |
| >>> for (name, val, summary) in accessors: |
| <dt>$name$ Method:</dt> |
| <dd class="value">$val$ |
| >>> if summary: |
| - $summary$ |
| >>> #endif |
| </dd> |
| >>> #endfor |
| >>> if prop_doc.type_descr not in (None, UNKNOWN): |
| <dt>Type:</dt> |
| <dd>$self.type_descr(var_doc, indent=6)$</dd> |
| >>> #endif |
| </dl> |
| >>> self.write_standard_fields(out, prop_doc) |
| </td></tr></table> |
| </div> |
| ''') |
| # \------------------------------------------------------------/ |
| |
| write_variable_details_entry = compile_template( |
| ''' |
| write_variable_details_entry(self, out, var_doc, descr, div_class) |
| ''', |
| # /------------------------- Template -------------------------\ |
| ''' |
| <a name="$var_doc.name$"></a> |
| <div$div_class$> |
| >>> self.write_table_header(out, "details") |
| <tr><td> |
| <h3 class="epydoc">$var_doc.name$</h3> |
| $descr$ |
| <dl class="fields"> |
| >>> if var_doc.type_descr not in (None, UNKNOWN): |
| <dt>Type:</dt> |
| <dd>$self.type_descr(var_doc, indent=6)$</dd> |
| >>> #endif |
| </dl> |
| >>> self.write_standard_fields(out, var_doc) |
| >>> if var_doc.value is not UNKNOWN: |
| <dl class="fields"> |
| <dt>Value:</dt> |
| <dd>$self.pprint_value(var_doc.value)$</dd> |
| </dl> |
| >>> #endif |
| </td></tr></table> |
| </div> |
| ''') |
| # \------------------------------------------------------------/ |
| |
| def variable_tooltip(self, var_doc): |
| if var_doc.value in (None, UNKNOWN): |
| return '' |
| s = var_doc.value.pyval_repr().to_plaintext(None) |
| if len(s) > self._variable_tooltip_linelen: |
| s = s[:self._variable_tooltip_linelen-3]+'...' |
| return ' title="%s"' % plaintext_to_html(s) |
| |
| def pprint_value(self, val_doc): |
| if val_doc is UNKNOWN: |
| return '??' |
| elif isinstance(val_doc, GenericValueDoc): |
| return ('<table><tr><td><pre class="variable">\n' + |
| val_doc.pyval_repr().to_html(None) + |
| '\n</pre></td></tr></table>\n') |
| else: |
| return self.href(val_doc) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Base Tree |
| #//////////////////////////////////////////////////////////// |
| |
| def base_tree(self, doc, width=None, postfix='', context=None): |
| """ |
| @return: The HTML code for a class's base tree. The tree is |
| drawn 'upside-down' and right justified, to allow for |
| multiple inheritance. |
| @rtype: C{string} |
| """ |
| if context is None: |
| context = doc.defining_module |
| if width == None: width = self.find_tree_width(doc, context) |
| if isinstance(doc, ClassDoc) and doc.bases != UNKNOWN: |
| bases = doc.bases |
| else: |
| bases = [] |
| |
| if postfix == '': |
| # [XX] use var name instead of canonical name? |
| s = (' '*(width-2) + '<strong class="uidshort">'+ |
| self.contextual_label(doc, context)+'</strong>\n') |
| else: s = '' |
| for i in range(len(bases)-1, -1, -1): |
| base = bases[i] |
| label = self.contextual_label(base, context) |
| s = (' '*(width-4-len(label)) + self.href(base, label) |
| +' --+'+postfix+'\n' + |
| ' '*(width-4) + |
| ' |'+postfix+'\n' + |
| s) |
| if i != 0: |
| s = (self.base_tree(base, width-4, ' |'+postfix, context)+s) |
| else: |
| s = (self.base_tree(base, width-4, ' '+postfix, context)+s) |
| return s |
| |
| def find_tree_width(self, doc, context): |
| """ |
| Helper function for L{base_tree}. |
| @return: The width of a base tree, when drawn |
| right-justified. This is used by L{base_tree} to |
| determine how far to indent lines of the base tree. |
| @rtype: C{int} |
| """ |
| if not isinstance(doc, ClassDoc): return 2 |
| if doc.bases == UNKNOWN: return 2 |
| width = 2 |
| for base in doc.bases: |
| width = max(width, len(self.contextual_label(base, context))+4, |
| self.find_tree_width(base, context)+4) |
| return width |
| |
| def contextual_label(self, doc, context): |
| """ |
| Return the label for C{doc} to be shown in C{context}. |
| """ |
| if doc.canonical_name is None: |
| if doc.parse_repr is not None: |
| return doc.parse_repr |
| else: |
| return '??' |
| else: |
| if context is UNKNOWN: |
| return str(doc.canonical_name) |
| else: |
| context_name = context.canonical_name |
| return str(doc.canonical_name.contextualize(context_name)) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Function Signatures |
| #//////////////////////////////////////////////////////////// |
| |
| def function_signature(self, api_doc, is_summary=False, |
| link_name=False, anchor=False, context=None): |
| """Render a function signature in HTML. |
| |
| @param api_doc: The object whose name is to be rendered. If a |
| C{VariableDoc}, its C{value} should be a C{RoutineDoc} |
| @type api_doc: L{VariableDoc} or L{RoutineDoc} |
| @param is_summary: True if the fuction is to be rendered in the summary. |
| type css_class: C{bool} |
| @param link_name: If True, the name is a link to the object anchor. |
| @type link_name: C{bool} |
| @param anchor: If True, the name is the object anchor. |
| @type anchor: C{bool} |
| @param context: If set, represent the function name from this context. |
| Only useful when C{api_doc} is a L{RoutineDoc}. |
| @type context: L{DottedName} |
| |
| @return: The HTML code for the object. |
| @rtype: C{str} |
| """ |
| if is_summary: css_class = 'summary-sig' |
| else: css_class = 'sig' |
| |
| # [XX] clean this up! |
| if isinstance(api_doc, VariableDoc): |
| func_doc = api_doc.value |
| # This should never happen, but just in case: |
| if api_doc.value in (None, UNKNOWN): |
| return (('<span class="%s"><span class="%s-name">%s'+ |
| '</span>(...)</span>') % |
| (css_class, css_class, api_doc.name)) |
| # Get the function's name. |
| name = self.summary_name(api_doc, css_class=css_class+'-name', |
| link_name=link_name, anchor=anchor) |
| else: |
| func_doc = api_doc |
| name = self.href(api_doc, css_class=css_class+'-name', |
| context=context) |
| |
| if func_doc.posargs == UNKNOWN: |
| args = ['...'] |
| else: |
| args = [self.func_arg(n, d, css_class) for (n, d) |
| in zip(func_doc.posargs, func_doc.posarg_defaults)] |
| if func_doc.vararg not in (None, UNKNOWN): |
| if func_doc.vararg == '...': |
| args.append('<span class="%s-arg">...</span>' % css_class) |
| else: |
| args.append('<span class="%s-arg">*%s</span>' % |
| (css_class, func_doc.vararg)) |
| if func_doc.kwarg not in (None, UNKNOWN): |
| args.append('<span class="%s-arg">**%s</span>' % |
| (css_class, func_doc.kwarg)) |
| |
| return ('<span class="%s">%s(%s)</span>' % |
| (css_class, name, ',\n '.join(args))) |
| |
| def summary_name(self, api_doc, css_class='summary-name', |
| link_name=False, anchor=False): |
| """Render an object name in HTML. |
| |
| @param api_doc: The object whose name is to be rendered |
| @type api_doc: L{APIDoc} |
| @param css_class: The CSS class to assign to the rendered name |
| type css_class: C{str} |
| @param link_name: If True, the name is a link to the object anchor. |
| @type link_name: C{bool} |
| @param anchor: If True, the name is the object anchor. |
| @type anchor: C{bool} |
| |
| @return: The HTML code for the object. |
| @rtype: C{str} |
| """ |
| if anchor: |
| rv = '<a name="%s"></a>' % api_doc.name |
| else: |
| rv = '' |
| |
| if link_name: |
| rv += self.href(api_doc, css_class=css_class) |
| else: |
| rv += '<span class="%s">%s</span>' % (css_class, api_doc.name) |
| |
| return rv |
| |
| # [xx] tuple args??? |
| def func_arg(self, name, default, css_class): |
| name = self._arg_name(name) |
| s = '<span class="%s-arg">%s</span>' % (css_class, name) |
| if default is not None: |
| s += ('=<span class="%s-default">%s</span>' % |
| (css_class, default.summary_pyval_repr().to_html(None))) |
| return s |
| |
| def _arg_name(self, arg): |
| if isinstance(arg, basestring): |
| return arg |
| elif len(arg) == 1: |
| return '(%s,)' % self._arg_name(arg[0]) |
| else: |
| return '(%s)' % (', '.join([self._arg_name(a) for a in arg])) |
| |
| |
| |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Import Lists |
| #//////////////////////////////////////////////////////////// |
| |
| def write_imports(self, out, doc): |
| assert isinstance(doc, NamespaceDoc) |
| imports = doc.select_variables(imported=True, |
| public=self._public_filter) |
| if not imports: return |
| |
| out('<p class="indent-wrapped-lines">') |
| out('<b>Imports:</b>\n ') |
| out(',\n '.join([self._import(v, doc) for v in imports])) |
| out('\n</p><br />\n') |
| |
| def _import(self, var_doc, context): |
| if var_doc.imported_from not in (None, UNKNOWN): |
| return self.href(var_doc.imported_from, |
| var_doc.name, context=context, |
| tooltip='%s' % var_doc.imported_from) |
| elif (var_doc.value not in (None, UNKNOWN) and not |
| isinstance(var_doc.value, GenericValueDoc)): |
| return self.href(var_doc.value, |
| var_doc.name, context=context, |
| tooltip='%s' % var_doc.value.canonical_name) |
| else: |
| return plaintext_to_html(var_doc.name) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Function Attributes |
| #//////////////////////////////////////////////////////////// |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Module Trees |
| #//////////////////////////////////////////////////////////// |
| |
| def write_module_list(self, out, doc): |
| if len(doc.submodules) == 0: return |
| self.write_table_header(out, "summary", "Submodules") |
| |
| for group_name in doc.group_names(): |
| if not doc.submodule_groups[group_name]: continue |
| if group_name: |
| self.write_group_header(out, group_name) |
| out(' <tr><td class="summary">\n' |
| ' <ul class="nomargin">\n') |
| for submodule in doc.submodule_groups[group_name]: |
| self.write_module_tree_item(out, submodule, package=doc) |
| out(' </ul></td></tr>\n') |
| |
| out(self.TABLE_FOOTER+'\n<br />\n') |
| |
| def write_module_tree_item(self, out, doc, package=None): |
| # If it's a private variable, then mark its <li>. |
| var = package and package.variables.get(doc.canonical_name[-1]) |
| priv = ((var is not None and var.is_public is False) or |
| (var is None and doc.canonical_name[-1].startswith('_'))) |
| out(' <li%s> <strong class="uidlink">%s</strong>' |
| % (priv and ' class="private"' or '', self.href(doc))) |
| if doc.summary not in (None, UNKNOWN): |
| out(': <em class="summary">'+ |
| self.description(doc.summary, doc, 8)+'</em>') |
| if doc.submodules != UNKNOWN and doc.submodules: |
| if priv: out('\n <ul class="private">\n') |
| else: out('\n <ul>\n') |
| for submodule in doc.submodules: |
| self.write_module_tree_item(out, submodule, package=doc) |
| out(' </ul>\n') |
| out(' </li>\n') |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Class trees |
| #//////////////////////////////////////////////////////////// |
| |
| write_class_tree_item = compile_template( |
| ''' |
| write_class_tree_item(self, out, doc, class_set) |
| ''', |
| # /------------------------- Template -------------------------\ |
| ''' |
| >>> if doc.summary in (None, UNKNOWN): |
| <li> <strong class="uidlink">$self.href(doc)$</strong> |
| >>> else: |
| <li> <strong class="uidlink">$self.href(doc)$</strong>: |
| <em class="summary">$self.description(doc.summary, doc, 8)$</em> |
| >>> # endif |
| >>> if doc.subclasses: |
| <ul> |
| >>> for subclass in sorted(set(doc.subclasses), key=lambda c:c.canonical_name[-1]): |
| >>> if subclass in class_set: |
| >>> self.write_class_tree_item(out, subclass, class_set) |
| >>> #endif |
| >>> #endfor |
| </ul> |
| >>> #endif |
| </li> |
| ''') |
| # \------------------------------------------------------------/ |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Standard Fields |
| #//////////////////////////////////////////////////////////// |
| |
| def write_standard_fields(self, out, doc): |
| """ |
| Write HTML code containing descriptions of any standard markup |
| fields that are defined by the given L{APIDoc} object (such as |
| C{@author} and C{@todo} fields). |
| |
| @param doc: The L{APIDoc} object containing the API documentation |
| for the object whose standard markup fields should be |
| described. |
| """ |
| fields = [] |
| field_values = {} |
| |
| for (field, arg, descr) in doc.metadata: |
| if field not in field_values: |
| fields.append(field) |
| if field.takes_arg: |
| subfields = field_values.setdefault(field,{}) |
| subfields.setdefault(arg,[]).append(descr) |
| else: |
| field_values.setdefault(field,[]).append(descr) |
| |
| if not fields: return |
| |
| out('<div class="fields">') |
| for field in fields: |
| if field.takes_arg: |
| for arg, descrs in field_values[field].items(): |
| self.write_standard_field(out, doc, field, descrs, arg) |
| |
| else: |
| self.write_standard_field(out, doc, field, field_values[field]) |
| |
| out('</div>') |
| |
| write_standard_field = compile_template( |
| """ |
| write_standard_field(self, out, doc, field, descrs, arg='') |
| |
| """, |
| # /------------------------- Template -------------------------\ |
| ''' |
| >>> if arg: arglabel = " (%s)" % arg |
| >>> else: arglabel = "" |
| >>> if len(descrs) == 1: |
| <p><strong>$field.singular+arglabel$:</strong> |
| $self.description(descrs[0], doc, 8)$ |
| </p> |
| >>> elif field.short: |
| <dl><dt>$field.plural+arglabel$:</dt> |
| <dd> |
| >>> for descr in descrs[:-1]: |
| $self.description(descr, doc, 10)$, |
| >>> # end for |
| $self.description(descrs[-1], doc, 10)$ |
| </dd> |
| </dl> |
| >>> else: |
| <strong>$field.plural+arglabel$:</strong> |
| <ul class="nomargin-top"> |
| >>> for descr in descrs: |
| <li> |
| $self.description(descr, doc, 8)$ |
| </li> |
| >>> # end for |
| </ul> |
| >>> # end else |
| >>> # end for |
| ''') |
| # \------------------------------------------------------------/ |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Index generation |
| #//////////////////////////////////////////////////////////// |
| |
| #: A list of metadata indices that should be generated. Each |
| #: entry in this list is a tuple C{(tag, label, short_label)}, |
| #: where C{tag} is the cannonical tag of a metadata field; |
| #: C{label} is a label for the index page; and C{short_label} |
| #: is a shorter label, used in the index selector. |
| METADATA_INDICES = [('bug', 'Bug List', 'Bugs'), |
| ('todo', 'To Do List', 'To Do'), |
| ('change', 'Change Log', 'Changes'), |
| ('deprecated', 'Deprecation List', 'Deprecations'), |
| ('since', 'Introductions List', 'Introductions'), |
| ] |
| |
| def build_identifier_index(self): |
| items = [] |
| for doc in self.indexed_docs: |
| name = plaintext_to_html(doc.canonical_name[-1]) |
| if isinstance(doc, RoutineDoc): name += '()' |
| url = self.url(doc) |
| if not url: continue |
| container = self.docindex.container(doc) |
| items.append( (name, url, container) ) |
| return sorted(items, key=lambda v:v[0].lower()) |
| |
| def _group_by_letter(self, items): |
| """Preserves sort order of the input.""" |
| index = {} |
| for item in items: |
| first_letter = item[0][0].upper() |
| if not ("A" <= first_letter <= "Z"): |
| first_letter = '_' |
| index.setdefault(first_letter, []).append(item) |
| return index |
| |
| def build_term_index(self): |
| items = [] |
| for doc in self.indexed_docs: |
| url = self.url(doc) |
| items += self._terms_from_docstring(url, doc, doc.descr) |
| for (field, arg, descr) in doc.metadata: |
| items += self._terms_from_docstring(url, doc, descr) |
| if hasattr(doc, 'type_descr'): |
| items += self._terms_from_docstring(url, doc, |
| doc.type_descr) |
| if hasattr(doc, 'return_descr'): |
| items += self._terms_from_docstring(url, doc, |
| doc.return_descr) |
| if hasattr(doc, 'return_type'): |
| items += self._terms_from_docstring(url, doc, |
| doc.return_type) |
| return sorted(items, key=lambda v:v[0].lower()) |
| |
| def _terms_from_docstring(self, base_url, container, parsed_docstring): |
| if parsed_docstring in (None, UNKNOWN): return [] |
| terms = [] |
| # Strip any existing anchor off: |
| base_url = re.sub('#.*', '', '%s' % (base_url,)) |
| for term in parsed_docstring.index_terms(): |
| anchor = self._term_index_to_anchor(term) |
| url = '%s#%s' % (base_url, anchor) |
| terms.append( (term.to_plaintext(None), url, container) ) |
| return terms |
| |
| def build_metadata_index(self, field_name): |
| # Build the index. |
| index = {} |
| for doc in self.indexed_docs: |
| if (not self._show_private and |
| self._doc_or_ancestor_is_private(doc)): |
| continue |
| descrs = {} |
| if doc.metadata is not UNKNOWN: |
| for (field, arg, descr) in doc.metadata: |
| if field.tags[0] == field_name: |
| descrs.setdefault(arg, []).append(descr) |
| for (arg, descr_list) in descrs.iteritems(): |
| index.setdefault(arg, []).append( (doc, descr_list) ) |
| return index |
| |
| def _term_index_to_anchor(self, term): |
| """ |
| Given the name of an inline index item, construct a URI anchor. |
| These anchors are used to create links from the index page to each |
| index item. |
| """ |
| # Include "-" so we don't accidentally collide with the name |
| # of a python identifier. |
| s = re.sub(r'\s\s+', '-', term.to_plaintext(None)) |
| return "index-"+re.sub("[^a-zA-Z0-9]", "_", s) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Redirect page |
| #//////////////////////////////////////////////////////////// |
| |
| def write_redirect_page(self, out): |
| """ |
| Build the auto-redirect page, which translates dotted names to |
| URLs using javascript. When the user visits |
| <redirect.html#dotted.name>, they will automatically get |
| redirected to the page for the object with the given |
| fully-qualified dotted name. E.g., for epydoc, |
| <redirect.html#epydoc.apidoc.UNKNOWN> redirects the user to |
| <epydoc.apidoc-module.html#UNKNOWN>. |
| """ |
| # Construct a list of all the module & class pages that we're |
| # documenting. The redirect_url javascript will scan through |
| # this list, looking for a page name that matches the |
| # requested dotted name. |
| pages = (['%s-m' % val_doc.canonical_name |
| for val_doc in self.module_list] + |
| ['%s-c' % val_doc.canonical_name |
| for val_doc in self.class_list]) |
| # Sort the pages from longest to shortest. This ensures that |
| # we find e.g. "x.y.z" in the list before "x.y". |
| pages = sorted(pages, key=lambda p:-len(p)) |
| |
| # Write the redirect page. |
| self._write_redirect_page(out, pages) |
| |
| _write_redirect_page = compile_template( |
| ''' |
| _write_redirect_page(self, out, pages) |
| ''', |
| # /------------------------- Template -------------------------\ |
| ''' |
| <html><head><title>Epydoc Redirect Page</title> |
| <meta http-equiv="cache-control" content="no-cache" /> |
| <meta http-equiv="expires" content="0" /> |
| <meta http-equiv="pragma" content="no-cache" /> |
| <script type="text/javascript" src="epydoc.js"></script> |
| </head> |
| <body> |
| <script type="text/javascript"> |
| <!-- |
| var pages = $"[%s]" % ", ".join(['"%s"' % v for v in pages])$; |
| var dottedName = get_anchor(); |
| if (dottedName) { |
| var target = redirect_url(dottedName); |
| if (target) window.location.replace(target); |
| } |
| // --> |
| </script> |
| |
| <h3>Epydoc Auto-redirect page</h3> |
| |
| <p>When javascript is enabled, this page will redirect URLs of |
| the form <tt>redirect.html#<i>dotted.name</i></tt> to the |
| documentation for the object with the given fully-qualified |
| dotted name.</p> |
| <p><a id="message"> </a></p> |
| |
| <script type="text/javascript"> |
| <!-- |
| if (dottedName) { |
| var msg = document.getElementById("message"); |
| msg.innerHTML = "No documentation found for <tt>"+ |
| dottedName+"</tt>"; |
| } |
| // --> |
| </script> |
| |
| </body> |
| </html> |
| ''') |
| # \------------------------------------------------------------/ |
| |
| #//////////////////////////////////////////////////////////// |
| #{ URLs list |
| #//////////////////////////////////////////////////////////// |
| |
| def write_api_list(self, out): |
| """ |
| Write a list of mapping name->url for all the documented objects. |
| """ |
| # Construct a list of all the module & class pages that we're |
| # documenting. The redirect_url javascript will scan through |
| # this list, looking for a page name that matches the |
| # requested dotted name. |
| skip = (ModuleDoc, ClassDoc, type(UNKNOWN)) |
| for val_doc in self.module_list: |
| self.write_url_record(out, val_doc) |
| for var in val_doc.variables.itervalues(): |
| if not isinstance(var.value, skip): |
| self.write_url_record(out, var) |
| |
| for val_doc in self.class_list: |
| self.write_url_record(out, val_doc) |
| for var in val_doc.variables.itervalues(): |
| self.write_url_record(out, var) |
| |
| def write_url_record(self, out, obj): |
| url = self.url(obj) |
| if url is not None: |
| out("%s\t%s\n" % (obj.canonical_name, url)) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Helper functions |
| #//////////////////////////////////////////////////////////// |
| |
| def _val_is_public(self, valdoc): |
| """Make a best-guess as to whether the given class is public.""" |
| container = self.docindex.container(valdoc) |
| if isinstance(container, NamespaceDoc): |
| for vardoc in container.variables.values(): |
| if vardoc in (UNKNOWN, None): continue |
| if vardoc.value is valdoc: |
| return vardoc.is_public |
| return True |
| |
| # [XX] Is it worth-while to pull the anchor tricks that I do here? |
| # Or should I just live with the fact that show/hide private moves |
| # stuff around? |
| write_table_header = compile_template( |
| ''' |
| write_table_header(self, out, css_class, heading=None, \ |
| private_link=True, colspan=2) |
| ''', |
| # /------------------------- Template -------------------------\ |
| ''' |
| >>> if heading is not None: |
| >>> anchor = "section-%s" % re.sub("\W", "", heading) |
| <!-- ==================== $heading.upper()$ ==================== --> |
| <a name="$anchor$"></a> |
| >>> #endif |
| <table class="$css_class$" border="1" cellpadding="3" |
| cellspacing="0" width="100%" bgcolor="white"> |
| >>> if heading is not None: |
| <tr bgcolor="#70b0f0" class="table-header"> |
| >>> if private_link and self._show_private: |
| <td colspan="$colspan$" class="table-header"> |
| <table border="0" cellpadding="0" cellspacing="0" width="100%"> |
| <tr valign="top"> |
| <td align="left"><span class="table-header">$heading$</span></td> |
| <td align="right" valign="top" |
| ><span class="options">[<a href="#$anchor$" |
| class="privatelink" onclick="toggle_private();" |
| >hide private</a>]</span></td> |
| </tr> |
| </table> |
| </td> |
| >>> else: |
| <td align="left" colspan="2" class="table-header"> |
| <span class="table-header">$heading$</span></td> |
| >>> #endif |
| </tr> |
| >>> #endif |
| ''') |
| # \------------------------------------------------------------/ |
| |
| TABLE_FOOTER = '</table>\n' |
| |
| PRIVATE_LINK = ''' |
| <span class="options">[<a href="javascript:void(0);" class="privatelink" |
| onclick="toggle_private();">hide private</a>]</span> |
| '''.strip() |
| |
| write_group_header = compile_template( |
| ''' |
| write_group_header(self, out, group, tr_class='') |
| ''', |
| # /------------------------- Template -------------------------\ |
| ''' |
| <tr bgcolor="#e8f0f8" $tr_class$> |
| <th colspan="2" class="group-header" |
| > $group$</th></tr> |
| ''') |
| # \------------------------------------------------------------/ |
| |
| _url_cache = {} |
| def url(self, obj): |
| """ |
| Return the URL for the given object, which can be a |
| C{VariableDoc}, a C{ValueDoc}, or a C{DottedName}. |
| """ |
| cached_url = self._url_cache.get(id(obj)) |
| if cached_url is not None: |
| return cached_url |
| else: |
| url = self._url_cache[id(obj)] = self._url(obj) |
| return url |
| |
| def _url(self, obj): |
| """ |
| Internal helper for L{url}. |
| """ |
| # Module: <canonical_name>-module.html |
| if isinstance(obj, ModuleDoc): |
| if obj not in self.module_set: return None |
| return urllib.quote('%s'%obj.canonical_name) + '-module.html' |
| # Class: <canonical_name>-class.html |
| elif isinstance(obj, ClassDoc): |
| if obj not in self.class_set: return None |
| return urllib.quote('%s'%obj.canonical_name) + '-class.html' |
| # Variable |
| elif isinstance(obj, VariableDoc): |
| val_doc = obj.value |
| if isinstance(val_doc, (ModuleDoc, ClassDoc)): |
| return self.url(val_doc) |
| elif obj.container in (None, UNKNOWN): |
| if val_doc in (None, UNKNOWN): return None |
| return self.url(val_doc) |
| elif obj.is_imported == True: |
| if obj.imported_from is not UNKNOWN: |
| return self.url(obj.imported_from) |
| else: |
| return None |
| else: |
| container_url = self.url(obj.container) |
| if container_url is None: return None |
| return '%s#%s' % (container_url, urllib.quote('%s'%obj.name)) |
| # Value (other than module or class) |
| elif isinstance(obj, ValueDoc): |
| container = self.docindex.container(obj) |
| if container is None: |
| return None # We couldn't find it! |
| else: |
| container_url = self.url(container) |
| if container_url is None: return None |
| anchor = urllib.quote('%s'%obj.canonical_name[-1]) |
| return '%s#%s' % (container_url, anchor) |
| # Dotted name: look up the corresponding APIDoc |
| elif isinstance(obj, DottedName): |
| val_doc = self.docindex.get_valdoc(obj) |
| if val_doc is None: return None |
| return self.url(val_doc) |
| # Special pages: |
| elif obj == 'indices': |
| return 'identifier-index.html' |
| elif obj == 'help': |
| return 'help.html' |
| elif obj == 'trees': |
| return self._trees_url |
| else: |
| raise ValueError, "Don't know what to do with %r" % obj |
| |
| def pysrc_link(self, api_doc): |
| if not self._incl_sourcecode: |
| return '' |
| url = self.pysrc_url(api_doc) |
| if url is not None: |
| return ('<span class="codelink"><a href="%s">source ' |
| 'code</a></span>' % url) |
| else: |
| return '' |
| |
| def pysrc_url(self, api_doc): |
| if isinstance(api_doc, VariableDoc): |
| if api_doc.value not in (None, UNKNOWN): |
| return pysrc_url(api_doc.value) |
| else: |
| return None |
| elif isinstance(api_doc, ModuleDoc): |
| if api_doc in self.modules_with_sourcecode: |
| return ('%s-pysrc.html' % |
| urllib.quote('%s' % api_doc.canonical_name)) |
| else: |
| return None |
| else: |
| module = api_doc.defining_module |
| if module == UNKNOWN: return None |
| module_pysrc_url = self.pysrc_url(module) |
| if module_pysrc_url is None: return None |
| module_name = module.canonical_name |
| if not module_name.dominates(api_doc.canonical_name, True): |
| log.debug('%r is in %r but name does not dominate' % |
| (api_doc, module)) |
| return module_pysrc_url |
| mname_len = len(module.canonical_name) |
| anchor = '%s' % api_doc.canonical_name[mname_len:] |
| return '%s#%s' % (module_pysrc_url, urllib.quote(anchor)) |
| |
| # We didn't find it: |
| return None |
| |
| # [xx] add code to automatically do <code> wrapping or the like? |
| def href(self, target, label=None, css_class=None, context=None, |
| tooltip=None): |
| """ |
| Return the HTML code for an HREF link to the given target |
| (which can be a C{VariableDoc}, a C{ValueDoc}, or a |
| C{DottedName}. |
| If a C{NamespaceDoc} C{context} is specified, the target label is |
| contextualized to it. |
| """ |
| assert isinstance(target, (APIDoc, DottedName)) |
| |
| # Pick a label, if none was given. |
| if label is None: |
| if isinstance(target, VariableDoc): |
| label = target.name |
| elif (isinstance(target, ValueDoc) and |
| target.canonical_name is not UNKNOWN): |
| label = target.canonical_name |
| elif isinstance(target, DottedName): |
| label = target |
| elif isinstance(target, GenericValueDoc): |
| raise ValueError("href() should not be called with " |
| "GenericValueDoc objects (perhaps you " |
| "meant to use the containing variable?)") |
| else: |
| raise ValueError("Unable to find a label for %r" % target) |
| |
| if context is not None and isinstance(label, DottedName): |
| label = label.contextualize(context.canonical_name.container()) |
| |
| label = plaintext_to_html(str(label)) |
| |
| # Munge names for scripts & unreachable values |
| if label.startswith('script-'): |
| label = label[7:] + ' (script)' |
| if label.startswith('??'): |
| label = '<i>unreachable</i>' + label[2:] |
| label = re.sub(r'-\d+$', '', label) |
| |
| # Get the url for the target. |
| url = self.url(target) |
| if url is None: |
| if tooltip: return '<span title="%s">%s</span>' % (tooltip, label) |
| else: return label |
| |
| # Construct a string for the class attribute. |
| if css_class is None: |
| css = '' |
| else: |
| css = ' class="%s"' % css_class |
| |
| onclick = '' |
| if ((isinstance(target, VariableDoc) and not target.is_public) or |
| (isinstance(target, ValueDoc) and |
| not isinstance(target, GenericValueDoc) and |
| not self._val_is_public(target))): |
| onclick = ' onclick="show_private();"' |
| |
| if tooltip: |
| tooltip = ' title="%s"' % tooltip |
| else: |
| tooltip = '' |
| |
| return '<a href="%s"%s%s%s>%s</a>' % (url, css, onclick, tooltip, label) |
| |
| def _attr_to_html(self, attr, api_doc, indent): |
| if api_doc in (None, UNKNOWN): |
| return '' |
| pds = getattr(api_doc, attr, None) # pds = ParsedDocstring. |
| if pds not in (None, UNKNOWN): |
| return self.docstring_to_html(pds, api_doc, indent) |
| elif isinstance(api_doc, VariableDoc): |
| return self._attr_to_html(attr, api_doc.value, indent) |
| |
| def summary(self, api_doc, indent=0): |
| return self._attr_to_html('summary', api_doc, indent) |
| |
| def descr(self, api_doc, indent=0): |
| return self._attr_to_html('descr', api_doc, indent) |
| |
| def type_descr(self, api_doc, indent=0): |
| return self._attr_to_html('type_descr', api_doc, indent) |
| |
| def return_type(self, api_doc, indent=0): |
| return self._attr_to_html('return_type', api_doc, indent) |
| |
| def return_descr(self, api_doc, indent=0): |
| return self._attr_to_html('return_descr', api_doc, indent) |
| |
| def docstring_to_html(self, parsed_docstring, where=None, indent=0): |
| if parsed_docstring in (None, UNKNOWN): return '' |
| linker = _HTMLDocstringLinker(self, where) |
| s = parsed_docstring.to_html(linker, indent=indent, |
| directory=self._directory, |
| docindex=self.docindex, |
| context=where).strip() |
| if self._mark_docstrings: |
| s = '<span class="docstring">%s</span><!--end docstring-->' % s |
| return s |
| |
| def description(self, parsed_docstring, where=None, indent=0): |
| assert isinstance(where, (APIDoc, type(None))) |
| if parsed_docstring in (None, UNKNOWN): return '' |
| linker = _HTMLDocstringLinker(self, where) |
| descr = parsed_docstring.to_html(linker, indent=indent, |
| directory=self._directory, |
| docindex=self.docindex, |
| context=where).strip() |
| if descr == '': return ' ' |
| return descr |
| |
| # [xx] Should this be defined by the APIDoc classes themselves?? |
| def doc_kind(self, doc): |
| if isinstance(doc, ModuleDoc) and doc.is_package == True: |
| return 'Package' |
| elif (isinstance(doc, ModuleDoc) and |
| doc.canonical_name[0].startswith('script')): |
| return 'Script' |
| elif isinstance(doc, ModuleDoc): |
| return 'Module' |
| elif isinstance(doc, ClassDoc): |
| return 'Class' |
| elif isinstance(doc, ClassMethodDoc): |
| return 'Class Method' |
| elif isinstance(doc, StaticMethodDoc): |
| return 'Static Method' |
| elif isinstance(doc, RoutineDoc): |
| if isinstance(self.docindex.container(doc), ClassDoc): |
| return 'Method' |
| else: |
| return 'Function' |
| else: |
| return 'Variable' |
| |
| def _doc_or_ancestor_is_private(self, api_doc): |
| name = api_doc.canonical_name |
| for i in range(len(name), 0, -1): |
| # Is it (or an ancestor) a private var? |
| var_doc = self.docindex.get_vardoc(name[:i]) |
| if var_doc is not None and var_doc.is_public == False: |
| return True |
| # Is it (or an ancestor) a private module? |
| val_doc = self.docindex.get_valdoc(name[:i]) |
| if (val_doc is not None and isinstance(val_doc, ModuleDoc) and |
| val_doc.canonical_name[-1].startswith('_')): |
| return True |
| return False |
| |
| def _private_subclasses(self, class_doc): |
| """Return a list of all subclasses of the given class that are |
| private, as determined by L{_val_is_private}. Recursive |
| subclasses are included in this list.""" |
| queue = [class_doc] |
| private = set() |
| for cls in queue: |
| if (isinstance(cls, ClassDoc) and |
| cls.subclasses not in (None, UNKNOWN)): |
| queue.extend(cls.subclasses) |
| private.update([c for c in cls.subclasses if |
| not self._val_is_public(c)]) |
| return private |
| |
| class _HTMLDocstringLinker(epydoc.markup.DocstringLinker): |
| def __init__(self, htmlwriter, container): |
| self.htmlwriter = htmlwriter |
| self.docindex = htmlwriter.docindex |
| self.container = container |
| |
| def translate_indexterm(self, indexterm): |
| key = self.htmlwriter._term_index_to_anchor(indexterm) |
| return ('<a name="%s"></a><i class="indexterm">%s</i>' % |
| (key, indexterm.to_html(self))) |
| |
| def translate_identifier_xref(self, identifier, label=None): |
| # Pick a label for this xref. |
| if label is None: label = plaintext_to_html(identifier) |
| |
| # Find the APIDoc for it (if it's available). |
| doc = self.docindex.find(identifier, self.container) |
| |
| # If we didn't find a target, then try checking in the contexts |
| # of the ancestor classes. |
| if doc is None and isinstance(self.container, RoutineDoc): |
| container = self.docindex.get_vardoc( |
| self.container.canonical_name) |
| while (doc is None and container not in (None, UNKNOWN) |
| and container.overrides not in (None, UNKNOWN)): |
| container = container.overrides |
| doc = self.docindex.find(identifier, container) |
| |
| # Translate it into HTML. |
| if doc is None: |
| self._failed_xref(identifier) |
| return '<code class="link">%s</code>' % label |
| else: |
| return self.htmlwriter.href(doc, label, 'link') |
| |
| # [xx] Should this be added to the DocstringLinker interface??? |
| # Currently, this is *only* used by dotgraph. |
| def url_for(self, identifier): |
| if isinstance(identifier, (basestring, DottedName)): |
| doc = self.docindex.find(identifier, self.container) |
| if doc: |
| return self.htmlwriter.url(doc) |
| else: |
| return None |
| |
| elif isinstance(identifier, APIDoc): |
| return self.htmlwriter.url(identifier) |
| doc = identifier |
| |
| else: |
| raise TypeError('Expected string or APIDoc') |
| |
| def _failed_xref(self, identifier): |
| """Add an identifier to the htmlwriter's failed crossreference |
| list.""" |
| # Don't count it as a failed xref if it's a parameter of the |
| # current function. |
| if (isinstance(self.container, RoutineDoc) and |
| identifier in self.container.all_args()): |
| return |
| |
| failed_xrefs = self.htmlwriter._failed_xrefs |
| context = self.container.canonical_name |
| failed_xrefs.setdefault(identifier,{})[context] = 1 |