rust_expect/dialog/
definition.rs1use std::collections::HashMap;
4use std::time::Duration;
5
6use crate::types::ControlChar;
7
8#[derive(Debug, Clone, Default)]
10pub struct DialogStep {
11 pub name: String,
13 pub expect: Option<String>,
15 pub send: Option<String>,
17 pub send_control: Option<ControlChar>,
19 pub timeout: Option<Duration>,
21 pub continue_on_timeout: bool,
23 pub next: Option<String>,
25 pub branches: HashMap<String, String>,
27}
28
29impl DialogStep {
30 #[must_use]
32 pub fn new(name: impl Into<String>) -> Self {
33 Self {
34 name: name.into(),
35 ..Default::default()
36 }
37 }
38
39 #[must_use]
41 pub fn expect(pattern: impl Into<String>) -> Self {
42 Self {
43 expect: Some(pattern.into()),
44 ..Default::default()
45 }
46 }
47
48 #[must_use]
50 pub fn send(text: impl Into<String>) -> Self {
51 Self {
52 send: Some(text.into()),
53 ..Default::default()
54 }
55 }
56
57 #[must_use]
59 pub fn with_expect(mut self, pattern: impl Into<String>) -> Self {
60 self.expect = Some(pattern.into());
61 self
62 }
63
64 #[must_use]
66 pub fn with_send(mut self, text: impl Into<String>) -> Self {
67 self.send = Some(text.into());
68 self
69 }
70
71 #[must_use]
73 pub const fn with_send_control(mut self, ctrl: ControlChar) -> Self {
74 self.send_control = Some(ctrl);
75 self
76 }
77
78 #[must_use]
81 pub fn then_send(mut self, text: impl Into<String>) -> Self {
82 self.send = Some(text.into());
83 self
84 }
85
86 #[must_use]
89 pub const fn then_send_control(mut self, ctrl: ControlChar) -> Self {
90 self.send_control = Some(ctrl);
91 self
92 }
93
94 #[must_use]
96 pub const fn timeout(mut self, timeout: Duration) -> Self {
97 self.timeout = Some(timeout);
98 self
99 }
100
101 #[must_use]
103 pub fn then(mut self, next: impl Into<String>) -> Self {
104 self.next = Some(next.into());
105 self
106 }
107
108 #[must_use]
110 pub fn branch(mut self, pattern: impl Into<String>, step: impl Into<String>) -> Self {
111 self.branches.insert(pattern.into(), step.into());
112 self
113 }
114
115 #[must_use]
117 pub const fn continue_on_timeout(mut self, cont: bool) -> Self {
118 self.continue_on_timeout = cont;
119 self
120 }
121
122 #[must_use]
124 pub fn expect_pattern(&self) -> Option<&str> {
125 self.expect.as_deref()
126 }
127
128 #[must_use]
130 pub fn send_text(&self) -> Option<&str> {
131 self.send.as_deref()
132 }
133
134 #[must_use]
136 pub const fn send_control(&self) -> Option<ControlChar> {
137 self.send_control
138 }
139
140 #[must_use]
142 pub const fn get_timeout(&self) -> Option<Duration> {
143 self.timeout
144 }
145
146 #[must_use]
148 pub const fn continues_on_timeout(&self) -> bool {
149 self.continue_on_timeout
150 }
151}
152
153#[derive(Debug, Clone, Default)]
155pub struct Dialog {
156 pub name: String,
158 pub description: String,
160 pub steps: Vec<DialogStep>,
162 pub entry: Option<String>,
164 pub variables: HashMap<String, String>,
166}
167
168impl Dialog {
169 #[must_use]
171 pub fn new() -> Self {
172 Self::default()
173 }
174
175 #[must_use]
177 pub fn named(name: impl Into<String>) -> Self {
178 Self {
179 name: name.into(),
180 ..Default::default()
181 }
182 }
183
184 #[must_use]
186 pub fn description(mut self, desc: impl Into<String>) -> Self {
187 self.description = desc.into();
188 self
189 }
190
191 #[must_use]
193 pub fn step(mut self, step: DialogStep) -> Self {
194 if self.entry.is_none() && !step.name.is_empty() {
195 self.entry = Some(step.name.clone());
196 }
197 self.steps.push(step);
198 self
199 }
200
201 #[must_use]
203 pub fn variable(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
204 self.variables.insert(name.into(), value.into());
205 self
206 }
207
208 #[must_use]
210 pub fn entry_point(mut self, step: impl Into<String>) -> Self {
211 self.entry = Some(step.into());
212 self
213 }
214
215 #[must_use]
217 pub const fn len(&self) -> usize {
218 self.steps.len()
219 }
220
221 #[must_use]
223 pub const fn is_empty(&self) -> bool {
224 self.steps.is_empty()
225 }
226
227 #[must_use]
229 pub fn steps(&self) -> &[DialogStep] {
230 &self.steps
231 }
232
233 #[must_use]
235 pub const fn variables(&self) -> &HashMap<String, String> {
236 &self.variables
237 }
238
239 #[must_use]
241 pub fn get_step(&self, name: &str) -> Option<&DialogStep> {
242 self.steps.iter().find(|s| s.name == name)
243 }
244
245 #[must_use]
247 pub fn substitute(&self, s: &str) -> String {
248 let mut result = s.to_string();
249 for (name, value) in &self.variables {
250 result = result.replace(&format!("${{{name}}}"), value);
251 result = result.replace(&format!("${name}"), value);
252 }
253 result
254 }
255}
256
257#[derive(Debug, Clone, Default)]
259pub struct DialogBuilder {
260 dialog: Dialog,
261}
262
263impl DialogBuilder {
264 #[must_use]
266 pub fn new() -> Self {
267 Self::default()
268 }
269
270 #[must_use]
272 pub fn named(name: impl Into<String>) -> Self {
273 Self {
274 dialog: Dialog::named(name),
275 }
276 }
277
278 #[must_use]
280 pub fn step(mut self, step: DialogStep) -> Self {
281 self.dialog = self.dialog.step(step);
282 self
283 }
284
285 #[must_use]
287 pub fn variable(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
288 self.dialog = self.dialog.variable(name, value);
289 self
290 }
291
292 #[must_use]
294 pub fn var(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
295 self.dialog = self.dialog.variable(name, value);
296 self
297 }
298
299 #[must_use]
301 pub fn expect_send(
302 mut self,
303 name: impl Into<String>,
304 expect: impl Into<String>,
305 send: impl Into<String>,
306 ) -> Self {
307 self.dialog = self
308 .dialog
309 .step(DialogStep::new(name).with_expect(expect).with_send(send));
310 self
311 }
312
313 #[must_use]
315 pub fn build(self) -> Dialog {
316 self.dialog
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn dialog_basic() {
326 let dialog = DialogBuilder::new()
327 .step(DialogStep::expect("login:").then_send("admin"))
328 .step(DialogStep::expect("password:").then_send("secret"))
329 .var("USER", "admin")
330 .build();
331
332 assert_eq!(dialog.len(), 2);
333 assert_eq!(dialog.substitute("${USER}"), "admin");
334 }
335
336 #[test]
337 fn dialog_empty() {
338 let dialog = Dialog::new();
339 assert!(dialog.is_empty());
340 assert_eq!(dialog.len(), 0);
341 }
342
343 #[test]
344 fn dialog_named_steps() {
345 let dialog = Dialog::named("login")
346 .step(
347 DialogStep::new("username")
348 .with_expect("login:")
349 .with_send("admin\n"),
350 )
351 .step(
352 DialogStep::new("password")
353 .with_expect("password:")
354 .with_send("secret\n"),
355 )
356 .variable("USER", "admin");
357
358 assert_eq!(dialog.name, "login");
359 assert_eq!(dialog.steps.len(), 2);
360 assert_eq!(dialog.substitute("${USER}"), "admin");
361 }
362
363 #[test]
364 fn dialog_step_accessors() {
365 let step = DialogStep::expect("prompt")
366 .then_send("response")
367 .timeout(Duration::from_secs(10));
368
369 assert_eq!(step.expect_pattern(), Some("prompt"));
370 assert_eq!(step.send_text(), Some("response"));
371 assert_eq!(step.get_timeout(), Some(Duration::from_secs(10)));
372 }
373
374 #[test]
375 fn dialog_variable_substitution() {
376 let dialog = DialogBuilder::new()
377 .var("name", "Alice")
378 .var("greeting", "Hello")
379 .build();
380
381 assert_eq!(dialog.substitute("${greeting}, ${name}!"), "Hello, Alice!");
382 }
383
384 #[test]
385 fn dialog_builder_named() {
386 let dialog = DialogBuilder::named("test")
387 .expect_send("step1", "prompt>", "command\n")
388 .variable("VAR", "value")
389 .build();
390
391 assert_eq!(dialog.name, "test");
392 assert_eq!(dialog.steps.len(), 1);
393 }
394}