use std::io;
use std::path::Path;
use crate::code_unit::CodeUnit;
use crate::grouper::DuplicateGroup;
use crate::output::reporter::display_path;
pub struct GroupSection {
title: &'static str,
empty_message: Option<&'static str>,
show_similarity: bool,
show_parent: bool,
}
impl GroupSection {
#[must_use]
pub const fn exact() -> Self {
Self {
title: "Exact Duplicates",
empty_message: Some("No exact duplicates found."),
show_similarity: false,
show_parent: false,
}
}
#[must_use]
pub const fn near() -> Self {
Self {
title: "Near Duplicates",
empty_message: Some("No near duplicates found."),
show_similarity: true,
show_parent: false,
}
}
#[must_use]
pub const fn sub_exact() -> Self {
Self {
title: "Sub-function Exact Duplicates",
empty_message: None,
show_similarity: false,
show_parent: true,
}
}
#[must_use]
pub const fn sub_near() -> Self {
Self {
title: "Sub-function Near Duplicates",
empty_message: None,
show_similarity: true,
show_parent: true,
}
}
pub fn write(
&self,
groups: &[DuplicateGroup],
base: Option<&Path>,
writer: &mut dyn io::Write,
) -> io::Result<()> {
if groups.is_empty() {
return self.write_empty(writer);
}
writeln!(writer, "{}", self.title)?;
writeln!(writer, "{}", "=".repeat(self.title.len()))?;
writeln!(writer)?;
for (index, group) in groups.iter().enumerate() {
self.write_group(index + 1, group, base, writer)?;
}
Ok(())
}
fn write_empty(&self, writer: &mut dyn io::Write) -> io::Result<()> {
match self.empty_message {
Some(message) => writeln!(writer, "{message}"),
None => Ok(()),
}
}
fn write_group(
&self,
number: usize,
group: &DuplicateGroup,
base: Option<&Path>,
writer: &mut dyn io::Write,
) -> io::Result<()> {
writeln!(writer, "{}", self.heading(number, group))?;
for member in &group.members {
writeln!(writer, "{}", self.member_line(member, base))?;
}
writeln!(writer)
}
fn heading(&self, number: usize, group: &DuplicateGroup) -> String {
let fingerprint = group.fingerprint.to_hex();
let members = group.members.len();
if self.show_similarity {
format!(
"Group {number} (fingerprint: {fingerprint}, similarity: {:.0}%, {members} members):",
group.similarity * 100.0
)
} else {
format!("Group {number} (fingerprint: {fingerprint}, {members} members):")
}
}
fn member_line(&self, member: &CodeUnit, base: Option<&Path>) -> String {
format!(
" - {} ({}){} at {}:{}-{}",
member.name,
member.kind,
self.parent_of(member),
display_path(base, &member.file),
member.line_start,
member.line_end,
)
}
fn parent_of(&self, member: &CodeUnit) -> String {
if self.show_parent {
member
.parent_name
.as_deref()
.map_or_else(String::new, |parent| format!(" in {parent}"))
} else {
String::new()
}
}
}