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 Id(String),
329 Text(String),
331 Point(Point),
333}
334
335impl fmt::Display for Target {
336 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337 match self {
338 Self::Id(id) => f.write_str(&format::id(id)),
339 Self::Point(point) => f.write_str(&format::point(*point)),
340 Self::Text(text) => f.write_str(&format::string(text)),
341 }
342 }
343}
344
345#[derive(Debug, Clone, PartialEq)]
347pub enum Keyboard {
348 Press(Key),
350 Release(Key),
352 Type(Key),
354 Typewrite(String),
356}
357
358impl fmt::Display for Keyboard {
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 match self {
361 Keyboard::Press(key) => {
362 write!(f, "press {}", format::key(*key))
363 }
364 Keyboard::Release(key) => {
365 write!(f, "release {}", format::key(*key))
366 }
367 Keyboard::Type(key) => {
368 write!(f, "type {}", format::key(*key))
369 }
370 Keyboard::Typewrite(text) => {
371 write!(f, "type \"{text}\"")
372 }
373 }
374 }
375}
376
377#[derive(Debug, Clone, Copy, PartialEq, Eq)]
381#[allow(missing_docs)]
382pub enum Key {
383 Enter,
384 Escape,
385 Tab,
386 Backspace,
387}
388
389impl From<Key> for keyboard::Key {
390 fn from(key: Key) -> Self {
391 match key {
392 Key::Enter => Self::Named(keyboard::key::Named::Enter),
393 Key::Escape => Self::Named(keyboard::key::Named::Escape),
394 Key::Tab => Self::Named(keyboard::key::Named::Tab),
395 Key::Backspace => Self::Named(keyboard::key::Named::Backspace),
396 }
397 }
398}
399
400mod format {
401 use super::*;
402
403 pub fn button_at(button: mouse::Button, at: Option<&Target>) -> String {
404 let button = self::button(button);
405
406 if let Some(at) = at {
407 if button.is_empty() {
408 at.to_string()
409 } else {
410 format!("{} {}", button, at)
411 }
412 } else {
413 button.to_owned()
414 }
415 }
416
417 pub fn button(button: mouse::Button) -> &'static str {
418 match button {
419 mouse::Button::Left => "",
420 mouse::Button::Right => "right",
421 mouse::Button::Middle => "middle",
422 mouse::Button::Back => "back",
423 mouse::Button::Forward => "forward",
424 mouse::Button::Other(_) => "other",
425 }
426 }
427
428 pub fn point(point: Point) -> String {
429 format!("({:.2}, {:.2})", point.x, point.y)
430 }
431
432 pub fn key(key: Key) -> &'static str {
433 match key {
434 Key::Enter => "enter",
435 Key::Escape => "escape",
436 Key::Tab => "tab",
437 Key::Backspace => "backspace",
438 }
439 }
440
441 pub fn string(text: &str) -> String {
442 format!("\"{}\"", text.escape_default())
443 }
444
445 pub fn id(id: &str) -> String {
446 format!("#{id}")
447 }
448}
449
450#[derive(Debug, Clone, PartialEq)]
455pub enum Expectation {
456 Text(String),
458}
459
460impl fmt::Display for Expectation {
461 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
462 match self {
463 Expectation::Text(text) => {
464 write!(f, "expect {}", format::string(text))
465 }
466 }
467 }
468}
469
470pub use parser::Error as ParseError;
471
472mod parser {
473 use super::*;
474
475 use nom::branch::alt;
476 use nom::bytes::complete::tag;
477 use nom::bytes::{is_not, take_while_m_n};
478 use nom::character::complete::{alphanumeric1, char, multispace0, multispace1};
479 use nom::combinator::{map, map_opt, map_res, opt, recognize, success, value, verify};
480 use nom::error::ParseError;
481 use nom::multi::{fold, many1_count};
482 use nom::number::float;
483 use nom::sequence::{delimited, preceded, separated_pair};
484 use nom::{Finish, IResult, Parser};
485
486 #[derive(Debug, Clone, thiserror::Error)]
488 #[error("parse error: {0}")]
489 pub struct Error(nom::error::Error<String>);
490
491 pub fn run(input: &str) -> Result<Instruction, Error> {
492 match instruction.parse_complete(input).finish() {
493 Ok((_rest, instruction)) => Ok(instruction),
494 Err(error) => Err(Error(error.cloned())),
495 }
496 }
497
498 fn instruction(input: &str) -> IResult<&str, Instruction> {
499 alt((
500 map(interaction, Instruction::Interact),
501 map(expectation, Instruction::Expect),
502 ))
503 .parse(input)
504 }
505
506 fn interaction(input: &str) -> IResult<&str, Interaction> {
507 alt((
508 map(mouse, Interaction::Mouse),
509 map(keyboard, Interaction::Keyboard),
510 ))
511 .parse(input)
512 }
513
514 fn mouse(input: &str) -> IResult<&str, Mouse> {
515 let mouse_move = preceded(tag("move "), target).map(Mouse::Move);
516
517 alt((mouse_move, mouse_click, mouse_press, mouse_release)).parse(input)
518 }
519
520 fn mouse_click(input: &str) -> IResult<&str, Mouse> {
521 let (input, _) = tag("click ")(input)?;
522 let (input, (button, target)) = mouse_button_at(input)?;
523
524 Ok((input, Mouse::Click { button, target }))
525 }
526
527 fn mouse_press(input: &str) -> IResult<&str, Mouse> {
528 let (input, _) = tag("press ")(input)?;
529 let (input, (button, target)) = mouse_button_at(input)?;
530
531 Ok((input, Mouse::Press { button, target }))
532 }
533
534 fn mouse_release(input: &str) -> IResult<&str, Mouse> {
535 let (input, _) = tag("release ")(input)?;
536 let (input, (button, target)) = mouse_button_at(input)?;
537
538 Ok((input, Mouse::Release { button, target }))
539 }
540
541 fn mouse_button_at(input: &str) -> IResult<&str, (mouse::Button, Option<Target>)> {
542 let (input, button) = mouse_button(input)?;
543 let (input, at) = opt(target).parse(input)?;
544
545 Ok((input, (button, at)))
546 }
547
548 fn target(input: &str) -> IResult<&str, Target> {
549 alt((
550 id.map(String::from).map(Target::Id),
551 string.map(Target::Text),
552 point.map(Target::Point),
553 ))
554 .parse(input)
555 }
556
557 fn mouse_button(input: &str) -> IResult<&str, mouse::Button> {
558 alt((
559 tag("right").map(|_| mouse::Button::Right),
560 success(mouse::Button::Left),
561 ))
562 .parse(input)
563 }
564
565 fn keyboard(input: &str) -> IResult<&str, Keyboard> {
566 alt((
567 map(preceded(tag("type "), string), Keyboard::Typewrite),
568 map(preceded(tag("type "), key), Keyboard::Type),
569 ))
570 .parse(input)
571 }
572
573 fn expectation(input: &str) -> IResult<&str, Expectation> {
574 map(preceded(tag("expect "), string), |text| {
575 Expectation::Text(text)
576 })
577 .parse(input)
578 }
579
580 fn key(input: &str) -> IResult<&str, Key> {
581 alt((
582 map(tag("enter"), |_| Key::Enter),
583 map(tag("escape"), |_| Key::Escape),
584 map(tag("tab"), |_| Key::Tab),
585 map(tag("backspace"), |_| Key::Backspace),
586 ))
587 .parse(input)
588 }
589
590 fn id(input: &str) -> IResult<&str, &str> {
591 preceded(
592 char('#'),
593 recognize(many1_count(alt((alphanumeric1, tag("_"), tag("-"))))),
594 )
595 .parse(input)
596 }
597
598 fn point(input: &str) -> IResult<&str, Point> {
599 let comma = whitespace(char(','));
600
601 map(
602 delimited(
603 char('('),
604 separated_pair(float(), comma, float()),
605 char(')'),
606 ),
607 |(x, y)| Point { x, y },
608 )
609 .parse(input)
610 }
611
612 pub fn whitespace<'a, O, E: ParseError<&'a str>, F>(
613 inner: F,
614 ) -> impl Parser<&'a str, Output = O, Error = E>
615 where
616 F: Parser<&'a str, Output = O, Error = E>,
617 {
618 delimited(multispace0, inner, multispace0)
619 }
620
621 fn string(input: &str) -> IResult<&str, String> {
644 #[derive(Debug, Clone, Copy)]
645 enum Fragment<'a> {
646 Literal(&'a str),
647 EscapedChar(char),
648 EscapedWS,
649 }
650
651 fn fragment(input: &str) -> IResult<&str, Fragment<'_>> {
652 alt((
653 map(string_literal, Fragment::Literal),
654 map(escaped_char, Fragment::EscapedChar),
655 value(Fragment::EscapedWS, escaped_whitespace),
656 ))
657 .parse(input)
658 }
659
660 fn string_literal<'a, E: ParseError<&'a str>>(
661 input: &'a str,
662 ) -> IResult<&'a str, &'a str, E> {
663 let not_quote_slash = is_not("\"\\");
664
665 verify(not_quote_slash, |s: &str| !s.is_empty()).parse(input)
666 }
667
668 fn unicode(input: &str) -> IResult<&str, char> {
669 let parse_hex = take_while_m_n(1, 6, |c: char| c.is_ascii_hexdigit());
670
671 let parse_delimited_hex =
672 preceded(char('u'), delimited(char('{'), parse_hex, char('}')));
673
674 let parse_u32 = map_res(parse_delimited_hex, move |hex| u32::from_str_radix(hex, 16));
675
676 map_opt(parse_u32, std::char::from_u32).parse(input)
677 }
678
679 fn escaped_char(input: &str) -> IResult<&str, char> {
680 preceded(
681 char('\\'),
682 alt((
683 unicode,
684 value('\n', char('n')),
685 value('\r', char('r')),
686 value('\t', char('t')),
687 value('\u{08}', char('b')),
688 value('\u{0C}', char('f')),
689 value('\\', char('\\')),
690 value('/', char('/')),
691 value('"', char('"')),
692 )),
693 )
694 .parse(input)
695 }
696
697 fn escaped_whitespace(input: &str) -> IResult<&str, &str> {
698 preceded(char('\\'), multispace1).parse(input)
699 }
700
701 let build_string = fold(0.., fragment, String::new, |mut string, fragment| {
702 match fragment {
703 Fragment::Literal(s) => string.push_str(s),
704 Fragment::EscapedChar(c) => string.push(c),
705 Fragment::EscapedWS => {}
706 }
707 string
708 });
709
710 delimited(char('"'), build_string, char('"')).parse(input)
711 }
712}