15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""Crocodile HTML output."""
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import shutil
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import time
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import xml.dom
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class CrocHtmlError(Exception):
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Coverage HTML error."""
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class HtmlElement(object):
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Node in a HTML file."""
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, doc, element):
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Constructor.
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      doc: XML document object.
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      element: XML element.
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.doc = doc
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.element = element
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def E(self, name, **kwargs):
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Adds a child element.
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      name: Name of element.
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      kwargs: Attributes for element.  To use an attribute which is a python
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          reserved word (i.e. 'class'), prefix the attribute name with 'e_'.
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      The child element.
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    he = HtmlElement(self.doc, self.doc.createElement(name))
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    element = he.element
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.element.appendChild(element)
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for k, v in kwargs.iteritems():
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if k.startswith('e_'):
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Remove prefix
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        element.setAttribute(k[2:], str(v))
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        element.setAttribute(k, str(v))
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return he
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def Text(self, text):
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Adds a text node.
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      text: Text to add.
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    t = self.doc.createTextNode(str(text))
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.element.appendChild(t)
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class HtmlFile(object):
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """HTML file."""
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, xml_impl, filename):
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Constructor.
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      xml_impl: DOMImplementation to use to create document.
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      filename: Path to file.
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.xml_impl = xml_impl
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    doctype = xml_impl.createDocumentType(
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        'HTML', '-//W3C//DTD HTML 4.01//EN',
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        'http://www.w3.org/TR/html4/strict.dtd')
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.doc = xml_impl.createDocument(None, 'html', doctype)
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.filename = filename
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Create head and body elements
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    root = HtmlElement(self.doc, self.doc.documentElement)
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.head = root.E('head')
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.body = root.E('body')
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def Write(self, cleanup=True):
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Writes the file.
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cleanup: If True, calls unlink() on the internal xml document.  This
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          frees up memory, but means that you can't use this file for anything
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          else.
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    f = open(self.filename, 'wt')
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.doc.writexml(f, encoding='UTF-8')
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    f.close()
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if cleanup:
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.doc.unlink()
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Prevent future uses of the doc now that we've unlinked it
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.doc = None
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#------------------------------------------------------------------------------
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)COV_TYPE_STRING = {None: 'm', 0: 'i', 1: 'E', 2: ' '}
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)COV_TYPE_CLASS = {None: 'missing', 0: 'instr', 1: 'covered', 2: ''}
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class CrocHtml(object):
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Crocodile HTML output class."""
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, cov, output_root, base_url=None):
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Constructor."""
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.cov = cov
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.output_root = output_root
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.base_url = base_url
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.xml_impl = xml.dom.getDOMImplementation()
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.time_string = 'Coverage information generated %s.' % time.asctime()
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def CreateHtmlDoc(self, filename, title):
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Creates a new HTML document.
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      filename: Filename to write to, relative to self.output_root.
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      title: Title of page
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      The document.
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    f = HtmlFile(self.xml_impl, self.output_root + '/' + filename)
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    f.head.E('title').Text(title)
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if self.base_url:
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      css_href = self.base_url + 'croc.css'
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      base_href = self.base_url + os.path.dirname(filename)
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not base_href.endswith('/'):
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        base_href += '/'
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      f.head.E('base', href=base_href)
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      css_href = '../' * (len(filename.split('/')) - 1) + 'croc.css'
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    f.head.E('link', rel='stylesheet', type='text/css', href=css_href)
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return f
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def AddCaptionForFile(self, body, path):
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Adds a caption for the file, with links to each parent dir.
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      body: Body elemement.
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      path: Path to file.
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # This is slightly different that for subdir, because it needs to have a
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # link to the current directory's index.html.
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    hdr = body.E('h2')
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    hdr.Text('Coverage for ')
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    dirs = [''] + path.split('/')
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    num_dirs = len(dirs)
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for i in range(num_dirs - 1):
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      hdr.E('a', href=(
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          '../' * (num_dirs - i - 2) + 'index.html')).Text(dirs[i] + '/')
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    hdr.Text(dirs[-1])
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def AddCaptionForSubdir(self, body, path):
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Adds a caption for the subdir, with links to each parent dir.
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      body: Body elemement.
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      path: Path to subdir.
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Link to parent dirs
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    hdr = body.E('h2')
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    hdr.Text('Coverage for ')
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    dirs = [''] + path.split('/')
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    num_dirs = len(dirs)
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for i in range(num_dirs - 1):
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      hdr.E('a', href=(
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          '../' * (num_dirs - i - 1) + 'index.html')).Text(dirs[i] + '/')
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    hdr.Text(dirs[-1] + '/')
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def AddSectionHeader(self, table, caption, itemtype, is_file=False):
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Adds a section header to the coverage table.
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      table: Table to add rows to.
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      caption: Caption for section, if not None.
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      itemtype: Type of items in this section, if not None.
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      is_file: Are items in this section files?
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if caption is not None:
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      table.E('tr').E('th', e_class='secdesc', colspan=8).Text(caption)
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sec_hdr = table.E('tr')
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if itemtype is not None:
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      sec_hdr.E('th', e_class='section').Text(itemtype)
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sec_hdr.E('th', e_class='section').Text('Coverage')
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sec_hdr.E('th', e_class='section', colspan=3).Text(
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        'Lines executed / instrumented / missing')
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graph = sec_hdr.E('th', e_class='section')
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graph.E('span', style='color:#00FF00').Text('exe')
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graph.Text(' / ')
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graph.E('span', style='color:#FFFF00').Text('inst')
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graph.Text(' / ')
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graph.E('span', style='color:#FF0000').Text('miss')
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if is_file:
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      sec_hdr.E('th', e_class='section').Text('Language')
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      sec_hdr.E('th', e_class='section').Text('Group')
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      sec_hdr.E('th', e_class='section', colspan=2)
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def AddItem(self, table, itemname, stats, attrs, link=None):
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Adds a bar graph to the element.  This is a series of <td> elements.
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      table: Table to add item to.
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      itemname: Name of item.
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      stats: Stats object.
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      attrs: Attributes dictionary; if None, no attributes will be printed.
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      link: Destination for itemname hyperlink, if not None.
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    row = table.E('tr')
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Add item name
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if itemname is not None:
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      item_elem = row.E('td')
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if link is not None:
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        item_elem = item_elem.E('a', href=link)
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      item_elem.Text(itemname)
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Get stats
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    stat_exe = stats.get('lines_executable', 0)
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    stat_ins = stats.get('lines_instrumented', 0)
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    stat_cov = stats.get('lines_covered', 0)
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    percent = row.E('td')
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Add text
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    row.E('td', e_class='number').Text(stat_cov)
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    row.E('td', e_class='number').Text(stat_ins)
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    row.E('td', e_class='number').Text(stat_exe - stat_ins)
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Add percent and graph; only fill in if there's something in there
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graph = row.E('td', e_class='graph', width=100)
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if stat_exe:
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      percent_cov = 100.0 * stat_cov / stat_exe
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      percent_ins = 100.0 * stat_ins / stat_exe
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Color percent based on thresholds
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      percent.Text('%.1f%%' % percent_cov)
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if percent_cov >= 80:
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        percent.element.setAttribute('class', 'high_pct')
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      elif percent_cov >= 60:
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        percent.element.setAttribute('class', 'mid_pct')
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        percent.element.setAttribute('class', 'low_pct')
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Graphs use integer values
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      percent_cov = int(percent_cov)
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      percent_ins = int(percent_ins)
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      graph.Text('.')
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      graph.E('span', style='padding-left:%dpx' % percent_cov,
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              e_class='g_covered')
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      graph.E('span', style='padding-left:%dpx' % (percent_ins - percent_cov),
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              e_class='g_instr')
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      graph.E('span', style='padding-left:%dpx' % (100 - percent_ins),
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              e_class='g_missing')
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if attrs:
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      row.E('td', e_class='stat').Text(attrs.get('language'))
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      row.E('td', e_class='stat').Text(attrs.get('group'))
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      row.E('td', colspan=2)
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def WriteFile(self, cov_file):
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Writes the HTML for a file.
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cov_file: croc.CoveredFile to write.
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print '  ' + cov_file.filename
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    title = 'Coverage for ' + cov_file.filename
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    f = self.CreateHtmlDoc(cov_file.filename + '.html', title)
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    body = f.body
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Write header section
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.AddCaptionForFile(body, cov_file.filename)
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Summary for this file
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    table = body.E('table')
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.AddSectionHeader(table, None, None, is_file=True)
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.AddItem(table, None, cov_file.stats, cov_file.attrs)
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    body.E('h2').Text('Line-by-line coverage:')
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Print line-by-line coverage
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if cov_file.local_path:
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      code_table = body.E('table').E('tr').E('td').E('pre')
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      flines = open(cov_file.local_path, 'rt')
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      lineno = 0
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for line in flines:
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        lineno += 1
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        line_cov = cov_file.lines.get(lineno, 2)
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        e_class = COV_TYPE_CLASS.get(line_cov)
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        code_table.E('span', e_class=e_class).Text('%4d  %s :  %s\n' % (
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            lineno,
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            COV_TYPE_STRING.get(line_cov),
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            line.rstrip()
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        ))
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      body.Text('Line-by-line coverage not available.  Make sure the directory'
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                ' containing this file has been scanned via ')
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      body.E('B').Text('add_files')
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      body.Text(' in a configuration file, or the ')
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      body.E('B').Text('--addfiles')
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      body.Text(' command line option.')
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # TODO: if file doesn't have a local path, try to find it by
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # reverse-mapping roots and searching for the file.
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    body.E('p', e_class='time').Text(self.time_string)
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    f.Write()
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def WriteSubdir(self, cov_dir):
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Writes the index.html for a subdirectory.
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cov_dir: croc.CoveredDir to write.
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print '  ' + cov_dir.dirpath + '/'
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Create the subdir if it doesn't already exist
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    subdir = self.output_root + '/' + cov_dir.dirpath
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not os.path.exists(subdir):
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      os.mkdir(subdir)
3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if cov_dir.dirpath:
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      title = 'Coverage for ' + cov_dir.dirpath + '/'
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      f = self.CreateHtmlDoc(cov_dir.dirpath + '/index.html', title)
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      title = 'Coverage summary'
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      f = self.CreateHtmlDoc('index.html', title)
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    body = f.body
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    dirs = [''] + cov_dir.dirpath.split('/')
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    num_dirs = len(dirs)
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sort_jsfile = '../' * (num_dirs - 1) + 'sorttable.js'
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    script = body.E('script', src=sort_jsfile)
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    body.E('/script')
3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Write header section
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if cov_dir.dirpath:
3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.AddCaptionForSubdir(body, cov_dir.dirpath)
3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      body.E('h2').Text(title)
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    table = body.E('table', e_class='sortable')
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    table.E('h3').Text('Coverage by Group')
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Coverage by group
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.AddSectionHeader(table, None, 'Group')
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for group in sorted(cov_dir.stats_by_group):
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.AddItem(table, group, cov_dir.stats_by_group[group], None)
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # List subdirs
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if cov_dir.subdirs:
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      table = body.E('table', e_class='sortable')
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      table.E('h3').Text('Subdirectories')
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.AddSectionHeader(table, None, 'Subdirectory')
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for d in sorted(cov_dir.subdirs):
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.AddItem(table, d + '/', cov_dir.subdirs[d].stats_by_group['all'],
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                     None, link=d + '/index.html')
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # List files
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if cov_dir.files:
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      table = body.E('table', e_class='sortable')
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      table.E('h3').Text('Files in This Directory')
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.AddSectionHeader(table, None, 'Filename',
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                            is_file=True)
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for filename in sorted(cov_dir.files):
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        cov_file = cov_dir.files[filename]
4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.AddItem(table, filename, cov_file.stats, cov_file.attrs,
4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                     link=filename + '.html')
4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    body.E('p', e_class='time').Text(self.time_string)
4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    f.Write()
4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def WriteRoot(self):
4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Writes the files in the output root."""
4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Find ourselves
4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    src_dir = os.path.split(self.WriteRoot.func_code.co_filename)[0]
4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Files to copy into output root
4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    copy_files = ['croc.css']
4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Third_party files to copy into output root
4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    third_party_files = ['sorttable.js']
4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Copy files from our directory into the output directory
4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for copy_file in copy_files:
4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print '  Copying %s' % copy_file
4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      shutil.copyfile(os.path.join(src_dir, copy_file),
4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                      os.path.join(self.output_root, copy_file))
4215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Copy third party files from third_party directory into
4225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # the output directory
4235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    src_dir = os.path.join(src_dir, 'third_party')
4245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for third_party_file in third_party_files:
4255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print '  Copying %s' % third_party_file
4265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      shutil.copyfile(os.path.join(src_dir, third_party_file),
4275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                      os.path.join(self.output_root, third_party_file))
4285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def Write(self):
4305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Writes HTML output."""
4315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print 'Writing HTML to %s...' % self.output_root
4335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Loop through the tree and write subdirs, breadth-first
4355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # TODO: switch to depth-first and sort values - makes nicer output?
4365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    todo = [self.cov.tree]
4375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    while todo:
4385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cov_dir = todo.pop(0)
4395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Append subdirs to todo list
4415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      todo += cov_dir.subdirs.values()
4425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Write this subdir
4445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.WriteSubdir(cov_dir)
4455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Write files in this subdir
4475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for cov_file in cov_dir.files.itervalues():
4485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.WriteFile(cov_file)
4495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Write files in root directory
4515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.WriteRoot()
452