#[macro_export]
macro_rules! csi {
($($arg:expr),*) => { concat!("\x1B[", $($arg),*) };
}
#[macro_export]
macro_rules! esc {
($($arg:expr),*) => { concat!("\x1B", $($arg),*) };
}
#[macro_export]
macro_rules! sgr {
($($arg:expr),*) => { concat!("\x1B[", $($arg),* , "m") };
}
#[macro_export]
macro_rules! sequence {
(
$(#[$meta:meta])*
struct $name:ident => $value:expr
) => {
$(#[$meta])*
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct $name;
impl ::std::fmt::Display for $name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(f, $value)
}
}
};
(
$(#[$meta:meta])*
enum $name:ident {
$(
$(#[$variant_meta:meta])*
$variant:ident => $variant_value:expr
),*
$(,)?
}
) => {
$(#[$meta])*
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum $name {
$(
$(#[$variant_meta])*
$variant,
)*
}
impl ::std::fmt::Display for $name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(f, "{}", match self {
$(
$name::$variant => $variant_value,
)*
})
}
}
};
(
$(#[$meta:meta])*
struct $type:ident(
$($fields:ty),*
$(,)?
)
=>
$write:expr
) => {
$(#[$meta])*
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct $type($(pub $fields),*);
impl ::std::fmt::Display for $type {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
let write: &dyn Fn(&Self, &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result =
&$write;
write(self, f)
}
}
};
}
#[macro_export]
macro_rules! queue {
($dst:expr, $($sequence:expr),* $(,)?) => {{
let mut error = None;
$(
if let Err(e) = write!($dst, "{}", $sequence) {
error = Some(e);
}
)*
if let Some(error) = error {
Err(error)
} else {
Ok(())
}
}}
}
#[macro_export]
macro_rules! execute {
($dst:expr, $($sequence:expr),* $(,)?) => {{
if let Err(e) = $crate::queue!($dst, $($sequence),*) {
Err(e)
} else {
$dst.flush()
}
}}
}
#[cfg(test)]
macro_rules! test_sequences {
(
$(
$name:ident(
$($left:expr => $right:expr),*
$(,)?
)
),*
$(,)?
) => {
#[cfg(test)]
mod tests {
use super::*;
$(
#[test]
fn $name() {
$(
assert_eq!(&format!("{}", $left), $right);
)*
}
)*
}
}
}
#[cfg(test)]
mod tests {
use std::io::{Error, ErrorKind, Write};
#[test]
fn csi() {
assert_eq!(csi!("foo"), "\x1B[foo");
}
#[test]
fn esc() {
assert_eq!(esc!("bar"), "\x1Bbar");
}
#[test]
fn sgr() {
assert_eq!(sgr!("bar"), "\x1B[barm");
}
#[test]
fn static_struct_sequence() {
sequence!(
struct TestSeq => csi!("foo")
);
assert_eq!(&format!("{}", TestSeq), "\x1B[foo");
}
#[test]
fn static_enum_sequence() {
sequence!(
enum TestSeq {
Foo => csi!("foo"),
Bar => esc!("bar"),
}
);
assert_eq!(&format!("{}", TestSeq::Foo), "\x1B[foo");
assert_eq!(&format!("{}", TestSeq::Bar), "\x1Bbar");
}
#[test]
fn dynamic_struct_sequence() {
sequence!(
struct TestSeq(u16) =>
|this, f| write!(f, csi!("foo{}bar"), this.0)
);
assert_eq!(&format!("{}", TestSeq(10)), "\x1B[foo10bar");
}
#[test]
fn queue_allows_trailing_comma() {
let mut writer = Writer::default();
assert!(queue!(&mut writer, "foo",).is_ok());
assert_eq!(&writer.buffer, "foo");
}
#[test]
fn queue_writes_single_sequence() {
let mut writer = Writer::default();
assert!(queue!(&mut writer, "foo").is_ok());
assert_eq!(&writer.buffer, "foo");
}
#[test]
fn queue_writes_multiple_sequences() {
let mut writer = Writer::default();
assert!(queue!(&mut writer, "foo", "bar", "baz").is_ok());
assert_eq!(&writer.buffer, "foobarbaz");
}
#[test]
fn queue_does_not_flush() {
let mut writer = Writer::default();
assert!(queue!(&mut writer, "foo").is_ok());
assert!(!writer.flushed);
assert!(writer.flushed_buffer.is_empty());
}
#[test]
fn execute_allows_trailing_comma() {
let mut writer = Writer::default();
assert!(execute!(&mut writer, "foo",).is_ok());
assert_eq!(&writer.flushed_buffer, "foo");
}
#[test]
fn execute_writes_single_sequence() {
let mut writer = Writer::default();
assert!(execute!(&mut writer, "foo").is_ok());
assert_eq!(&writer.flushed_buffer, "foo");
}
#[test]
fn execute_writes_multiple_sequences() {
let mut writer = Writer::default();
assert!(execute!(&mut writer, "foo", "bar", "baz").is_ok());
assert_eq!(&writer.flushed_buffer, "foobarbaz");
}
#[test]
fn execute_does_flush() {
let mut writer = Writer::default();
assert!(execute!(&mut writer, "foo").is_ok());
assert!(writer.flushed);
assert_eq!(&writer.flushed_buffer, "foo");
assert!(writer.buffer.is_empty());
}
#[derive(Default)]
struct Writer {
buffer: String,
flushed_buffer: String,
flushed: bool,
}
impl Write for Writer {
fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
let s = std::str::from_utf8(buf).map_err(|_| ErrorKind::InvalidData)?;
self.buffer.push_str(s);
Ok(s.len())
}
fn flush(&mut self) -> Result<(), Error> {
self.flushed_buffer = self.buffer.clone();
self.buffer = String::new();
self.flushed = true;
Ok(())
}
}
}