minicrossterm/src/macros.rs

242 lines
6.7 KiB
Rust

/// Append a the first few characters of an ANSI escape code to the given string.
#[macro_export]
#[doc(hidden)]
macro_rules! csi {
($( $l:expr ),*) => { concat!("\x1B[", $( $l ),*) };
}
/// Writes an ansi code to the given writer.
#[doc(hidden)]
#[macro_export]
macro_rules! write_ansi_code {
($writer:expr, $ansi_code:expr) => {{
use std::{
error::Error,
io::{self, ErrorKind},
};
write!($writer, "{}", $ansi_code)
.map_err(|e| io::Error::new(ErrorKind::Other, e.description()))
.map_err($crate::ErrorKind::IoError)
}};
}
/// Writes/executes the given command.
#[doc(hidden)]
#[macro_export]
macro_rules! handle_command {
($writer:expr, $command:expr) => {{
// Silent warning when the macro is used inside the `command` module
#[allow(unused_imports)]
use $crate::{write_ansi_code, Command};
#[cfg(windows)]
{
let command = $command;
// ansi code is not always a string, however it does implement `Display` and `Write`.
// In order to check if the code is supported we have to make it a string.
let ansi_code = format!("{}", command.ansi_code());
if command.is_ansi_code_supported() {
write_ansi_code!($writer, &ansi_code)
} else {
command.execute_winapi().map_err($crate::ErrorKind::from)
}
}
#[cfg(unix)]
{
write_ansi_code!($writer, $command.ansi_code())
}
}};
}
/// Queues one or more command(s) for further execution.
///
/// Queued commands will be executed in the following cases:
///
/// * When `flush` is called manually on the given type implementing `io::Write`.
/// * The terminal will `flush` automatically if the buffer is full.
/// * Each line is flushed in case of `stdout`, because it is line buffered.
///
/// # Arguments
///
/// - [std::io::Writer](https://doc.rust-lang.org/std/io/trait.Write.html)
///
/// ANSI escape codes are written on the given 'writer', after which they are flushed.
///
/// - [Command](./trait.Command.html)
///
/// One or more commands
///
/// # Examples
///
/// ```rust
/// use std::io::{Write, stdout};
/// use crossterm::{queue, style::Print};
///
/// fn main() {
/// let mut stdout = stdout();
///
/// // `Print` will executed executed when `flush` is called.
/// queue!(stdout, Print("foo".to_string()));
///
/// // some other code (no execution happening here) ...
///
/// // when calling `flush` on `stdout`, all commands will be written to the stdout and therefore executed.
/// stdout.flush();
///
/// // ==== Output ====
/// // foo
/// }
/// ```
///
/// Have a look over at the [Command API](./#command-api) for more details.
///
/// # Notes
///
/// In case of Windows versions lower than 10, a direct WinApi call will be made.
/// The reason for this is that Windows versions lower than 10 do not support ANSI codes,
/// and can therefore not be written to the given `writer`.
/// Therefore, there is no difference between [execute](macro.execute.html)
/// and [queue](macro.queue.html) for those old Windows versions.
///
#[macro_export]
macro_rules! queue {
($writer:expr, $($command:expr), * $(,)?) => {{
// Silent warning when the macro is used inside the `command` module
#[allow(unused_imports)]
use $crate::{Command, handle_command};
#[allow(unused_assignments)]
let mut error = Ok(());
$(
error = handle_command!($writer, $command);
)*
error
}}
}
/// Executes one or more command(s).
///
/// # Arguments
///
/// - [std::io::Writer](https://doc.rust-lang.org/std/io/trait.Write.html)
///
/// ANSI escape codes are written on the given 'writer', after which they are flushed.
///
/// - [Command](./trait.Command.html)
///
/// One or more commands
///
/// # Examples
///
/// ```rust
/// use std::io::{Write, stdout};
/// use crossterm::{execute, style::Print};
///
/// fn main() {
/// // will be executed directly
/// execute!(stdout(), Print("sum:\n".to_string()));
///
/// // will be executed directly
/// execute!(stdout(), Print("1 + 1= ".to_string()), Print((1+1).to_string()));
///
/// // ==== Output ====
/// // sum:
/// // 1 + 1 = 2
/// }
/// ```
///
/// Have a look over at the [Command API](./#command-api) for more details.
///
/// # Notes
///
/// * In the case of UNIX and Windows 10, ANSI codes are written to the given 'writer'.
/// * In case of Windows versions lower than 10, a direct WinApi call will be made.
/// The reason for this is that Windows versions lower than 10 do not support ANSI codes,
/// and can therefore not be written to the given `writer`.
/// Therefore, there is no difference between [execute](macro.execute.html)
/// and [queue](macro.queue.html) for those old Windows versions.
#[macro_export]
macro_rules! execute {
($write:expr, $($command:expr), * $(,)? ) => {{
// Silent warning when the macro is used inside the `command` module
#[allow(unused_imports)]
use $crate::{handle_command, Command};
#[allow(unused_assignments)]
let mut error = Ok(());
$(
if let Err(e) = handle_command!($write, $command) {
error = Err(e);
}else {
$write.flush().map_err($crate::ErrorKind::IoError).unwrap();
}
)*
error
}}
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_display {
(for $($t:ty),+) => {
$(impl ::std::fmt::Display for $t {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::result::Result<(), ::std::fmt::Error> {
$crate::queue!(f, self).map_err(|_| ::std::fmt::Error)
}
})*
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_from {
($from:path, $to:expr) => {
impl From<$from> for ErrorKind {
fn from(e: $from) -> Self {
$to(e)
}
}
};
}
#[cfg(test)]
mod tests {
use std::io::{stdout, Write};
use crate::command::Command;
#[cfg(windows)]
use crate::error::ErrorKind;
pub struct FakeCommand;
impl Command for FakeCommand {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
""
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<(), ErrorKind> {
Ok(())
}
}
#[test]
fn test_queue() {
assert!(queue!(stdout(), FakeCommand,).is_ok());
assert!(queue!(stdout(), FakeCommand).is_ok());
}
#[test]
fn test_execute() {
assert!(execute!(stdout(), FakeCommand,).is_ok());
assert!(execute!(stdout(), FakeCommand).is_ok());
}
}