use std::fmt;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use waos_package::ProgramId;
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub enum ToWaOS {
Run {
id: ProgramId,
#[serde(with = "serde_bytes")]
component: Vec<u8>,
},
Open { id: ProgramId, files: Vec<PathBuf> },
Screenshot { id: ProgramId },
Input {
id: ProgramId,
window: u32,
input: Input,
},
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub enum Input {
Tap { x: f64, y: f64 },
Text(String),
Key(String),
Scroll { x: f64, y: f64, dx: f64, dy: f64 },
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum FromWaOS {
Started,
Output {
stream: OutputStream,
#[serde(with = "serde_bytes")]
bytes: Vec<u8>,
},
Ended(RunEnd),
Refused(String),
Opened,
Screenshots(Vec<WindowPicture>),
InputDone {
new_picture: bool,
},
Unreached(Unreached),
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WindowPicture {
pub window: u32,
pub title: String,
pub width: u32,
pub height: u32,
pub in_sight: bool,
#[serde(with = "serde_bytes")]
pub png: Option<Vec<u8>>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Unreached {
NotRunByCargoWaos,
NoWindow { window: u32, windows: u32 },
OutsideWindow { width: u32, height: u32 },
NotEditing,
UnknownKey(String),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum OutputStream {
Stdout,
Stderr,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum RunEnd {
Returned { success: bool },
Failed(String),
Stopped,
Replaced,
}
impl fmt::Debug for ToWaOS {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ToWaOS::Run { id, component } => f
.debug_struct("Run")
.field("id", &format_args!("{id}"))
.field("component", &format_args!("{} bytes", component.len()))
.finish(),
ToWaOS::Open { id, files } => f
.debug_struct("Open")
.field("id", &format_args!("{id}"))
.field("files", files)
.finish(),
ToWaOS::Screenshot { id } => f
.debug_struct("Screenshot")
.field("id", &format_args!("{id}"))
.finish(),
ToWaOS::Input { id, window, input } => f
.debug_struct("Input")
.field("id", &format_args!("{id}"))
.field("window", window)
.field("input", input)
.finish(),
}
}
}
impl fmt::Debug for Input {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Input::Tap { x, y } => f.debug_struct("Tap").field("x", x).field("y", y).finish(),
Input::Text(text) => f
.debug_tuple("Text")
.field(&format_args!("{} characters", text.chars().count()))
.finish(),
Input::Key(key) => f.debug_tuple("Key").field(key).finish(),
Input::Scroll { x, y, dx, dy } => f
.debug_struct("Scroll")
.field("x", x)
.field("y", y)
.field("dx", dx)
.field("dy", dy)
.finish(),
}
}
}
impl fmt::Debug for WindowPicture {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WindowPicture")
.field("window", &self.window)
.field("title", &self.title)
.field("width", &self.width)
.field("height", &self.height)
.field("in_sight", &self.in_sight)
.field("png_bytes", &self.png.as_ref().map(Vec::len))
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq, Serialize, Deserialize)]
enum ToWaOSAsSequence {
Run { id: ProgramId, component: Vec<u8> },
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
enum FromWaOSAsSequence {
Started,
Output {
stream: OutputStream,
bytes: Vec<u8>,
},
}
fn bytes() -> Vec<u8> {
(0..=u8::MAX).cycle().take(1000).collect()
}
#[test]
fn a_run_is_encoded_as_it_was_when_its_component_was_a_sequence() {
let id: ProgramId = "dev.example.triangle".parse().unwrap();
let run = ToWaOS::Run {
id: id.clone(),
component: bytes(),
};
let as_sequence = ToWaOSAsSequence::Run {
id,
component: bytes(),
};
let encoded = postcard::to_allocvec(&run).unwrap();
assert_eq!(encoded, postcard::to_allocvec(&as_sequence).unwrap());
assert_eq!(
postcard::from_bytes::<ToWaOSAsSequence>(&encoded).unwrap(),
as_sequence
);
}
#[test]
fn output_is_encoded_as_it_was_when_its_bytes_were_a_sequence() {
let output = FromWaOS::Output {
stream: OutputStream::Stderr,
bytes: bytes(),
};
let as_sequence = FromWaOSAsSequence::Output {
stream: OutputStream::Stderr,
bytes: bytes(),
};
let encoded = postcard::to_allocvec(&output).unwrap();
assert_eq!(encoded, postcard::to_allocvec(&as_sequence).unwrap());
assert_eq!(postcard::from_bytes::<FromWaOS>(&encoded).unwrap(), output);
}
#[test]
fn an_opening_reads_back_as_written_and_prints_its_files() {
let open = ToWaOS::Open {
id: "dev.example.notes".parse().unwrap(),
files: vec![PathBuf::from("/Users/me/notes.md")],
};
let encoded = postcard::to_allocvec(&open).unwrap();
assert_eq!(postcard::from_bytes::<ToWaOS>(&encoded).unwrap(), open);
assert_eq!(
format!("{open:?}"),
"Open { id: dev.example.notes, files: [\"/Users/me/notes.md\"] }"
);
let opened = postcard::to_allocvec(&FromWaOS::Opened).unwrap();
assert_eq!(
postcard::from_bytes::<FromWaOS>(&opened).unwrap(),
FromWaOS::Opened
);
}
#[test]
fn a_run_prints_its_component_as_a_count() {
let run = ToWaOS::Run {
id: "dev.example.triangle".parse().unwrap(),
component: vec![0; 1024],
};
assert_eq!(
format!("{run:?}"),
"Run { id: dev.example.triangle, component: 1024 bytes }"
);
}
#[test]
fn a_screenshot_and_each_input_read_back_as_written() {
let id: ProgramId = "dev.example.notes".parse().unwrap();
let inputs = [
Input::Tap { x: 120.5, y: 48.0 },
Input::Text("milk, eggs".into()),
Input::Key("cmd+s".into()),
Input::Scroll {
x: 10.0,
y: 20.0,
dx: 0.0,
dy: -200.0,
},
];
let messages = std::iter::once(ToWaOS::Screenshot { id: id.clone() }).chain(
inputs.into_iter().map(|input| ToWaOS::Input {
id: id.clone(),
window: 2,
input,
}),
);
for message in messages {
let encoded = postcard::to_allocvec(&message).unwrap();
assert_eq!(postcard::from_bytes::<ToWaOS>(&encoded).unwrap(), message);
}
}
#[test]
fn typed_text_is_left_out_of_what_a_message_prints() {
let input = ToWaOS::Input {
id: "dev.example.notes".parse().unwrap(),
window: 1,
input: Input::Text("hunter2".into()),
};
let printed = format!("{input:?}");
assert!(!printed.contains("hunter2"), "{printed}");
assert!(printed.contains("7 characters"), "{printed}");
}
#[test]
fn a_picture_prints_its_png_as_a_count() {
let picture = WindowPicture {
window: 1,
title: "Notes".into(),
width: 1600,
height: 1200,
in_sight: true,
png: Some(vec![0; 2048]),
};
let printed = format!("{:?}", FromWaOS::Screenshots(vec![picture]));
assert!(printed.contains("png_bytes: Some(2048)"), "{printed}");
assert!(!printed.contains("0, 0"), "{printed}");
}
}