Skip to main content

textfsm_core/clitable/
completion.rs

1//! Completion syntax expansion for index file patterns.
2//!
3//! Supports the `[[...]]` variable-length completion syntax:
4//! - `sh[[ow]]` expands to `sh(o(w)?)?`
5//! - `sh[[ow]] ver[[sion]]` expands to `sh(o(w)?)? ver(s(i(o(n)?)?)?)?`
6
7use super::CliTableError;
8
9/// Expand `[[...]]` completion syntax to regex pattern.
10///
11/// Each character inside `[[...]]` becomes optionally matched,
12/// building from left to right.
13///
14/// # Examples
15///
16/// ```rust,ignore
17/// assert_eq!(expand_completion("sh[[ow]]").unwrap(), "sh(o(w)?)?");
18/// assert_eq!(expand_completion("sh[[ow]] ver[[sion]]").unwrap(), "sh(o(w)?)? ver(s(i(o(n)?)?)?)?");
19/// assert_eq!(expand_completion("no completion").unwrap(), "no completion");
20/// ```
21pub fn expand_completion(pattern: &str) -> Result<String, CliTableError> {
22    let mut result = String::with_capacity(pattern.len() * 2);
23    let mut chars = pattern.chars().peekable();
24
25    while let Some(c) = chars.next() {
26        if c == '[' && chars.peek() == Some(&'[') {
27            // Start of [[...]] completion
28            chars.next(); // consume second '['
29
30            let mut completion_chars = Vec::new();
31
32            // Collect characters until ]]
33            loop {
34                match chars.next() {
35                    Some(']') if chars.peek() == Some(&']') => {
36                        chars.next(); // consume second ']'
37                        break;
38                    }
39                    Some(ch) => completion_chars.push(ch),
40                    None => {
41                        return Err(CliTableError::InvalidCompletion(
42                            "unclosed [[...]] completion".into(),
43                        ));
44                    }
45                }
46            }
47
48            // Expand the completion
49            // "ow" becomes "(o(w)?)?"
50            if completion_chars.is_empty() {
51                // Empty completion [[]] - just skip
52                continue;
53            }
54
55            result.push('(');
56            for (i, ch) in completion_chars.iter().enumerate() {
57                // Escape regex metacharacters
58                if is_regex_meta(*ch) {
59                    result.push('\\');
60                }
61                result.push(*ch);
62                if i < completion_chars.len() - 1 {
63                    result.push('(');
64                }
65            }
66            // Add closing )?
67            for _ in 0..completion_chars.len() {
68                result.push_str(")?");
69            }
70        } else {
71            result.push(c);
72        }
73    }
74
75    Ok(result)
76}
77
78/// Check if a character is a regex metacharacter that needs escaping.
79fn is_regex_meta(c: char) -> bool {
80    matches!(
81        c,
82        '.' | '*' | '+' | '?' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '^' | '$' | '\\'
83    )
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_simple_completion() {
92        assert_eq!(expand_completion("sh[[ow]]").unwrap(), "sh(o(w)?)?");
93    }
94
95    #[test]
96    fn test_multiple_completions() {
97        assert_eq!(
98            expand_completion("sh[[ow]] ver[[sion]]").unwrap(),
99            "sh(o(w)?)? ver(s(i(o(n)?)?)?)?",
100        );
101    }
102
103    #[test]
104    fn test_no_completion() {
105        assert_eq!(expand_completion("show version").unwrap(), "show version");
106    }
107
108    #[test]
109    fn test_empty_completion() {
110        assert_eq!(expand_completion("sh[[]]ow").unwrap(), "show");
111    }
112
113    #[test]
114    fn test_single_char_completion() {
115        assert_eq!(expand_completion("sh[[o]]w").unwrap(), "sh(o)?w");
116    }
117
118    #[test]
119    fn test_completion_at_end() {
120        assert_eq!(
121            expand_completion("sh[[ow]] int[[erfaces]]").unwrap(),
122            "sh(o(w)?)? int(e(r(f(a(c(e(s)?)?)?)?)?)?)?",
123        );
124    }
125
126    #[test]
127    fn test_unclosed_completion() {
128        let result = expand_completion("sh[[ow");
129        assert!(matches!(result, Err(CliTableError::InvalidCompletion(_))));
130    }
131
132    #[test]
133    fn test_regex_metachar_in_completion() {
134        // A dot inside completion should be escaped
135        assert_eq!(expand_completion("test[[.x]]").unwrap(), "test(\\.(x)?)?");
136    }
137
138    #[test]
139    fn test_real_ntc_pattern() {
140        // Real pattern from ntc-templates
141        let result = expand_completion("sh[[ow]] (in[[terfaces]] e[[thernet]]|in[[terfaces]])$");
142        assert!(result.is_ok());
143        let expanded = result.unwrap();
144        assert!(expanded.contains("sh(o(w)?)?"));
145        assert!(expanded.contains("in(t(e(r(f(a(c(e(s)?)?)?)?)?)?)?)?"));
146    }
147}