Index: /branches/working-0711/ccl/library/cover.lisp
===================================================================
--- /branches/working-0711/ccl/library/cover.lisp	(revision 8562)
+++ /branches/working-0711/ccl/library/cover.lisp	(revision 8563)
@@ -15,5 +15,4 @@
 (defconstant $totally-covered-style 5)
 (defconstant $partially-covered-style 6)
-
 
 (defvar *coverage-subnotes* (make-hash-table :test #'eq))
@@ -207,9 +206,12 @@
 
 
-(defun report-coverage (output-file &key (external-format :default))
+(defun report-coverage (output-file &key (external-format :default) statistics)
   "Print a code coverage report of all instrumented files into DIRECTORY.
 If DIRECTORY does not exist, it will be created. The main report will be
 printed to the file cover-index.html. The external format of the source
 files can be specified with the EXTERNAL-FORMAT parameter.
+If :STATISTICS is non-nil, a CSV file is generated with a table.  If
+:STATISTICS is a filename, that file is used, else 'statistics.csv' is
+written to the output directory.
 "
   (get-subnotes)
@@ -221,5 +223,5 @@
       as file = (and (consp data)
                      (or (probe-file (car data))
-                         (progn (warn "Cannot find ~s" (car data)) nil)))
+                         (progn (warn "Cannot find ~s, won't report coverage" (car data)) nil)))
       do (when file
            (let* ((src-name (enough-namestring file coverage-dir))
@@ -234,27 +236,43 @@
     (when (null paths)
       (error "No code coverage data available"))
-    (let ((index-file (merge-pathnames output-file "index.html")))
-      (with-open-file (stream index-file
-                              :direction :output
-                              :if-exists :supersede
-                              :if-does-not-exist :create)
-        (write-coverage-styles stream)
-        (unless paths
-          (warn "No coverage data found for any file, producing an empty report. Maybe you forgot to (SETQ CCL::*COMPILE-CODE-COVERAGE* T) before compiling?")
-          (format stream "<h3>No code coverage data found.</h3>")
-          (return-from report-coverage))
-        (format stream "<table class='summary'>")
-        (coverage-stats-head-html stream)
-        (loop for prev = nil then source-file
-          for (source-file report-name . functions) in paths
-          for even = nil then (not even)
-          do (when (or (null prev)
-                       (not (equal (pathname-directory (pathname source-file))
-                                   (pathname-directory (pathname prev)))))
-               (format stream "<tr class='subheading'><td colspan='11'>~A</td></tr>~%"
-                       (namestring (make-pathname :name nil :type nil :defaults source-file))))
-          do (coverage-stats-data-html stream source-file functions even report-name))
-        (format stream "</table>"))
-      index-file)))
+    (let* ((index-file (merge-pathnames output-file "index.html"))
+	   (stats-file (and statistics (merge-pathnames (if (or (stringp statistics)
+								(pathnamep statistics))
+							    (merge-pathnames statistics "statistics.csv")
+							    "statistics.csv")
+							output-file))))
+      (with-open-file (html-stream index-file
+				   :direction :output
+				   :if-exists :supersede
+				   :if-does-not-exist :create)
+	(if stats-file
+	    (with-open-file (stats-stream stats-file
+					  :direction :output
+					  :if-exists :supersede
+					  :if-does-not-exist :create)
+	      (report-coverage-to-streams paths html-stream stats-stream))
+	    (report-coverage-to-streams paths html-stream nil)))
+      (values index-file stats-file))))
+
+(defun report-coverage-to-streams (paths html-stream stats-stream)
+  (write-coverage-styles html-stream)
+  (unless paths
+    (warn "No coverage data found for any file, producing an empty report. Maybe you forgot to (SETQ CCL::*COMPILE-CODE-COVERAGE* T) before compiling?")
+    (format html-stream "<h3>No code coverage data found.</h3>~%")
+    (when stats-stream (format stats-stream "No code coverage data found.~%"))
+    (return-from report-coverage-to-streams))
+  (format html-stream "<table class='summary'>")
+  (coverage-stats-head html-stream stats-stream)
+  (loop for prev = nil then source-file
+	for (source-file report-name . functions) in paths
+	for even = nil then (not even)
+	do (when (or (null prev)
+		     (not (equal (pathname-directory (pathname source-file))
+				 (pathname-directory (pathname prev)))))
+	     (let ((dir (namestring (make-pathname :name nil :type nil :defaults source-file))))
+	       (format html-stream "<tr class='subheading'><td colspan='11'>~A</td></tr>~%" dir)
+	       (when stats-stream (format stats-stream "~a~%" dir))))
+	do (coverage-stats-data html-stream stats-stream source-file functions even report-name))
+  (format html-stream "</table>"))
 
 (defun colorize-function (function styles)
@@ -319,6 +337,6 @@
 
     (format html-stream "<table class='summary'>")
-    (coverage-stats-head-html html-stream)
-    (coverage-stats-data-html html-stream file functions)
+    (coverage-stats-head html-stream nil)
+    (coverage-stats-data html-stream nil file functions)
     (format html-stream "</table>")
 
@@ -363,21 +381,32 @@
 
 
-(defun coverage-stats-head-html (html-stream)
+(defun coverage-stats-head (html-stream stats-stream)
   (format html-stream "<tr class='head-row'><td></td><td class='main-head' colspan='3'>Expressions</td><td class='main-head' colspan='7'>Functions</td></tr>")
   (format html-stream "<tr class='head-row'>~{<td width='60px'>~A</td>~}</tr>"
-          (list "Source file"
-                "Total" "Covered" "% covered"
-                "Total" "Fully covered" "% fully covered" "Partly covered" "% partly covered" "Not entered" "% not entered")))
-
-(defun coverage-stats-data-html (html-stream source-file functions &optional evenp report-name)
+          '("Source file"
+	    "Total" "Covered" "% covered"
+	    "Total" "Fully covered" "% fully covered" "Partly covered" "% partly covered" "Not entered" "% not entered"))
+  (when stats-stream
+    (format stats-stream "~{~a~^,~}"
+	    '("Source file" "Expressions Total" "Expressions Covered" "% Expressions Covered"
+	      "Functions Total" "Functions Fully Covered" "% Functions Fully Covered"
+	      "Functions Partly Covered" "% Functions Partly Covered"
+	      "Functions Not Entered" "% Functions Not Entered"))))
+
+(defun coverage-stats-data (html-stream stats-stream source-file functions &optional evenp report-name)
   (format html-stream "<tr class='~:[odd~;even~]'>" evenp)
   (if report-name
     (format html-stream "<td class='text-cell'><a href='~a.html'>~a</a></td>" report-name source-file)
     (format html-stream "<td class='text-cell'>~a</td>" source-file))
-  (format html-stream "~{<td>~:[-~;~:*~a~]</td><td>~:[-~;~:*~a~]</td><td>~:[-~;~:*~5,1f%~]</td>~}"
-          (count-covered-expressions functions))
+  (when stats-stream
+    (format stats-stream "~a," source-file))
+  (let ((exp-counts (count-covered-expressions functions)))
+    (format html-stream "~{<td>~:[-~;~:*~a~]</td><td>~:[-~;~:*~a~]</td><td>~:[-~;~:*~5,1f%~]</td>~}" exp-counts)
+    (when stats-stream
+      (format stats-stream "~{~:[~;~:*~a~],~:[~;~:*~a~],~:[~;~:*~5,1f%~],~}" exp-counts)))
   (destructuring-bind (total . counts) (count-covered-functions functions)
-    (format html-stream "<td>~:[-~;~:*~a~]</td>~{<td>~:[-~;~:*~a~]</td><td>~:[-~;~:*~5,1f%~]</td>~}</tr>"
-            total counts)))
+    (format html-stream "<td>~:[-~;~:*~a~]</td>~{<td>~:[-~;~:*~a~]</td><td>~:[-~;~:*~5,1f%~]</td>~}</tr>" total counts)
+    (when stats-stream
+      (format stats-stream "~:[~;~:*~a~],~{~:[~;~:*~a~],~:[-~;~:*~5,1f%~]~^,~}~%" total counts))))
 
 (defun count-covered-functions (functions)
