use std::collections::HashMap;
use once_cell::sync::Lazy;
use regex::{Regex, RegexSet};
mod builder;
mod config;
mod diagnostics;
mod execution;
mod runtime;
mod transitions;
pub use config::{
DeviceCommandExecutionConfig, DeviceHandlerConfig, DeviceInputRule, DevicePromptRule,
DevicePromptWithSysRule, DeviceShellFlavor, DeviceTransitionRule, input_rule, prompt_rule,
prompt_with_sys_rule, transition_rule,
};
pub use diagnostics::StateMachineDiagnostics;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum CommandExecutionStrategy {
PromptDriven,
ShellExitStatus {
marker: String,
shell_flavor: DeviceShellFlavor,
},
}
pub struct DeviceHandler {
current_state_index: usize,
all_states: Vec<String>,
all_regex: RegexSet,
regex_index_map: HashMap<usize, usize>,
prompt_index: (usize, usize),
sys_prompt_index: (usize, usize),
input_map: HashMap<String, (bool, String, bool)>,
edges: Vec<(String, String, String, bool, bool)>,
ignore_errors: Option<RegexSet>,
pub dyn_param: HashMap<String, String>,
catch_map: HashMap<usize, (Regex, String)>,
sys: Option<String>,
current_prompt: Option<String>,
prompt_patterns: Vec<(String, String)>,
command_execution: CommandExecutionStrategy,
}
type ExitPath = Option<(String, Vec<(String, String)>)>;
static PRE_STATE: Lazy<Vec<String>> = Lazy::new(|| {
vec![
"Output".to_string(),
"More".to_string(),
"Error".to_string(),
]
});
pub static IGNORE_START_LINE: Lazy<Regex> =
Lazy::new(
|| match Regex::new(r"^(\r+(\s+\r+)*)|(\u{8}+(\s+\u{8}+)*)") {
Ok(re) => re,
Err(err) => panic!("invalid IGNORE_START_LINE regex: {err}"),
},
);
pub static STRIP_OSC_ESCAPE: Lazy<Regex> =
Lazy::new(|| match Regex::new(r"\x1B\][^\x07\x1B]*(?:\x07|\x1B\\)") {
Ok(re) => re,
Err(err) => panic!("invalid STRIP_OSC_ESCAPE regex: {err}"),
});
pub static STRIP_DCS_ESCAPE: Lazy<Regex> = Lazy::new(|| match Regex::new(r"\x1BP(?s:.*?)\x1B\\") {
Ok(re) => re,
Err(err) => panic!("invalid STRIP_DCS_ESCAPE regex: {err}"),
});
pub static STRIP_CSI_ESCAPE: Lazy<Regex> =
Lazy::new(|| match Regex::new(r"\x1B\[[0-?]*[ -/]*[@-~]") {
Ok(re) => re,
Err(err) => panic!("invalid STRIP_CSI_ESCAPE regex: {err}"),
});
pub static STRIP_SIMPLE_ESCAPE: Lazy<Regex> =
Lazy::new(|| match Regex::new(r"\x1B(?:[@-Z\\-_]|[=>])") {
Ok(re) => re,
Err(err) => panic!("invalid STRIP_SIMPLE_ESCAPE regex: {err}"),
});
#[cfg(test)]
fn build_test_handler() -> DeviceHandler {
let mut dyn_param = HashMap::new();
dyn_param.insert("EnablePassword".to_string(), "secret\n".to_string());
DeviceHandler::new(DeviceHandlerConfig {
prompt: vec![
prompt_rule("Login", &[r"^dev>\s*$"]),
prompt_rule("Enable", &[r"^dev#\s*$"]),
prompt_rule("Config", &[r"^dev\(cfg\)#\s*$"]),
],
write: vec![
input_rule(
"EnablePassword",
true,
"EnablePassword",
true,
&[r"^Password:\s*$"],
),
input_rule("Confirm", false, "y", false, &[r"^\[y\/n\]\?\s*$"]),
],
more_regex: vec![r"^--More--$".to_string()],
error_regex: vec![r"^ERROR: .+$".to_string()],
edges: vec![
transition_rule("Login", "enable", "Enable", false, false),
transition_rule("Enable", "configure terminal", "Config", false, false),
transition_rule("Config", "exit", "Enable", true, false),
transition_rule("Enable", "exit", "Login", true, false),
],
ignore_errors: vec![r"^ERROR: benign$".to_string()],
dyn_param,
..Default::default()
})
.expect("test handler config should be valid")
}