Skip to main content

buildlog_consultant/
lines.rs

1//! Module providing utilities for working with collections of text lines.
2//!
3//! This module defines the `Lines` trait and its implementations to provide
4//! consistent interfaces for iterating through lines in different directions
5//! and with different options.
6
7/// Trait for collections of text lines that provides bidirectional iteration.
8///
9/// This trait provides methods to iterate forward and backward through
10/// collections of text lines, with optional limits and offset information.
11pub trait Lines<'a> {
12    /// Iterates forward through the lines.
13    ///
14    /// # Arguments
15    /// * `limit` - Optional maximum number of lines to return
16    ///
17    /// # Returns
18    /// An iterator yielding lines in forward order
19    fn iter_forward(&'a self, limit: Option<usize>) -> impl Iterator<Item = &'a str>;
20
21    /// Iterates forward through the lines with indices.
22    ///
23    /// # Arguments
24    /// * `limit` - Optional maximum number of lines to return
25    ///
26    /// # Returns
27    /// An iterator yielding (index, line) pairs in forward order
28    fn enumerate_forward(&'a self, limit: Option<usize>) -> impl Iterator<Item = (usize, &'a str)> {
29        self.iter_forward(limit).enumerate()
30    }
31
32    /// Iterates backward through the lines.
33    ///
34    /// # Arguments
35    /// * `limit` - Optional maximum number of lines to return
36    ///
37    /// # Returns
38    /// An iterator yielding lines in reverse order
39    fn iter_backward(&'a self, limit: Option<usize>) -> impl DoubleEndedIterator<Item = &'a str>;
40
41    /// Iterates forward through the last N lines with indices.
42    ///
43    /// # Arguments
44    /// * `limit` - Maximum number of lines to return from the end
45    ///
46    /// # Returns
47    /// An iterator yielding (index, line) pairs for the last `limit` lines
48    fn enumerate_tail_forward(&'a self, limit: usize) -> impl Iterator<Item = (usize, &'a str)> {
49        let start_offset = self.len().saturating_sub(limit);
50        self.iter_forward(None)
51            .skip(start_offset)
52            .enumerate()
53            .map(move |(i, s)| (i + start_offset, s))
54    }
55
56    /// Iterates backward through the lines with original indices.
57    ///
58    /// # Arguments
59    /// * `limit` - Optional maximum number of lines to return
60    ///
61    /// # Returns
62    /// An iterator yielding (original_index, line) pairs in reverse order
63    fn enumerate_backward(
64        &'a self,
65        limit: Option<usize>,
66    ) -> impl Iterator<Item = (usize, &'a str)> {
67        let len = self.len();
68        self.iter_backward(limit)
69            .enumerate()
70            .map(move |(i, s)| (len - i - 1, s))
71    }
72
73    /// Returns the number of lines in the collection.
74    ///
75    /// # Returns
76    /// The line count
77    fn len(&self) -> usize;
78
79    /// Checks if the collection is empty.
80    ///
81    /// # Returns
82    /// `true` if the collection contains no lines, `false` otherwise
83    fn is_empty(&self) -> bool;
84}
85
86impl<'a> Lines<'a> for Vec<&'a str> {
87    fn iter_forward(&'a self, limit: Option<usize>) -> impl Iterator<Item = &'a str> {
88        let limit = limit.unwrap_or(self.len());
89        self.iter().take(limit).cloned()
90    }
91
92    fn enumerate_tail_forward(&'a self, limit: usize) -> impl Iterator<Item = (usize, &'a str)> {
93        let start_offset = self.len().saturating_sub(limit);
94
95        self[start_offset..]
96            .iter()
97            .cloned()
98            .enumerate()
99            .map(move |(i, s)| (i + start_offset, s))
100    }
101
102    fn iter_backward(&'a self, limit: Option<usize>) -> impl DoubleEndedIterator<Item = &'a str> {
103        let limit = limit.unwrap_or(self.len());
104        self.iter().rev().take(limit).cloned()
105    }
106
107    fn len(&self) -> usize {
108        self.len()
109    }
110
111    fn is_empty(&self) -> bool {
112        self.is_empty()
113    }
114}
115
116impl<'a> Lines<'a> for Vec<String> {
117    fn iter_forward(&'a self, limit: Option<usize>) -> impl Iterator<Item = &'a str> {
118        let limit = limit.unwrap_or(self.len());
119        self.iter().take(limit).map(|s| s.as_str())
120    }
121
122    fn iter_backward(&'a self, limit: Option<usize>) -> impl DoubleEndedIterator<Item = &'a str> {
123        let limit = limit.unwrap_or(self.len());
124        self.iter().rev().take(limit).map(|s| s.as_str())
125    }
126
127    fn enumerate_tail_forward(&'a self, limit: usize) -> impl Iterator<Item = (usize, &'a str)> {
128        let start_offset = self.len().saturating_sub(limit);
129
130        self[start_offset..]
131            .iter()
132            .map(|s| s.as_str())
133            .enumerate()
134            .map(move |(i, s)| (i + start_offset, s))
135    }
136
137    fn len(&self) -> usize {
138        self.len()
139    }
140
141    fn is_empty(&self) -> bool {
142        self.is_empty()
143    }
144}
145
146impl<'a> Lines<'a> for &'a [&'a str] {
147    fn iter_forward(&'a self, limit: Option<usize>) -> impl Iterator<Item = &'a str> {
148        let limit = limit.unwrap_or(self.len());
149        self.iter().take(limit).cloned()
150    }
151
152    fn iter_backward(&'a self, limit: Option<usize>) -> impl DoubleEndedIterator<Item = &'a str> {
153        let limit = limit.unwrap_or(self.len());
154        self.iter().rev().take(limit).cloned()
155    }
156
157    fn enumerate_tail_forward(&'a self, limit: usize) -> impl Iterator<Item = (usize, &'a str)> {
158        let start_offset = self.len().saturating_sub(limit);
159
160        self[start_offset..]
161            .iter()
162            .cloned()
163            .enumerate()
164            .map(move |(i, s)| (i + start_offset, s))
165    }
166
167    fn len(&self) -> usize {
168        <[&str]>::len(self)
169    }
170
171    fn is_empty(&self) -> bool {
172        <[&str]>::is_empty(self)
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn test_iter_forward() {
182        let lines = vec!["a", "b", "c", "d", "e"];
183        let iter = lines.iter_forward(None);
184        assert_eq!(iter.collect::<Vec<_>>(), vec!["a", "b", "c", "d", "e"]);
185        let iter = lines.iter_forward(Some(3));
186        assert_eq!(iter.collect::<Vec<_>>(), vec!["a", "b", "c"]);
187    }
188
189    #[test]
190    fn test_iter_backward() {
191        let lines = vec!["a", "b", "c", "d", "e"];
192        let iter = lines.iter_backward(None);
193        assert_eq!(iter.collect::<Vec<_>>(), vec!["e", "d", "c", "b", "a"]);
194        let iter = lines.iter_backward(Some(3));
195        assert_eq!(iter.collect::<Vec<_>>(), vec!["e", "d", "c"]);
196    }
197
198    #[test]
199    fn test_enumerate_tail_forward() {
200        let lines = vec!["a", "b", "c", "d", "e"];
201        let iter = lines.enumerate_tail_forward(3);
202        assert_eq!(iter.collect::<Vec<_>>(), vec![(2, "c"), (3, "d"), (4, "e")]);
203    }
204
205    #[test]
206    fn test_enumerate_forward() {
207        let lines = vec!["a", "b", "c", "d", "e"];
208        let iter = lines.enumerate_forward(None);
209        assert_eq!(
210            iter.collect::<Vec<_>>(),
211            vec![(0, "a"), (1, "b"), (2, "c"), (3, "d"), (4, "e")]
212        );
213        let iter = lines.enumerate_forward(Some(3));
214        assert_eq!(iter.collect::<Vec<_>>(), vec![(0, "a"), (1, "b"), (2, "c")]);
215    }
216
217    #[test]
218    fn test_enumerate_backward() {
219        let lines = vec!["a", "b", "c", "d", "e"];
220        let iter = lines.enumerate_backward(None);
221        assert_eq!(
222            iter.collect::<Vec<_>>(),
223            vec![(4, "e"), (3, "d"), (2, "c"), (1, "b"), (0, "a")]
224        );
225        let iter = lines.enumerate_backward(Some(3));
226        assert_eq!(iter.collect::<Vec<_>>(), vec![(4, "e"), (3, "d"), (2, "c")]);
227    }
228
229    #[test]
230    fn test_string_vec_impl() {
231        let lines: Vec<String> = vec!["a".to_string(), "b".to_string(), "c".to_string()];
232        assert_eq!(lines.len(), 3);
233        assert!(!lines.is_empty());
234
235        // Test iter_forward
236        let forward = lines.iter_forward(None).collect::<Vec<_>>();
237        assert_eq!(forward, vec!["a", "b", "c"]);
238
239        let limited = lines.iter_forward(Some(2)).collect::<Vec<_>>();
240        assert_eq!(limited, vec!["a", "b"]);
241
242        // Test iter_backward
243        let backward = lines.iter_backward(None).collect::<Vec<_>>();
244        assert_eq!(backward, vec!["c", "b", "a"]);
245
246        let limited_backward = lines.iter_backward(Some(2)).collect::<Vec<_>>();
247        assert_eq!(limited_backward, vec!["c", "b"]);
248
249        // Test enumerate_tail_forward
250        let tail = lines.enumerate_tail_forward(2).collect::<Vec<_>>();
251        assert_eq!(tail, vec![(1, "b"), (2, "c")]);
252    }
253
254    #[test]
255    fn test_slice_impl() {
256        let slice = &["a", "b", "c"][..];
257        assert_eq!(slice.len(), 3);
258        assert!(!slice.is_empty());
259
260        // Test iter_forward
261        let forward = slice.iter_forward(None).collect::<Vec<_>>();
262        assert_eq!(forward, vec!["a", "b", "c"]);
263
264        let limited = slice.iter_forward(Some(2)).collect::<Vec<_>>();
265        assert_eq!(limited, vec!["a", "b"]);
266
267        // Test iter_backward
268        let backward = slice.iter_backward(None).collect::<Vec<_>>();
269        assert_eq!(backward, vec!["c", "b", "a"]);
270
271        let limited_backward = slice.iter_backward(Some(2)).collect::<Vec<_>>();
272        assert_eq!(limited_backward, vec!["c", "b"]);
273
274        // Test enumerate_tail_forward
275        let tail = slice.enumerate_tail_forward(2).collect::<Vec<_>>();
276        assert_eq!(tail, vec![(1, "b"), (2, "c")]);
277    }
278
279    #[test]
280    fn test_empty_collections() {
281        let empty_vec: Vec<&str> = Vec::new();
282        assert_eq!(empty_vec.len(), 0);
283        assert!(empty_vec.is_empty());
284        assert_eq!(empty_vec.iter_forward(None).count(), 0);
285        assert_eq!(empty_vec.iter_backward(None).count(), 0);
286        assert_eq!(empty_vec.enumerate_forward(None).count(), 0);
287        assert_eq!(empty_vec.enumerate_backward(None).count(), 0);
288        assert_eq!(empty_vec.enumerate_tail_forward(5).count(), 0);
289
290        let empty_string_vec: Vec<String> = Vec::new();
291        assert_eq!(empty_string_vec.len(), 0);
292        assert!(empty_string_vec.is_empty());
293        assert_eq!(empty_string_vec.iter_forward(None).count(), 0);
294        assert_eq!(empty_string_vec.iter_backward(None).count(), 0);
295
296        let empty_slice: &[&str] = &[];
297        assert_eq!(empty_slice.len(), 0);
298        assert!(empty_slice.is_empty());
299        assert_eq!(empty_slice.iter_forward(None).count(), 0);
300        assert_eq!(empty_slice.iter_backward(None).count(), 0);
301    }
302}