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 } => {
53 Mouse::Move(Target::Point(*position))
54 }
55 mouse::Event::ButtonPressed(button) => Mouse::Press {
56 button: *button,
57 target: None,
58 },
59 mouse::Event::ButtonReleased(button) => Mouse::Release {
60 button: *button,
61 target: None,
62 },
63 _ => None?,
64 }),
65 Event::Keyboard(keyboard) => Self::Keyboard(match keyboard {
66 keyboard::Event::KeyPressed { key, text, .. } => match key {
67 keyboard::Key::Named(keyboard::key::Named::Enter) => {
68 Keyboard::Press(Key::Enter)
69 }
70 keyboard::Key::Named(keyboard::key::Named::Escape) => {
71 Keyboard::Press(Key::Escape)
72 }
73 keyboard::Key::Named(keyboard::key::Named::Tab) => {
74 Keyboard::Press(Key::Tab)
75 }
76 keyboard::Key::Named(keyboard::key::Named::Backspace) => {
77 Keyboard::Press(Key::Backspace)
78 }
79 _ => Keyboard::Typewrite(text.as_ref()?.to_string()),
80 },
81 keyboard::Event::KeyReleased { key, .. } => match key {
82 keyboard::Key::Named(keyboard::key::Named::Enter) => {
83 Keyboard::Release(Key::Enter)
84 }
85 keyboard::Key::Named(keyboard::key::Named::Escape) => {
86 Keyboard::Release(Key::Escape)
87 }
88 keyboard::Key::Named(keyboard::key::Named::Tab) => {
89 Keyboard::Release(Key::Tab)
90 }
91 keyboard::Key::Named(keyboard::key::Named::Backspace) => {
92 Keyboard::Release(Key::Backspace)
93 }
94 _ => None?,
95 },
96 keyboard::Event::ModifiersChanged(_) => None?,
97 }),
98 _ => None?,
99 })
100 }
101
102 pub fn merge(self, next: Self) -> (Self, Option<Self>) {
114 match (self, next) {
115 (Self::Mouse(current), Self::Mouse(next)) => {
116 match (current, next) {
117 (Mouse::Move(_), Mouse::Move(to)) => {
118 (Self::Mouse(Mouse::Move(to)), None)
119 }
120 (
121 Mouse::Move(to),
122 Mouse::Press {
123 button,
124 target: None,
125 },
126 ) => (
127 Self::Mouse(Mouse::Press {
128 button,
129 target: Some(to),
130 }),
131 None,
132 ),
133 (
134 Mouse::Move(to),
135 Mouse::Release {
136 button,
137 target: None,
138 },
139 ) => (
140 Self::Mouse(Mouse::Release {
141 button,
142 target: Some(to),
143 }),
144 None,
145 ),
146 (
147 Mouse::Press {
148 button: press,
149 target: press_at,
150 },
151 Mouse::Release {
152 button: release,
153 target: release_at,
154 },
155 ) if press == release
156 && release_at.as_ref().is_none_or(|release_at| {
157 Some(release_at) == press_at.as_ref()
158 }) =>
159 {
160 (
161 Self::Mouse(Mouse::Click {
162 button: press,
163 target: press_at,
164 }),
165 None,
166 )
167 }
168 (
169 Mouse::Press {
170 button,
171 target: Some(press_at),
172 },
173 Mouse::Move(move_at),
174 ) if press_at == move_at => (
175 Self::Mouse(Mouse::Press {
176 button,
177 target: Some(press_at),
178 }),
179 None,
180 ),
181 (
182 Mouse::Click {
183 button,
184 target: Some(click_at),
185 },
186 Mouse::Move(move_at),
187 ) if click_at == move_at => (
188 Self::Mouse(Mouse::Click {
189 button,
190 target: Some(click_at),
191 }),
192 None,
193 ),
194 (current, next) => {
195 (Self::Mouse(current), Some(Self::Mouse(next)))
196 }
197 }
198 }
199 (Self::Keyboard(current), Self::Keyboard(next)) => {
200 match (current, next) {
201 (
202 Keyboard::Typewrite(current),
203 Keyboard::Typewrite(next),
204 ) => (
205 Self::Keyboard(Keyboard::Typewrite(format!(
206 "{current}{next}"
207 ))),
208 None,
209 ),
210 (Keyboard::Press(current), Keyboard::Release(next))
211 if current == next =>
212 {
213 (Self::Keyboard(Keyboard::Type(current)), None)
214 }
215 (current, next) => {
216 (Self::Keyboard(current), Some(Self::Keyboard(next)))
217 }
218 }
219 }
220 (current, next) => (current, Some(next)),
221 }
222 }
223
224 pub fn events(
229 &self,
230 find_target: impl FnOnce(&Target) -> Option<Point>,
231 ) -> Option<Vec<Event>> {
232 let mouse_move_ =
233 |to| Event::Mouse(mouse::Event::CursorMoved { position: to });
234
235 let mouse_press =
236 |button| Event::Mouse(mouse::Event::ButtonPressed(button));
237
238 let mouse_release =
239 |button| Event::Mouse(mouse::Event::ButtonReleased(button));
240
241 let key_press = |key| simulator::press_key(key, None);
242
243 let key_release = |key| simulator::release_key(key);
244
245 Some(match self {
246 Interaction::Mouse(mouse) => match mouse {
247 Mouse::Move(to) => vec![mouse_move_(find_target(to)?)],
248 Mouse::Press {
249 button,
250 target: Some(at),
251 } => vec![mouse_move_(find_target(at)?), mouse_press(*button)],
252 Mouse::Press {
253 button,
254 target: None,
255 } => {
256 vec![mouse_press(*button)]
257 }
258 Mouse::Release {
259 button,
260 target: Some(at),
261 } => {
262 vec![mouse_move_(find_target(at)?), mouse_release(*button)]
263 }
264 Mouse::Release {
265 button,
266 target: None,
267 } => {
268 vec![mouse_release(*button)]
269 }
270 Mouse::Click {
271 button,
272 target: Some(at),
273 } => {
274 vec![
275 mouse_move_(find_target(at)?),
276 mouse_press(*button),
277 mouse_release(*button),
278 ]
279 }
280 Mouse::Click {
281 button,
282 target: None,
283 } => {
284 vec![mouse_press(*button), mouse_release(*button)]
285 }
286 },
287 Interaction::Keyboard(keyboard) => match keyboard {
288 Keyboard::Press(key) => vec![key_press(*key)],
289 Keyboard::Release(key) => vec![key_release(*key)],
290 Keyboard::Type(key) => vec![key_press(*key), key_release(*key)],
291 Keyboard::Typewrite(text) => {
292 simulator::typewrite(text).collect()
293 }
294 },
295 })
296 }
297}
298
299impl fmt::Display for Interaction {
300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 match self {
302 Interaction::Mouse(mouse) => mouse.fmt(f),
303 Interaction::Keyboard(keyboard) => keyboard.fmt(f),
304 }
305 }
306}
307
308#[derive(Debug, Clone, PartialEq)]
310pub enum Mouse {
311 Move(Target),
313 Press {
315 button: mouse::Button,
317 target: Option<Target>,
319 },
320 Release {
322 button: mouse::Button,
324 target: Option<Target>,
326 },
327 Click {
329 button: mouse::Button,
331 target: Option<Target>,
333 },
334}
335
336impl fmt::Display for Mouse {
337 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338 match self {
339 Mouse::Move(target) => {
340 write!(f, "move {}", target)
341 }
342 Mouse::Press { button, target } => {
343 write!(
344 f,
345 "press {}",
346 format::button_at(*button, target.as_ref())
347 )
348 }
349 Mouse::Release { button, target } => {
350 write!(
351 f,
352 "release {}",
353 format::button_at(*button, target.as_ref())
354 )
355 }
356 Mouse::Click { button, target } => {
357 write!(
358 f,
359 "click {}",
360 format::button_at(*button, target.as_ref())
361 )
362 }
363 }
364 }
365}
366
367#[derive(Debug, Clone, PartialEq)]
369pub enum Target {
370 Point(Point),
372 Text(String),
374}
375
376impl fmt::Display for Target {
377 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
378 match self {
379 Self::Point(point) => f.write_str(&format::point(*point)),
380 Self::Text(text) => f.write_str(&format::string(text)),
381 }
382 }
383}
384
385#[derive(Debug, Clone, PartialEq)]
387pub enum Keyboard {
388 Press(Key),
390 Release(Key),
392 Type(Key),
394 Typewrite(String),
396}
397
398impl fmt::Display for Keyboard {
399 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
400 match self {
401 Keyboard::Press(key) => {
402 write!(f, "press {}", format::key(*key))
403 }
404 Keyboard::Release(key) => {
405 write!(f, "release {}", format::key(*key))
406 }
407 Keyboard::Type(key) => {
408 write!(f, "type {}", format::key(*key))
409 }
410 Keyboard::Typewrite(text) => {
411 write!(f, "type \"{text}\"")
412 }
413 }
414 }
415}
416
417#[derive(Debug, Clone, Copy, PartialEq, Eq)]
421#[allow(missing_docs)]
422pub enum Key {
423 Enter,
424 Escape,
425 Tab,
426 Backspace,
427}
428
429impl From<Key> for keyboard::Key {
430 fn from(key: Key) -> Self {
431 match key {
432 Key::Enter => Self::Named(keyboard::key::Named::Enter),
433 Key::Escape => Self::Named(keyboard::key::Named::Escape),
434 Key::Tab => Self::Named(keyboard::key::Named::Tab),
435 Key::Backspace => Self::Named(keyboard::key::Named::Backspace),
436 }
437 }
438}
439
440mod format {
441 use super::*;
442
443 pub fn button_at(button: mouse::Button, at: Option<&Target>) -> String {
444 let button = self::button(button);
445
446 if let Some(at) = at {
447 if button.is_empty() {
448 at.to_string()
449 } else {
450 format!("{} {}", button, at)
451 }
452 } else {
453 button.to_owned()
454 }
455 }
456
457 pub fn button(button: mouse::Button) -> &'static str {
458 match button {
459 mouse::Button::Left => "",
460 mouse::Button::Right => "right",
461 mouse::Button::Middle => "middle",
462 mouse::Button::Back => "back",
463 mouse::Button::Forward => "forward",
464 mouse::Button::Other(_) => "other",
465 }
466 }
467
468 pub fn point(point: Point) -> String {
469 format!("({:.2}, {:.2})", point.x, point.y)
470 }
471
472 pub fn key(key: Key) -> &'static str {
473 match key {
474 Key::Enter => "enter",
475 Key::Escape => "escape",
476 Key::Tab => "tab",
477 Key::Backspace => "backspace",
478 }
479 }
480
481 pub fn string(text: &str) -> String {
482 format!("\"{}\"", text.escape_default())
483 }
484}
485
486#[derive(Debug, Clone, PartialEq)]
491pub enum Expectation {
492 Text(String),
494}
495
496impl fmt::Display for Expectation {
497 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
498 match self {
499 Expectation::Text(text) => {
500 write!(f, "expect {}", format::string(text))
501 }
502 }
503 }
504}
505
506pub use parser::Error as ParseError;
507
508mod parser {
509 use super::*;
510
511 use nom::branch::alt;
512 use nom::bytes::complete::tag;
513 use nom::bytes::{is_not, take_while_m_n};
514 use nom::character::complete::{char, multispace0, multispace1};
515 use nom::combinator::{map, map_opt, map_res, opt, success, value, verify};
516 use nom::error::ParseError;
517 use nom::multi::fold;
518 use nom::number::float;
519 use nom::sequence::{delimited, preceded, separated_pair};
520 use nom::{Finish, IResult, Parser};
521
522 #[derive(Debug, Clone, thiserror::Error)]
524 #[error("parse error: {0}")]
525 pub struct Error(nom::error::Error<String>);
526
527 pub fn run(input: &str) -> Result<Instruction, Error> {
528 match instruction.parse_complete(input).finish() {
529 Ok((_rest, instruction)) => Ok(instruction),
530 Err(error) => Err(Error(error.cloned())),
531 }
532 }
533
534 fn instruction(input: &str) -> IResult<&str, Instruction> {
535 alt((
536 map(interaction, Instruction::Interact),
537 map(expectation, Instruction::Expect),
538 ))
539 .parse(input)
540 }
541
542 fn interaction(input: &str) -> IResult<&str, Interaction> {
543 alt((
544 map(mouse, Interaction::Mouse),
545 map(keyboard, Interaction::Keyboard),
546 ))
547 .parse(input)
548 }
549
550 fn mouse(input: &str) -> IResult<&str, Mouse> {
551 let mouse_move = preceded(tag("move "), target).map(Mouse::Move);
552
553 alt((mouse_move, mouse_click, mouse_press, mouse_release)).parse(input)
554 }
555
556 fn mouse_click(input: &str) -> IResult<&str, Mouse> {
557 let (input, _) = tag("click ")(input)?;
558 let (input, (button, target)) = mouse_button_at(input)?;
559
560 Ok((input, Mouse::Click { button, target }))
561 }
562
563 fn mouse_press(input: &str) -> IResult<&str, Mouse> {
564 let (input, _) = tag("press ")(input)?;
565 let (input, (button, target)) = mouse_button_at(input)?;
566
567 Ok((input, Mouse::Press { button, target }))
568 }
569
570 fn mouse_release(input: &str) -> IResult<&str, Mouse> {
571 let (input, _) = tag("release ")(input)?;
572 let (input, (button, target)) = mouse_button_at(input)?;
573
574 Ok((input, Mouse::Release { button, target }))
575 }
576
577 fn mouse_button_at(
578 input: &str,
579 ) -> IResult<&str, (mouse::Button, Option<Target>)> {
580 let (input, button) = mouse_button(input)?;
581 let (input, at) = opt(target).parse(input)?;
582
583 Ok((input, (button, at)))
584 }
585
586 fn target(input: &str) -> IResult<&str, Target> {
587 alt((string.map(Target::Text), point.map(Target::Point))).parse(input)
588 }
589
590 fn mouse_button(input: &str) -> IResult<&str, mouse::Button> {
591 alt((
592 tag("right").map(|_| mouse::Button::Right),
593 success(mouse::Button::Left),
594 ))
595 .parse(input)
596 }
597
598 fn keyboard(input: &str) -> IResult<&str, Keyboard> {
599 alt((
600 map(preceded(tag("type "), string), Keyboard::Typewrite),
601 map(preceded(tag("type "), key), Keyboard::Type),
602 ))
603 .parse(input)
604 }
605
606 fn expectation(input: &str) -> IResult<&str, Expectation> {
607 map(preceded(tag("expect "), string), |text| {
608 Expectation::Text(text)
609 })
610 .parse(input)
611 }
612
613 fn key(input: &str) -> IResult<&str, Key> {
614 alt((
615 map(tag("enter"), |_| Key::Enter),
616 map(tag("escape"), |_| Key::Escape),
617 map(tag("tab"), |_| Key::Tab),
618 map(tag("backspace"), |_| Key::Backspace),
619 ))
620 .parse(input)
621 }
622
623 fn point(input: &str) -> IResult<&str, Point> {
624 let comma = whitespace(char(','));
625
626 map(
627 delimited(
628 char('('),
629 separated_pair(float(), comma, float()),
630 char(')'),
631 ),
632 |(x, y)| Point { x, y },
633 )
634 .parse(input)
635 }
636
637 pub fn whitespace<'a, O, E: ParseError<&'a str>, F>(
638 inner: F,
639 ) -> impl Parser<&'a str, Output = O, Error = E>
640 where
641 F: Parser<&'a str, Output = O, Error = E>,
642 {
643 delimited(multispace0, inner, multispace0)
644 }
645
646 fn string(input: &str) -> IResult<&str, String> {
669 #[derive(Debug, Clone, Copy)]
670 enum Fragment<'a> {
671 Literal(&'a str),
672 EscapedChar(char),
673 EscapedWS,
674 }
675
676 fn fragment(input: &str) -> IResult<&str, Fragment<'_>> {
677 alt((
678 map(string_literal, Fragment::Literal),
679 map(escaped_char, Fragment::EscapedChar),
680 value(Fragment::EscapedWS, escaped_whitespace),
681 ))
682 .parse(input)
683 }
684
685 fn string_literal<'a, E: ParseError<&'a str>>(
686 input: &'a str,
687 ) -> IResult<&'a str, &'a str, E> {
688 let not_quote_slash = is_not("\"\\");
689
690 verify(not_quote_slash, |s: &str| !s.is_empty()).parse(input)
691 }
692
693 fn unicode(input: &str) -> IResult<&str, char> {
694 let parse_hex =
695 take_while_m_n(1, 6, |c: char| c.is_ascii_hexdigit());
696
697 let parse_delimited_hex =
698 preceded(char('u'), delimited(char('{'), parse_hex, char('}')));
699
700 let parse_u32 = map_res(parse_delimited_hex, move |hex| {
701 u32::from_str_radix(hex, 16)
702 });
703
704 map_opt(parse_u32, std::char::from_u32).parse(input)
705 }
706
707 fn escaped_char(input: &str) -> IResult<&str, char> {
708 preceded(
709 char('\\'),
710 alt((
711 unicode,
712 value('\n', char('n')),
713 value('\r', char('r')),
714 value('\t', char('t')),
715 value('\u{08}', char('b')),
716 value('\u{0C}', char('f')),
717 value('\\', char('\\')),
718 value('/', char('/')),
719 value('"', char('"')),
720 )),
721 )
722 .parse(input)
723 }
724
725 fn escaped_whitespace(input: &str) -> IResult<&str, &str> {
726 preceded(char('\\'), multispace1).parse(input)
727 }
728
729 let build_string =
730 fold(0.., fragment, String::new, |mut string, fragment| {
731 match fragment {
732 Fragment::Literal(s) => string.push_str(s),
733 Fragment::EscapedChar(c) => string.push(c),
734 Fragment::EscapedWS => {}
735 }
736 string
737 });
738
739 delimited(char('"'), build_string, char('"')).parse(input)
740 }
741}