use crate::config::RedactionSummaryItem; use crate::ui::theme::{ThemeEntry, ThemeStyle};
use owo_colors::OwoColorize;
use std::collections::HashMap;
use std::io::{self, Write};
use anyhow::Result;
use crate::utils::redaction::RedactionMatch; use crate::tools::sanitize_shell::CompiledRules; use crate::ui::output_format; use crate::commands::stats::format_rule_name_for_json;
pub fn print_summary<W: Write>(
summary: &[RedactionSummaryItem],
writer: &mut W, theme_map: &HashMap<ThemeEntry, ThemeStyle>,
) -> Result<()> {
if summary.is_empty() {
writeln!(io::stderr(), "\n{}\n", output_format::get_styled_text("No redactions applied.", ThemeEntry::Info, theme_map))?;
return Ok(());
}
let header = output_format::get_styled_text("\n--- Redaction Summary ---", ThemeEntry::Header, theme_map);
writeln!(io::stderr(), "{}", header)?;
for item in summary {
let rule_name_styled = output_format::get_styled_text(&item.rule_name, ThemeEntry::SummaryRuleName, theme_map);
let occurrences_styled = output_format::get_styled_text(
&format!(" ({} occurrences)", item.occurrences),
ThemeEntry::SummaryOccurrences,
theme_map,
);
writeln!(writer, "{}{}", rule_name_styled, occurrences_styled)?;
if !item.original_texts.is_empty() {
writeln!(writer, " {}", output_format::get_styled_text("Original Values:", ThemeEntry::Info, theme_map))?;
for text in &item.original_texts {
writeln!(writer, " - {}", text.red())?;
}
}
if !item.sanitized_texts.is_empty() {
writeln!(writer, " {}", output_format::get_styled_text("Sanitized Values:", ThemeEntry::Info, theme_map))?;
for text in &item.sanitized_texts {
writeln!(writer, " - {}", text.green())?;
}
}
}
writeln!(io::stderr(), "{}\n", output_format::get_styled_text("-------------------------", ThemeEntry::Header, theme_map))?;
Ok(())
}
pub fn print_summary_for_stats_mode<W: Write>(
aggregated_matches: &HashMap<String, Vec<&RedactionMatch>>,
compiled_rules: &CompiledRules, writer: &mut W,
theme_map: &HashMap<ThemeEntry, ThemeStyle>,
sample_matches_count: Option<usize>,
) -> Result<()> {
let header = output_format::get_styled_text("\n--- Redaction Statistics ---", ThemeEntry::Header, theme_map);
writeln!(writer, "{}", header)?;
let mut active_rule_names: Vec<String> = compiled_rules.rules.iter()
.map(|r| r.name.clone())
.collect();
active_rule_names.sort();
let mut has_any_matches = false;
for rule_name in active_rule_names {
let matches_for_rule = aggregated_matches.get(&rule_name);
let total_occurrences = matches_for_rule.map_or(0, |matches| matches.len());
if total_occurrences == 0 {
continue; }
has_any_matches = true;
let display_name = format_rule_name_for_json(&rule_name);
let match_plural = if total_occurrences == 1 { "match" } else { "matches" };
let line_content = format!("{}: {} {}", display_name, total_occurrences, match_plural);
let styled_line = output_format::get_styled_text(&line_content, ThemeEntry::SummaryRuleName, theme_map);
writeln!(writer, "{}", styled_line)?;
if let Some(matches) = matches_for_rule {
if let Some(num_samples) = sample_matches_count {
if num_samples > 0 {
writeln!(writer, " {}", output_format::get_styled_text("Sample Matches:", ThemeEntry::Info, theme_map))?;
let mut unique_samples: Vec<String> = matches
.iter()
.map(|m| m.original_string.clone())
.collect::<std::collections::HashSet<_>>()
.into_iter()
.collect();
unique_samples.sort();
for (i, sample) in unique_samples.iter().take(num_samples).enumerate() {
writeln!(writer, " - {}", sample.red())?;
if i == num_samples - 1 && unique_samples.len() > num_samples {
writeln!(writer, " ... ({} more unique samples)", unique_samples.len() - num_samples)?;
}
}
}
}
}
}
if !has_any_matches {
writeln!(writer, "\n{}\n", output_format::get_styled_text("No redaction matches found.", ThemeEntry::Info, theme_map))?;
}
writeln!(writer, "{}\n", output_format::get_styled_text("--------------------------", ThemeEntry::Header, theme_map))?;
Ok(())
}