From e8ee3365eff0dbb1daa517447b46b6ea148ab949 Mon Sep 17 00:00:00 2001
From: Brian Warner <warner@lothar.com>
Date: Wed, 3 Jan 2007 22:06:51 -0700
Subject: [PATCH] move figleaf2html and figleaf_htmlizer.py into our tree, for
 easier customization

---
 Makefile                               |   3 +-
 misc/figleaf2html                      |   3 +
 src/allmydata/util/figleaf_htmlizer.py | 211 +++++++++++++++++++++++++
 3 files changed, 216 insertions(+), 1 deletion(-)
 create mode 100644 misc/figleaf2html
 create mode 100644 src/allmydata/util/figleaf_htmlizer.py

diff --git a/Makefile b/Makefile
index 2c32b3b9..5f0d4927 100644
--- a/Makefile
+++ b/Makefile
@@ -36,7 +36,8 @@ test-figleaf:
 	$(PP) trial --reporter=bwverbose-figleaf $(TEST)
 
 figleaf-output:
-	figleaf2html -d coverage-html -x src/allmydata/test/figleaf.excludes
+	$(PP) python misc/figleaf2html -d coverage-html -x src/allmydata/test/figleaf.excludes
+	@echo "now point your browser at coverage-html/index.html"
 # after doing test-figleaf and figleaf-output, point your browser at
 # coverage-html/index.html
 
