use super::Backend;
use crate::ProgressState;
use std::io::Write;
use termpulse_core::{OscSequence, Terminator, sanitize_label};
pub struct OscBackend<W: Write> {
writer: W,
}
impl<W: Write> OscBackend<W> {
pub fn new(writer: W) -> Self {
Self { writer }
}
}
impl OscBackend<std::io::Stderr> {
pub fn stderr() -> Self {
Self::new(std::io::stderr())
}
}
impl<W: Write + Send> Backend for OscBackend<W> {
fn emit(&mut self, state: ProgressState, percent: Option<u8>, label: &str) {
let sanitized = sanitize_label(label);
let label_opt = if sanitized.is_empty() {
None
} else {
Some(sanitized)
};
let seq = OscSequence {
state,
percent,
label: label_opt,
terminator: Terminator::St,
};
let mut buf = [0u8; 256];
if let Ok(n) = seq.write_to(&mut buf) {
let _ = self.writer.write_all(&buf[..n]);
let _ = self.writer.flush();
}
}
fn clear(&mut self) {
let seq = OscSequence::clear();
let mut buf = [0u8; 64];
if let Ok(n) = seq.write_to(&mut buf) {
let _ = self.writer.write_all(&buf[..n]);
let _ = self.writer.flush();
}
}
fn name(&self) -> &'static str {
"osc"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn emits_osc_sequence() {
let mut buf = Vec::new();
let mut backend = OscBackend::new(&mut buf);
backend.emit(ProgressState::Normal, Some(50), "Building");
assert_eq!(buf, b"\x1b]9;4;1;50;Building\x1b\\");
}
#[test]
fn clears_progress() {
let mut buf = Vec::new();
let mut backend = OscBackend::new(&mut buf);
backend.clear();
assert_eq!(buf, b"\x1b]9;4;0;0;\x1b\\");
}
#[test]
fn sanitizes_label() {
let mut buf = Vec::new();
let mut backend = OscBackend::new(&mut buf);
backend.emit(ProgressState::Normal, Some(10), "evil\x1binject");
assert_eq!(buf, b"\x1b]9;4;1;10;evil\x1b\\");
}
}