freeswitch_log_parser/
chain.rs1use std::cell::RefCell;
2use std::rc::Rc;
3
4pub struct TrackedChain {
8 segments: Vec<Box<dyn Iterator<Item = String>>>,
9 current: usize,
10 lines_emitted: u64,
11 starts: Rc<RefCell<Vec<u64>>>,
12 emit_sentinel: bool,
13}
14
15pub struct SegmentTracker {
20 filenames: Vec<String>,
21 starts: Rc<RefCell<Vec<u64>>>,
22}
23
24impl TrackedChain {
25 pub fn new(
30 segments: Vec<(String, Box<dyn Iterator<Item = String>>)>,
31 ) -> (Self, SegmentTracker) {
32 let (filenames, iters): (Vec<_>, Vec<_>) = segments.into_iter().unzip();
33 let starts = Rc::new(RefCell::new(if iters.is_empty() {
34 Vec::new()
35 } else {
36 vec![1u64]
37 }));
38 let tracker = SegmentTracker {
39 filenames: filenames.clone(),
40 starts: starts.clone(),
41 };
42 let chain = TrackedChain {
43 segments: iters,
44 current: 0,
45 lines_emitted: 0,
46 starts,
47 emit_sentinel: false,
48 };
49 (chain, tracker)
50 }
51}
52
53impl Iterator for TrackedChain {
54 type Item = String;
55
56 fn next(&mut self) -> Option<String> {
57 if self.emit_sentinel {
58 self.emit_sentinel = false;
59 return Some("\x00".to_string());
60 }
61 loop {
62 if self.current >= self.segments.len() {
63 return None;
64 }
65 if let Some(line) = self.segments[self.current].next() {
66 self.lines_emitted += 1;
67 return Some(line);
68 }
69 self.current += 1;
70 if self.current < self.segments.len() {
71 self.starts.borrow_mut().push(self.lines_emitted + 1);
72 self.emit_sentinel = true;
73 return self.next();
74 }
75 }
76 }
77}
78
79impl SegmentTracker {
80 pub fn segment_for_line(&self, line_number: u64) -> Option<(usize, &str)> {
84 if line_number == 0 {
85 return None;
86 }
87 let starts = self.starts.borrow();
88 let idx = starts.partition_point(|&s| s <= line_number);
89 if idx == 0 {
90 return None;
91 }
92 let seg = idx - 1;
93 Some((seg, &self.filenames[seg]))
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 fn seg(name: &str, lines: Vec<&str>) -> (String, Box<dyn Iterator<Item = String>>) {
102 let owned: Vec<String> = lines.into_iter().map(String::from).collect();
103 (name.to_string(), Box::new(owned.into_iter()))
104 }
105
106 #[test]
107 fn single_segment() {
108 let (chain, tracker) = TrackedChain::new(vec![seg("a.log", vec!["x", "y", "z"])]);
109 let lines: Vec<_> = chain.collect();
110 assert_eq!(lines, ["x", "y", "z"]);
111 assert_eq!(tracker.segment_for_line(1), Some((0, "a.log")));
112 assert_eq!(tracker.segment_for_line(3), Some((0, "a.log")));
113 }
114
115 #[test]
116 fn two_segments() {
117 let (chain, tracker) = TrackedChain::new(vec![
118 seg("a.log", vec!["a1", "a2"]),
119 seg("b.log", vec!["b1"]),
120 ]);
121 let lines: Vec<_> = chain.collect();
122 assert_eq!(lines, ["a1", "a2", "\x00", "b1"]);
123 assert_eq!(tracker.segment_for_line(1), Some((0, "a.log")));
124 assert_eq!(tracker.segment_for_line(2), Some((0, "a.log")));
125 assert_eq!(tracker.segment_for_line(3), Some((1, "b.log")));
126 }
127
128 #[test]
129 fn empty_segment_skipped() {
130 let (chain, tracker) = TrackedChain::new(vec![
131 seg("a.log", vec!["a1"]),
132 seg("empty.log", vec![]),
133 seg("c.log", vec!["c1"]),
134 ]);
135 let lines: Vec<_> = chain.collect();
136 assert_eq!(lines, ["a1", "\x00", "\x00", "c1"]);
137 assert_eq!(tracker.segment_for_line(1), Some((0, "a.log")));
138 assert_eq!(tracker.segment_for_line(2), Some((2, "c.log")));
139 }
140
141 #[test]
142 fn line_zero_returns_none() {
143 let (_chain, tracker) = TrackedChain::new(vec![seg("a.log", vec!["x"])]);
144 assert_eq!(tracker.segment_for_line(0), None);
145 }
146
147 #[test]
148 fn empty_chain() {
149 let (chain, tracker) = TrackedChain::new(vec![]);
150 let lines: Vec<String> = chain.collect();
151 assert!(lines.is_empty());
152 assert_eq!(tracker.segment_for_line(1), None);
153 }
154}