use std::collections::HashMap;
use std::time::Duration;
use crate::types::ControlChar;
#[derive(Debug, Clone, Default)]
pub struct DialogStep {
pub name: String,
pub expect: Option<String>,
pub send: Option<String>,
pub send_control: Option<ControlChar>,
pub timeout: Option<Duration>,
pub continue_on_timeout: bool,
pub next: Option<String>,
pub branches: HashMap<String, String>,
}
impl DialogStep {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
..Default::default()
}
}
#[must_use]
pub fn expect(pattern: impl Into<String>) -> Self {
Self {
expect: Some(pattern.into()),
..Default::default()
}
}
#[must_use]
pub fn send(text: impl Into<String>) -> Self {
Self {
send: Some(text.into()),
..Default::default()
}
}
#[must_use]
pub fn with_expect(mut self, pattern: impl Into<String>) -> Self {
self.expect = Some(pattern.into());
self
}
#[must_use]
pub fn with_send(mut self, text: impl Into<String>) -> Self {
self.send = Some(text.into());
self
}
#[must_use]
pub const fn with_send_control(mut self, ctrl: ControlChar) -> Self {
self.send_control = Some(ctrl);
self
}
#[must_use]
pub fn then_send(mut self, text: impl Into<String>) -> Self {
self.send = Some(text.into());
self
}
#[must_use]
pub const fn then_send_control(mut self, ctrl: ControlChar) -> Self {
self.send_control = Some(ctrl);
self
}
#[must_use]
pub const fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
#[must_use]
pub fn then(mut self, next: impl Into<String>) -> Self {
self.next = Some(next.into());
self
}
#[must_use]
pub fn branch(mut self, pattern: impl Into<String>, step: impl Into<String>) -> Self {
self.branches.insert(pattern.into(), step.into());
self
}
#[must_use]
pub const fn continue_on_timeout(mut self, cont: bool) -> Self {
self.continue_on_timeout = cont;
self
}
#[must_use]
pub fn expect_pattern(&self) -> Option<&str> {
self.expect.as_deref()
}
#[must_use]
pub fn send_text(&self) -> Option<&str> {
self.send.as_deref()
}
#[must_use]
pub const fn send_control(&self) -> Option<ControlChar> {
self.send_control
}
#[must_use]
pub const fn get_timeout(&self) -> Option<Duration> {
self.timeout
}
#[must_use]
pub const fn continues_on_timeout(&self) -> bool {
self.continue_on_timeout
}
}
#[derive(Debug, Clone, Default)]
pub struct Dialog {
pub name: String,
pub description: String,
pub steps: Vec<DialogStep>,
pub entry: Option<String>,
pub variables: HashMap<String, String>,
}
impl Dialog {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn named(name: impl Into<String>) -> Self {
Self {
name: name.into(),
..Default::default()
}
}
#[must_use]
pub fn description(mut self, desc: impl Into<String>) -> Self {
self.description = desc.into();
self
}
#[must_use]
pub fn step(mut self, step: DialogStep) -> Self {
if self.entry.is_none() && !step.name.is_empty() {
self.entry = Some(step.name.clone());
}
self.steps.push(step);
self
}
#[must_use]
pub fn variable(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.variables.insert(name.into(), value.into());
self
}
#[must_use]
pub fn entry_point(mut self, step: impl Into<String>) -> Self {
self.entry = Some(step.into());
self
}
#[must_use]
pub const fn len(&self) -> usize {
self.steps.len()
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.steps.is_empty()
}
#[must_use]
pub fn steps(&self) -> &[DialogStep] {
&self.steps
}
#[must_use]
pub const fn variables(&self) -> &HashMap<String, String> {
&self.variables
}
#[must_use]
pub fn get_step(&self, name: &str) -> Option<&DialogStep> {
self.steps.iter().find(|s| s.name == name)
}
#[must_use]
pub fn substitute(&self, s: &str) -> String {
let mut result = s.to_string();
for (name, value) in &self.variables {
result = result.replace(&format!("${{{name}}}"), value);
result = result.replace(&format!("${name}"), value);
}
result
}
}
#[derive(Debug, Clone, Default)]
pub struct DialogBuilder {
dialog: Dialog,
}
impl DialogBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn named(name: impl Into<String>) -> Self {
Self {
dialog: Dialog::named(name),
}
}
#[must_use]
pub fn step(mut self, step: DialogStep) -> Self {
self.dialog = self.dialog.step(step);
self
}
#[must_use]
pub fn variable(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.dialog = self.dialog.variable(name, value);
self
}
#[must_use]
pub fn var(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.dialog = self.dialog.variable(name, value);
self
}
#[must_use]
pub fn expect_send(
mut self,
name: impl Into<String>,
expect: impl Into<String>,
send: impl Into<String>,
) -> Self {
self.dialog = self
.dialog
.step(DialogStep::new(name).with_expect(expect).with_send(send));
self
}
#[must_use]
pub fn build(self) -> Dialog {
self.dialog
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dialog_basic() {
let dialog = DialogBuilder::new()
.step(DialogStep::expect("login:").then_send("admin"))
.step(DialogStep::expect("password:").then_send("secret"))
.var("USER", "admin")
.build();
assert_eq!(dialog.len(), 2);
assert_eq!(dialog.substitute("${USER}"), "admin");
}
#[test]
fn dialog_empty() {
let dialog = Dialog::new();
assert!(dialog.is_empty());
assert_eq!(dialog.len(), 0);
}
#[test]
fn dialog_named_steps() {
let dialog = Dialog::named("login")
.step(
DialogStep::new("username")
.with_expect("login:")
.with_send("admin\n"),
)
.step(
DialogStep::new("password")
.with_expect("password:")
.with_send("secret\n"),
)
.variable("USER", "admin");
assert_eq!(dialog.name, "login");
assert_eq!(dialog.steps.len(), 2);
assert_eq!(dialog.substitute("${USER}"), "admin");
}
#[test]
fn dialog_step_accessors() {
let step = DialogStep::expect("prompt")
.then_send("response")
.timeout(Duration::from_secs(10));
assert_eq!(step.expect_pattern(), Some("prompt"));
assert_eq!(step.send_text(), Some("response"));
assert_eq!(step.get_timeout(), Some(Duration::from_secs(10)));
}
#[test]
fn dialog_variable_substitution() {
let dialog = DialogBuilder::new()
.var("name", "Alice")
.var("greeting", "Hello")
.build();
assert_eq!(dialog.substitute("${greeting}, ${name}!"), "Hello, Alice!");
}
#[test]
fn dialog_builder_named() {
let dialog = DialogBuilder::named("test")
.expect_send("step1", "prompt>", "command\n")
.variable("VAR", "value")
.build();
assert_eq!(dialog.name, "test");
assert_eq!(dialog.steps.len(), 1);
}
}