1pub mod error;
2pub use error::{Result, TextFsmError};
3use log::{debug, trace, warn};
4pub use pest::Parser;
5pub use pest::iterators::Pair;
6use pest_derive::Parser;
7use regex::Regex;
8use serde::{Deserialize, Serialize};
9use std::collections::{HashMap, VecDeque};
10use std::fmt;
11
12#[cfg(feature = "clitable")]
13pub mod cli_table;
14pub mod export;
15pub mod varsubst;
16#[cfg(feature = "clitable")]
17pub use cli_table::CliTable;
18pub use export::{OutputFormat, TextFsmExport};
19
20pub struct TextFsmIter<R> {
22 fsm: TextFSM,
23 lines: std::io::Lines<R>,
24 eof_processed: bool,
25 current_line: Option<String>,
26}
27
28impl<R: std::io::BufRead> Iterator for TextFsmIter<R> {
29 type Item = Result<DataRecord>;
30
31 fn next(&mut self) -> Option<Self::Item> {
32 if !self.fsm.records.is_empty() {
34 return Some(Ok(self.fsm.records.pop_front().unwrap()));
35 }
36
37 if self.eof_processed {
38 return None;
39 }
40
41 loop {
42 let line = if let Some(ref l) = self.current_line {
43 l.clone()
44 } else {
45 match self.lines.next() {
46 Some(Ok(l)) => l,
47 Some(Err(e)) => return Some(Err(TextFsmError::IoError(e))),
48 None => {
49 if self.fsm.curr_state != "End" {
51 if let Err(e) = self.fsm.set_curr_state("EOF") {
52 return Some(Err(e));
53 }
54 if let Err(e) = self.fsm.parse_line("") {
55 return Some(Err(e));
56 }
57 if !self.fsm.records.is_empty() {
58 return Some(Ok(self.fsm.records.pop_front().unwrap()));
60 }
61 if let Err(e) = self.fsm.set_curr_state("End") {
62 return Some(Err(e));
63 }
64 }
65 self.eof_processed = true;
66 return None;
67 }
68 }
69 };
70
71 match self.fsm.parse_line(&line) {
73 Ok(ParseStatus::NextLine(maybe_next_state)) => {
74 self.current_line = None;
75 if let Some(next_state) = maybe_next_state {
76 match next_state {
77 NextState::Error(msg) => {
78 return Some(Err(TextFsmError::StateError(format!(
79 "Error state reached! msg: {:?}",
80 msg
81 ))));
82 }
83 NextState::NamedState(name) => {
84 if let Err(e) = self.fsm.set_curr_state(&name) {
85 return Some(Err(e));
86 }
87 }
88 }
89 }
90 }
91 Ok(ParseStatus::SameLine(maybe_next_state)) => {
92 self.current_line = Some(line.clone());
94 if let Some(next_state) = maybe_next_state {
95 match next_state {
96 NextState::Error(msg) => {
97 return Some(Err(TextFsmError::StateError(format!(
98 "Error state reached! msg: {:?}",
99 msg
100 ))));
101 }
102 NextState::NamedState(name) => {
103 if let Err(e) = self.fsm.set_curr_state(&name) {
104 return Some(Err(e));
105 }
106 }
107 }
108 }
109 }
110 Err(e) => return Some(Err(e)),
111 }
112
113 if self.fsm.curr_state == "EOF" || self.fsm.curr_state == "End" {
114 self.eof_processed = true;
115 if !self.fsm.records.is_empty() {
116 return Some(Ok(self.fsm.records.pop_front().unwrap()));
117 }
118 return None; }
120
121 if !self.fsm.records.is_empty() {
123 return Some(Ok(self.fsm.records.pop_front().unwrap()));
124 }
125 }
126 }
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
131pub struct DataRecord {
132 #[serde(flatten)]
134 pub fields: HashMap<String, Value>,
135 #[serde(skip_deserializing)]
137 pub record_key: Option<String>,
138}
139
140impl DataRecord {
141 pub fn new() -> Self {
143 Default::default()
144 }
145
146 pub fn overwrite_from(&mut self, from: DataRecord) {
148 for (k, v) in from.fields {
149 self.fields.insert(k, v);
150 }
151 }
152
153 pub fn compare_sets(result: &[Self], other: &[Self]) -> (Vec<Vec<String>>, Vec<Vec<String>>) {
156 let mut only_in_result: Vec<Vec<String>> = vec![];
157 let mut only_in_other: Vec<Vec<String>> = vec![];
158
159 for (i, irec) in result.iter().enumerate() {
160 let mut vo: Vec<String> = vec![];
161 for (k, v) in &irec.fields {
162 if i < other.len() {
163 let v0 = other[i].get(k);
164 if v0.is_none() || v0.unwrap() != v {
165 vo.push(format!("{}:{:?}", &k, &v));
166 }
167 } else {
168 vo.push(format!("{}:{:?}", &k, &v));
169 }
170 }
171 only_in_result.push(vo);
172 }
173
174 for (i, irec) in other.iter().enumerate() {
175 let mut vo: Vec<String> = vec![];
176 for (k, v) in &irec.fields {
177 if i < result.len() {
178 let v0 = result[i].get(k);
179 if v0.is_none() || v0.unwrap() != v {
180 vo.push(format!("{}:{:?}", &k, &v));
181 }
182 } else {
183 vo.push(format!("{}:{:?}", &k, &v));
184 }
185 }
186 only_in_other.push(vo);
187 }
188 (only_in_result, only_in_other)
189 }
190
191 pub fn insert(&mut self, name: String, value: String) {
194 use std::collections::hash_map::Entry;
195 match self.fields.entry(name) {
196 Entry::Occupied(mut entry) => {
197 let old_value = entry.get_mut();
198 if let Value::Single(old_str) = old_value {
199 let s = std::mem::take(old_str);
200 *old_value = Value::List(vec![s, value]);
201 } else if let Value::List(list) = old_value {
202 list.push(value);
203 }
204 }
205 Entry::Vacant(entry) => {
206 entry.insert(Value::Single(value));
207 }
208 }
209 }
210
211 pub fn append_value(&mut self, name: String, value: Value) {
213 if let Some(old_value) = self.fields.get_mut(&name) {
214 match old_value {
215 Value::Single(old_str_ref) => match value {
216 Value::Single(val) => {
217 *old_value = Value::Single(val);
218 }
219 Value::List(lst) => {
220 panic!(
221 "can not append list {:?} to single {:?} in var {}",
222 &lst, &old_str_ref, &name
223 );
224 }
225 },
226 Value::List(list) => match value {
227 Value::Single(val) => {
228 list.push(val);
229 }
230 Value::List(mut lst) => {
231 list.append(&mut lst);
232 }
233 },
234 }
235 } else {
236 self.fields.insert(name, value);
237 }
238 }
239
240 pub fn remove(&mut self, key: &str) {
242 self.fields.remove(key);
243 }
244
245 pub fn keys(&self) -> std::collections::hash_map::Keys<'_, String, Value> {
247 self.fields.keys()
248 }
249
250 pub fn get(&self, key: &str) -> Option<&Value> {
252 self.fields.get(key)
253 }
254
255 pub fn iter(&self) -> std::collections::hash_map::Iter<'_, String, Value> {
257 self.fields.iter()
258 }
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
263#[serde(untagged)]
264pub enum Value {
265 Single(String),
267 List(Vec<String>),
269}
270
271impl fmt::Display for Value {
272 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
273 match self {
274 Value::Single(s) => write!(f, "{}", s),
275 Value::List(l) => write!(f, "{:?}", l),
276 }
277 }
278}
279
280#[derive(Parser, Debug, Default, Clone)]
282#[grammar = "textfsm.pest"]
283pub struct TextFSMParser {
284 pub values: HashMap<String, ValueDefinition>,
286 pub mandatory_values: Vec<String>,
288 pub states: HashMap<String, StateCompiled>,
290}
291
292#[derive(Debug, Default, Clone)]
294pub struct TextFSM {
295 pub parser: TextFSMParser,
297 pub curr_state: String,
299 pub curr_record: DataRecord,
301 pub filldown_record: DataRecord,
303 pub records: VecDeque<DataRecord>,
305}
306
307#[derive(Debug, PartialEq, Clone)]
309pub enum LineAction {
310 Continue(Option<NextState>),
312 Next(Option<NextState>),
314}
315
316pub enum ParseStatus {
317 NextLine(Option<NextState>),
318 SameLine(Option<NextState>),
319}
320
321impl Default for LineAction {
322 fn default() -> LineAction {
323 LineAction::Next(None)
324 }
325}
326
327#[derive(Debug, Default, PartialEq, Clone)]
329pub enum RecordAction {
330 #[default]
332 NoRecord,
333 Record,
335 Clear,
337 Clearall,
339}
340
341#[derive(Debug, PartialEq, Clone)]
343pub enum NextState {
344 Error(Option<String>),
346 NamedState(String),
348}
349
350#[derive(Debug, Default, PartialEq, Clone)]
352pub struct RuleTransition {
353 line_action: LineAction,
354 record_action: RecordAction,
355}
356
357#[derive(Debug, Default, PartialEq, Clone)]
359pub struct StateRule {
360 rule_match: String,
362 transition: RuleTransition,
364}
365
366#[derive(Debug, Default, PartialEq, Clone)]
368pub struct ValueDefinition {
369 name: String,
371 is_filldown: bool,
373 is_key: bool,
375 is_required: bool,
377 is_list: bool,
379 is_fillup: bool,
381 regex_pattern: String,
383 options: Option<String>,
385}
386
387#[derive(Debug, Clone)]
389pub enum MultiRegex {
390 Classic(regex::Regex),
392 Fancy(fancy_regex::Regex),
394}
395
396#[derive(Debug, Clone)]
398pub struct CapturedVariable {
399 pub name: String,
400 pub is_list: bool,
401 pub is_key: bool,
402 pub is_filldown: bool,
403 pub is_fillup: bool,
404}
405
406#[derive(Debug, Clone)]
408pub struct StateRuleCompiled {
409 _rule_match: String,
410 _expanded_rule_match: String,
411 captured_vars: Vec<CapturedVariable>,
413 maybe_regex: Option<MultiRegex>,
415 transition: RuleTransition,
417}
418
419#[derive(Debug, Clone)]
421pub struct StateCompiled {
422 name: String,
424 rules: Vec<StateRuleCompiled>,
426}
427
428#[derive(Debug, Clone)]
430pub enum DataRecordConversion {
431 LowercaseKeys,
433}
434
435impl TextFSMParser {
436 fn _log_pair(indent: usize, pair: &Pair<'_, Rule>) {
437 let spaces = " ".repeat(indent);
438 trace!("{}Rule: {:?}", spaces, pair.as_rule());
439 trace!("{}Span: {:?}", spaces, pair.as_span());
440 trace!("{}Text: {}", spaces, pair.as_str());
441 for p in pair.clone().into_inner() {
442 Self::_log_pair(indent + 2, &p);
443 }
444 }
445 pub fn parse_state_rule_transition(pair: &Pair<'_, Rule>) -> RuleTransition {
446 let mut record_action: RecordAction = Default::default();
447 let mut line_action: LineAction = Default::default();
448 for pair in pair.clone().into_inner() {
450 match pair.as_rule() {
451 Rule::record_action => {
452 record_action = match pair.as_str() {
453 "Record" => RecordAction::Record,
454 "NoRecord" => RecordAction::NoRecord,
455 "Clear" => RecordAction::Clear,
456 "Clearall" => RecordAction::Clearall,
457 x => panic!("Record action {} not supported", x),
458 };
459 }
460 Rule::line_action => {
461 line_action = match pair.as_str() {
462 "Continue" => LineAction::Continue(None),
463 "Next" => LineAction::Next(None),
464 x => panic!("Record action {} not supported", x),
465 };
466 }
467 Rule::err_state => {
468 let mut maybe_err_msg: Option<String> = None;
469 for p in pair.clone().into_inner() {
470 if p.as_rule() == Rule::err_msg {
471 maybe_err_msg = Some(p.as_str().to_string());
472 }
473 }
474 let next_state = NextState::Error(maybe_err_msg);
475 line_action = LineAction::Next(Some(next_state));
476 }
477 Rule::next_state => {
478 let next_state = NextState::NamedState(pair.as_str().to_string());
479 match line_action {
480 LineAction::Next(None) => {
481 line_action = LineAction::Next(Some(next_state));
482 }
483 LineAction::Continue(None) => {
484 line_action = LineAction::Continue(Some(next_state));
485 }
486 _ => {
487 panic!(
488 "Line action {:?} does not support next state (attempted {:?})",
489 &line_action,
490 pair.as_str()
491 );
492 }
493 }
494 }
495 x => {
496 panic!("Rule {:?} not supported!", &x);
497 }
498 }
499 }
500 RuleTransition {
501 record_action,
502 line_action,
503 }
504 }
505 pub fn parse_state_rule(pair: &Pair<'_, Rule>) -> StateRule {
506 let mut rule_match: Option<String> = None;
507 let mut transition: RuleTransition = Default::default();
511 let mut has_action = false;
512 let spaces = "";
513 for pair in pair.clone().into_inner() {
514 match pair.as_rule() {
515 Rule::rule_match => {
516 rule_match = Some(pair.as_str().to_string());
517 }
518 Rule::transition_action => {
519 has_action = true;
520 transition = Self::parse_state_rule_transition(&pair);
521 }
523 x => {
524 println!("{}state Rule: {:?}", spaces, pair.as_rule());
525 println!("{}Span: {:?}", spaces, pair.as_span());
526 println!("{}Text: {}", spaces, pair.as_str());
527 panic!("state rule {:?} not supported", &x);
528 }
529 }
530 }
531 let mut rule_match = rule_match.expect("rule_match must be always set");
532 if (rule_match.ends_with(" ") || rule_match.ends_with("\t")) && !has_action {
533 println!(
534 "WARNING: '{}' has trailing spaces without transition action!",
535 &rule_match
536 );
537 rule_match = rule_match.trim_end().to_string();
538 }
539 if rule_match.contains(r#"\<"#) {
540 println!("WARNING: replacing \\< with < in '{}'", &rule_match);
541 rule_match = rule_match.replace("\\<", "<");
542 }
543 if rule_match.contains(r#"\>"#) {
544 println!("WARNING: replacing \\> with > in '{}'", &rule_match);
545 rule_match = rule_match.replace("\\>", ">");
546 }
547 StateRule {
548 rule_match,
549 transition,
550 }
551 }
552
553 pub fn compile_state_rule(
554 rule: &StateRule,
555 values: &HashMap<String, ValueDefinition>,
556 ) -> Result<StateRuleCompiled> {
557 let mut expanded_rule_match: String = String::new();
558 let rule_match = rule.rule_match.clone();
559 let mut captured_vars: Vec<CapturedVariable> = vec![];
560 let varsubst = varsubst::VariableParser::parse_dollar_string(&rule_match)
561 .map_err(|e| TextFsmError::ParseError(e.to_string()))?;
562 {
564 use varsubst::ParseChunk;
565 for i in &varsubst {
566 match i {
567 ParseChunk::DollarDollar => expanded_rule_match.push('$'),
568 ParseChunk::Text(s) => expanded_rule_match.push_str(s),
569 ParseChunk::Variable(v) => match values.get(v) {
570 Some(val) => {
571 let v_out = format!("(?P<{}>{})", v, val.regex_pattern);
572 expanded_rule_match.push_str(&v_out);
573 captured_vars.push(CapturedVariable {
574 name: v.clone(),
575 is_list: val.is_list,
576 is_key: val.is_key,
577 is_filldown: val.is_filldown,
578 is_fillup: val.is_fillup,
579 });
580 }
581 None => {
582 return Err(TextFsmError::ParseError(format!(
583 "Can not find variable '{}' while parsing rule_match '{}'",
584 &v, &rule.rule_match
585 )));
586 }
587 },
588 }
589 }
590 }
591 let regex_val = match Regex::new(&expanded_rule_match) {
594 Ok(r) => MultiRegex::Classic(r),
595 Err(_e) => {
596 use fancy_regex::Error;
597 use fancy_regex::ParseError;
598
599 let freg = loop {
600 let fancy_regex = fancy_regex::Regex::new(&expanded_rule_match);
601 match fancy_regex {
602 Ok(x) => {
603 break x;
604 }
605 Err(Error::ParseError(pos, e)) => {
606 println!("STR:{}", &expanded_rule_match[0..pos + 1]);
607 println!("ERR:{}^", " ".repeat(pos));
608 match e {
609 ParseError::TargetNotRepeatable => {
610 if let Some(char_index) =
611 expanded_rule_match.char_indices().nth(pos)
612 {
613 println!(
614 "WARNING: repeat quantifier on a lookahead, lookbehind or other zero-width item"
615 );
616 expanded_rule_match.remove(char_index.0);
617 } else {
618 return Err(TextFsmError::ParseError(
619 "Can not fix up regex!".to_string(),
620 ));
621 }
622 }
623 e => {
624 return Err(TextFsmError::ParseError(format!(
625 "Error: {:?}",
626 &e
627 )));
628 }
629 }
630 }
631 x => {
632 return Err(TextFsmError::ParseError(format!("Error: {:?}", &x)));
633 }
634 }
635 };
636 MultiRegex::Fancy(freg)
637 }
638 };
639 let maybe_regex = Some(regex_val);
640 let transition = rule.transition.clone();
641 let _rule_match = rule_match;
642 let _expanded_rule_match = expanded_rule_match;
643
644 Ok(StateRuleCompiled {
645 _rule_match,
646 _expanded_rule_match,
647 captured_vars,
648 maybe_regex,
649 transition,
650 })
651 }
652 pub fn parse_and_compile_state_definition(
653 pair: &Pair<'_, Rule>,
654 values: &HashMap<String, ValueDefinition>,
655 ) -> Result<StateCompiled> {
656 let mut name: Option<String> = None;
657 let mut rules: Vec<StateRuleCompiled> = vec![];
659
660 for pair in pair.clone().into_inner() {
661 match pair.as_rule() {
662 Rule::state_header => {
663 name = Some(pair.as_str().to_string());
664 }
666 Rule::rules => {
667 for pair in pair.clone().into_inner() {
668 let rule = Self::parse_state_rule(&pair);
669 trace!("PARSED RULE [{:?}]: {:#?}", &name, &rule);
670 let compiled_rule = Self::compile_state_rule(&rule, values)?;
671 rules.push(compiled_rule);
672 }
673 }
674 x => {
675 let spaces = "";
676 println!("{}state def Rule: {:?}", spaces, pair.as_rule());
677 println!("{}Span: {:?}", spaces, pair.as_span());
678 println!("{}Text: {}", spaces, pair.as_str());
679 return Err(TextFsmError::ParseError(format!(
680 "Rule not supported in state definition: {:?}",
681 &x
682 )));
683 }
684 }
685 }
686 let name =
687 name.ok_or_else(|| TextFsmError::InternalError("state must have a name".to_string()))?;
688 Ok(StateCompiled { name, rules })
689 }
690
691 pub fn parse_value_definition(pair: &Pair<'_, Rule>) -> Result<ValueDefinition> {
692 let mut name: Option<String> = None;
694 let mut regex_pattern: Option<String> = None;
695 let mut options: Option<String> = None;
696 let mut is_filldown = false;
697 let mut is_key = false;
698 let mut is_required = false;
699 let mut is_list = false;
700 let mut is_fillup = false;
701
702 for p in pair.clone().into_inner() {
703 match p.as_rule() {
704 Rule::options => options = Some(p.as_str().to_string()),
705 Rule::identifier => name = Some(p.as_str().to_string()),
706 Rule::regex_pattern => {
707 regex_pattern = Some(p.as_str().to_string());
708 }
709 x => {
710 return Err(TextFsmError::ParseError(format!(
711 "Rule {:?} in value definition",
712 x
713 )));
714 }
715 }
716 }
718 if let (Some(name), Some(mut regex_pattern)) = (name.clone(), regex_pattern.clone()) {
719 if let Some(ref opts) = options {
720 let opts = opts.split(",");
721 for word in opts {
722 match word {
723 "Filldown" => is_filldown = true,
724 "Key" => is_key = true,
725 "Required" => is_required = true,
726 "List" => is_list = true,
727 "Fillup" => is_fillup = true,
728 x => {
729 return Err(TextFsmError::ParseError(format!(
730 "Unknown option {:?}",
731 &x
732 )));
733 }
734 }
735 }
736 }
737 if regex_pattern.contains(r#"\<"#) {
738 println!("WARNING: replacing \\< with < in value '{}'", &name);
739 regex_pattern = regex_pattern.replace("\\<", "<");
740 }
741 if regex_pattern.contains(r#"\>"#) {
742 println!("WARNING: replacing \\> with > in value '{}'", &name);
743 regex_pattern = regex_pattern.replace("\\>", ">");
744 }
745 Ok(ValueDefinition {
746 name,
747 regex_pattern,
748 is_filldown,
749 is_key,
750 is_required,
751 is_list,
752 is_fillup,
753 options,
754 })
755 } else {
756 Err(TextFsmError::ParseError(format!(
757 "Error parsing value: {:?} {:?} [ {:?} ]",
758 &name, ®ex_pattern, &options
759 )))
760 }
761 }
762 pub fn parse_value_defs(
763 pair: &Pair<'_, Rule>,
764 ) -> Result<(HashMap<String, ValueDefinition>, Vec<String>)> {
765 let mut vals = HashMap::new();
766 let mut mandatory_values: Vec<String> = vec![];
767 for pair in pair.clone().into_inner() {
768 if Rule::value_definition == pair.as_rule() {
769 let val = Self::parse_value_definition(&pair)?;
770 if val.is_required {
771 mandatory_values.push(val.name.clone());
772 }
773 vals.insert(val.name.clone(), val);
774 }
775 }
776 Ok((vals, mandatory_values))
777 }
778
779 pub fn from_string(content: &str) -> Result<Self> {
781 let mut template = content.to_string();
782 if !template.ends_with('\n') {
784 template.push('\n');
785 }
786 template.push_str("\n\n");
787
788 let mut seen_eoi = false;
789 let mut values: HashMap<String, ValueDefinition> = HashMap::new();
790 let mut states: HashMap<String, StateCompiled> = HashMap::new();
791 let mut mandatory_values: Vec<String> = vec![];
792
793 let end_state = NextState::NamedState("End".to_string());
794 let eof_rule = StateRule {
795 rule_match: ".*".to_string(),
796 transition: RuleTransition {
797 line_action: LineAction::Next(Some(end_state)),
798 record_action: RecordAction::Record,
799 },
800 };
801
802 let compiled_eof_rule = Self::compile_state_rule(&eof_rule, &values)?;
803
804 let eof_state = StateCompiled {
805 name: "EOF".to_string(),
806 rules: vec![compiled_eof_rule],
807 };
808 states.insert(eof_state.name.clone(), eof_state);
809
810 match TextFSMParser::parse(Rule::file, &template) {
811 Ok(pairs) => {
812 for pair in pairs.clone() {
813 match pair.as_rule() {
814 Rule::value_definitions => {
815 (values, mandatory_values) = Self::parse_value_defs(&pair)?;
816 }
817 Rule::state_definitions => {
818 for pair in pair.clone().into_inner() {
819 match pair.as_rule() {
820 Rule::state_definition => {
821 trace!("STATE DEFINITION");
822 Self::_log_pair(0, &pair);
823 let state = Self::parse_and_compile_state_definition(
824 &pair, &values,
825 )?;
826 trace!("STATE DEFINITION END: {:?}", &state);
827 if &state.name != "EOF" && states.contains_key(&state.name)
828 {
829 return Err(TextFsmError::StateError(format!(
830 "State {} already defined in the file!",
831 &state.name
832 )));
833 }
834 states.insert(state.name.clone(), state);
835 }
836 x => {
837 return Err(TextFsmError::ParseError(format!(
838 "state definition rule {:?} not supported",
839 x
840 )));
841 }
842 }
843 }
844 }
845 Rule::EOI => {
846 seen_eoi = true;
847 }
848 x => {
849 return Err(TextFsmError::ParseError(format!(
850 "RULE {:?} not supported",
851 &x
852 )));
853 }
854 }
855 }
857
858 if !seen_eoi {
859 println!("WARNING: EOI token not seen");
860 }
861
862 if !states.contains_key("Start") {
863 return Err(TextFsmError::StateError(
864 "Start state not found".to_string(),
865 ));
866 }
867
868 Ok(TextFSMParser {
869 values,
870 mandatory_values,
871 states,
872 })
873 }
874 Err(e) => Err(TextFsmError::ParseError(format!("Error: {}", e))),
875 }
876 }
877
878 pub fn from_file<P: AsRef<std::path::Path>>(fname: P) -> Result<Self> {
880 let path = fname.as_ref();
881 let content = std::fs::read_to_string(path)?;
882 Self::from_string(&content)
883 .map_err(|e| TextFsmError::ParseError(format!("file {} Error: {}", path.display(), e)))
884 }
885}
886
887impl TextFSM {
888 pub fn from_string(content: &str) -> Result<Self> {
890 let parser = TextFSMParser::from_string(content)?;
891 let curr_state = "Start".to_string();
892 Ok(TextFSM {
893 parser,
894 curr_state,
895 ..Default::default()
896 })
897 }
898
899 pub fn from_file<P: AsRef<std::path::Path>>(fname: P) -> Result<Self> {
901 let parser = TextFSMParser::from_file(fname)?;
902 let curr_state = "Start".to_string();
903 Ok(TextFSM {
904 parser,
905 curr_state,
906 ..Default::default()
907 })
908 }
909
910 pub fn reset(&mut self) {
913 self.curr_state = "Start".to_string();
914 self.curr_record = DataRecord::default();
915 self.filldown_record = DataRecord::default();
916 self.records.clear();
917 }
918
919 pub fn set_curr_state(&mut self, state_name: &str) -> Result<()> {
921 if state_name != "End" && !self.parser.states.contains_key(state_name) {
922 return Err(TextFsmError::StateError(format!(
923 "State '{}' not found!",
924 state_name
925 )));
926 }
927 self.curr_state = state_name.to_string();
928 Ok(())
929 }
930
931 pub fn is_key_value(&self, value_name: &str) -> Option<bool> {
932 self.parser.values.get(value_name).map(|val| val.is_key)
933 }
934
935 pub fn is_filldown_value(&self, value_name: &str) -> Option<bool> {
936 self.parser
937 .values
938 .get(value_name)
939 .map(|val| val.is_filldown)
940 }
941
942 pub fn is_fillup_value(&self, value_name: &str) -> Option<bool> {
943 self.parser.values.get(value_name).map(|val| val.is_fillup)
944 }
945
946 pub fn is_list_value(&self, value_name: &str) -> Option<bool> {
947 self.parser.values.get(value_name).map(|val| val.is_list)
948 }
949
950 pub fn insert_value_optimized(
952 &self,
953 curr_record: &mut DataRecord,
954 filldown_record: &mut DataRecord,
955 var_info: &CapturedVariable,
956 maybe_value: Option<&str>,
957 aline: &str,
958 ) -> Result<()> {
959 let name = &var_info.name;
960
961 let ins_value = if let Some(value) = maybe_value {
962 trace!("SET VAR '{}' = '{}'", name, value);
963
964 if var_info.is_list {
965 Value::List(vec![value.to_string()])
966 } else {
967 Value::Single(value.to_string())
968 }
969 } else {
970 warn!(
971 "WARNING: Could not capture '{}' from string '{}'",
972 name, aline
973 );
974
975 if var_info.is_list {
976 Value::List(vec![format!("None")])
977 } else {
978 Value::Single(String::new())
979 }
980 };
981
982 curr_record.fields.insert(name.clone(), ins_value.clone());
983
984 if var_info.is_key {
985 curr_record.record_key = if let Some(k) = curr_record.record_key.as_ref() {
986 Some(format!("{}/{:?}", k, &ins_value))
987 } else {
988 Some(format!("{:?}", &ins_value))
989 };
990
991 trace!("RECORD KEY: '{:?}'", &curr_record.record_key);
992 }
993
994 if var_info.is_filldown {
995 filldown_record.fields.insert(name.clone(), ins_value);
996 }
997
998 Ok(())
999 }
1000
1001 fn process_record_action(
1002 curr_record: &mut DataRecord,
1003 filldown_record: &mut DataRecord,
1004 records: &mut VecDeque<DataRecord>,
1005 mandatory_values: &[String],
1006 values: &HashMap<String, ValueDefinition>,
1007 action: RecordAction,
1008 ) -> Result<()> {
1009 match action {
1010 RecordAction::Record => {
1011 let mut mandatory_count = 0;
1012 let number_of_values = curr_record.keys().len();
1013
1014 for k in mandatory_values {
1015 if curr_record.get(k).is_some() {
1016 mandatory_count += 1;
1017 }
1018 }
1019 if number_of_values > 0 {
1020 if mandatory_count == mandatory_values.len() {
1021 let mut new_rec: DataRecord = filldown_record.clone();
1022 std::mem::swap(&mut new_rec, curr_record);
1024 for v in values.values() {
1027 if new_rec.get(&v.name).is_none() {
1028 if v.is_list {
1029 new_rec.fields.insert(v.name.clone(), Value::List(vec![]));
1030 } else {
1031 new_rec
1032 .fields
1033 .insert(v.name.clone(), Value::Single(String::new()));
1034 }
1035 }
1036 }
1037 trace!("RECORD: {:?}", &new_rec);
1038 records.push_back(new_rec);
1039 } else {
1040 trace!("RECORD: no required fields set");
1041 }
1042 } else {
1043 trace!("RECORD: record is empty, not dumping");
1044 }
1045 }
1046 RecordAction::NoRecord => {} RecordAction::Clear => {
1048 let mut rem_keys: Vec<String> = vec![];
1049 for (ref k, _v) in curr_record.iter() {
1050 if let Some(val) = values.get(*k) {
1051 if !val.is_filldown {
1052 rem_keys.push(k.to_string());
1053 }
1054 } else {
1055 return Err(TextFsmError::InternalError(format!(
1056 "is_filldown_value for {} failed",
1057 k
1058 )));
1059 }
1060 }
1061 for k in rem_keys {
1062 curr_record.remove(&k);
1063 }
1064 }
1065 RecordAction::Clearall => {
1066 *curr_record = Default::default();
1068 *filldown_record = Default::default();
1069 }
1070 }
1071 Ok(())
1072 }
1073
1074 pub fn parse_line(&mut self, aline: &str) -> Result<ParseStatus> {
1076 let mut tmp_datarec = DataRecord::new();
1078 let mut tmp_filldown_rec = DataRecord::new();
1079 let mut fillup_fields: Vec<String> = vec![];
1081
1082 let state_name = &self.curr_state;
1083 let state_def = self.parser.states.get(state_name);
1084
1085 if let Some(curr_state) = state_def {
1086 trace!("CURR STATE: {:?}", &curr_state);
1087 for rule in &curr_state.rules {
1088 let mut transition = RuleTransition {
1089 line_action: LineAction::Continue(None),
1090 ..Default::default()
1091 };
1092 trace!("TRY RULE: {:?}", &rule);
1093 let mut capture_matched = false;
1094 tmp_datarec.fields.clear();
1095 tmp_datarec.record_key = None;
1096 tmp_filldown_rec.fields.clear();
1097 fillup_fields.clear();
1098
1099 match &rule.maybe_regex {
1100 Some(MultiRegex::Classic(rx)) => {
1101 debug!("RULE(CLASSIC REGEX): {:?}", &rule);
1102 if let Some(caps) = rx.captures(aline) {
1103 for var in &rule.captured_vars {
1104 let maybe_value = caps.name(&var.name).map(|x| x.as_str());
1105 self.insert_value_optimized(
1106 &mut tmp_datarec,
1107 &mut tmp_filldown_rec,
1108 var,
1109 maybe_value,
1110 aline,
1111 )?;
1112 if var.is_fillup {
1113 fillup_fields.push(var.name.clone());
1114 }
1115 }
1116 capture_matched = true;
1117 }
1118 }
1119 Some(MultiRegex::Fancy(rx)) => {
1120 debug!("RULE(FANCY REGEX): {:?}", &rule);
1121 if let Ok(Some(caps)) = rx.captures(aline) {
1122 for var in &rule.captured_vars {
1123 let maybe_value = caps.name(&var.name).map(|x| x.as_str());
1124 self.insert_value_optimized(
1125 &mut tmp_datarec,
1126 &mut tmp_filldown_rec,
1127 var,
1128 maybe_value,
1129 aline,
1130 )?;
1131 if var.is_fillup {
1132 fillup_fields.push(var.name.clone());
1133 }
1134 }
1135 capture_matched = true;
1136 }
1137 }
1138 x => {
1139 return Err(TextFsmError::ParseError(format!(
1140 "Regex {:?} on rule is not supported",
1141 &x
1142 )));
1143 }
1144 }
1145 if capture_matched {
1146 trace!("TMP_REC: {:?}", &tmp_datarec);
1147 trace!("TMP_FILLDOWN: {:?}", &tmp_filldown_rec);
1148 for (name, v) in tmp_datarec.fields.drain() {
1149 if fillup_fields.contains(&name) {
1150 let name_ref = &name;
1151 for fillup_record in self.records.iter_mut().rev() {
1152 if let Some(ref oldval) = fillup_record.fields.get(name_ref) {
1153 match oldval {
1154 Value::Single(s) => {
1155 if !s.is_empty() {
1156 break;
1157 }
1158 }
1159 Value::List(_lst) => {
1160 return Err(TextFsmError::ParseError(
1161 "fillup not supported for lists!".to_string(),
1162 ));
1163 }
1164 }
1165 }
1166 fillup_record.fields.insert(name.clone(), v.clone());
1167 }
1168 }
1169 self.curr_record.append_value(name, v);
1170 }
1171 trace!("TMP KEY: {:?}", &tmp_datarec.record_key);
1172 self.curr_record.record_key = tmp_datarec.record_key;
1173 for (name, v) in tmp_filldown_rec.fields.drain() {
1177 self.filldown_record.append_value(name, v);
1178 }
1179 transition = rule.transition.clone();
1180 }
1181 Self::process_record_action(
1184 &mut self.curr_record,
1185 &mut self.filldown_record,
1186 &mut self.records,
1187 &self.parser.mandatory_values,
1188 &self.parser.values,
1189 transition.record_action,
1190 )?;
1191
1192 match transition.line_action {
1193 LineAction::Next(x) => return Ok(ParseStatus::NextLine(x)),
1194 LineAction::Continue(maybe_next_state) => {
1195 if let Some(next_state) = maybe_next_state {
1196 return Ok(ParseStatus::SameLine(Some(next_state)));
1197 }
1198 } }
1200 }
1201 } else {
1202 return Err(TextFsmError::StateError(format!(
1203 "State {} not found!",
1204 &self.curr_state
1205 )));
1206 }
1207 Ok(ParseStatus::NextLine(None))
1208 }
1209
1210 pub fn lowercase_keys(src: &VecDeque<DataRecord>) -> Vec<DataRecord> {
1212 let mut out = vec![];
1213
1214 for irec in src {
1215 let mut hm = DataRecord::new();
1216 hm.record_key = irec.record_key.clone();
1217 for (k, v) in irec.iter() {
1218 let kl = k.to_lowercase();
1219 hm.fields.insert(kl, v.clone());
1220 }
1221 out.push(hm);
1222 }
1223 out
1224 }
1225
1226 pub fn parse_reader<R: std::io::BufRead>(self, reader: R) -> TextFsmIter<R> {
1232 TextFsmIter {
1233 fsm: self,
1234 lines: reader.lines(),
1235 eof_processed: false,
1236 current_line: None,
1237 }
1238 }
1239
1240 pub fn parse_string(
1246 &mut self,
1247 input: &str,
1248 conversion: Option<DataRecordConversion>,
1249 ) -> Result<Vec<DataRecord>> {
1250 for (_lineno, aline) in input.lines().enumerate() {
1251 debug!("LINE:#{}: '{}'", _lineno + 1, &aline);
1252 loop {
1253 let status = self.parse_line(aline)?;
1254 match status {
1255 ParseStatus::NextLine(maybe_next_state) => {
1256 if let Some(next_state) = maybe_next_state {
1257 match next_state {
1258 NextState::Error(maybe_msg) => {
1259 return Err(TextFsmError::StateError(format!(
1260 "Error state reached! msg: {:?}",
1261 &maybe_msg
1262 )));
1263 }
1264 NextState::NamedState(name) => {
1265 self.set_curr_state(&name)?;
1266 }
1267 }
1268 }
1269 break;
1270 }
1271 ParseStatus::SameLine(maybe_next_state) => {
1272 if let Some(next_state) = maybe_next_state {
1273 match next_state {
1274 NextState::Error(maybe_msg) => {
1275 return Err(TextFsmError::StateError(format!(
1276 "Error state reached! msg: {:?}",
1277 &maybe_msg
1278 )));
1279 }
1280 NextState::NamedState(name) => {
1281 self.set_curr_state(&name)?;
1282 }
1283 }
1284 }
1285 }
1286 }
1287 }
1288 if &self.curr_state == "EOF" || &self.curr_state == "End" {
1289 break;
1290 }
1291 }
1292 if &self.curr_state != "End" {
1293 self.set_curr_state("EOF")?;
1294 self.parse_line("")?;
1295 self.set_curr_state("End")?;
1297 }
1298 match conversion {
1299 None => Ok(self.records.clone().into()),
1300 Some(DataRecordConversion::LowercaseKeys) => Ok(Self::lowercase_keys(&self.records)),
1301 }
1302 }
1303
1304 pub fn parse_file<P: AsRef<std::path::Path>>(
1310 &mut self,
1311 fname: P,
1312 conversion: Option<DataRecordConversion>,
1313 ) -> Result<Vec<DataRecord>> {
1314 let input = std::fs::read_to_string(fname)?;
1315 self.parse_string(&input, conversion)
1316 }
1317}