diff --git a/misc/figleaf2html b/misc/figleaf2html
new file mode 100644
index 00000000..9157f813
--- /dev/null
+++ b/misc/figleaf2html
@@ -0,0 +1,3 @@
+#! /usr/bin/env python
+from allmydata.util import figleaf_htmlizer
+figleaf_htmlizer.main()
diff --git a/src/allmydata/util/figleaf_htmlizer.py b/src/allmydata/util/figleaf_htmlizer.py
new file mode 100644
index 00000000..63a7960d
--- /dev/null
+++ b/src/allmydata/util/figleaf_htmlizer.py
@@ -0,0 +1,211 @@
+#! /usr/bin/env python
+import sys
+import figleaf
+from cPickle import load
+import os
+import re
+
+from optparse import OptionParser
+
+import logging
+logging.basicConfig(level=logging.DEBUG)
+
+logger = logging.getLogger('figleaf.htmlizer')
+
+def read_exclude_patterns(f):
+	if not f:
+		return []
+	exclude_patterns = []
+
+	fp = open(f)
+	for line in fp:
+		line = line.rstrip()
+		if line and not line.startswith('#'):
+			pattern = re.compile(line)
+		exclude_patterns.append(pattern)
+
+	return exclude_patterns
+
+def report_as_html(coverage, directory, exclude_patterns=[], ):
+	### now, output.
+
+	keys = coverage.keys()
+	info_dict = {}
+	for k in keys:
+		skip = False
+		for pattern in exclude_patterns:
+			if pattern.search(k):
+				logger.debug('SKIPPING %s -- matches exclusion pattern' % k)
+				skip = True
+				break
+
+		if skip:
+			continue
+
+		if k.endswith('figleaf.py'):
+			continue
+
+                if not k.startswith("/"):
+                        continue
+
+		try:
+			pyfile = open(k)
+#            print 'opened', k
+		except IOError:
+			logger.warning('CANNOT OPEN: %s' % k)
+			continue
+
+		try:
+			lines = figleaf.get_lines(pyfile)
+		except KeyboardInterrupt:
+			raise
+		except Exception, e:
+			pyfile.close()
+			logger.warning('ERROR: %s %s' % (k, str(e)))
+			continue
+
+		# ok, got all the info.  now annotate file ==> html.
+
+		covered = coverage[k]
+		n_covered = n_lines = 0
+
+		pyfile = open(k)
+		output = []
+		for i, line in enumerate(pyfile):
+			is_covered = False
+			is_line = False
+
+			i += 1
+
+			if i in covered:
+				is_covered = True
+
+				n_covered += 1
+				n_lines += 1
+			elif i in lines:
+				is_line = True
+
+				n_lines += 1
+
+			color = 'black'
+			if is_covered:
+				color = 'green'
+			elif is_line:
+				color = 'red'
+
+			line = escape_html(line.rstrip())
+			output.append('<font color="%s">%4d. %s</font>' % (color, i, line.rstrip()))
+
+		try:
+			pcnt = n_covered * 100. / n_lines
+		except ZeroDivisionError:
+			pcnt = 100
+		info_dict[k] = (n_lines, n_covered, pcnt)
+
+		html_outfile = make_html_filename(os.path.basename(k))
+		html_outfp = open(os.path.join(directory, html_outfile), 'w')
+		html_outfp.write('source file: <b>%s</b><br>\n' % (k,))
+		html_outfp.write('file stats: <b>%d lines, %d executed: %.1f%% covered</b>\n' % (n_lines, n_covered, pcnt))
+
+		html_outfp.write('<pre>\n')
+		html_outfp.write("\n".join(output))
+		html_outfp.close()
+
+	### print a summary, too.
+
+	info_dict_items = info_dict.items()
+
+	def sort_by_pcnt(a, b):
+		a = a[1][2]
+		b = b[1][2]
+
+		return -cmp(a,b)
+	info_dict_items.sort(sort_by_pcnt)
+
+	summary_lines = sum([ v[0] for (k, v) in info_dict_items])
+	summary_cover = sum([ v[1] for (k, v) in info_dict_items])
+
+	summary_pcnt = 100
+	if summary_lines:
+		summary_pcnt = float(summary_cover) * 100. / float(summary_lines)
+
+
+	pcnts = [ float(v[1]) * 100. / float(v[0]) for (k, v) in info_dict_items if v[0] ]
+	pcnt_90 = [ x for x in pcnts if x >= 90 ]
+	pcnt_75 = [ x for x in pcnts if x >= 75 ]
+	pcnt_50 = [ x for x in pcnts if x >= 50 ]
+
+	index_fp = open('%s/index.html' % (directory,), 'w')
+	index_fp.write('<title>figleaf code coverage report</title>\n')
+	index_fp.write('<h2>Summary</h2> %d files total: %d files &gt; 90%%, %d files &gt; 75%%, %d files &gt; 50%%<p>' % (len(pcnts), len(pcnt_90), len(pcnt_75), len(pcnt_50)))
+	index_fp.write('<table border=1><tr><th>Filename</th><th># lines</th><th># covered</th><th>% covered</th></tr>\n')
+	index_fp.write('<tr><td><b>totals:</b></td><td><b>%d</b></td><td><b>%d</b></td><td><b>%.1f%%</b></td></tr><tr></tr>\n' % (summary_lines, summary_cover, summary_pcnt,))
+
+	for filename, (n_lines, n_covered, percent_covered,) in info_dict_items:
+		html_outfile = make_html_filename(os.path.basename(filename))
+
+		index_fp.write('<tr><td><a href="./%s">%s</a></td><td>%d</td><td>%d</td><td>%.1f</td></tr>\n' % (html_outfile, filename, n_lines, n_covered, percent_covered,))
+
+	index_fp.write('</table>\n')
+	index_fp.close()
+
+	logger.info('reported on %d file(s) total\n' % len(info_dict))
+	return len(info_dict)
+
+def prepare_reportdir(dirname='html'):
+	try:
+		os.mkdir(dirname)
+	except OSError:                         # already exists
+		pass
+
+def make_html_filename(orig):
+	orig = os.path.splitdrive(orig)[1].replace('_', '__')
+	return orig.replace(os.path.sep, '_') + '.html'
+
+def escape_html(s):
+	s = s.replace("&", "&amp;")
+	s = s.replace("<", "&lt;")
+	s = s.replace(">", "&gt;")
+	s = s.replace('"', "&quot;")
+	return s
+
+def main():
+	###
+
+	option_parser = OptionParser()
+
+	option_parser.add_option('-x', '--exclude-patterns', action="store",
+							 dest="exclude_patterns_file",
+							 help="file containing regexp patterns to exclude")
+
+	option_parser.add_option('-d', '--output-directory', action='store',
+				 dest="output_dir",
+				 default = "html",
+				 help="directory for HTML output")
+
+	option_parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='Suppress all but error messages')
+
+	(options, args) = option_parser.parse_args()
+
+	if options.quiet:
+		logging.disable(logging.DEBUG)
+
+	### load
+
+	if not args:
+		args = ['.figleaf']
+
+	coverage = {}
+	for filename in args:
+		logger.debug("loading coverage info from '%s'\n" % (filename,))
+		d = figleaf.read_coverage(filename)
+		coverage = figleaf.combine_coverage(coverage, d)
+
+	if not coverage:
+		logger.warning('EXITING -- no coverage info!\n')
+		sys.exit(-1)
+
+	### make directory
+	prepare_reportdir(options.output_dir)
+	report_as_html(coverage, options.output_dir, read_exclude_patterns(options.exclude_patterns_file))
+
-- 
2.45.2