git_bot_feedback/file_utils/
mod.rs1use std::ops::Range;
2
3pub mod file_filter;
4use crate::DiffHunkHeader;
5
6#[derive(PartialEq, Clone, 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
42#[derive(Debug, Clone, Default)]
44#[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
45pub struct FileDiffLines {
46 pub added_lines: Vec<u32>,
48
49 pub added_ranges: Vec<Range<u32>>,
54
55 pub diff_hunks: Vec<Range<u32>>,
59}
60
61impl FileDiffLines {
62 pub fn with_info(added_lines: Vec<u32>, diff_chunks: Vec<Range<u32>>) -> Self {
64 let added_ranges = Self::consolidate_numbers_to_ranges(&added_lines);
65 Self {
66 added_lines,
67 added_ranges,
68 diff_hunks: diff_chunks,
69 }
70 }
71
72 fn consolidate_numbers_to_ranges(lines: &[u32]) -> Vec<Range<u32>> {
76 let mut iter_lines = lines.iter().enumerate();
77 if let Some((_, start)) = iter_lines.next() {
78 let mut range_start = *start;
79 let mut ranges: Vec<Range<u32>> = Vec::new();
80 let last_entry = lines.len() - 1;
81 for (index, number) in iter_lines {
82 if let Some(prev) = lines.get(index - 1)
83 && (number - 1) != *prev
84 {
85 ranges.push(range_start..(*prev + 1));
88 range_start = *number;
91 }
92 if index == last_entry {
93 ranges.push(range_start..(*number + 1));
95 }
96 }
97 ranges
98 } else {
99 Vec::new()
100 }
101 }
102
103 pub fn get_ranges(&self, lines_changed_only: &LinesChangedOnly) -> Option<Vec<Range<u32>>> {
104 match lines_changed_only {
105 LinesChangedOnly::Diff => Some(self.diff_hunks.to_vec()),
106 LinesChangedOnly::On => Some(self.added_ranges.to_vec()),
107 _ => None,
108 }
109 }
110
111 pub fn is_hunk_in_diff(&self, hunk: &DiffHunkHeader) -> Option<(u32, u32)> {
114 let (start_line, end_line) = if hunk.old_lines > 0 {
115 let start = hunk.old_start;
117 (start, start + hunk.old_lines)
118 } else {
119 let start = hunk.new_start;
121 (start, start + 1)
123 };
124 let inclusive_end = end_line - 1;
125 for range in &self.diff_hunks {
126 if range.contains(&start_line) && range.contains(&inclusive_end) {
127 return Some((start_line, end_line));
128 }
129 }
130 None
131 }
132
133 pub fn is_line_in_diff(&self, line: &u32) -> bool {
136 for range in &self.diff_hunks {
137 if range.contains(line) {
138 return true;
139 }
140 }
141 false
142 }
143}
144
145#[cfg(test)]
146mod test {
147 use super::{FileDiffLines, LinesChangedOnly};
148
149 #[test]
150 fn get_ranges_none() {
151 let file_obj = FileDiffLines::default();
152 let ranges = file_obj.get_ranges(&LinesChangedOnly::Off);
153 assert!(ranges.is_none());
154 }
155
156 #[test]
157 fn get_ranges_diff() {
158 let diff_chunks = vec![1..11];
159 let added_lines = vec![4, 5, 9];
160 let file_obj = FileDiffLines::with_info(added_lines, diff_chunks.clone());
161 let ranges = file_obj.get_ranges(&LinesChangedOnly::Diff);
162 assert_eq!(ranges.unwrap(), diff_chunks);
163 }
164
165 #[test]
166 fn get_ranges_added() {
167 let diff_chunks = vec![1..11];
168 let added_lines = vec![4, 5, 9];
169 let file_obj = FileDiffLines::with_info(added_lines, diff_chunks);
170 let ranges = file_obj.get_ranges(&LinesChangedOnly::On);
171 assert_eq!(ranges.unwrap(), vec![4..6, 9..10]);
172 }
173
174 #[test]
175 fn line_not_in_diff() {
176 let file_obj = FileDiffLines::default();
177 assert!(!file_obj.is_line_in_diff(&42));
178 }
179}