git_bot_feedback/file_utils/
mod.rs1use std::ops::Range;
2
3pub mod file_filter;
4use crate::DiffHunkHeader;
5
6#[derive(PartialEq, Clone, Copy, Debug, Default)]
8#[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
9pub enum LinesChangedOnly {
10 #[default]
15 Off,
16
17 Diff,
24
25 On,
30}
31
32impl LinesChangedOnly {
33 pub(crate) fn is_change_valid(&self, added_lines: bool, diff_hunks: bool) -> bool {
34 match self {
35 LinesChangedOnly::Off => true,
36 LinesChangedOnly::Diff => diff_hunks,
37 LinesChangedOnly::On => added_lines,
38 }
39 }
40}
41
42impl std::fmt::Display for LinesChangedOnly {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 match self {
45 LinesChangedOnly::Off => write!(f, "false"),
46 LinesChangedOnly::Diff => write!(f, "diff"),
47 LinesChangedOnly::On => write!(f, "true"),
48 }
49 }
50}
51
52#[derive(Debug, Clone, Default)]
54#[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
55pub struct FileDiffLines {
56 pub added_lines: Vec<u32>,
58
59 pub added_ranges: Vec<Range<u32>>,
64
65 pub diff_hunks: Vec<Range<u32>>,
69}
70
71impl FileDiffLines {
72 pub fn with_info(added_lines: Vec<u32>, diff_chunks: Vec<Range<u32>>) -> Self {
74 let added_ranges = Self::consolidate_numbers_to_ranges(&added_lines);
75 Self {
76 added_lines,
77 added_ranges,
78 diff_hunks: diff_chunks,
79 }
80 }
81
82 fn consolidate_numbers_to_ranges(lines: &[u32]) -> Vec<Range<u32>> {
86 let mut iter_lines = lines.iter().enumerate();
87 if let Some((_, start)) = iter_lines.next() {
88 let mut range_start = *start;
89 let mut ranges: Vec<Range<u32>> = Vec::new();
90 let last_entry = lines.len() - 1;
91 for (index, number) in iter_lines {
92 if let Some(prev) = lines.get(index - 1)
93 && (number - 1) != *prev
94 {
95 ranges.push(range_start..(*prev + 1));
98 range_start = *number;
101 }
102 if index == last_entry {
103 ranges.push(range_start..(*number + 1));
105 }
106 }
107 ranges
108 } else {
109 Vec::new()
110 }
111 }
112
113 pub fn get_ranges(&self, lines_changed_only: &LinesChangedOnly) -> Option<Vec<Range<u32>>> {
114 match lines_changed_only {
115 LinesChangedOnly::Diff => Some(self.diff_hunks.to_vec()),
116 LinesChangedOnly::On => Some(self.added_ranges.to_vec()),
117 _ => None,
118 }
119 }
120
121 pub fn is_hunk_in_diff(&self, hunk: &DiffHunkHeader) -> Option<(u32, u32)> {
124 let (start_line, end_line) = if hunk.old_lines > 0 {
125 let start = hunk.old_start;
127 (start, start + hunk.old_lines)
128 } else {
129 let start = hunk.new_start;
131 (start, start + 1)
133 };
134 let inclusive_end = end_line - 1;
135 for range in &self.diff_hunks {
136 if range.contains(&start_line) && range.contains(&inclusive_end) {
137 return Some((start_line, end_line));
138 }
139 }
140 None
141 }
142
143 pub fn is_line_in_diff(&self, line: &u32) -> bool {
146 for range in &self.diff_hunks {
147 if range.contains(line) {
148 return true;
149 }
150 }
151 false
152 }
153}
154
155#[cfg(test)]
156mod test {
157 use super::{FileDiffLines, LinesChangedOnly};
158
159 #[test]
160 fn display_lines_changed_only() {
161 assert_eq!(LinesChangedOnly::Off.to_string(), "false");
162 assert_eq!(LinesChangedOnly::Diff.to_string(), "diff");
163 assert_eq!(LinesChangedOnly::On.to_string(), "true");
164 }
165
166 #[test]
167 fn get_ranges_none() {
168 let file_obj = FileDiffLines::default();
169 let ranges = file_obj.get_ranges(&LinesChangedOnly::Off);
170 assert!(ranges.is_none());
171 }
172
173 #[test]
174 fn get_ranges_diff() {
175 #[allow(clippy::single_range_in_vec_init)]
176 let diff_chunks = vec![1..11];
177 let added_lines = vec![4, 5, 9];
178 let file_obj = FileDiffLines::with_info(added_lines, diff_chunks.clone());
179 let ranges = file_obj.get_ranges(&LinesChangedOnly::Diff);
180 assert_eq!(ranges.unwrap(), diff_chunks);
181 }
182
183 #[test]
184 fn get_ranges_added() {
185 #[allow(clippy::single_range_in_vec_init)]
186 let diff_chunks = vec![1..11];
187 let added_lines = vec![4, 5, 9];
188 let file_obj = FileDiffLines::with_info(added_lines, diff_chunks);
189 let ranges = file_obj.get_ranges(&LinesChangedOnly::On);
190 assert_eq!(ranges.unwrap(), vec![4..6, 9..10]);
191 }
192
193 #[test]
194 fn line_not_in_diff() {
195 let file_obj = FileDiffLines::default();
196 assert!(!file_obj.is_line_in_diff(&42));
197 }
198}