use std::fmt;
use std::fs::File;
use std::io::{self, Seek, Write as IoWrite};
use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd};
pub trait Write {
fn write_to(&self, os: &mut RawOstream);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
Default,
SavedColor,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Colors {
Disable,
Enable,
Auto,
}
#[derive(Debug, Clone, Copy)]
pub struct SavedColors {
fg: Color,
bg: Color,
bold: bool,
underline: bool,
}
impl Default for SavedColors {
fn default() -> Self {
Self {
fg: Color::Default,
bg: Color::Default,
bold: false,
underline: false,
}
}
}
pub struct RawOstream {
buffer: Vec<u8>,
unbuffered: bool,
position: usize,
indent_level: u32,
color_enabled: bool,
current_fg: Color,
current_bg: Color,
current_bold: bool,
current_underline: bool,
saved_colors: Option<SavedColors>,
error_flag: bool,
}
impl RawOstream {
pub fn new() -> Self {
Self {
buffer: Vec::new(),
unbuffered: false,
position: 0,
indent_level: 0,
color_enabled: false,
current_fg: Color::Default,
current_bg: Color::Default,
current_bold: false,
current_underline: false,
saved_colors: None,
error_flag: false,
}
}
pub fn write_bytes(&mut self, data: &[u8]) {
if self.unbuffered {
self.flush_impl();
}
self.buffer.extend_from_slice(data);
self.position += data.len();
}
pub fn write_str(&mut self, s: &str) {
self.write_bytes(s.as_bytes());
}
pub fn write_char(&mut self, c: char) {
let mut buf = [0u8; 4];
let s = c.encode_utf8(&mut buf);
self.write_str(s);
}
pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
let mut temp = Vec::new();
temp.write_fmt(args).map_err(|_| fmt::Error)?;
self.write_bytes(&temp);
Ok(())
}
pub fn write_unsigned(&mut self, n: u64) {
let s = n.to_string();
self.write_str(&s);
}
pub fn write_signed(&mut self, n: i64) {
let s = n.to_string();
self.write_str(&s);
}
pub fn write_hex(&mut self, n: u64) {
let s = format!("{:x}", n);
self.write_str(&s);
}
pub fn write_hex_upper(&mut self, n: u64) {
let s = format!("0x{:X}", n);
self.write_str(&s);
}
pub fn write_hex_lower(&mut self, n: u64) {
let s = format!("0x{:x}", n);
self.write_str(&s);
}
pub fn write_octal(&mut self, n: u64) {
let s = format!("{:o}", n);
self.write_str(&s);
}
pub fn write_binary(&mut self, n: u64) {
let s = format!("0b{:b}", n);
self.write_str(&s);
}
pub fn write_double(&mut self, d: f64, precision: usize) {
let s = format!("{:.prec$}", d, prec = precision);
self.write_str(&s);
}
pub fn write_scientific(&mut self, d: f64) {
let s = format!("{:e}", d);
self.write_str(&s);
}
pub fn write_escaped(&mut self, s: &str) {
for c in s.chars() {
match c {
'\n' => self.write_str("\\n"),
'\r' => self.write_str("\\r"),
'\t' => self.write_str("\\t"),
'\\' => self.write_str("\\\\"),
'"' => self.write_str("\\\""),
'\0' => self.write_str("\\0"),
c if c.is_ascii_graphic() || c == ' ' => self.write_char(c),
c if (c as u32) < 0x80 => {
let escaped = format!("\\x{:02x}", c as u8);
self.write_str(&escaped);
}
c => {
let escaped = format!("\\u{{{:x}}}", c as u32);
self.write_str(&escaped);
}
}
}
}
pub fn indent(&mut self, num_spaces: u32) {
self.indent_level = self.indent_level.saturating_add(num_spaces);
}
pub fn unindent(&mut self, num_spaces: u32) {
self.indent_level = self.indent_level.saturating_sub(num_spaces);
}
pub fn get_indent_level(&self) -> u32 {
self.indent_level
}
pub fn set_indent_level(&mut self, level: u32) {
self.indent_level = level;
}
pub fn write_indent(&mut self) {
for _ in 0..self.indent_level {
self.write_char(' ');
}
}
pub fn writeln_indented(&mut self, s: &str) {
self.write_indent();
self.write_str(s);
self.write_char('\n');
}
pub fn tell(&self) -> usize {
self.position
}
pub fn seek(&mut self, pos: usize) {
if pos <= self.buffer.len() {
self.position = pos;
}
}
pub fn size(&self) -> usize {
self.buffer.len()
}
pub fn flush(&mut self) {
self.flush_impl();
}
fn flush_impl(&mut self) {}
pub fn set_unbuffered(&mut self) {
self.unbuffered = true;
}
pub fn set_buffered(&mut self) {
self.unbuffered = false;
}
pub fn enable_colors(&mut self, enable: bool) {
self.color_enabled = enable;
}
pub fn has_colors(&self) -> bool {
self.color_enabled
}
pub fn is_displayed(&self) -> bool {
false
}
pub fn change_color(&mut self, color: Color, bold: bool, bg: bool) {
if !self.color_enabled {
return;
}
if bg {
self.current_bg = color;
} else {
self.current_fg = color;
}
if bold {
self.current_bold = bold;
}
self.write_ansi_color(color, bold, bg);
}
pub fn reset_color(&mut self) {
if !self.color_enabled {
return;
}
self.current_fg = Color::Default;
self.current_bg = Color::Default;
self.current_bold = false;
self.current_underline = false;
self.write_str("\x1b[0m");
}
fn write_ansi_color(&mut self, color: Color, bold: bool, bg: bool) {
let base = if bg { 40 } else { 30 };
let color_code: u8 = match color {
Color::Black => 0,
Color::Red => 1,
Color::Green => 2,
Color::Yellow => 3,
Color::Blue => 4,
Color::Magenta => 5,
Color::Cyan => 6,
Color::White => 7,
Color::Default => 9,
Color::SavedColor => return,
};
if bold {
let seq = format!("\x1b[1;{}m", base + color_code);
self.write_str(&seq);
} else {
let seq = format!("\x1b[{}m", base + color_code);
self.write_str(&seq);
}
}
pub fn set_underline(&mut self) {
if self.color_enabled {
self.current_underline = true;
self.write_str("\x1b[4m");
}
}
pub fn clear_underline(&mut self) {
if self.color_enabled {
self.current_underline = false;
self.reset_color();
}
}
pub fn set_bold(&mut self) {
if self.color_enabled {
self.current_bold = true;
self.write_str("\x1b[1m");
}
}
pub fn clear_bold(&mut self) {
if self.color_enabled {
self.current_bold = false;
self.reset_color();
}
}
pub fn save_color_state(&mut self) {
self.saved_colors = Some(SavedColors {
fg: self.current_fg,
bg: self.current_bg,
bold: self.current_bold,
underline: self.current_underline,
});
}
pub fn restore_color_state(&mut self) {
if let Some(saved) = self.saved_colors.take() {
self.change_color(saved.fg, saved.bold, false);
self.change_color(saved.bg, false, true);
if saved.underline {
self.set_underline();
}
}
}
pub fn has_error(&self) -> bool {
self.error_flag
}
pub fn clear_error(&mut self) {
self.error_flag = false;
}
pub fn set_error(&mut self) {
self.error_flag = true;
}
pub fn data(&self) -> &[u8] {
&self.buffer
}
pub fn str(&self) -> &str {
std::str::from_utf8(&self.buffer).unwrap_or("<invalid utf8>")
}
pub fn take_buffer(&mut self) -> Vec<u8> {
self.position = 0;
std::mem::take(&mut self.buffer)
}
pub fn reset(&mut self) {
self.buffer.clear();
self.position = 0;
self.error_flag = false;
}
}
impl Default for RawOstream {
fn default() -> Self {
Self::new()
}
}
pub struct RawStringOstream {
inner: RawOstream,
}
impl RawStringOstream {
pub fn new() -> Self {
Self {
inner: RawOstream::new(),
}
}
pub fn write_bytes(&mut self, data: &[u8]) {
self.inner.write_bytes(data);
}
pub fn write_str(&mut self, s: &str) {
self.inner.write_str(s);
}
pub fn write_char(&mut self, c: char) {
self.inner.write_char(c);
}
pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
self.inner.write_fmt(args)
}
pub fn write_unsigned(&mut self, n: u64) {
self.inner.write_unsigned(n);
}
pub fn write_signed(&mut self, n: i64) {
self.inner.write_signed(n);
}
pub fn write_hex(&mut self, n: u64) {
self.inner.write_hex(n);
}
pub fn write_escaped(&mut self, s: &str) {
self.inner.write_escaped(s);
}
pub fn indent(&mut self, n: u32) {
self.inner.indent(n);
}
pub fn unindent(&mut self, n: u32) {
self.inner.unindent(n);
}
pub fn write_indent(&mut self) {
self.inner.write_indent();
}
pub fn str(&self) -> &str {
self.inner.str()
}
pub fn take_buffer(&mut self) -> Vec<u8> {
self.inner.take_buffer()
}
pub fn tell(&self) -> usize {
self.inner.tell()
}
pub fn seek(&mut self, pos: usize) {
self.inner.seek(pos);
}
pub fn size(&self) -> usize {
self.inner.size()
}
pub fn flush(&mut self) {
self.inner.flush();
}
pub fn reset(&mut self) {
self.inner.reset();
}
pub fn has_error(&self) -> bool {
self.inner.has_error()
}
pub fn clear_error(&mut self) {
self.inner.clear_error();
}
}
impl Default for RawStringOstream {
fn default() -> Self {
Self::new()
}
}
pub struct RawFdOstream {
file: Option<File>,
should_close: bool,
buffer: Vec<u8>,
position: usize,
error_flag: bool,
color_enabled: bool,
current_fg: Color,
current_bg: Color,
current_bold: bool,
current_underline: bool,
}
impl RawFdOstream {
pub fn new(fd: RawFd, should_close: bool) -> Self {
let file = if fd >= 0 {
unsafe { Some(File::from_raw_fd(fd)) }
} else {
None
};
Self {
file,
should_close,
buffer: Vec::new(),
position: 0,
error_flag: false,
color_enabled: false,
current_fg: Color::Default,
current_bg: Color::Default,
current_bold: false,
current_underline: false,
}
}
pub fn from_file(file: File) -> Self {
Self {
file: Some(file),
should_close: true,
buffer: Vec::new(),
position: 0,
error_flag: false,
color_enabled: false,
current_fg: Color::Default,
current_bg: Color::Default,
current_bold: false,
current_underline: false,
}
}
pub fn stdout() -> Self {
Self::new(1, false)
}
pub fn stderr() -> Self {
Self::new(2, false)
}
fn flush_buffer_to_file(&mut self) -> io::Result<()> {
if self.buffer.is_empty() {
return Ok(());
}
if let Some(ref mut f) = self.file {
match f.write_all(&self.buffer) {
Ok(()) => {
self.position += self.buffer.len();
self.buffer.clear();
Ok(())
}
Err(e) => {
self.error_flag = true;
Err(e)
}
}
} else {
self.error_flag = true;
Err(io::Error::new(
io::ErrorKind::NotConnected,
"invalid file descriptor",
))
}
}
pub fn write_bytes(&mut self, data: &[u8]) {
if data.is_empty() {
return;
}
self.buffer.extend_from_slice(data);
if self.buffer.len() >= 4096 {
let _ = self.flush_buffer_to_file();
}
}
pub fn write_str(&mut self, s: &str) {
self.write_bytes(s.as_bytes());
}
pub fn write_char(&mut self, c: char) {
let mut buf = [0u8; 4];
let s = c.encode_utf8(&mut buf);
self.write_str(s);
}
pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
let mut temp = Vec::new();
temp.write_fmt(args).map_err(|_| fmt::Error)?;
self.write_bytes(&temp);
Ok(())
}
pub fn flush(&mut self) {
let _ = self.flush_buffer_to_file();
}
pub fn tell(&self) -> usize {
if let Some(ref f) = self.file {
f.metadata()
.map(|m| m.len() as usize)
.unwrap_or(self.position)
} else {
self.position
}
}
pub fn seek(&mut self, pos: u64) -> io::Result<u64> {
self.flush();
if let Some(ref mut f) = self.file {
f.seek(io::SeekFrom::Start(pos))
} else {
self.error_flag = true;
Err(io::Error::new(
io::ErrorKind::NotConnected,
"invalid file descriptor",
))
}
}
pub fn has_error(&self) -> bool {
self.error_flag
}
pub fn clear_error(&mut self) {
self.error_flag = false;
}
pub fn enable_colors(&mut self, enable: bool) {
self.color_enabled = enable;
}
pub fn has_colors(&self) -> bool {
self.color_enabled
}
pub fn is_displayed(&self) -> bool {
false
}
pub fn change_color(&mut self, color: Color, bold: bool, bg: bool) {
if !self.color_enabled {
return;
}
if bg {
self.current_bg = color;
} else {
self.current_fg = color;
}
if bold {
self.current_bold = bold;
}
let base = if bg { 40 } else { 30 };
let color_code: u8 = match color {
Color::Black => 0,
Color::Red => 1,
Color::Green => 2,
Color::Yellow => 3,
Color::Blue => 4,
Color::Magenta => 5,
Color::Cyan => 6,
Color::White => 7,
Color::Default => 9,
Color::SavedColor => return,
};
if bold {
let seq = format!("\x1b[1;{}m", base + color_code);
self.write_str(&seq);
} else {
let seq = format!("\x1b[{}m", base + color_code);
self.write_str(&seq);
}
}
pub fn reset_color(&mut self) {
if self.color_enabled {
self.current_fg = Color::Default;
self.current_bg = Color::Default;
self.current_bold = false;
self.current_underline = false;
self.write_str("\x1b[0m");
}
}
pub fn raw_fd(&self) -> Option<RawFd> {
self.file.as_ref().map(|_| {
0 })
}
}
impl Drop for RawFdOstream {
fn drop(&mut self) {
let _ = self.flush_buffer_to_file();
if !self.should_close {
if let Some(f) = self.file.take() {
let _fd = f.into_raw_fd(); }
}
}
}
pub struct RawNullOstream {
bytes_written: usize,
position: usize,
}
impl RawNullOstream {
pub fn new() -> Self {
Self {
bytes_written: 0,
position: 0,
}
}
pub fn write_bytes(&mut self, data: &[u8]) {
self.bytes_written += data.len();
self.position += data.len();
}
pub fn write_str(&mut self, s: &str) {
self.bytes_written += s.len();
self.position += s.len();
}
pub fn write_char(&mut self, _c: char) {
self.bytes_written += 1;
self.position += 1;
}
pub fn write_fmt(&mut self, _args: fmt::Arguments<'_>) -> fmt::Result {
Ok(())
}
pub fn flush(&mut self) {}
pub fn tell(&self) -> usize {
self.position
}
pub fn bytes_written(&self) -> usize {
self.bytes_written
}
pub fn reset(&mut self) {
self.position = 0;
}
}
impl Default for RawNullOstream {
fn default() -> Self {
Self::new()
}
}
pub trait RawOstreamTrait {
fn write_bytes(&mut self, data: &[u8]);
fn write_str(&mut self, s: &str);
fn flush_op(&mut self);
fn tell_op(&self) -> usize;
}
pub struct BufferOstream<'a> {
inner: &'a mut dyn RawOstreamTrait,
buffer: Vec<u8>,
buffer_size: usize,
position: usize,
}
impl RawOstreamTrait for RawOstream {
fn write_bytes(&mut self, data: &[u8]) {
self.write_bytes(data);
}
fn write_str(&mut self, s: &str) {
self.write_str(s);
}
fn flush_op(&mut self) {
self.flush();
}
fn tell_op(&self) -> usize {
self.tell()
}
}
impl RawOstreamTrait for RawFdOstream {
fn write_bytes(&mut self, data: &[u8]) {
self.write_bytes(data);
}
fn write_str(&mut self, s: &str) {
self.write_str(s);
}
fn flush_op(&mut self) {
self.flush();
}
fn tell_op(&self) -> usize {
self.tell()
}
}
impl<'a> BufferOstream<'a> {
pub fn new(inner: &'a mut dyn RawOstreamTrait, buffer_size: usize) -> Self {
Self {
inner,
buffer: Vec::with_capacity(buffer_size),
buffer_size,
position: 0,
}
}
pub fn write_bytes(&mut self, data: &[u8]) {
self.buffer.extend_from_slice(data);
self.position += data.len();
if self.buffer.len() >= self.buffer_size {
self.flush();
}
}
pub fn write_str(&mut self, s: &str) {
self.buffer.extend_from_slice(s.as_bytes());
self.position += s.len();
if self.buffer.len() >= self.buffer_size {
self.flush();
}
}
pub fn write_char(&mut self, c: char) {
let mut buf = [0u8; 4];
let s = c.encode_utf8(&mut buf);
self.write_str(s);
}
pub fn flush(&mut self) {
if !self.buffer.is_empty() {
let buf = std::mem::take(&mut self.buffer);
self.inner.write_bytes(&buf);
self.inner.flush_op();
}
}
pub fn tell(&self) -> usize {
self.position
}
pub fn inner(&self) -> &dyn RawOstreamTrait {
self.inner
}
}
impl<'a> Drop for BufferOstream<'a> {
fn drop(&mut self) {
self.flush();
}
}
pub struct RawOstreamWriter<'a> {
os: &'a mut RawOstream,
}
impl<'a> RawOstreamWriter<'a> {
pub fn new(os: &'a mut RawOstream) -> Self {
Self { os }
}
}
impl<'a> fmt::Write for RawOstreamWriter<'a> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.os.write_str(s);
Ok(())
}
}
#[macro_export]
macro_rules! raw_write {
($os:expr_2021, $($arg:tt)*) => {
{
use std::fmt::Write;
let mut w = $crate::support::raw_ostream::RawOstreamWriter::new($os);
let _ = write!(w, $($arg)*);
}
};
}
#[macro_export]
macro_rules! raw_writeln {
($os:expr_2021) => {
$os.write_char('\n')
};
($os:expr_2021, $($arg:tt)*) => {
{
$crate::raw_write!($os, $($arg)*);
$os.write_char('\n');
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write as _;
#[test]
fn test_raw_string_ostream_basic() {
let mut os = RawStringOstream::new();
os.write_str("hello");
os.write_str(" ");
os.write_str("world");
assert_eq!(os.str(), "hello world");
}
#[test]
fn test_raw_string_ostream_empty() {
let os = RawStringOstream::new();
assert_eq!(os.str(), "");
assert_eq!(os.tell(), 0);
}
#[test]
fn test_raw_string_ostream_tell() {
let mut os = RawStringOstream::new();
os.write_str("abc");
assert_eq!(os.tell(), 3);
os.write_str("def");
assert_eq!(os.tell(), 6);
}
#[test]
fn test_raw_string_ostream_reset() {
let mut os = RawStringOstream::new();
os.write_str("hello");
os.reset();
assert_eq!(os.str(), "");
os.write_str("world");
assert_eq!(os.str(), "world");
}
#[test]
fn test_raw_string_ostream_bytes() {
let mut os = RawStringOstream::new();
os.write_bytes(&[0x48, 0x65, 0x6c, 0x6c, 0x6f]);
assert_eq!(os.str(), "Hello");
}
#[test]
fn test_write_unsigned() {
let mut os = RawStringOstream::new();
os.write_unsigned(42);
assert_eq!(os.str(), "42");
}
#[test]
fn test_write_signed() {
let mut os = RawStringOstream::new();
os.write_signed(-17);
assert_eq!(os.str(), "-17");
}
#[test]
fn test_write_hex() {
let mut os = RawStringOstream::new();
os.write_hex(255);
assert_eq!(os.str(), "ff");
}
#[test]
fn test_write_hex_upper() {
let mut os = RawOstream::new();
os.write_hex_upper(0xABCD);
assert_eq!(os.str(), "0xABCD");
}
#[test]
fn test_write_hex_lower() {
let mut os = RawOstream::new();
os.write_hex_lower(0xABCD);
assert_eq!(os.str(), "0xabcd");
}
#[test]
fn test_write_octal() {
let mut os = RawOstream::new();
os.write_octal(8);
assert_eq!(os.str(), "10");
}
#[test]
fn test_write_binary() {
let mut os = RawOstream::new();
os.write_binary(5);
assert_eq!(os.str(), "0b101");
}
#[test]
fn test_write_double() {
let mut os = RawOstream::new();
os.write_double(3.14159, 2);
assert_eq!(os.str(), "3.14");
}
#[test]
fn test_write_scientific() {
let mut os = RawOstream::new();
os.write_scientific(1.5e10);
assert!(os.str().contains('e'));
}
#[test]
fn test_write_escaped_newline() {
let mut os = RawStringOstream::new();
os.write_escaped("a\nb");
assert_eq!(os.str(), "a\\nb");
}
#[test]
fn test_write_escaped_tab() {
let mut os = RawStringOstream::new();
os.write_escaped("a\tb");
assert_eq!(os.str(), "a\\tb");
}
#[test]
fn test_write_escaped_quote() {
let mut os = RawStringOstream::new();
os.write_escaped("a\"b");
assert_eq!(os.str(), "a\\\"b");
}
#[test]
fn test_write_escaped_backslash() {
let mut os = RawStringOstream::new();
os.write_escaped("a\\b");
assert_eq!(os.str(), "a\\\\b");
}
#[test]
fn test_write_escaped_null() {
let mut os = RawStringOstream::new();
os.write_escaped("\0");
assert_eq!(os.str(), "\\0");
}
#[test]
fn test_write_escaped_nonascii() {
let mut os = RawStringOstream::new();
os.write_escaped("\x01");
assert_eq!(os.str(), "\\x01");
}
#[test]
fn test_indent_unindent() {
let mut os = RawOstream::new();
assert_eq!(os.get_indent_level(), 0);
os.indent(4);
assert_eq!(os.get_indent_level(), 4);
os.indent(2);
assert_eq!(os.get_indent_level(), 6);
os.unindent(3);
assert_eq!(os.get_indent_level(), 3);
os.unindent(10);
assert_eq!(os.get_indent_level(), 0);
}
#[test]
fn test_write_indent() {
let mut os = RawOstream::new();
os.indent(4);
os.write_indent();
assert_eq!(os.str(), " ");
os.indent(2);
os.write_indent();
assert_eq!(os.str(), " ");
}
#[test]
fn test_set_indent_level() {
let mut os = RawOstream::new();
os.set_indent_level(8);
assert_eq!(os.get_indent_level(), 8);
os.write_indent();
assert_eq!(os.str(), " ");
}
#[test]
fn test_seek_within_buffer() {
let mut os = RawOstream::new();
os.write_str("abcdef");
assert_eq!(os.tell(), 6);
os.seek(3);
assert_eq!(os.tell(), 3);
os.write_str("XYZ");
assert_eq!(os.str(), "abcXYZ");
}
#[test]
fn test_seek_past_end() {
let mut os = RawOstream::new();
os.write_str("ab");
os.seek(100);
assert_eq!(os.tell(), 2);
}
#[test]
fn test_size() {
let mut os = RawOstream::new();
os.write_str("hello");
assert_eq!(os.size(), 5);
os.write_str("world");
assert_eq!(os.size(), 10);
}
#[test]
fn test_has_error_default() {
let os = RawOstream::new();
assert!(!os.has_error());
}
#[test]
fn test_set_and_clear_error() {
let mut os = RawOstream::new();
assert!(!os.has_error());
os.set_error();
assert!(os.has_error());
os.clear_error();
assert!(!os.has_error());
}
#[test]
fn test_has_colors_default() {
let os = RawOstream::new();
assert!(!os.has_colors());
}
#[test]
fn test_enable_colors() {
let mut os = RawOstream::new();
os.enable_colors(true);
assert!(os.has_colors());
os.enable_colors(false);
assert!(!os.has_colors());
}
#[test]
fn test_change_color_no_escape_when_disabled() {
let mut os = RawOstream::new();
os.change_color(Color::Red, false, false);
assert_eq!(os.str(), "");
}
#[test]
fn test_change_color_with_escape_when_enabled() {
let mut os = RawOstream::new();
os.enable_colors(true);
os.change_color(Color::Red, false, false);
assert!(os.str().contains("\x1b[31m"));
}
#[test]
fn test_reset_color() {
let mut os = RawOstream::new();
os.enable_colors(true);
os.reset_color();
assert_eq!(os.str(), "\x1b[0m");
}
#[test]
fn test_save_restore_colors() {
let mut os = RawOstream::new();
os.enable_colors(true);
os.change_color(Color::Green, true, false);
os.save_color_state();
os.change_color(Color::Red, false, false);
os.restore_color_state();
assert!(os.str().contains("\x1b[1;32m"));
}
#[test]
fn test_null_ostream_discards() {
let mut os = RawNullOstream::new();
os.write_str("hello world");
assert_eq!(os.tell(), 11);
assert_eq!(os.bytes_written(), 11);
}
#[test]
fn test_null_ostream_multiple_writes() {
let mut os = RawNullOstream::new();
os.write_str("abc");
os.write_str("def");
assert_eq!(os.bytes_written(), 6);
}
#[test]
fn test_fd_ostream_write_and_flush() {
use std::io::Seek;
let path = format!("/tmp/raw_ostream_test_{}", std::process::id());
let mut file = File::create(&path).unwrap();
file.write_all(b"initial").unwrap();
file.seek(io::SeekFrom::Start(0)).unwrap();
let fd = file.into_raw_fd();
let mut os = RawFdOstream::new(fd, true);
os.write_str("hello");
os.flush();
assert!(!os.has_error());
let _ = std::fs::remove_file(&path);
}
#[test]
fn test_fd_ostream_has_error() {
let mut os = RawFdOstream::new(-1, false);
assert!(!os.has_error());
os.write_str("test");
os.flush();
assert!(os.has_error());
}
#[test]
fn test_buffer_ostream_flushes_when_full() {
let mut inner = RawOstream::new();
{
let mut buf_os = BufferOstream::new(&mut inner, 5);
buf_os.write_str("hello"); }
assert_eq!(inner.str(), "hello");
}
#[test]
fn test_buffer_ostream_flushes_on_drop() {
let mut inner = RawOstream::new();
{
let mut buf_os = BufferOstream::new(&mut inner, 100);
buf_os.write_str("world");
}
assert_eq!(inner.str(), "world");
}
#[test]
fn test_buffer_ostream_tell() {
let mut inner = RawOstream::new();
{
let mut buf_os = BufferOstream::new(&mut inner, 100);
buf_os.write_str("abc");
assert_eq!(buf_os.tell(), 3);
}
}
#[test]
fn test_raw_write_macro() {
let mut os = RawOstream::new();
raw_write!(&mut os, "{} + {} = {}", 2, 3, 5);
assert_eq!(os.str(), "2 + 3 = 5");
}
#[test]
fn test_raw_writeln_macro() {
let mut os = RawOstream::new();
raw_writeln!(&mut os, "line 1");
raw_writeln!(&mut os, "line 2");
assert_eq!(os.str(), "line 1\nline 2\n");
}
#[test]
fn test_raw_writeln_empty() {
let mut os = RawOstream::new();
raw_writeln!(&mut os);
assert_eq!(os.str(), "\n");
}
#[test]
fn test_string_ostream_has_error() {
let mut os = RawStringOstream::new();
assert!(!os.has_error());
}
#[test]
fn test_string_ostream_clear_error() {
let mut os = RawStringOstream::new();
os.clear_error();
assert!(!os.has_error());
}
#[test]
fn test_string_ostream_size() {
let mut os = RawStringOstream::new();
os.write_str("test");
assert_eq!(os.size(), 4);
}
#[test]
fn test_string_ostream_seek() {
let mut os = RawStringOstream::new();
os.write_str("abcdef");
os.seek(2);
assert_eq!(os.tell(), 2);
}
#[test]
fn test_saved_colors_default() {
let sc = SavedColors::default();
assert_eq!(sc.fg, Color::Default);
assert_eq!(sc.bg, Color::Default);
assert!(!sc.bold);
assert!(!sc.underline);
}
}