module Rcov class HTMLCoverage < BaseFormatter # :nodoc: include XX::XHTML include XX::XMLish require 'fileutils' JAVASCRIPT_PROLOG = <<-EOS // span.cross-ref { display: none }" ) // ]]> EOS CSS_PROLOG = <<-EOS span.cross-ref-title { font-size: 140%; } span.cross-ref a { text-decoration: none; } span.cross-ref { background-color:#f3f7fa; border: 1px dashed #333; margin: 1em; padding: 0.5em; overflow: hidden; } a.crossref-toggle { text-decoration: none; } span.marked0 { background-color: rgb(185, 210, 200); display: block; } span.marked1 { background-color: rgb(190, 215, 205); display: block; } span.inferred0 { background-color: rgb(255, 255, 240); display: block; } span.inferred1 { background-color: rgb(255, 255, 240); display: block; } span.uncovered0 { background-color: rgb(225, 110, 110); display: block; } span.uncovered1 { background-color: rgb(235, 120, 120); display: block; } span.overview { border-bottom: 8px solid black; } div.overview { border-bottom: 8px solid black; } body { font-family: verdana, arial, helvetica; } div.footer { font-size: 68%; margin-top: 1.5em; } h1, h2, h3, h4, h5, h6 { margin-bottom: 0.5em; } h5 { margin-top: 0.5em; } .hidden { display: none; } div.separator { height: 10px; } /* Commented out for better readability, esp. on IE */ /* table tr td, table tr th { font-size: 68%; } td.value table tr td { font-size: 11px; } */ table.percent_graph { height: 12px; border: #808080 1px solid; empty-cells: show; } table.percent_graph td.covered { height: 10px; background: #00f000; } table.percent_graph td.uncovered { height: 10px; background: #e00000; } table.percent_graph td.NA { height: 10px; background: #eaeaea; } table.report { border-collapse: collapse; width: 100%; } table.report td.heading { background: #dcecff; border: #d0d0d0 1px solid; font-weight: bold; text-align: center; } table.report td.heading:hover { background: #c0ffc0; } table.report td.text { border: #d0d0d0 1px solid; } table.report td.value, table.report td.lines_total, table.report td.lines_code { text-align: right; border: #d0d0d0 1px solid; } table.report tr.light { background-color: rgb(240, 240, 245); } table.report tr.dark { background-color: rgb(230, 230, 235); } EOS DEFAULT_OPTS = {:color => false, :fsr => 30, :destdir => "coverage", :callsites => false, :cross_references => false, :validator_links => true, :charset => nil } def initialize(opts = {}) options = DEFAULT_OPTS.clone.update(opts) super(options) @dest = options[:destdir] @color = options[:color] @fsr = options[:fsr] @do_callsites = options[:callsites] @do_cross_references = options[:cross_references] @span_class_index = 0 @show_validator_links = options[:validator_links] @charset = options[:charset] end def execute return if @files.empty? FileUtils.mkdir_p @dest create_index(File.join(@dest, "index.html")) each_file_pair_sorted do |filename, fileinfo| create_file(File.join(@dest, mangle_filename(filename)), fileinfo) end end private def blurb xmlish_ { p_ { t_{ "Generated using the " } a_(:href => "http://eigenclass.org/hiki.rb?rcov") { t_{ "rcov code coverage analysis tool for Ruby" } } t_{ " version #{Rcov::VERSION}." } } }.pretty end def output_color_table? true end def default_color "rgb(240, 240, 245)" end def default_title "C0 code coverage information" end def format_overview(*file_infos) table_text = xmlish_ { table_(:class => "report") { thead_ { tr_ { ["Name", "Total lines", "Lines of code", "Total coverage", "Code coverage"].each do |heading| td_(:class => "heading") { heading } end } } tbody_ { color_class_index = 1 color_classes = %w[light dark] file_infos.each do |f| color_class_index += 1 color_class_index %= color_classes.size tr_(:class => color_classes[color_class_index]) { td_ { case f.name when "TOTAL" then t_ { "TOTAL" } else a_(:href => mangle_filename(f.name)){ t_ { f.name } } end } [[f.num_lines, "lines_total"], [f.num_code_lines, "lines_code"]].each do |value, css_class| td_(:class => css_class) { tt_{ value } } end [[f.total_coverage, "coverage_total"], [f.code_coverage, "coverage_code"]].each do |value, css_class| value *= 100 td_ { table_(:cellpadding => "0", :cellspacing => "0", :align => "right") { tr_ { td_ { tt_(:class => css_class) { "%3.1f%%" % value } x_ " " } ivalue = value.round td_ { table_(:class => "percent_graph", :cellpadding => "0", :cellspacing => "0", :width => "100") { tr_ { td_(:class => "covered", :width => ivalue.to_s) td_(:class => "uncovered", :width => (100-ivalue).to_s) } } } } } } end } end } } } table_text.pretty end class SummaryFileInfo # :nodoc: def initialize(obj) @o = obj end def num_lines @o.num_lines end def num_code_lines @o.num_code_lines end def code_coverage @o.code_coverage end def total_coverage @o.total_coverage end def name "TOTAL" end end def create_index(destname) files = [SummaryFileInfo.new(self)] + each_file_pair_sorted.map{|k,v| v} title = default_title output = xhtml_ { html_ { head_ { if @charset meta_("http-equiv".to_sym => "Content-Type", :content => "text/html;charset=#{@charset}") end title_{ title } style_(:type => "text/css") { t_{ "body { background-color: #{default_color}; }" } } style_(:type => "text/css") { CSS_PROLOG } script_(:type => "text/javascript") { h_{ JAVASCRIPT_PROLOG } } } body_ { h3_{ t_{ title } } p_ { t_{ "Generated on #{Time.new.to_s} with " } a_(:href => Rcov::UPSTREAM_URL){ "rcov #{Rcov::VERSION}" } } p_ { "Threshold: #{@output_threshold}%" } if @output_threshold != 101 hr_ x_{ format_overview(*files) } hr_ x_{ blurb } if @show_validator_links p_ { a_(:href => "http://validator.w3.org/check/referer") { img_(:src => "http://www.w3.org/Icons/valid-xhtml11", :alt => "Valid XHTML 1.1!", :height => "31", :width => "88") } a_(:href => "http://jigsaw.w3.org/css-validator/check/referer") { img_(:style => "border:0;width:88px;height:31px", :src => "http://jigsaw.w3.org/css-validator/images/vcss", :alt => "Valid CSS!") } } end } } } lines = output.pretty.to_a lines.unshift lines.pop if /DOCTYPE/ =~ lines[-1] File.open(destname, "w") do |f| f.puts lines end end def format_lines(file) result = "" last = nil end_of_span = "" format_line = "%#{file.num_lines.to_s.size}d" file.num_lines.times do |i| line = file.lines[i].chomp marked = file.coverage[i] count = file.counts[i] spanclass = span_class(file, marked, count) if spanclass != last result += end_of_span case spanclass when nil end_of_span = "" else result += %[] end_of_span = "" end end result += %[] + (format_line % (i+1)) + " " + create_cross_refs(file.name, i+1, CGI.escapeHTML(line)) + "\n" last = spanclass end result += end_of_span "
#{result}" end def create_cross_refs(filename, lineno, linetext) return linetext unless @callsite_analyzer && @do_callsites ref_blocks = [] _get_defsites(ref_blocks, filename, lineno, "Calls", linetext) do |ref| if ref.file where = "at #{normalize_filename(ref.file)}:#{ref.line}" else where = "(C extension/core)" end CGI.escapeHTML("%7d %s" % [ref.count, "#{ref.klass}##{ref.mid} " + where]) end _get_callsites(ref_blocks, filename, lineno, "Called by", linetext) do |ref| r = "%7d %s" % [ref.count, "#{normalize_filename(ref.file||'C code')}:#{ref.line} " + "in '#{ref.klass}##{ref.mid}'"] CGI.escapeHTML(r) end create_cross_reference_block(linetext, ref_blocks) end def create_cross_reference_block(linetext, ref_blocks) return linetext if ref_blocks.empty? ret = "" @cross_ref_idx ||= 0 @known_files ||= sorted_file_pairs.map{|fname, finfo| normalize_filename(fname)} ret << %[#{linetext}] ret << %[] ret << "\n" ref_blocks.each do |refs, toplabel, label_proc| unless !toplabel || toplabel.empty? ret << %!#{toplabel}\n! end refs.each do |dst| dstfile = normalize_filename(dst.file) if dst.file dstline = dst.line label = label_proc.call(dst) if dst.file && @known_files.include?(dstfile) ret << %[#{label}] else ret << label end ret << "\n" end end ret << "" end def span_class(sourceinfo, marked, count) @span_class_index ^= 1 case marked when true "marked#{@span_class_index}" when :inferred "inferred#{@span_class_index}" else "uncovered#{@span_class_index}" end end def create_file(destfile, fileinfo) #$stderr.puts "Generating #{destfile.inspect}" body = format_overview(fileinfo) + format_lines(fileinfo) title = fileinfo.name + " - #{default_title}" do_ctable = output_color_table? output = xhtml_ { html_ { head_ { if @charset meta_("http-equiv".to_sym => "Content-Type", :content => "text/html;charset=#{@charset}") end title_{ title } style_(:type => "text/css") { t_{ "body { background-color: #{default_color}; }" } } style_(:type => "text/css") { CSS_PROLOG } script_(:type => "text/javascript") { h_ { JAVASCRIPT_PROLOG } } style_(:type => "text/css") { h_ { colorscale } } } body_ { h3_{ t_{ default_title } } p_ { t_{ "Generated on #{Time.new.to_s} with " } a_(:href => Rcov::UPSTREAM_URL){ "rcov #{Rcov::VERSION}" } } hr_ if do_ctable # this kludge needed to ensure .pretty doesn't mangle it x_ { <