textfsm_core/parser/
state.rs1use std::collections::HashMap;
4
5
6use crate::template::ValueDef;
7use crate::types::{ListItem, Value, ValueOption};
8
9#[derive(Debug, Clone)]
11pub struct ValueState {
12 pub def: ValueDef,
14
15 pub index: usize,
17
18 current: Value,
20
21 filldown_cache: Option<Value>,
23}
24
25impl ValueState {
26 pub fn new(def: ValueDef, index: usize) -> Self {
28 let initial = if def.has_option(ValueOption::List) {
29 Value::List(Vec::new())
30 } else {
31 Value::Empty
32 };
33
34 Self {
35 def,
36 index,
37 current: initial,
38 filldown_cache: None,
39 }
40 }
41
42 pub fn assign(&mut self, value: String, all_results: &mut [Vec<Value>]) {
44 if self.def.has_option(ValueOption::List) {
45 self.assign_list(value.clone());
46 } else {
47 self.current = Value::Single(value.clone());
48 }
49
50 if self.def.has_option(ValueOption::Filldown) {
52 self.filldown_cache = Some(self.current.clone());
53 }
54
55 if self.def.has_option(ValueOption::Fillup) && !value.is_empty() {
57 self.backfill(&value, all_results);
58 }
59 }
60
61 fn assign_list(&mut self, value: String) {
62 let item = if let Some(ref regex) = self.def.compiled_regex {
63 if let Ok(Some(caps)) = regex.captures(&value) {
65 let dict: HashMap<String, String> = regex
66 .capture_names()
67 .flatten()
68 .filter_map(|name| {
69 caps.name(name)
70 .map(|m| (name.to_string(), m.as_str().to_string()))
71 })
72 .collect();
73
74 if !dict.is_empty() {
75 ListItem::Dict(dict)
76 } else {
77 ListItem::String(value)
78 }
79 } else {
80 ListItem::String(value)
81 }
82 } else {
83 ListItem::String(value)
84 };
85
86 if let Value::List(ref mut list) = self.current {
87 list.push(item);
88 }
89 }
90
91 fn backfill(&self, value: &str, results: &mut [Vec<Value>]) {
92 for record in results.iter_mut().rev() {
94 if self.index < record.len() {
95 if record[self.index].is_empty() {
96 record[self.index] = Value::Single(value.to_string());
97 } else {
98 break;
100 }
101 }
102 }
103 }
104
105 pub fn assign_none(&mut self) {
115 if self.def.has_option(ValueOption::List) {
116 return;
118 }
119 self.current = Value::Empty;
120 if self.def.has_option(ValueOption::Filldown) {
121 self.filldown_cache = Some(Value::Empty);
122 }
123 }
124
125 pub fn clear(&mut self) {
127 if self.def.has_option(ValueOption::Filldown) {
128 if let Some(ref cached) = self.filldown_cache {
130 self.current = cached.clone();
131 return;
132 }
133 }
134
135 if self.def.has_option(ValueOption::List) && !self.def.has_option(ValueOption::Filldown) {
137 self.current = Value::List(Vec::new());
138 } else if !self.def.has_option(ValueOption::Filldown) {
139 self.current = Value::Empty;
140 }
141 }
142
143 pub fn clear_all(&mut self) {
145 self.filldown_cache = None;
146 self.current = if self.def.has_option(ValueOption::List) {
147 Value::List(Vec::new())
148 } else {
149 Value::Empty
150 };
151 }
152
153 pub fn satisfies_required(&self) -> bool {
155 if !self.def.has_option(ValueOption::Required) {
156 return true;
157 }
158 !self.current.is_empty()
159 }
160
161 pub fn take_for_record(&mut self) -> Value {
163 if self.def.has_option(ValueOption::List) {
164 self.current.clone()
167 } else {
168 std::mem::take(&mut self.current)
169 }
170 }
171
172 pub fn current(&self) -> &Value {
174 &self.current
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 fn make_def(name: &str, options: &[ValueOption]) -> ValueDef {
182 ValueDef {
183 name: name.to_string(),
184 pattern: "(\\S+)".to_string(),
185 options: options.iter().cloned().collect(),
186 template_pattern: format!("(?P<{}>\\S+)", name),
187 compiled_regex: None,
188 }
189 }
190
191 #[test]
192 fn test_simple_assign() {
193 let def = make_def("Test", &[]);
194 let mut state = ValueState::new(def, 0);
195 let mut results = Vec::new();
196
197 state.assign("hello".to_string(), &mut results);
198 assert_eq!(state.current(), &Value::Single("hello".into()));
199 }
200
201 #[test]
202 fn test_filldown() {
203 let def = make_def("Test", &[ValueOption::Filldown]);
204 let mut state = ValueState::new(def, 0);
205 let mut results = Vec::new();
206
207 state.assign("cached".to_string(), &mut results);
208 assert_eq!(state.current(), &Value::Single("cached".into()));
209
210 state.clear();
211 assert_eq!(state.current(), &Value::Single("cached".into()));
213
214 state.clear_all();
215 assert_eq!(state.current(), &Value::Empty);
217 }
218
219 #[test]
220 fn test_list() {
221 let def = make_def("Items", &[ValueOption::List]);
222 let mut state = ValueState::new(def, 0);
223 let mut results = Vec::new();
224
225 state.assign("one".to_string(), &mut results);
226 state.assign("two".to_string(), &mut results);
227 state.assign("three".to_string(), &mut results);
228
229 match state.current() {
230 Value::List(items) => {
231 assert_eq!(items.len(), 3);
232 }
233 _ => panic!("Expected List value"),
234 }
235 }
236
237 #[test]
238 fn test_required() {
239 let def = make_def("Required", &[ValueOption::Required]);
240 let mut state = ValueState::new(def, 0);
241
242 assert!(!state.satisfies_required()); let mut results = Vec::new();
245 state.assign("value".to_string(), &mut results);
246 assert!(state.satisfies_required()); }
248
249 #[test]
250 fn test_assign_none_clears_value() {
251 let def = make_def("Test", &[]);
252 let mut state = ValueState::new(def, 0);
253 let mut results = Vec::new();
254
255 state.assign("hello".to_string(), &mut results);
256 assert_eq!(state.current(), &Value::Single("hello".into()));
257
258 state.assign_none();
259 assert_eq!(state.current(), &Value::Empty);
260 }
261
262 #[test]
263 fn test_assign_none_clears_filldown_cache() {
264 let def = make_def("Test", &[ValueOption::Filldown]);
265 let mut state = ValueState::new(def, 0);
266 let mut results = Vec::new();
267
268 state.assign("cached".to_string(), &mut results);
270 assert_eq!(state.current(), &Value::Single("cached".into()));
271
272 state.clear();
274 assert_eq!(state.current(), &Value::Single("cached".into()));
275
276 state.assign_none();
278 assert_eq!(state.current(), &Value::Empty);
279
280 state.clear();
282 assert_eq!(state.current(), &Value::Empty);
283 }
284
285 #[test]
286 fn test_assign_none_skips_list() {
287 let def = make_def("Items", &[ValueOption::List]);
288 let mut state = ValueState::new(def, 0);
289 let mut results = Vec::new();
290
291 state.assign("one".to_string(), &mut results);
292 state.assign_none();
294 match state.current() {
295 Value::List(items) => assert_eq!(items.len(), 1),
296 _ => panic!("Expected List value"),
297 }
298 }
299}