Source code for fvm.tables

# Copyright 2024-2026 Universidad de Sevilla
# SPDX-License-Identifier: Apache-2.0

"""Functions to display tables in the console and save them as HTML and text files."""
import os

from rich.table import Table
from rich.console import Console

[docs] def show_step_summary(step_summary, error, warning, inconclusive=None, proven=None, outdir=None, step=None): """ Displays a table with the step summary. :param step_summary: dict with the data summary Example structure:: { "Violation": {"count": 2, "checks": {...}}, "Caution": {"count": 1, "checks": {...}}, ... } :type step_summary: dict :param error: category name used as 'Error' row (e.g., 'Violation') :type error: str :param warning: category name used as 'Warning' row (e.g., 'Caution') :type warning: str :param inconclusive: category name used as 'Inconclusive' row (optional) :type inconclusive: str or None :param proven: category name used as 'Proven' row (optional) :type proven: str or None :param outdir: directory where the HTML file will be saved :type outdir: str or None :param step: name of the step :type step: str or None """ step_summary_console = Console(force_terminal=True, force_interactive=False, record=True) categories = { f"{error}": error, f"{warning}": warning, } if inconclusive: categories[f"{inconclusive}"] = inconclusive if proven: categories[f"{proven}"] = proven row_colors = { f"{error}": "red", f"{warning}": "yellow", f"{inconclusive}": "white", f"{proven}": "green" } # Check if at least one row has checks show_checks = any( step_summary.get(cat, {}).get("checks") for cat in categories.values() ) table = Table(title=f"[cyan]{step} summary[/cyan]") table.add_column("Severity", style="bold", justify="left") table.add_column("Count", style="bold", justify="right") if show_checks: table.add_column("Checks", style="bold", justify="left") # Add rows for label, category_name in categories.items(): data = step_summary.get(category_name, {"count": 0, "checks": {}}) count = data.get("count", 0) checks = data.get("checks", {}) # Skip optional rows with 0 count if label in [f"{inconclusive}", f"{proven}"] and count == 0: continue # Row color: green if count is 0 for Error/Warning if label in [f"{error}", f"{warning}"] and count == 0: color = "green" else: color = row_colors[label] if show_checks: if checks: checks_str = "\n".join([f"{k}: {v}" for k, v in checks.items()]) else: checks_str = "-" table.add_row( f"[{color}]{label}[/{color}]", f"[{color}]{count}[/{color}]", f"[{color}]{checks_str}[/{color}]" ) else: table.add_row( f"[{color}]{label}[/{color}]", f"[{color}]{count}[/{color}]" ) step_summary_console.print(table) html_file = os.path.join(outdir, f"{step}_summary.html") text_file = os.path.join(outdir, f"{step}_summary.txt") step_summary_console.save_html(html_file, clear=False) step_summary_console.save_text(text_file)
[docs] def show_friendliness_score(score, outdir=None, step=None): """ Displays the friendliness score in a table format. :param score: friendliness score as a float (0 to 100) :type score: float :param outdir: directory where the HTML file will be saved :type outdir: str or None :param step: name of the step :type step: str or None """ friendliness_console = Console(force_terminal=True, force_interactive=False, record=True) table = Table(show_header=True) table.add_column("Friendliness", justify="center") table.add_row(f"{score:.2f}%", style="bold green") friendliness_console.print(table) html_file = os.path.join(outdir, f"{step}_summary.html") text_file = os.path.join(outdir, f"{step}_summary.txt") friendliness_console.save_html(html_file, clear=False) friendliness_console.save_text(text_file)
[docs] def show_coverage_summary(data, title="xxx", outdir=None, step=None): """ Displays a table with the coverage summary. :param data: list of dicts with the data summary Example structure:: [ { "Status": "pass" or "fail" or "omit", "Coverage Type": "toggle" or "fsm state" or ..., "Intermediate Column 1": value, ... "Percentage": "85.00%", "Goal": "80.00%" }, ... ] :type data: list of dicts :param title: Title of the table :type title: str :param outdir: directory where the HTML file will be saved :type outdir: str or None :param step: name of the step :type step: str or None """ console = Console(force_terminal=True, force_interactive=False, record=True) table = Table(title=f"[cyan]{title}[/cyan]", show_header=True, header_style="bold") if not data: return # Fixed columns table.add_column("Status", justify="center") table.add_column("Coverage Type", justify="left", style="cyan") # Intermediate columns excluded = {"Status", "Coverage Type", "Percentage", "Goal"} intermediate_cols = [k for k in data[0].keys() if k not in excluded] for col in intermediate_cols: table.add_column(col, justify="right") # Final columns table.add_column("Percentage", justify="right") table.add_column("Goal", justify="right") for row in data: # Color Status and Percentage status = row.get("Status", "omit") if status == "pass": status_str = f"[bold green]{status}[/bold green]" perc_str = f"[bold green]{row['Percentage']}[/bold green]" elif status == "fail": status_str = f"[bold red]{status}[/bold red]" perc_str = f"[bold red]{row['Percentage']}[/bold red]" else: status_str = f"[bold white]{status}[/bold white]" perc_str = f"[bold white]{row['Percentage']}[/bold white]" # Intermediate column values in order intermediate_vals = [str(row[col]) for col in intermediate_cols] table.add_row( status_str, row["Coverage Type"], *intermediate_vals, perc_str, row["Goal"] ) console.print(table) html_file = os.path.join(outdir, f"{step}_summary.html") text_file = os.path.join(outdir, f"{step}_summary.txt") console.save_html(html_file, clear=False) console.save_text(text_file)
[docs] def show_prove_summary(data, title="Property Summary", outdir=None, step=None): """ Displays a table with the prove summary. :param data: dict with the data summary Example structure:: { "Proven": {"count": 5, "items": [...]}, "Vacuous": {"count": 2, "items": [...]}, "Fired": {"count": 1, "items": [...]}, ... } :type data: dict :param title: Title of the table :type title: str :param outdir: directory where the HTML file will be saved :type outdir: str or None :param step: name of the step :type step: str or None """ console = Console(force_terminal=True, force_interactive=False, record=True) table = Table(title=f"[cyan]{title}[/cyan]", show_header=True, header_style="bold") if not data: return category_colors = { "Proven": "bold green", "Vacuous": "bold yellow", "Fired": "bold red", "Fired with Warning": "bold red", "Covered": "bold green", "Covered with Warning": "bold yellow", "Uncoverable": "bold red", "Inconclusive": "bold white" } has_items = any(info['items'] for cat, info in data.items() if cat not in ["Proven", "Covered"]) table.add_column("Result", max_width=12) table.add_column("Count", justify="right") if has_items: table.add_column("Names", max_width=50) for category, info in data.items(): count = str(info['count']) items = "-" if category not in ["Proven", "Covered"] and has_items: items = ", ".join(info['items']) if info['items'] else "-" style = category_colors.get(category, "") if has_items: table.add_row(category, count, items, style=style) else: table.add_row(category, count, style=style) console.print(table) html_file = os.path.join(outdir, f"{step}_summary.html") text_file = os.path.join(outdir, f"{step}_summary.txt") console.save_html(html_file, clear=False) console.save_text(text_file)