aboutsummaryrefslogtreecommitdiffstats
path: root/examples/interhack.rs
blob: c0c575e6eddbf4396d6c30274fa9811c2ff53fce (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#![allow(clippy::trivial_regex)]

use futures::stream::Stream as _;
use std::io::Write as _;
use tokio::io::AsyncRead as _;

mod input;

struct Interhack {
    process: tokio_pty_process_stream::Process<input::buf::Stdin>,
    stdin: input::evented_stdin::Stdin,
    read_buf: [u8; 4096],
    raw_screen: Option<crossterm::RawScreen>,
}

impl Interhack {
    fn new() -> Self {
        Self {
            process: tokio_pty_process_stream::Process::new(
                "nethack",
                &[],
                input::buf::Stdin::new(),
            ),
            stdin: input::evented_stdin::Stdin::new(),
            read_buf: [0; 4096],
            raw_screen: None,
        }
    }

    fn filter_input(&self, buf: Vec<u8>) -> Vec<u8> {
        lazy_static::lazy_static! {
            static ref RE: regex::bytes::Regex = regex::bytes::Regex::new(
                "\x05"
            ).unwrap();
        }
        if let Some(m) = RE.find(&buf) {
            let mut new: Vec<u8> = vec![];
            new.extend(buf[..m.start()].iter());
            new.extend(b"E- Elbereth\n");
            new.extend(buf[m.end()..].iter());
            new
        } else {
            buf
        }
    }

    fn filter_output(&self, buf: Vec<u8>) -> Vec<u8> {
        lazy_static::lazy_static! {
            static ref RE: regex::bytes::Regex = regex::bytes::Regex::new(
                r"Elbereth"
            ).unwrap();
        }
        if let Some(m) = RE.find(&buf) {
            let mut new: Vec<u8> = vec![];
            new.extend(buf[..m.start()].iter());
            new.extend(b"\x1b[35m");
            new.extend(buf[m.start()..m.end()].iter());
            new.extend(b"\x1b[m");
            new.extend(buf[m.end()..].iter());
            new
        } else {
            buf
        }
    }
}

#[allow(clippy::type_complexity)]
impl Interhack {
    const POLL_FNS:
        &'static [&'static dyn for<'a> Fn(
            &'a mut Self,
        )
            -> component_future::Poll<(), ()>] =
        &[&Self::poll_input, &Self::poll_process, &Self::poll_screen];

    fn poll_input(&mut self) -> component_future::Poll<(), ()> {
        let n = component_future::try_ready!(self
            .stdin
            .poll_read(&mut self.read_buf)
            .map_err(|e| panic!(e)));
        let input = self.filter_input(self.read_buf[..n].to_vec());
        self.process.input().send(&input);
        Ok(component_future::Async::DidWork)
    }

    fn poll_process(&mut self) -> component_future::Poll<(), ()> {
        let event = component_future::try_ready!(self
            .process
            .poll()
            .map_err(|e| panic!(e)));
        match event {
            Some(tokio_pty_process_stream::Event::Output { data }) => {
                let output = self.filter_output(data);
                let stdout = std::io::stdout();
                let mut stdout = stdout.lock();
                stdout.write_all(&output).unwrap();
                stdout.flush().unwrap();
            }
            None => return Ok(component_future::Async::Ready(())),
            _ => {}
        }
        Ok(component_future::Async::DidWork)
    }

    fn poll_screen(&mut self) -> component_future::Poll<(), ()> {
        if self.raw_screen.is_none() {
            self.raw_screen =
                Some(crossterm::RawScreen::into_raw_mode().unwrap());
            Ok(component_future::Async::DidWork)
        } else {
            Ok(component_future::Async::NothingToDo)
        }
    }
}

impl futures::future::Future for Interhack {
    type Item = ();
    type Error = ();

    fn poll(&mut self) -> futures::Poll<Self::Item, Self::Error> {
        component_future::poll_future(self, Self::POLL_FNS)
    }
}

fn main() {
    tokio::run(futures::future::lazy(Interhack::new));
}