dry4rust/output/
group_section.rs1use std::fmt::Write as _;
7use std::path::Path;
8
9use crate::code_unit::CodeUnit;
10use crate::grouper::DuplicateGroup;
11use crate::output::reporter::display_path;
12
13pub struct GroupSection {
21 title: &'static str,
22 empty_message: Option<&'static str>,
23 show_similarity: bool,
24 show_parent: bool,
25}
26
27impl GroupSection {
28 #[must_use]
30 pub const fn exact() -> Self {
31 Self {
32 title: "Exact Duplicates",
33 empty_message: Some("No exact duplicates found."),
34 show_similarity: false,
35 show_parent: false,
36 }
37 }
38
39 #[must_use]
41 pub const fn near() -> Self {
42 Self {
43 title: "Near Duplicates",
44 empty_message: Some("No near duplicates found."),
45 show_similarity: true,
46 show_parent: false,
47 }
48 }
49
50 #[must_use]
53 pub const fn sub_exact() -> Self {
54 Self {
55 title: "Sub-function Exact Duplicates",
56 empty_message: None,
57 show_similarity: false,
58 show_parent: true,
59 }
60 }
61
62 #[must_use]
64 pub const fn sub_near() -> Self {
65 Self {
66 title: "Sub-function Near Duplicates",
67 empty_message: None,
68 show_similarity: true,
69 show_parent: true,
70 }
71 }
72
73 #[must_use]
75 pub fn write(&self, groups: &[DuplicateGroup], base: Option<&Path>) -> String {
76 if groups.is_empty() {
77 return self.write_empty();
78 }
79 let mut out = String::new();
80 let _ = writeln!(out, "{}", self.title);
81 let _ = writeln!(out, "{}", "=".repeat(self.title.len()));
82 let _ = writeln!(out);
83 for (index, group) in groups.iter().enumerate() {
84 out.push_str(&self.write_group(index + 1, group, base));
85 }
86 out
87 }
88
89 fn write_empty(&self) -> String {
90 match self.empty_message {
91 Some(message) => format!("{message}\n"),
92 None => String::new(),
93 }
94 }
95
96 fn write_group(&self, number: usize, group: &DuplicateGroup, base: Option<&Path>) -> String {
97 let mut out = String::new();
98 let _ = writeln!(out, "{}", self.heading(number, group));
99 for member in &group.members {
100 let _ = writeln!(out, "{}", self.member_line(member, base));
101 }
102 let _ = writeln!(out);
103 out
104 }
105
106 fn heading(&self, number: usize, group: &DuplicateGroup) -> String {
107 let fingerprint = group.fingerprint.to_hex();
108 let members = group.members.len();
109 if self.show_similarity {
110 format!(
111 "Group {number} (fingerprint: {fingerprint}, similarity: {:.0}%, {members} members):",
112 group.similarity * 100.0
113 )
114 } else {
115 format!("Group {number} (fingerprint: {fingerprint}, {members} members):")
116 }
117 }
118
119 fn member_line(&self, member: &CodeUnit, base: Option<&Path>) -> String {
120 format!(
121 " - {} ({}){} at {}:{}-{}",
122 member.name,
123 member.kind,
124 self.parent_of(member),
125 display_path(base, &member.file),
126 member.line_start,
127 member.line_end,
128 )
129 }
130
131 fn parent_of(&self, member: &CodeUnit) -> String {
132 if self.show_parent {
133 member
134 .parent_name
135 .as_deref()
136 .map_or_else(String::new, |parent| format!(" in {parent}"))
137 } else {
138 String::new()
139 }
140 }
141}