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_some()
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.into_iter())
99 .map(|(k, v)| (k.to_lowercase(), v.as_string()))
100 .collect()
101 })
102 .collect())
103 }
104
105 fn process_line(&mut self, line: &str) -> Result<(), ParseError> {
107 let state = match self.template.get_state(&self.current_state) {
108 Some(s) => s,
109 None => return Ok(()), };
111
112 for rule in &state.rules {
113 if let Ok(Some(captures)) = rule.regex.captures(line) {
114 for vs in &mut self.value_states {
116 if let Some(matched) = captures.name(&vs.def.name) {
117 vs.assign(matched.as_str().to_string(), &mut self.results);
118 }
119 }
120
121 match rule.record_op {
123 RecordOp::Record => self.append_record(),
124 RecordOp::Clear => self.clear_values(),
125 RecordOp::ClearAll => self.clear_all_values(),
126 RecordOp::NoRecord => {}
127 }
128
129 match rule.line_op {
131 LineOp::Error => {
132 let message = match &rule.transition {
133 Transition::State(msg) => msg.clone(),
134 _ => "state error".into(),
135 };
136 return Err(ParseError::RuleError {
137 rule_line: rule.line_num,
138 message,
139 });
140 }
141 LineOp::Continue => {
142 continue;
144 }
145 LineOp::Next => {
146 self.apply_transition(&rule.transition);
148 break;
149 }
150 }
151 }
152 }
153
154 Ok(())
155 }
156
157 fn append_record(&mut self) {
159 for vs in &self.value_states {
161 if !vs.satisfies_required() {
162 self.clear_values();
164 return;
165 }
166 }
167
168 let record: Vec<Value> = self
170 .value_states
171 .iter_mut()
172 .map(|vs| vs.take_for_record())
173 .collect();
174
175 if record.iter().all(|v| v.is_empty()) {
177 return;
178 }
179
180 self.results.push(record);
181 self.clear_values();
182 }
183
184 fn clear_values(&mut self) {
186 for vs in &mut self.value_states {
187 vs.clear();
188 }
189 }
190
191 fn clear_all_values(&mut self) {
193 for vs in &mut self.value_states {
194 vs.clear_all();
195 }
196 }
197
198 fn apply_transition(&mut self, transition: &Transition) {
200 match transition {
201 Transition::Stay => {}
202 Transition::State(name) => {
203 self.current_state = name.clone();
204 }
205 Transition::End => {
206 self.current_state = "End".to_string();
207 }
208 Transition::Eof => {
209 self.current_state = "EOF".to_string();
210 }
211 }
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::template::Template;
219
220 #[test]
221 fn test_simple_parse() {
222 let template_str = r#"
223Value Interface (\S+)
224Value Status (up|down)
225
226Start
227 ^Interface: ${Interface} is ${Status} -> Record
228"#;
229
230 let template = Template::parse_str(template_str).unwrap();
231 let mut parser = template.parser();
232
233 let input = "Interface: eth0 is up\nInterface: eth1 is down\n";
234 let results = parser.parse_text(input).unwrap();
235
236 assert_eq!(results.len(), 2);
237 assert_eq!(results[0][0], Value::Single("eth0".into()));
238 assert_eq!(results[0][1], Value::Single("up".into()));
239 assert_eq!(results[1][0], Value::Single("eth1".into()));
240 assert_eq!(results[1][1], Value::Single("down".into()));
241 }
242
243 #[test]
244 fn test_parse_to_dicts() {
245 let template_str = r#"
246Value Name (\S+)
247Value Age (\d+)
248
249Start
250 ^Name: ${Name}, Age: ${Age} -> Record
251"#;
252
253 let template = Template::parse_str(template_str).unwrap();
254 let mut parser = template.parser();
255
256 let input = "Name: Alice, Age: 30\nName: Bob, Age: 25\n";
257 let results = parser.parse_text_to_dicts(input).unwrap();
258
259 assert_eq!(results.len(), 2);
260 assert_eq!(results[0].get("name"), Some(&"Alice".to_string()));
261 assert_eq!(results[0].get("age"), Some(&"30".to_string()));
262 }
263
264 #[test]
265 fn test_required_skips_empty() {
266 let template_str = r#"
267Value Required Name (\S+)
268Value Optional (\S+)
269
270Start
271 ^Name: ${Name}
272 ^Optional: ${Optional}
273 ^--- -> Record
274"#;
275
276 let template = Template::parse_str(template_str).unwrap();
277 let mut parser = template.parser();
278
279 let input = "Optional: foo\n---\nName: bar\n---\n";
281 let results = parser.parse_text(input).unwrap();
282
283 assert_eq!(results.len(), 1);
284 assert_eq!(results[0][0], Value::Single("bar".into()));
285 }
286}