textfsm_core/parser/
mod.rs1mod state;
4
5pub use state::ValueState;
6
7use std::collections::HashMap;
8
9use crate::error::ParseError;
10use crate::template::Template;
11use crate::types::{LineOp, RecordOp, Transition, Value};
12
13pub struct Parser<'t> {
15 template: &'t Template,
17
18 current_state: String,
20
21 value_states: Vec<ValueState>,
23
24 results: Vec<Vec<Value>>,
26}
27
28impl<'t> Parser<'t> {
29 pub fn new(template: &'t Template) -> Self {
31 let value_states = template
32 .values()
33 .iter()
34 .enumerate()
35 .map(|(idx, def)| ValueState::new(def.clone(), idx))
36 .collect();
37
38 Self {
39 template,
40 current_state: "Start".to_string(),
41 value_states,
42 results: Vec::new(),
43 }
44 }
45
46 pub fn reset(&mut self) {
48 self.current_state = "Start".to_string();
49 self.results.clear();
50 for vs in &mut self.value_states {
51 vs.clear_all();
52 }
53 }
54
55 pub fn parse_text(&mut self, text: &str) -> Result<Vec<Vec<Value>>, ParseError> {
57 self.parse_text_with_eof(text, true)
58 }
59
60 pub fn parse_text_with_eof(
62 &mut self,
63 text: &str,
64 eof: bool,
65 ) -> Result<Vec<Vec<Value>>, ParseError> {
66 for line in text.lines() {
67 self.process_line(line)?;
68
69 if self.current_state == "End" || self.current_state == "EOF" {
70 break;
71 }
72 }
73
74 if self.current_state != "End"
76 && self.template.get_state("EOF").is_none()
77 && eof
78 {
79 self.append_record();
80 }
81
82 Ok(std::mem::take(&mut self.results))
83 }
84
85 pub fn parse_text_to_dicts(
87 &mut self,
88 text: &str,
89 ) -> Result<Vec<HashMap<String, String>>, ParseError> {
90 let results = self.parse_text(text)?;
91 let header = self.template.header();
92
93 Ok(results
94 .into_iter()
95 .map(|row| {
96 header
97 .iter()
98 .zip(row)
99 .map(|(k, v)| (k.to_lowercase(), v.as_string()))
100 .collect()
101 })
102 .collect())
103 }
104
105 #[cfg(feature = "serde")]
124 pub fn parse_text_into<T>(&mut self, text: &str) -> Result<Vec<T>, ParseError>
125 where
126 T: serde::de::DeserializeOwned,
127 {
128 let results = self.parse_text(text)?;
129
130 let header: Vec<String> = self
132 .template
133 .header()
134 .iter()
135 .map(|s| s.to_lowercase())
136 .collect();
137
138 results
139 .into_iter()
140 .map(|record| {
141 crate::de::from_record_borrowed(&header, record)
143 .map_err(|e| ParseError::DeserializeError(e.to_string()))
144 })
145 .collect()
146 }
147
148 fn process_line(&mut self, line: &str) -> Result<(), ParseError> {
150 let state = match self.template.get_state(&self.current_state) {
151 Some(s) => s,
152 None => return Ok(()), };
154
155 for rule in &state.rules {
156 if let Ok(Some(captures)) = rule.regex.captures(line) {
157 for vs in &mut self.value_states {
159 if let Some(matched) = captures.name(&vs.def.name) {
160 vs.assign(matched.as_str().to_string(), &mut self.results);
161 }
162 }
163
164 match rule.record_op {
166 RecordOp::Record => self.append_record(),
167 RecordOp::Clear => self.clear_values(),
168 RecordOp::ClearAll => self.clear_all_values(),
169 RecordOp::NoRecord => {}
170 }
171
172 match rule.line_op {
174 LineOp::Error => {
175 let message = match &rule.transition {
176 Transition::State(msg) => msg.clone(),
177 _ => "state error".into(),
178 };
179 return Err(ParseError::RuleError {
180 rule_line: rule.line_num,
181 message,
182 });
183 }
184 LineOp::Continue => {
185 continue;
187 }
188 LineOp::Next => {
189 self.apply_transition(&rule.transition);
191 break;
192 }
193 }
194 }
195 }
196
197 Ok(())
198 }
199
200 fn append_record(&mut self) {
202 for vs in &self.value_states {
204 if !vs.satisfies_required() {
205 self.clear_values();
207 return;
208 }
209 }
210
211 let record: Vec<Value> = self
213 .value_states
214 .iter_mut()
215 .map(|vs| vs.take_for_record())
216 .collect();
217
218 if record.iter().all(|v| v.is_empty()) {
220 return;
221 }
222
223 self.results.push(record);
224 self.clear_values();
225 }
226
227 fn clear_values(&mut self) {
229 for vs in &mut self.value_states {
230 vs.clear();
231 }
232 }
233
234 fn clear_all_values(&mut self) {
236 for vs in &mut self.value_states {
237 vs.clear_all();
238 }
239 }
240
241 fn apply_transition(&mut self, transition: &Transition) {
243 match transition {
244 Transition::Stay => {}
245 Transition::State(name) => {
246 self.current_state = name.clone();
247 }
248 Transition::End => {
249 self.current_state = "End".to_string();
250 }
251 Transition::Eof => {
252 self.current_state = "EOF".to_string();
253 }
254 }
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use crate::template::Template;
262
263 #[test]
264 fn test_simple_parse() {
265 let template_str = r#"
266Value Interface (\S+)
267Value Status (up|down)
268
269Start
270 ^Interface: ${Interface} is ${Status} -> Record
271"#;
272
273 let template = Template::parse_str(template_str).unwrap();
274 let mut parser = template.parser();
275
276 let input = "Interface: eth0 is up\nInterface: eth1 is down\n";
277 let results = parser.parse_text(input).unwrap();
278
279 assert_eq!(results.len(), 2);
280 assert_eq!(results[0][0], Value::Single("eth0".into()));
281 assert_eq!(results[0][1], Value::Single("up".into()));
282 assert_eq!(results[1][0], Value::Single("eth1".into()));
283 assert_eq!(results[1][1], Value::Single("down".into()));
284 }
285
286 #[test]
287 fn test_parse_to_dicts() {
288 let template_str = r#"
289Value Name (\S+)
290Value Age (\d+)
291
292Start
293 ^Name: ${Name}, Age: ${Age} -> Record
294"#;
295
296 let template = Template::parse_str(template_str).unwrap();
297 let mut parser = template.parser();
298
299 let input = "Name: Alice, Age: 30\nName: Bob, Age: 25\n";
300 let results = parser.parse_text_to_dicts(input).unwrap();
301
302 assert_eq!(results.len(), 2);
303 assert_eq!(results[0].get("name"), Some(&"Alice".to_string()));
304 assert_eq!(results[0].get("age"), Some(&"30".to_string()));
305 }
306
307 #[test]
308 fn test_required_skips_empty() {
309 let template_str = r#"
310Value Required Name (\S+)
311Value Optional (\S+)
312
313Start
314 ^Name: ${Name}
315 ^Optional: ${Optional}
316 ^--- -> Record
317"#;
318
319 let template = Template::parse_str(template_str).unwrap();
320 let mut parser = template.parser();
321
322 let input = "Optional: foo\n---\nName: bar\n---\n";
324 let results = parser.parse_text(input).unwrap();
325
326 assert_eq!(results.len(), 1);
327 assert_eq!(results[0][0], Value::Single("bar".into()));
328 }
329}