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
53pub(crate) const SEGMENT_BOUNDARY: &str = "\x00";
57
58impl Iterator for TrackedChain {
59 type Item = String;
60
61 fn next(&mut self) -> Option<String> {
62 if self.emit_sentinel {
63 self.emit_sentinel = false;
64 return Some(SEGMENT_BOUNDARY.to_string());
65 }
66 loop {
67 if self.current >= self.segments.len() {
68 return None;
69 }
70 if let Some(line) = self.segments[self.current].next() {
71 self.lines_emitted += 1;
72 return Some(line);
73 }
74 self.current += 1;
75 if self.current < self.segments.len() {
76 self.starts.borrow_mut().push(self.lines_emitted + 1);
77 self.emit_sentinel = true;
78 return self.next();
79 }
80 }
81 }
82}
83
84impl SegmentTracker {
85 pub fn segment_for_line(&self, line_number: u64) -> Option<(usize, &str)> {
89 if line_number == 0 {
90 return None;
91 }
92 let starts = self.starts.borrow();
93 let idx = starts.partition_point(|&s| s <= line_number);
94 if idx == 0 {
95 return None;
96 }
97 let seg = idx - 1;
98 Some((seg, &self.filenames[seg]))
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 fn seg(name: &str, lines: Vec<&str>) -> (String, Box<dyn Iterator<Item = String>>) {
107 let owned: Vec<String> = lines.into_iter().map(String::from).collect();
108 (name.to_string(), Box::new(owned.into_iter()))
109 }
110
111 #[test]
112 fn single_segment() {
113 let (chain, tracker) = TrackedChain::new(vec![seg("a.log", vec!["x", "y", "z"])]);
114 let lines: Vec<_> = chain.collect();
115 assert_eq!(lines, ["x", "y", "z"]);
116 assert_eq!(tracker.segment_for_line(1), Some((0, "a.log")));
117 assert_eq!(tracker.segment_for_line(3), Some((0, "a.log")));
118 }
119
120 #[test]
121 fn two_segments() {
122 let (chain, tracker) = TrackedChain::new(vec![
123 seg("a.log", vec!["a1", "a2"]),
124 seg("b.log", vec!["b1"]),
125 ]);
126 let lines: Vec<_> = chain.collect();
127 assert_eq!(lines, ["a1", "a2", "\x00", "b1"]);
128 assert_eq!(tracker.segment_for_line(1), Some((0, "a.log")));
129 assert_eq!(tracker.segment_for_line(2), Some((0, "a.log")));
130 assert_eq!(tracker.segment_for_line(3), Some((1, "b.log")));
131 }
132
133 #[test]
134 fn empty_segment_skipped() {
135 let (chain, tracker) = TrackedChain::new(vec![
136 seg("a.log", vec!["a1"]),
137 seg("empty.log", vec![]),
138 seg("c.log", vec!["c1"]),
139 ]);
140 let lines: Vec<_> = chain.collect();
141 assert_eq!(lines, ["a1", "\x00", "\x00", "c1"]);
142 assert_eq!(tracker.segment_for_line(1), Some((0, "a.log")));
143 assert_eq!(tracker.segment_for_line(2), Some((2, "c.log")));
144 }
145
146 #[test]
147 fn line_zero_returns_none() {
148 let (_chain, tracker) = TrackedChain::new(vec![seg("a.log", vec!["x"])]);
149 assert_eq!(tracker.segment_for_line(0), None);
150 }
151
152 #[test]
153 fn empty_chain() {
154 let (chain, tracker) = TrackedChain::new(vec![]);
155 let lines: Vec<String> = chain.collect();
156 assert!(lines.is_empty());
157 assert_eq!(tracker.segment_for_line(1), None);
158 }
159}