use std::collections::HashMap;
use super::pattern::{Pattern, PatternSet};
pub type PatternHandler = Box<dyn Fn(&str) -> HandlerAction + Send + Sync>;
#[derive(Debug, Clone, Default)]
pub enum HandlerAction {
#[default]
Continue,
Return(String),
Abort(String),
Respond(String),
}
pub struct PersistentPattern {
pub pattern: Pattern,
pub handler: PatternHandler,
pub enabled: bool,
pub priority: i32,
}
impl PersistentPattern {
#[must_use]
pub fn new(pattern: Pattern, handler: PatternHandler) -> Self {
Self {
pattern,
handler,
enabled: true,
priority: 0,
}
}
pub fn with_response(pattern: Pattern, response: impl Into<String>) -> Self {
let response = response.into();
Self::new(
pattern,
Box::new(move |_| HandlerAction::Respond(response.clone())),
)
}
pub fn with_abort(pattern: Pattern, message: impl Into<String>) -> Self {
let message = message.into();
Self::new(
pattern,
Box::new(move |_| HandlerAction::Abort(message.clone())),
)
}
#[must_use]
pub const fn with_priority(mut self, priority: i32) -> Self {
self.priority = priority;
self
}
pub const fn disable(&mut self) {
self.enabled = false;
}
pub const fn enable(&mut self) {
self.enabled = true;
}
}
impl std::fmt::Debug for PersistentPattern {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PersistentPattern")
.field("pattern", &self.pattern)
.field("enabled", &self.enabled)
.field("priority", &self.priority)
.finish_non_exhaustive()
}
}
#[derive(Default)]
pub struct PatternManager {
before_patterns: HashMap<String, PersistentPattern>,
after_patterns: HashMap<String, PersistentPattern>,
next_id: usize,
}
impl PatternManager {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_before(&mut self, pattern: PersistentPattern) -> String {
let id = self.generate_id("before");
self.before_patterns.insert(id.clone(), pattern);
id
}
pub fn add_after(&mut self, pattern: PersistentPattern) -> String {
let id = self.generate_id("after");
self.after_patterns.insert(id.clone(), pattern);
id
}
pub fn remove_before(&mut self, id: &str) -> Option<PersistentPattern> {
self.before_patterns.remove(id)
}
pub fn remove_after(&mut self, id: &str) -> Option<PersistentPattern> {
self.after_patterns.remove(id)
}
#[must_use]
pub fn get_before(&self, id: &str) -> Option<&PersistentPattern> {
self.before_patterns.get(id)
}
pub fn get_before_mut(&mut self, id: &str) -> Option<&mut PersistentPattern> {
self.before_patterns.get_mut(id)
}
#[must_use]
pub fn get_after(&self, id: &str) -> Option<&PersistentPattern> {
self.after_patterns.get(id)
}
pub fn get_after_mut(&mut self, id: &str) -> Option<&mut PersistentPattern> {
self.after_patterns.get_mut(id)
}
#[must_use]
pub fn check_before(&self, buffer: &str) -> Option<(String, HandlerAction)> {
self.check_patterns(&self.before_patterns, buffer)
}
#[must_use]
pub fn check_after(&self, buffer: &str) -> Option<(String, HandlerAction)> {
self.check_patterns(&self.after_patterns, buffer)
}
#[must_use]
pub fn before_pattern_set(&self) -> PatternSet {
self.patterns_to_set(&self.before_patterns)
}
#[must_use]
pub fn after_pattern_set(&self) -> PatternSet {
self.patterns_to_set(&self.after_patterns)
}
pub fn clear_before(&mut self) {
self.before_patterns.clear();
}
pub fn clear_after(&mut self) {
self.after_patterns.clear();
}
pub fn clear_all(&mut self) {
self.before_patterns.clear();
self.after_patterns.clear();
}
#[must_use]
pub fn before_count(&self) -> usize {
self.before_patterns.len()
}
#[must_use]
pub fn after_count(&self) -> usize {
self.after_patterns.len()
}
fn generate_id(&mut self, prefix: &str) -> String {
let id = format!("{prefix}_{}", self.next_id);
self.next_id += 1;
id
}
#[allow(clippy::unused_self)]
fn check_patterns(
&self,
patterns: &HashMap<String, PersistentPattern>,
buffer: &str,
) -> Option<(String, HandlerAction)> {
let mut sorted: Vec<_> = patterns.iter().filter(|(_, p)| p.enabled).collect();
sorted.sort_by_key(|(_, p)| p.priority);
for (id, persistent) in sorted {
if persistent.pattern.matches(buffer).is_some() {
let action = (persistent.handler)(buffer);
if !matches!(action, HandlerAction::Continue) {
return Some((id.clone(), action));
}
}
}
None
}
#[allow(clippy::unused_self)]
fn patterns_to_set(&self, patterns: &HashMap<String, PersistentPattern>) -> PatternSet {
let mut sorted: Vec<_> = patterns.iter().filter(|(_, p)| p.enabled).collect();
sorted.sort_by_key(|(_, p)| p.priority);
PatternSet::from_patterns(sorted.into_iter().map(|(_, p)| p.pattern.clone()).collect())
}
}
impl std::fmt::Debug for PatternManager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PatternManager")
.field("before_count", &self.before_patterns.len())
.field("after_count", &self.after_patterns.len())
.finish()
}
}
pub struct PatternBuilder {
manager: PatternManager,
}
impl PatternBuilder {
#[must_use]
pub fn new() -> Self {
Self {
manager: PatternManager::new(),
}
}
#[must_use]
pub fn with_password_handler(mut self, password: impl Into<String>) -> Self {
let password = password.into();
let pattern = PersistentPattern::with_response(
Pattern::regex(r"[Pp]assword:?\s*$").unwrap_or_else(|_| Pattern::literal("Password:")),
format!("{password}\n"),
);
self.manager.add_before(pattern);
self
}
#[must_use]
pub fn with_sudo_handler(mut self, password: impl Into<String>) -> Self {
let password = password.into();
let pattern = PersistentPattern::with_response(
Pattern::regex(r"\[sudo\] password")
.unwrap_or_else(|_| Pattern::literal("[sudo] password")),
format!("{password}\n"),
);
self.manager.add_before(pattern);
self
}
#[must_use]
pub fn with_error_pattern(mut self, pattern: Pattern, message: impl Into<String>) -> Self {
let persistent = PersistentPattern::with_abort(pattern, message);
self.manager.add_before(persistent);
self
}
#[must_use]
pub fn with_yes_handler(mut self) -> Self {
let pattern = PersistentPattern::with_response(
Pattern::regex(r"\(yes/no\)\??\s*$").unwrap_or_else(|_| Pattern::literal("(yes/no)")),
"yes\n",
);
self.manager.add_before(pattern);
self
}
#[must_use]
pub fn with_yn_handler(mut self) -> Self {
let pattern = PersistentPattern::with_response(
Pattern::regex(r"\[y/n\]\??\s*$").unwrap_or_else(|_| Pattern::literal("[y/n]")),
"y\n",
);
self.manager.add_before(pattern);
self
}
#[must_use]
pub fn with_continue_handler(mut self) -> Self {
let pattern = PersistentPattern::with_response(
Pattern::regex(r"Press (?:Enter|any key) to continue")
.unwrap_or_else(|_| Pattern::literal("Press Enter")),
"\n",
);
self.manager.add_before(pattern);
self
}
#[must_use]
pub fn build(self) -> PatternManager {
self.manager
}
}
impl Default for PatternBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pattern_manager_before() {
let mut manager = PatternManager::new();
let pattern = PersistentPattern::with_response(Pattern::literal("password:"), "secret\n");
let id = manager.add_before(pattern);
let result = manager.check_before("Enter password: ");
assert!(result.is_some());
let (matched_id, action) = result.unwrap();
assert_eq!(matched_id, id);
assert!(matches!(action, HandlerAction::Respond(_)));
}
#[test]
fn pattern_manager_priority() {
let mut manager = PatternManager::new();
let low = PersistentPattern::new(
Pattern::literal("test"),
Box::new(|_| HandlerAction::Respond("low".into())),
)
.with_priority(10);
let high = PersistentPattern::new(
Pattern::literal("test"),
Box::new(|_| HandlerAction::Respond("high".into())),
)
.with_priority(1);
manager.add_before(low);
manager.add_before(high);
let result = manager.check_before("test");
assert!(result.is_some());
if let Some((_, HandlerAction::Respond(s))) = result {
assert_eq!(s, "high");
} else {
panic!("Expected Respond action");
}
}
#[test]
fn pattern_manager_disable() {
let mut manager = PatternManager::new();
let pattern = PersistentPattern::with_response(Pattern::literal("test"), "response");
let id = manager.add_before(pattern);
assert!(manager.check_before("test").is_some());
manager.get_before_mut(&id).unwrap().disable();
assert!(manager.check_before("test").is_none());
}
#[test]
fn pattern_builder() {
let manager = PatternBuilder::new()
.with_password_handler("secret")
.with_yes_handler()
.build();
assert_eq!(manager.before_count(), 2);
}
}