docs: def renderIndex(self, relpath="", refresh=0, refresh_index=0):
          """Returns HTML index code for this entry.
          If 'relpath' is empty, renders complete index.html file.
          If 'relpath' is not empty, then index is being included into a top-level log, and
          relpath should be passed to all sub-renderers.
          In this case the entry may make use of its cached_include file, if that is valid.
          If 'refresh' is set to a timestamp, then any subproducts (thumbnails, HTML caches, etc.) older than the timestamp will need to be regenerated.
          If 'refresh_index' is set to a timestamp, then any index files older than the timestamp will need to be regenerated.
          If 'relpath' is empty and 'prev', 'next' and/or 'up' is set, then Prev/Next/Up links will be inserted
          """
          # check if cache can be used
          refresh_index = max(refresh, refresh_index)
          dprintf(2, "%s: rendering HTML index with relpath='%s', refresh=%s refresh_index=%s\n", self.pathname, relpath,
                  time.strftime("%x %X", time.localtime(refresh)),
                  time.strftime("%x %X", time.localtime(refresh_index)))
          if relpath and self.cached_include_valid:
              try:
                  if os.path.getmtime(self.cached_include) >= refresh_index:
                      dprintf(2, "using include cache %s\n", self.cached_include)
                      return open(self.cached_include).read()
                  else:
                      dprintf(2, "include cache %s out of date, will regenerate\n", self.cached_include)
                      self.cached_include_valid = False
              except:
                  print("Error reading cached include code from %s, will regenerate" % self.cached_include)
                  if verbosity.get_verbose() > 0:
                      dprint(1, "Error traceback follows:")
                      traceback.print_exc()
                  self.cached_include_valid = False
          # form up attributes for % operator
          attrs = dict(self.__dict__)
          attrs['timestr'] = time.strftime("%x %X", time.localtime(self.timestamp))
          attrs['relpath'] = relpath
          html = ""
          # replace title and comments for ignored entries
          if self.ignore:
              attrs['title'] = "This is not a real log entry"
              attrs['comment'] = """This entry was saved by PURR because the user
        chose to ignore and/or banish some data products. PURR has stored this
        information here for its opwn internal and highly nefarious purposes.
        This entry is will not appear in the log."""
          # replace < and > in title and comments
          attrs['title'] = attrs['title'].replace("<", "&lt;").replace(">", "&gt;")
          # write header if asked
          if not relpath:
              icon = Purr.RenderIndex.renderIcon(24, "..")
              html += """<HTML><BODY>
        <TITLE>%(title)s</TITLE>""" % attrs
              if self._prev_link or self._next_link or self._up_link:
                  html += """<DIV ALIGN=right><P>%s %s %s</P></DIV>""" % (
                      (self._prev_link and "<A HREF=\"%s\">&lt;&lt;Previous</A>" % self._prev_link) or "",
                      (self._up_link and "<A HREF=\"%s\">Up</A>" % self._up_link) or "",
                      (self._next_link and "<A HREF=\"%s\">Next&gt;&gt;</A>" % self._next_link) or ""
                  )
              html += ("<H2>" + icon + """ <A CLASS="TITLE" TIMESTAMP=%(timestamp)d>%(title)s</A></H2>""") % attrs
          else:
              icon = Purr.RenderIndex.renderIcon(24)
              html += """
          <HR WIDTH=100%%>
          <H2>""" + icon + """ %(title)s</H2>""" % attrs
          # write comments
          html += """
          <DIV ALIGN=right><P><SMALL>Logged on %(timestr)s</SMALL></P></DIV>\n
  
          <A CLASS="COMMENTS">\n""" % attrs
          # add comments
          logmode = False
          for cmt in self.comment.split("\n"):
              cmt = cmt.replace("<", "&lt;").replace(">", "&gt;").replace("&lt;BR&gt;", "<BR>")
              html += """      <P>%s</P>\n""" % cmt
          html += """    </A>\n"""
          # add data products
          if self.dps:
              have_real_dps = bool([dp for dp in self.dps if not dp.ignored])
              if have_real_dps:
                  html += """
          <H3>Data products</H3>
          <TABLE BORDER=1 FRAME=box RULES=all CELLPADDING=5>\n"""
              for dp in self.dps:
                  dpattrs = dict(dp.__dict__)
                  dpattrs['comment'] = dpattrs['comment'].replace("<", "&lt;"). \
                      replace(">", "&gt;").replace('"', "''")
                  # if generating complete index, write empty anchor for each DP
                  if not relpath:
                      if dp.ignored:
                          html += """
              <A CLASS="DP" SRC="%(sourcepath)s" POLICY="%(policy)s" COMMENT="%(comment)s"></A>\n""" % dpattrs
                      # write normal anchor for normal products
                      else:
                          dpattrs['relpath'] = relpath
                          dpattrs['basename'] = os.path.basename(dp.filename)
                          html += """
              <A CLASS="DP" FILENAME="%(filename)s" SRC="%(sourcepath)s" POLICY="%(policy)s" QUIET=%(quiet)d TIMESTAMP=%(timestamp).6f RENDER="%(render)s" COMMENT="%(comment)s"></A>\n""" % dpattrs
                  # render a table row
                  if not dp.ignored:
                      renderer = Purr.Render.makeRenderer(dp.render, dp, refresh=refresh)
                      html += Purr.Render.renderInTable(renderer, relpath)
              if have_real_dps:
                  html += """
          </TABLE>"""
          # write footer
          if not relpath:
              html += "</BODY></HTML>\n"
          else:
              # now, write to include cache, if being included
              open(self.cached_include, 'w').write(html)
              self.cached_include_valid = True
          return html