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 {
164 if let Some(matched) = captures.name(&vs.def.name) {
165 vs.assign(matched.as_str().to_string(), &mut self.results);
166 } else if rule.regex_pattern.contains(&format!("(?P<{}>", vs.def.name)) {
167 vs.assign_none();
168 }
169 }
170
171 match rule.record_op {
173 RecordOp::Record => self.append_record(),
174 RecordOp::Clear => self.clear_values(),
175 RecordOp::ClearAll => self.clear_all_values(),
176 RecordOp::NoRecord => {}
177 }
178
179 match rule.line_op {
181 LineOp::Error => {
182 let message = match &rule.transition {
183 Transition::State(msg) => msg.clone(),
184 _ => "state error".into(),
185 };
186 return Err(ParseError::RuleError {
187 rule_line: rule.line_num,
188 message,
189 });
190 }
191 LineOp::Continue => {
192 continue;
194 }
195 LineOp::Next => {
196 self.apply_transition(&rule.transition);
198 break;
199 }
200 }
201 }
202 }
203
204 Ok(())
205 }
206
207 fn append_record(&mut self) {
209 for vs in &self.value_states {
211 if !vs.satisfies_required() {
212 self.clear_values();
214 return;
215 }
216 }
217
218 let record: Vec<Value> = self
220 .value_states
221 .iter_mut()
222 .map(|vs| vs.take_for_record())
223 .collect();
224
225 if record.iter().all(|v| v.is_empty()) {
227 return;
228 }
229
230 self.results.push(record);
231 self.clear_values();
232 }
233
234 fn clear_values(&mut self) {
236 for vs in &mut self.value_states {
237 vs.clear();
238 }
239 }
240
241 fn clear_all_values(&mut self) {
243 for vs in &mut self.value_states {
244 vs.clear_all();
245 }
246 }
247
248 fn apply_transition(&mut self, transition: &Transition) {
250 match transition {
251 Transition::Stay => {}
252 Transition::State(name) => {
253 self.current_state = name.clone();
254 }
255 Transition::End => {
256 self.current_state = "End".to_string();
257 }
258 Transition::Eof => {
259 self.current_state = "EOF".to_string();
260 }
261 }
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268 use crate::template::Template;
269
270 #[test]
271 fn test_simple_parse() {
272 let template_str = r#"Value Interface (\S+)
273Value Status (up|down)
274
275Start
276 ^Interface: ${Interface} is ${Status} -> Record
277"#;
278
279 let template = Template::parse_str(template_str).unwrap();
280 let mut parser = template.parser();
281
282 let input = "Interface: eth0 is up\nInterface: eth1 is down\n";
283 let results = parser.parse_text(input).unwrap();
284
285 assert_eq!(results.len(), 2);
286 assert_eq!(results[0][0], Value::Single("eth0".into()));
287 assert_eq!(results[0][1], Value::Single("up".into()));
288 assert_eq!(results[1][0], Value::Single("eth1".into()));
289 assert_eq!(results[1][1], Value::Single("down".into()));
290 }
291
292 #[test]
293 fn test_parse_to_dicts() {
294 let template_str = r#"Value Name (\S+)
295Value Age (\d+)
296
297Start
298 ^Name: ${Name}, Age: ${Age} -> Record
299"#;
300
301 let template = Template::parse_str(template_str).unwrap();
302 let mut parser = template.parser();
303
304 let input = "Name: Alice, Age: 30\nName: Bob, Age: 25\n";
305 let results = parser.parse_text_to_dicts(input).unwrap();
306
307 assert_eq!(results.len(), 2);
308 assert_eq!(results[0].get("name"), Some(&"Alice".to_string()));
309 assert_eq!(results[0].get("age"), Some(&"30".to_string()));
310 }
311
312 #[test]
313 fn test_required_skips_empty() {
314 let template_str = r#"Value Required Name (\S+)
315Value Optional (\S+)
316
317Start
318 ^Name: ${Name}
319 ^Optional: ${Optional}
320 ^--- -> Record
321"#;
322
323 let template = Template::parse_str(template_str).unwrap();
324 let mut parser = template.parser();
325
326 let input = "Optional: foo\n---\nName: bar\n---\n";
328 let results = parser.parse_text(input).unwrap();
329
330 assert_eq!(results.len(), 1);
331 assert_eq!(results[0][0], Value::Single("bar".into()));
332 }
333
334 #[test]
335 fn test_filldown_clears_when_optional_group_unmatched() {
336 let template_str = r#"Value Filldown PREFIX (\S+)
340Value Filldown PREFIX_LENGTH (\d+)
341
342Start
343 ^Prefix:\s+${PREFIX}\s*(len:\s*${PREFIX_LENGTH})?
344 ^--- -> Record
345
346EOF
347"#;
348
349 let template = Template::parse_str(template_str).unwrap();
350 let mut parser = template.parser();
351
352 let input = "\
355Prefix: 10.0.0.0 len: 24
356---
357Prefix: 192.168.1.0
358---
359";
360 let results = parser.parse_text(input).unwrap();
361
362 assert_eq!(results.len(), 2);
363 assert_eq!(results[0][0], Value::Single("10.0.0.0".into()));
365 assert_eq!(results[0][1], Value::Single("24".into()));
366 assert_eq!(results[1][0], Value::Single("192.168.1.0".into()));
368 assert_eq!(results[1][1], Value::Empty);
369 }
370
371 #[test]
372 fn test_rule_with_escaped_angle_brackets() {
373 let template_str = r#"Value DateTime (\S+)
375
376Start
377 ^\s*<${DateTime}> -> Record
378"#;
379
380 let template = Template::parse_str(template_str).unwrap();
381 let mut parser = template.parser();
382
383 let input = " <2020/11/18> some text\n";
384 let results = parser.parse_text(input).unwrap();
385
386 assert_eq!(results.len(), 1);
387 assert_eq!(results[0][0], Value::Single("2020/11/18".into()));
388 }
389}