1use crate::core::keyboard;
3use crate::core::mouse;
4use crate::core::{Event, Point};
5use crate::simulator;
6
7use std::fmt;
8
9#[derive(Debug, Clone, PartialEq)]
13pub enum Instruction {
14 Interact(Interaction),
16 Expect(Expectation),
18}
19
20impl Instruction {
21 pub fn parse(line: &str) -> Result<Self, ParseError> {
23 parser::run(line)
24 }
25}
26
27impl fmt::Display for Instruction {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 match self {
30 Instruction::Interact(interaction) => interaction.fmt(f),
31 Instruction::Expect(expectation) => expectation.fmt(f),
32 }
33 }
34}
35
36#[derive(Debug, Clone, PartialEq)]
38pub enum Interaction {
39 Mouse(Mouse),
41 Keyboard(Keyboard),
43}
44
45impl Interaction {
46 pub fn from_event(event: &Event) -> Option<Self> {
50 Some(match event {
51 Event::Mouse(mouse) => Self::Mouse(match mouse {
52 mouse::Event::CursorMoved { position } => Mouse::Move(Target::Point(*position)),
53 mouse::Event::ButtonPressed(button) => Mouse::Press {
54 button: *button,
55 target: None,
56 },
57 mouse::Event::ButtonReleased(button) => Mouse::Release {
58 button: *button,
59 target: None,
60 },
61 _ => None?,
62 }),
63 Event::Keyboard(keyboard) => Self::Keyboard(match keyboard {
64 keyboard::Event::KeyPressed { key, text, .. } => match key {
65 keyboard::Key::Named(keyboard::key::Named::Enter) => {
66 Keyboard::Press(Key::Enter)
67 }
68 keyboard::Key::Named(keyboard::key::Named::Escape) => {
69 Keyboard::Press(Key::Escape)
70 }
71 keyboard::Key::Named(keyboard::key::Named::Tab) => Keyboard::Press(Key::Tab),
72 keyboard::Key::Named(keyboard::key::Named::Backspace) => {
73 Keyboard::Press(Key::Backspace)
74 }
75 _ => Keyboard::Typewrite(text.as_ref()?.to_string()),
76 },
77 keyboard::Event::KeyReleased { key, .. } => match key {
78 keyboard::Key::Named(keyboard::key::Named::Enter) => {
79 Keyboard::Release(Key::Enter)
80 }
81 keyboard::Key::Named(keyboard::key::Named::Escape) => {
82 Keyboard::Release(Key::Escape)
83 }
84 keyboard::Key::Named(keyboard::key::Named::Tab) => Keyboard::Release(Key::Tab),
85 keyboard::Key::Named(keyboard::key::Named::Backspace) => {
86 Keyboard::Release(Key::Backspace)
87 }
88 _ => None?,
89 },
90 keyboard::Event::ModifiersChanged(_) => None?,
91 }),
92 _ => None?,
93 })
94 }
95
96 pub fn merge(self, next: Self) -> (Self, Option<Self>) {
108 match (self, next) {
109 (Self::Mouse(current), Self::Mouse(next)) => match (current, next) {
110 (Mouse::Move(_), Mouse::Move(to)) => (Self::Mouse(Mouse::Move(to)), None),
111 (
112 Mouse::Move(to),
113 Mouse::Press {
114 button,
115 target: None,
116 },
117 ) => (
118 Self::Mouse(Mouse::Press {
119 button,
120 target: Some(to),
121 }),
122 None,
123 ),
124 (
125 Mouse::Move(to),
126 Mouse::Release {
127 button,
128 target: None,
129 },
130 ) => (
131 Self::Mouse(Mouse::Release {
132 button,
133 target: Some(to),
134 }),
135 None,
136 ),
137 (
138 Mouse::Press {
139 button: press,
140 target: press_at,
141 },
142 Mouse::Release {
143 button: release,
144 target: release_at,
145 },
146 ) if press == release
147 && release_at
148 .as_ref()
149 .is_none_or(|release_at| Some(release_at) == press_at.as_ref()) =>
150 {
151 (
152 Self::Mouse(Mouse::Click {
153 button: press,
154 target: press_at,
155 }),
156 None,
157 )
158 }
159 (
160 Mouse::Press {
161 button,
162 target: Some(press_at),
163 },
164 Mouse::Move(move_at),
165 ) if press_at == move_at => (
166 Self::Mouse(Mouse::Press {
167 button,
168 target: Some(press_at),
169 }),
170 None,
171 ),
172 (
173 Mouse::Click {
174 button,
175 target: Some(click_at),
176 },
177 Mouse::Move(move_at),
178 ) if click_at == move_at => (
179 Self::Mouse(Mouse::Click {
180 button,
181 target: Some(click_at),
182 }),
183 None,
184 ),
185 (current, next) => (Self::Mouse(current), Some(Self::Mouse(next))),
186 },
187 (Self::Keyboard(current), Self::Keyboard(next)) => match (current, next) {
188 (Keyboard::Typewrite(current), Keyboard::Typewrite(next)) => (
189 Self::Keyboard(Keyboard::Typewrite(format!("{current}{next}"))),
190 None,
191 ),
192 (Keyboard::Press(current), Keyboard::Release(next)) if current == next => {
193 (Self::Keyboard(Keyboard::Type(current)), None)
194 }
195 (current, next) => (Self::Keyboard(current), Some(Self::Keyboard(next))),
196 },
197 (current, next) => (current, Some(next)),
198 }
199 }
200
201 pub fn events(&self, find_target: impl FnOnce(&Target) -> Option<Point>) -> Option<Vec<Event>> {
206 let mouse_move_ = |to| Event::Mouse(mouse::Event::CursorMoved { position: to });
207
208 let mouse_press = |button| Event::Mouse(mouse::Event::ButtonPressed(button));
209
210 let mouse_release = |button| Event::Mouse(mouse::Event::ButtonReleased(button));
211
212 let key_press = |key| simulator::press_key(key, None);
213
214 let key_release = |key| simulator::release_key(key);
215
216 Some(match self {
217 Interaction::Mouse(mouse) => match mouse {
218 Mouse::Move(to) => vec![mouse_move_(find_target(to)?)],
219 Mouse::Press {
220 button,
221 target: Some(at),
222 } => vec![mouse_move_(find_target(at)?), mouse_press(*button)],
223 Mouse::Press {
224 button,
225 target: None,
226 } => {
227 vec![mouse_press(*button)]
228 }
229 Mouse::Release {
230 button,
231 target: Some(at),
232 } => {
233 vec![mouse_move_(find_target(at)?), mouse_release(*button)]
234 }
235 Mouse::Release {
236 button,
237 target: None,
238 } => {
239 vec![mouse_release(*button)]
240 }
241 Mouse::Click {
242 button,
243 target: Some(at),
244 } => {
245 vec![
246 mouse_move_(find_target(at)?),
247 mouse_press(*button),
248 mouse_release(*button),
249 ]
250 }
251 Mouse::Click {
252 button,
253 target: None,
254 } => {
255 vec![mouse_press(*button), mouse_release(*button)]
256 }
257 },
258 Interaction::Keyboard(keyboard) => match keyboard {
259 Keyboard::Press(key) => vec![key_press(*key)],
260 Keyboard::Release(key) => vec![key_release(*key)],
261 Keyboard::Type(key) => vec![key_press(*key), key_release(*key)],
262 Keyboard::Typewrite(text) => simulator::typewrite(text).collect(),
263 },
264 })
265 }
266}
267
268impl fmt::Display for Interaction {
269 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270 match self {
271 Interaction::Mouse(mouse) => mouse.fmt(f),
272 Interaction::Keyboard(keyboard) => keyboard.fmt(f),
273 }
274 }
275}
276
277#[derive(Debug, Clone, PartialEq)]
279pub enum Mouse {
280 Move(Target),
282 Press {
284 button: mouse::Button,
286 target: Option<Target>,
288 },
289 Release {
291 button: mouse::Button,
293 target: Option<Target>,
295 },
296 Click {
298 button: mouse::Button,
300 target: Option<Target>,
302 },
303}
304
305impl fmt::Display for Mouse {
306 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307 match self {
308 Mouse::Move(target) => {
309 write!(f, "move {}", target)
310 }
311 Mouse::Press { button, target } => {
312 write!(f, "press {}", format::button_at(*button, target.as_ref()))
313 }
314 Mouse::Release { button, target } => {
315 write!(f, "release {}", format::button_at(*button, target.as_ref()))
316 }
317 Mouse::Click { button, target } => {
318 write!(f, "click {}", format::button_at(*button, target.as_ref()))
319 }
320 }
321 }
322}
323
324#[derive(Debug, Clone, PartialEq)]
326pub enum Target {
327 Point(Point),
329 Text(String),
331}
332
333impl fmt::Display for Target {
334 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335 match self {
336 Self::Point(point) => f.write_str(&format::point(*point)),
337 Self::Text(text) => f.write_str(&format::string(text)),
338 }
339 }
340}
341
342#[derive(Debug, Clone, PartialEq)]
344pub enum Keyboard {
345 Press(Key),
347 Release(Key),
349 Type(Key),
351 Typewrite(String),
353}
354
355impl fmt::Display for Keyboard {
356 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357 match self {
358 Keyboard::Press(key) => {
359 write!(f, "press {}", format::key(*key))
360 }
361 Keyboard::Release(key) => {
362 write!(f, "release {}", format::key(*key))
363 }
364 Keyboard::Type(key) => {
365 write!(f, "type {}", format::key(*key))
366 }
367 Keyboard::Typewrite(text) => {
368 write!(f, "type \"{text}\"")
369 }
370 }
371 }
372}
373
374#[derive(Debug, Clone, Copy, PartialEq, Eq)]
378#[allow(missing_docs)]
379pub enum Key {
380 Enter,
381 Escape,
382 Tab,
383 Backspace,
384}
385
386impl From<Key> for keyboard::Key {
387 fn from(key: Key) -> Self {
388 match key {
389 Key::Enter => Self::Named(keyboard::key::Named::Enter),
390 Key::Escape => Self::Named(keyboard::key::Named::Escape),
391 Key::Tab => Self::Named(keyboard::key::Named::Tab),
392 Key::Backspace => Self::Named(keyboard::key::Named::Backspace),
393 }
394 }
395}
396
397mod format {
398 use super::*;
399
400 pub fn button_at(button: mouse::Button, at: Option<&Target>) -> String {
401 let button = self::button(button);
402
403 if let Some(at) = at {
404 if button.is_empty() {
405 at.to_string()
406 } else {
407 format!("{} {}", button, at)
408 }
409 } else {
410 button.to_owned()
411 }
412 }
413
414 pub fn button(button: mouse::Button) -> &'static str {
415 match button {
416 mouse::Button::Left => "",
417 mouse::Button::Right => "right",
418 mouse::Button::Middle => "middle",
419 mouse::Button::Back => "back",
420 mouse::Button::Forward => "forward",
421 mouse::Button::Other(_) => "other",
422 }
423 }
424
425 pub fn point(point: Point) -> String {
426 format!("({:.2}, {:.2})", point.x, point.y)
427 }
428
429 pub fn key(key: Key) -> &'static str {
430 match key {
431 Key::Enter => "enter",
432 Key::Escape => "escape",
433 Key::Tab => "tab",
434 Key::Backspace => "backspace",
435 }
436 }
437
438 pub fn string(text: &str) -> String {
439 format!("\"{}\"", text.escape_default())
440 }
441}
442
443#[derive(Debug, Clone, PartialEq)]
448pub enum Expectation {
449 Text(String),
451}
452
453impl fmt::Display for Expectation {
454 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
455 match self {
456 Expectation::Text(text) => {
457 write!(f, "expect {}", format::string(text))
458 }
459 }
460 }
461}
462
463pub use parser::Error as ParseError;
464
465mod parser {
466 use super::*;
467
468 use nom::branch::alt;
469 use nom::bytes::complete::tag;
470 use nom::bytes::{is_not, take_while_m_n};
471 use nom::character::complete::{char, multispace0, multispace1};
472 use nom::combinator::{map, map_opt, map_res, opt, success, value, verify};
473 use nom::error::ParseError;
474 use nom::multi::fold;
475 use nom::number::float;
476 use nom::sequence::{delimited, preceded, separated_pair};
477 use nom::{Finish, IResult, Parser};
478
479 #[derive(Debug, Clone, thiserror::Error)]
481 #[error("parse error: {0}")]
482 pub struct Error(nom::error::Error<String>);
483
484 pub fn run(input: &str) -> Result<Instruction, Error> {
485 match instruction.parse_complete(input).finish() {
486 Ok((_rest, instruction)) => Ok(instruction),
487 Err(error) => Err(Error(error.cloned())),
488 }
489 }
490
491 fn instruction(input: &str) -> IResult<&str, Instruction> {
492 alt((
493 map(interaction, Instruction::Interact),
494 map(expectation, Instruction::Expect),
495 ))
496 .parse(input)
497 }
498
499 fn interaction(input: &str) -> IResult<&str, Interaction> {
500 alt((
501 map(mouse, Interaction::Mouse),
502 map(keyboard, Interaction::Keyboard),
503 ))
504 .parse(input)
505 }
506
507 fn mouse(input: &str) -> IResult<&str, Mouse> {
508 let mouse_move = preceded(tag("move "), target).map(Mouse::Move);
509
510 alt((mouse_move, mouse_click, mouse_press, mouse_release)).parse(input)
511 }
512
513 fn mouse_click(input: &str) -> IResult<&str, Mouse> {
514 let (input, _) = tag("click ")(input)?;
515 let (input, (button, target)) = mouse_button_at(input)?;
516
517 Ok((input, Mouse::Click { button, target }))
518 }
519
520 fn mouse_press(input: &str) -> IResult<&str, Mouse> {
521 let (input, _) = tag("press ")(input)?;
522 let (input, (button, target)) = mouse_button_at(input)?;
523
524 Ok((input, Mouse::Press { button, target }))
525 }
526
527 fn mouse_release(input: &str) -> IResult<&str, Mouse> {
528 let (input, _) = tag("release ")(input)?;
529 let (input, (button, target)) = mouse_button_at(input)?;
530
531 Ok((input, Mouse::Release { button, target }))
532 }
533
534 fn mouse_button_at(input: &str) -> IResult<&str, (mouse::Button, Option<Target>)> {
535 let (input, button) = mouse_button(input)?;
536 let (input, at) = opt(target).parse(input)?;
537
538 Ok((input, (button, at)))
539 }
540
541 fn target(input: &str) -> IResult<&str, Target> {
542 alt((string.map(Target::Text), point.map(Target::Point))).parse(input)
543 }
544
545 fn mouse_button(input: &str) -> IResult<&str, mouse::Button> {
546 alt((
547 tag("right").map(|_| mouse::Button::Right),
548 success(mouse::Button::Left),
549 ))
550 .parse(input)
551 }
552
553 fn keyboard(input: &str) -> IResult<&str, Keyboard> {
554 alt((
555 map(preceded(tag("type "), string), Keyboard::Typewrite),
556 map(preceded(tag("type "), key), Keyboard::Type),
557 ))
558 .parse(input)
559 }
560
561 fn expectation(input: &str) -> IResult<&str, Expectation> {
562 map(preceded(tag("expect "), string), |text| {
563 Expectation::Text(text)
564 })
565 .parse(input)
566 }
567
568 fn key(input: &str) -> IResult<&str, Key> {
569 alt((
570 map(tag("enter"), |_| Key::Enter),
571 map(tag("escape"), |_| Key::Escape),
572 map(tag("tab"), |_| Key::Tab),
573 map(tag("backspace"), |_| Key::Backspace),
574 ))
575 .parse(input)
576 }
577
578 fn point(input: &str) -> IResult<&str, Point> {
579 let comma = whitespace(char(','));
580
581 map(
582 delimited(
583 char('('),
584 separated_pair(float(), comma, float()),
585 char(')'),
586 ),
587 |(x, y)| Point { x, y },
588 )
589 .parse(input)
590 }
591
592 pub fn whitespace<'a, O, E: ParseError<&'a str>, F>(
593 inner: F,
594 ) -> impl Parser<&'a str, Output = O, Error = E>
595 where
596 F: Parser<&'a str, Output = O, Error = E>,
597 {
598 delimited(multispace0, inner, multispace0)
599 }
600
601 fn string(input: &str) -> IResult<&str, String> {
624 #[derive(Debug, Clone, Copy)]
625 enum Fragment<'a> {
626 Literal(&'a str),
627 EscapedChar(char),
628 EscapedWS,
629 }
630
631 fn fragment(input: &str) -> IResult<&str, Fragment<'_>> {
632 alt((
633 map(string_literal, Fragment::Literal),
634 map(escaped_char, Fragment::EscapedChar),
635 value(Fragment::EscapedWS, escaped_whitespace),
636 ))
637 .parse(input)
638 }
639
640 fn string_literal<'a, E: ParseError<&'a str>>(
641 input: &'a str,
642 ) -> IResult<&'a str, &'a str, E> {
643 let not_quote_slash = is_not("\"\\");
644
645 verify(not_quote_slash, |s: &str| !s.is_empty()).parse(input)
646 }
647
648 fn unicode(input: &str) -> IResult<&str, char> {
649 let parse_hex = take_while_m_n(1, 6, |c: char| c.is_ascii_hexdigit());
650
651 let parse_delimited_hex =
652 preceded(char('u'), delimited(char('{'), parse_hex, char('}')));
653
654 let parse_u32 = map_res(parse_delimited_hex, move |hex| u32::from_str_radix(hex, 16));
655
656 map_opt(parse_u32, std::char::from_u32).parse(input)
657 }
658
659 fn escaped_char(input: &str) -> IResult<&str, char> {
660 preceded(
661 char('\\'),
662 alt((
663 unicode,
664 value('\n', char('n')),
665 value('\r', char('r')),
666 value('\t', char('t')),
667 value('\u{08}', char('b')),
668 value('\u{0C}', char('f')),
669 value('\\', char('\\')),
670 value('/', char('/')),
671 value('"', char('"')),
672 )),
673 )
674 .parse(input)
675 }
676
677 fn escaped_whitespace(input: &str) -> IResult<&str, &str> {
678 preceded(char('\\'), multispace1).parse(input)
679 }
680
681 let build_string = fold(0.., fragment, String::new, |mut string, fragment| {
682 match fragment {
683 Fragment::Literal(s) => string.push_str(s),
684 Fragment::EscapedChar(c) => string.push(c),
685 Fragment::EscapedWS => {}
686 }
687 string
688 });
689
690 delimited(char('"'), build_string, char('"')).parse(input)
691 }
692}