# frozen_string_literal: true# Released under the MIT License.# Copyright, 2018-2025, by Samuel Williams.require_relative"statistics"require_relative"wrapper"moduleCovered# Generates a detailed terminal coverage report.classSummary# Initialize the report with an optional coverage threshold.# @parameter threshold [Numeric | Nil] The minimum ratio a file must meet to be omitted from the detailed output.definitialize(threshold: 1.0)@threshold=thresholdend# Build a styled terminal for the given output.# @parameter output [IO] The output stream.# @returns [Console::Terminal] The styled terminal wrapper.defterminal(output)require"console/terminal"Console::Terminal.for(output).tapdo|terminal|terminal[:path]||=terminal.style(nil,nil,:bold,:underline)terminal[:brief_path]||=terminal.style(:yellow)terminal[:uncovered_prefix]||=terminal.style(:red)terminal[:covered_prefix]||=terminal.style(:green)terminal[:ignored_prefix]||=terminal.style(nil,nil,:faint)terminal[:header_prefix]||=terminal.style(nil,nil,:faint)terminal[:uncovered_code]||=terminal.style(:red)terminal[:covered_code]||=terminal.style(:green)terminal[:ignored_code]||=terminal.style(nil,nil,:faint)terminal[:annotations]||=terminal.style(:blue)terminal[:error]||=terminal.style(:red)endend# Enumerate coverage below the threshold and return aggregate statistics.# @parameter wrapper [Covered::Base] The coverage wrapper to enumerate.# @yields {|coverage| ...} Coverage whose ratio is below the configured threshold.# @parameter coverage [Covered::Coverage] The coverage object below the threshold.# @returns [Covered::Statistics] Statistics for all coverage objects, including omitted ones.defeach(wrapper)statistics=Statistics.newwrapper.eachdo|coverage|statistics<<coverageif@threshold.nil?orcoverage.ratio<@thresholdyieldcoverageendendreturnstatisticsend# Print any annotations for the given line.# @parameter terminal [Console::Terminal] The terminal to write to.# @parameter coverage [Covered::Coverage] The coverage being rendered.# @parameter line [String] The source line.# @parameter line_offset [Integer] The current line number.defprint_annotations(terminal,coverage,line,line_offset)ifannotations=coverage.annotations[line_offset]prefix="#{line_offset}|".rjust(8)+"*|".rjust(8)terminal.writeprefix,style: :ignored_prefixterminal.writeline.match(/^\s+/)terminal.puts"\##{annotations.join(", ")}",style: :annotationsendend# Print the line and hit-count header.# @parameter terminal [Console::Terminal] The terminal to write to.defprint_line_header(terminal)prefix="Line|".rjust(8)+"Hits|".rjust(8)terminal.putsprefix,style: :header_prefixend# Print a single source line with coverage styling.# @parameter terminal [Console::Terminal] The terminal to write to.# @parameter line [String] The source line.# @parameter line_offset [Integer] The current line number.# @parameter count [Integer | Nil] The execution count for the line.defprint_line(terminal,line,line_offset,count)prefix="#{line_offset}|".rjust(8)+"#{count}|".rjust(8)ifcount==nilterminal.writeprefix,style: :ignored_prefixterminal.writeline,style: :ignored_codeelsifcount==0terminal.writeprefix,style: :uncovered_prefixterminal.writeline,style: :uncovered_codeelseterminal.writeprefix,style: :covered_prefixterminal.writeline,style: :covered_codeend# If there was no newline at end of file, we add one:unlessline.end_with?$/terminal.putsendend# Print line-by-line coverage for one source file.# @parameter terminal [Console::Terminal] The terminal to write to.# @parameter coverage [Covered::Coverage] The coverage to render.defprint_coverage(terminal,coverage)line_offset=1counts=coverage.countscoverage.readdo|file|print_line_header(terminal)file.each_linedo|line|count=counts[line_offset]print_annotations(terminal,coverage,line,line_offset)print_line(terminal,line,line_offset,count)line_offset+=1endendend# Print an error raised while rendering coverage.# @parameter terminal [Console::Terminal] The terminal to write to.# @parameter error [Exception] The rendering error.defprint_error(terminal,error)terminal.puts"Error: #{error.message}",style: :errorterminal.putserror.backtraceend# Print the detailed coverage report.# A coverage array gives, for each line, the number of line executions by the interpreter. A `nil` value means coverage is finished for this line (lines like `else` and `end`).# @parameter wrapper [Covered::Base] The coverage wrapper to report.# @parameter output [IO] The output stream.# @parameter options [Hash] Options forwarded to {print_coverage}.defcall(wrapper,output=$stdout,**options)terminal=self.terminal(output)statistics=self.each(wrapper)do|coverage|path=wrapper.relative_path(coverage.path)terminal.puts""terminal.putspath,style: :pathbeginprint_coverage(terminal,coverage,**options)rescue=>errorprint_error(terminal,error)endcoverage.print(output)endterminal.putsstatistics.print(output)endend# Generates a detailed report without applying a coverage threshold.classFullSummary<Summary# Initialize a full summary report.definitializesuper(threshold: nil)endend# Suppresses coverage report output.classQuiet# Generate no output.# @parameter wrapper [Covered::Base] The coverage wrapper to ignore.# @parameter output [IO] The output stream to ignore.defcall(wrapper,output=$stdout)# Silent.endendend