dry4rust/output/
group_section.rs1use std::io;
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 pub fn write(
79 &self,
80 groups: &[DuplicateGroup],
81 base: Option<&Path>,
82 writer: &mut dyn io::Write,
83 ) -> io::Result<()> {
84 if groups.is_empty() {
85 return self.write_empty(writer);
86 }
87 writeln!(writer, "{}", self.title)?;
88 writeln!(writer, "{}", "=".repeat(self.title.len()))?;
89 writeln!(writer)?;
90 for (index, group) in groups.iter().enumerate() {
91 self.write_group(index + 1, group, base, writer)?;
92 }
93 Ok(())
94 }
95
96 fn write_empty(&self, writer: &mut dyn io::Write) -> io::Result<()> {
97 match self.empty_message {
98 Some(message) => writeln!(writer, "{message}"),
99 None => Ok(()),
100 }
101 }
102
103 fn write_group(
104 &self,
105 number: usize,
106 group: &DuplicateGroup,
107 base: Option<&Path>,
108 writer: &mut dyn io::Write,
109 ) -> io::Result<()> {
110 writeln!(writer, "{}", self.heading(number, group))?;
111 for member in &group.members {
112 writeln!(writer, "{}", self.member_line(member, base))?;
113 }
114 writeln!(writer)
115 }
116
117 fn heading(&self, number: usize, group: &DuplicateGroup) -> String {
118 let fingerprint = group.fingerprint.to_hex();
119 let members = group.members.len();
120 if self.show_similarity {
121 format!(
122 "Group {number} (fingerprint: {fingerprint}, similarity: {:.0}%, {members} members):",
123 group.similarity * 100.0
124 )
125 } else {
126 format!("Group {number} (fingerprint: {fingerprint}, {members} members):")
127 }
128 }
129
130 fn member_line(&self, member: &CodeUnit, base: Option<&Path>) -> String {
131 format!(
132 " - {} ({}){} at {}:{}-{}",
133 member.name,
134 member.kind,
135 self.parent_of(member),
136 display_path(base, &member.file),
137 member.line_start,
138 member.line_end,
139 )
140 }
141
142 fn parent_of(&self, member: &CodeUnit) -> String {
143 if self.show_parent {
144 member
145 .parent_name
146 .as_deref()
147 .map_or_else(String::new, |parent| format!(" in {parent}"))
148 } else {
149 String::new()
150 }
151 }
152}