Files
hexyl/src/lib.rs
T

756 lines
26 KiB
Rust
Raw Normal View History

2020-04-29 08:22:03 +02:00
pub(crate) mod input;
2019-07-14 17:23:31 +01:00
pub mod squeezer;
2020-04-29 08:22:03 +02:00
pub use input::*;
use std::io::{self, BufReader, Read, Write};
use ansi_term::Color;
use ansi_term::Color::Fixed;
2019-07-17 15:13:20 +01:00
use crate::squeezer::{SqueezeAction, Squeezer};
const COLOR_NULL: Color = Fixed(242); // grey
const COLOR_OFFSET: Color = Fixed(242); // grey
const COLOR_ASCII_PRINTABLE: Color = Color::Cyan;
const COLOR_ASCII_WHITESPACE: Color = Color::Green;
const COLOR_ASCII_OTHER: Color = Color::Purple;
const COLOR_NONASCII: Color = Color::Yellow;
pub enum ByteCategory {
Null,
AsciiPrintable,
AsciiWhitespace,
AsciiOther,
NonAscii,
}
#[derive(Copy, Clone)]
struct Byte(u8);
impl Byte {
fn category(self) -> ByteCategory {
if self.0 == 0x00 {
ByteCategory::Null
2020-06-07 15:26:07 +02:00
} else if self.0.is_ascii_graphic() {
ByteCategory::AsciiPrintable
} else if self.0.is_ascii_whitespace() {
ByteCategory::AsciiWhitespace
} else if self.0.is_ascii() {
ByteCategory::AsciiOther
} else {
ByteCategory::NonAscii
}
}
fn color(self) -> &'static Color {
use crate::ByteCategory::*;
match self.category() {
Null => &COLOR_NULL,
AsciiPrintable => &COLOR_ASCII_PRINTABLE,
AsciiWhitespace => &COLOR_ASCII_WHITESPACE,
AsciiOther => &COLOR_ASCII_OTHER,
NonAscii => &COLOR_NONASCII,
}
}
fn as_char(self) -> char {
use crate::ByteCategory::*;
match self.category() {
Null => '0',
AsciiPrintable => self.0 as char,
AsciiWhitespace if self.0 == 0x20 => ' ',
AsciiWhitespace => '_',
AsciiOther => '•',
NonAscii => '×',
}
}
}
struct BorderElements {
left_corner: char,
horizontal_line: char,
column_separator: char,
right_corner: char,
}
2022-09-17 09:46:15 -04:00
#[derive(Clone, Copy)]
pub enum BorderStyle {
Unicode,
Ascii,
None,
}
impl BorderStyle {
fn header_elems(&self) -> Option<BorderElements> {
match self {
BorderStyle::Unicode => Some(BorderElements {
left_corner: '┌',
horizontal_line: '─',
column_separator: '┬',
right_corner: '┐',
}),
BorderStyle::Ascii => Some(BorderElements {
left_corner: '+',
horizontal_line: '-',
column_separator: '+',
right_corner: '+',
}),
BorderStyle::None => None,
}
}
fn footer_elems(&self) -> Option<BorderElements> {
match self {
BorderStyle::Unicode => Some(BorderElements {
left_corner: '└',
horizontal_line: '─',
column_separator: '┴',
right_corner: '┘',
}),
BorderStyle::Ascii => Some(BorderElements {
left_corner: '+',
horizontal_line: '-',
column_separator: '+',
right_corner: '+',
}),
BorderStyle::None => None,
}
}
fn outer_sep(&self) -> char {
match self {
BorderStyle::Unicode => '│',
BorderStyle::Ascii => '|',
BorderStyle::None => ' ',
}
}
fn inner_sep(&self) -> char {
match self {
BorderStyle::Unicode => '┊',
BorderStyle::Ascii => '|',
BorderStyle::None => ' ',
}
}
}
2022-09-17 09:46:15 -04:00
pub struct PrinterBuilder<'a, Writer: Write> {
writer: &'a mut Writer,
show_color: bool,
show_char_panel: bool,
show_position_panel: bool,
border_style: BorderStyle,
use_squeeze: bool,
panels: u16,
}
impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
pub fn new(writer: &'a mut Writer) -> Self {
PrinterBuilder {
writer,
show_color: true,
show_char_panel: true,
show_position_panel: true,
border_style: BorderStyle::Unicode,
use_squeeze: true,
panels: 2,
}
}
pub fn show_color(mut self, show_color: bool) -> Self {
self.show_color = show_color;
self
}
pub fn show_char_panel(mut self, show_char_panel: bool) -> Self {
self.show_char_panel = show_char_panel;
self
}
pub fn show_position_panel(mut self, show_position_panel: bool) -> Self {
self.show_position_panel = show_position_panel;
self
}
pub fn with_border_style(mut self, border_style: BorderStyle) -> Self {
self.border_style = border_style;
self
}
2022-10-03 23:07:52 -04:00
pub fn enable_squeezing(mut self, enable: bool) -> Self {
self.use_squeeze = enable;
2022-09-17 09:46:15 -04:00
self
}
2022-10-04 08:49:19 -04:00
pub fn num_panels(mut self, num: u16) -> Self {
self.panels = num;
2022-09-17 09:46:15 -04:00
self
}
pub fn build(self) -> Printer<'a, Writer> {
Printer::new(
self.writer,
self.show_color,
self.show_char_panel,
self.show_position_panel,
self.border_style,
self.use_squeeze,
self.panels,
)
}
}
2019-07-14 18:18:13 +01:00
pub struct Printer<'a, Writer: Write> {
idx: u64,
/// the buffer containing all the bytes in a line for character printing
line_buf: Vec<u8>,
2019-07-14 18:18:13 +01:00
writer: &'a mut Writer,
2022-05-22 14:14:07 -05:00
show_char_panel: bool,
show_position_panel: bool,
border_style: BorderStyle,
2022-05-22 14:14:07 -05:00
byte_hex_panel: Vec<String>,
byte_char_panel: Vec<String>,
byte_hex_panel_g: Vec<String>,
byte_char_panel_g: Vec<String>,
squeezer: Squeezer,
display_offset: u64,
/// The number of panels to draw.
2022-09-05 09:51:34 -04:00
panels: u16,
}
2019-07-14 18:18:13 +01:00
impl<'a, Writer: Write> Printer<'a, Writer> {
2022-09-17 09:46:15 -04:00
fn new(
2019-07-14 18:18:13 +01:00
writer: &'a mut Writer,
show_color: bool,
2022-05-22 14:14:07 -05:00
show_char_panel: bool,
show_position_panel: bool,
border_style: BorderStyle,
use_squeeze: bool,
2022-09-05 09:51:34 -04:00
panels: u16,
2019-07-14 18:18:13 +01:00
) -> Printer<'a, Writer> {
Printer {
idx: 0,
line_buf: vec![],
2019-07-14 18:18:13 +01:00
writer,
2022-05-22 14:14:07 -05:00
show_char_panel,
show_position_panel,
border_style,
byte_hex_panel: (0u8..=u8::MAX)
.map(|i| {
let byte_hex = format!(" {:02x}", i);
if show_color {
Byte(i).color().paint(byte_hex).to_string()
} else {
byte_hex
}
})
.collect(),
byte_char_panel: (0u8..=u8::MAX)
.map(|i| {
let byte_char = format!("{}", Byte(i).as_char());
if show_color {
Byte(i).color().paint(byte_char).to_string()
} else {
byte_char
}
})
.collect(),
byte_hex_panel_g: (0u8..=u8::MAX)
.map(|i| {
let byte_hex = format!("{:02x}", i);
let style = COLOR_OFFSET.normal();
if show_color {
style.paint(byte_hex).to_string()
} else {
byte_hex
}
})
.collect(),
byte_char_panel_g: (0u8..=u8::MAX)
.map(|i| {
let byte_char = format!("{}", Byte(i).as_char());
let style = COLOR_OFFSET.normal();
if show_color {
style.paint(byte_char).to_string()
} else {
byte_char
}
})
.collect(),
2022-09-05 09:51:34 -04:00
squeezer: Squeezer::new(use_squeeze, 8 * panels as u64),
2019-10-07 19:20:02 +01:00
display_offset: 0,
2022-09-05 09:51:34 -04:00
panels,
}
}
pub fn display_offset(&mut self, display_offset: u64) -> &mut Self {
2019-10-07 19:20:02 +01:00
self.display_offset = display_offset;
self
}
2022-10-07 19:54:07 -04:00
fn write_border(&mut self, border_elements: BorderElements) -> io::Result<()> {
2022-04-22 15:33:26 -05:00
let h = border_elements.horizontal_line;
let c = border_elements.column_separator;
let l = border_elements.left_corner;
let r = border_elements.right_corner;
let h8 = h.to_string().repeat(8);
let h25 = h.to_string().repeat(25);
2022-05-22 14:14:07 -05:00
if self.show_position_panel {
2022-04-22 15:33:26 -05:00
write!(self.writer, "{l}{h8}{c}", l = l, c = c, h8 = h8).ok();
} else {
write!(self.writer, "{}", l).ok();
}
2022-09-05 09:51:34 -04:00
for _ in 0..self.panels - 1 {
write!(self.writer, "{h25}{c}", h25 = h25, c = c).ok();
}
if self.show_char_panel {
write!(self.writer, "{h25}{c}", h25 = h25, c = c).ok();
} else {
write!(self.writer, "{h25}", h25 = h25).ok();
}
2022-04-22 15:33:26 -05:00
2022-05-22 14:14:07 -05:00
if self.show_char_panel {
2022-09-05 09:51:34 -04:00
for _ in 0..self.panels - 1 {
write!(self.writer, "{h8}{c}", h8 = h8, c = c).ok();
}
writeln!(self.writer, "{h8}{r}", h8 = h8, r = r).ok();
2022-04-22 15:33:26 -05:00
} else {
writeln!(self.writer, "{r}", r = r).ok();
}
2022-10-07 19:54:07 -04:00
Ok(())
}
2022-10-07 19:54:07 -04:00
pub fn print_header(&mut self) -> io::Result<()> {
2022-04-22 15:48:54 -05:00
if let Some(e) = self.border_style.header_elems() {
2022-10-07 19:54:07 -04:00
self.write_border(e)?
2022-04-22 15:48:54 -05:00
}
2022-10-07 19:54:07 -04:00
Ok(())
2022-04-22 15:33:26 -05:00
}
2022-10-07 19:54:07 -04:00
pub fn print_footer(&mut self) -> io::Result<()> {
2022-04-22 15:48:54 -05:00
if let Some(e) = self.border_style.footer_elems() {
2022-10-07 19:54:07 -04:00
self.write_border(e)?
2022-04-22 15:48:54 -05:00
}
2022-10-07 19:54:07 -04:00
Ok(())
}
fn print_position_panel(&mut self) -> io::Result<()> {
match self.squeezer.action() {
SqueezeAction::Print => {
2022-10-07 19:54:07 -04:00
self.writer
.write_all(self.byte_char_panel_g[b'*' as usize].as_bytes())?;
self.writer.write_all(b" ")?;
self.writer.write_all(
self.border_style
.outer_sep()
.encode_utf8(&mut [0; 4])
.as_bytes(),
2022-07-12 11:49:50 -04:00
)?;
Ok(())
}
SqueezeAction::Ignore => {
let byte_index: [u8; 8] = (self.idx + self.display_offset).to_be_bytes();
let mut i = 0;
while byte_index[i] == 0x0 && i < 4 {
i += 1;
}
for &byte in byte_index.iter().skip(i) {
2022-10-07 19:54:07 -04:00
self.writer
.write_all(self.byte_hex_panel_g[byte as usize].as_bytes())?;
}
2022-10-07 19:54:07 -04:00
self.writer.write_all(
self.border_style
.outer_sep()
.encode_utf8(&mut [0; 4])
.as_bytes(),
)?;
Ok(())
}
_ => unreachable!(),
}
}
pub fn print_char_panel(&mut self) -> io::Result<()> {
match self.squeezer.action() {
SqueezeAction::Print => {
2022-09-05 09:51:34 -04:00
for i in 0..self.panels {
2022-10-07 19:54:07 -04:00
self.writer.write_all(b" ")?;
if i == self.panels - 1 {
2022-10-07 19:54:07 -04:00
self.writer.write_all(
self.border_style
.outer_sep()
.encode_utf8(&mut [0; 4])
.as_bytes(),
)?;
self.writer.write_all(b"\n")?;
} else {
2022-10-07 19:54:07 -04:00
self.writer.write_all(
self.border_style
.inner_sep()
.encode_utf8(&mut [0; 4])
.as_bytes(),
)?;
}
}
}
SqueezeAction::Ignore => {
let mut idx = 0;
for &b in self.line_buf.iter() {
2022-10-07 19:54:07 -04:00
self.writer
.write_all(self.byte_char_panel[b as usize].as_bytes())?;
if idx == 8 * self.panels - 1 {
2022-10-07 19:54:07 -04:00
self.writer.write_all(
self.border_style
.outer_sep()
.encode_utf8(&mut [0; 4])
.as_bytes(),
)?;
self.writer.write_all(b"\n")?;
} else if idx % 8 == 7 {
2022-10-07 19:54:07 -04:00
self.writer.write_all(
self.border_style
.inner_sep()
.encode_utf8(&mut [0; 4])
.as_bytes(),
)?;
}
idx += 1;
}
// there is space left over at the end
if idx < 8 * self.panels - 1 {
for i in idx..8 * self.panels {
2022-10-07 19:54:07 -04:00
self.writer.write_all(b" ")?;
if i == 8 * self.panels - 1 {
2022-10-07 19:54:07 -04:00
self.writer.write_all(
self.border_style
.outer_sep()
.encode_utf8(&mut [0; 4])
.as_bytes(),
)?;
self.writer.write_all(b"\n")?;
} else if i % 8 == 7 {
2022-10-07 19:54:07 -04:00
self.writer.write_all(
self.border_style
.inner_sep()
.encode_utf8(&mut [0; 4])
.as_bytes(),
)?;
}
}
}
}
_ => unreachable!(),
}
Ok(())
}
pub fn print_byte(&mut self, b: u8) -> io::Result<()> {
match self.squeezer.action() {
SqueezeAction::Print => {
2022-10-07 19:54:07 -04:00
self.writer.write_all(b" ")?;
}
SqueezeAction::Ignore => {
// print the byte
2022-10-07 19:54:07 -04:00
self.writer
.write_all(self.byte_hex_panel[b as usize].as_bytes())?;
}
_ => unreachable!(),
}
// byte is last in panel
if self.idx % 8 == 7 {
// byte is last in last panel
if self.idx % (8 * self.panels as u64) == 8 * self.panels as u64 - 1 {
2022-10-07 19:54:07 -04:00
self.writer.write_all(b" ")?;
self.writer.write_all(
self.border_style
.outer_sep()
.encode_utf8(&mut [0; 4])
.as_bytes(),
)?;
} else {
2022-10-07 19:54:07 -04:00
self.writer.write_all(b" ")?;
self.writer.write_all(
self.border_style
.inner_sep()
.encode_utf8(&mut [0; 4])
.as_bytes(),
)?;
}
}
Ok(())
}
pub fn print_line(&mut self, b: u8) -> io::Result<()> {
2022-10-08 08:39:18 -04:00
let mut is_flushed = false;
let old_active = self.squeezer.active();
self.squeezer.process(b, self.idx);
2019-07-14 21:31:51 +01:00
2022-10-07 19:54:07 -04:00
// the header should be the first thing printed
if self.idx == 0 {
self.print_header()?;
}
// flush the rest of the line buffer before continuing to write
2022-10-08 08:39:18 -04:00
if old_active && !self.squeezer.active() {
self.writer.write_all(
self.border_style
.outer_sep()
.encode_utf8(&mut [0; 4])
.as_bytes(),
)?;
let old_idx = self.idx;
self.idx -= self.line_buf.len() as u64;
2022-10-08 09:03:36 -04:00
if self.show_position_panel {
self.print_position_panel()?;
}
for b in self.line_buf.clone() {
self.print_byte(b)?;
self.idx += 1;
}
2022-10-08 08:39:18 -04:00
self.idx = old_idx;
is_flushed = true;
}
self.line_buf.push(b);
if !self.squeezer.active() || self.squeezer.action() == SqueezeAction::Print {
// print the left border and position panel if there's a new line
2022-10-08 08:39:18 -04:00
if self.idx % (8 * self.panels as u64) == 0 && !is_flushed {
2022-10-07 19:54:07 -04:00
self.writer.write_all(
self.border_style
.outer_sep()
.encode_utf8(&mut [0; 4])
.as_bytes(),
)?;
if self.show_position_panel {
self.print_position_panel()?;
}
}
2019-07-14 21:31:51 +01:00
self.print_byte(b)?;
if self.idx % (8 * self.panels as u64) == 8 * self.panels as u64 - 1 {
if self.show_char_panel {
self.print_char_panel()?;
} else {
2022-10-07 19:54:07 -04:00
self.writer.write_all(b"\n")?;
2019-07-14 21:31:51 +01:00
}
2022-10-08 08:39:18 -04:00
self.line_buf.clear();
2019-07-14 21:31:51 +01:00
}
}
self.idx += 1;
if self.idx % (8 * self.panels as u64) == 0 {
2022-10-08 08:39:18 -04:00
self.line_buf.clear();
self.squeezer.advance();
}
2020-10-30 19:32:45 +01:00
Ok(())
}
/// Loop through the given `Reader`, printing until the `Reader` buffer
/// is exhausted.
pub fn print_all<Reader: Read>(&mut self, reader: Reader) -> io::Result<()> {
let mut is_empty = true;
let buf = BufReader::new(reader);
for b in buf.bytes() {
if is_empty {
is_empty = false;
}
if self.print_line(b?).is_err() {
break;
}
}
// special ending
if is_empty {
2022-10-07 19:54:07 -04:00
self.print_header()?;
2022-05-22 14:14:07 -05:00
if self.show_position_panel {
write!(self.writer, "{0:9}", "│")?;
2022-04-22 15:33:26 -05:00
}
write!(
2020-11-01 21:04:39 +01:00
self.writer,
2022-04-22 15:33:26 -05:00
"{0:2}{1:24}{0}{0:>26}",
"│", "No content to print"
2020-11-01 21:04:39 +01:00
)
.ok();
2022-05-22 14:14:07 -05:00
if self.show_char_panel {
write!(self.writer, "{0:>9}{0:>9}", "│")?;
}
writeln!(self.writer)?;
} else if self.squeezer.active() {
// input was squeezed at the end
write!(self.writer, "{}", self.border_style.outer_sep())?;
2022-10-08 09:03:36 -04:00
if self.show_position_panel {
self.print_position_panel()?;
}
2022-10-07 20:20:01 -04:00
// print empty bytes
for i in 0..8 * self.panels {
write!(self.writer, " ")?;
if i % 8 == 7 {
if i % (8 * self.panels) == 8 * self.panels - 1 {
write!(self.writer, " {}", self.border_style.outer_sep())?;
} else {
write!(self.writer, " {}", self.border_style.inner_sep())?;
}
}
}
2022-10-08 09:03:36 -04:00
if self.show_char_panel {
// print empty char panel
for i in 0..self.panels {
write!(self.writer, " ")?;
if i == self.panels - 1 {
writeln!(self.writer, "{}", self.border_style.outer_sep())?;
} else {
write!(self.writer, "{}", self.border_style.inner_sep())?;
}
}
}
} else {
// finish unfinished last line
if self.idx % (8 * self.panels as u64) != 0 {
for i in self.idx % (8 * self.panels as u64)..8 * self.panels as u64 {
// print empty byte space
write!(self.writer, " ")?;
if i == (8 * self.panels - 1) as u64 {
write!(self.writer, " {}", self.border_style.outer_sep())?;
} else if i % 8 == 7 {
write!(self.writer, " {}", self.border_style.inner_sep())?;
}
}
2022-10-07 21:30:15 -04:00
if self.show_char_panel {
self.print_char_panel()?;
} else {
writeln!(self.writer)?;
2022-10-07 21:30:15 -04:00
}
2022-04-22 15:33:26 -05:00
}
2019-07-14 21:31:51 +01:00
}
2022-10-07 19:54:07 -04:00
self.print_footer()?;
2019-07-14 21:31:51 +01:00
self.writer.flush()?;
2019-07-14 21:31:51 +01:00
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::io;
use std::str;
use super::*;
2022-04-22 15:59:10 -05:00
fn assert_print_all_output<Reader: Read>(input: Reader, expected_string: String) {
2019-07-14 21:31:51 +01:00
let mut output = vec![];
2022-07-12 11:49:50 -04:00
let mut printer = Printer::new(
&mut output,
false,
true,
true,
BorderStyle::Unicode,
true,
2,
);
2019-07-14 21:31:51 +01:00
2020-03-09 20:23:46 +01:00
printer.print_all(input).unwrap();
2019-07-14 21:31:51 +01:00
let actual_string: &str = str::from_utf8(&output).unwrap();
2022-04-22 15:33:26 -05:00
assert_eq!(actual_string, expected_string,)
2019-07-14 21:31:51 +01:00
}
#[test]
fn empty_file_passes() {
let input = io::empty();
let expected_string = "\
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
2020-10-30 19:32:45 +01:00
│ │ No content to print │ │ │ │
2019-07-14 21:31:51 +01:00
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
2019-10-07 19:20:02 +01:00
"
.to_owned();
2019-07-14 21:31:51 +01:00
assert_print_all_output(input, expected_string);
}
#[test]
fn short_input_passes() {
let input = io::Cursor::new(b"spam");
let expected_string = "\
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│00000000│ 73 70 61 6d ┊ │spam ┊ │
2019-07-14 21:31:51 +01:00
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
2019-10-07 19:20:02 +01:00
"
.to_owned();
2019-07-14 21:31:51 +01:00
assert_print_all_output(input, expected_string);
}
2019-10-07 19:20:02 +01:00
#[test]
fn display_offset() {
let input = io::Cursor::new(b"spamspamspamspamspam");
let expected_string = "\
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
2019-10-07 19:20:02 +01:00
│deadbeef│ 73 70 61 6d 73 70 61 6d ┊ 73 70 61 6d 73 70 61 6d │spamspam┊spamspam│
│deadbeff│ 73 70 61 6d ┊ │spam ┊ │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
"
.to_owned();
2019-10-07 19:20:02 +01:00
let mut output = vec![];
2022-07-12 11:49:50 -04:00
let mut printer: Printer<Vec<u8>> = Printer::new(
&mut output,
false,
true,
true,
BorderStyle::Unicode,
true,
2,
);
2019-10-07 19:20:02 +01:00
printer.display_offset(0xdeadbeef);
2020-03-09 20:23:46 +01:00
printer.print_all(input).unwrap();
2019-10-07 19:20:02 +01:00
let actual_string: &str = str::from_utf8(&output).unwrap();
assert_eq!(actual_string, expected_string)
}
2022-07-17 13:37:25 -04:00
#[test]
2022-09-05 09:51:34 -04:00
fn multiple_panels() {
2022-07-17 13:37:25 -04:00
let input = io::Cursor::new(b"supercalifragilisticexpialidocioussupercalifragilisticexpialidocioussupercalifragilisticexpialidocious");
let expected_string = "\
┌────────┬─────────────────────────┬─────────────────────────┬─────────────────────────┬─────────────────────────┬────────┬────────┬────────┬────────┐
│00000000│ 73 75 70 65 72 63 61 6c ┊ 69 66 72 61 67 69 6c 69 ┊ 73 74 69 63 65 78 70 69 ┊ 61 6c 69 64 6f 63 69 6f │supercal┊ifragili┊sticexpi┊alidocio│
│00000020│ 75 73 73 75 70 65 72 63 ┊ 61 6c 69 66 72 61 67 69 ┊ 6c 69 73 74 69 63 65 78 ┊ 70 69 61 6c 69 64 6f 63 │ussuperc┊alifragi┊listicex┊pialidoc│
│00000040│ 69 6f 75 73 73 75 70 65 ┊ 72 63 61 6c 69 66 72 61 ┊ 67 69 6c 69 73 74 69 63 ┊ 65 78 70 69 61 6c 69 64 │ioussupe┊rcalifra┊gilistic┊expialid│
│00000060│ 6f 63 69 6f 75 73 ┊ ┊ ┊ │ocious ┊ ┊ ┊ │
└────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┴────────┴────────┴────────┴────────┘
"
.to_owned();
let mut output = vec![];
let mut printer: Printer<Vec<u8>> = Printer::new(
&mut output,
false,
true,
true,
BorderStyle::Unicode,
true,
4,
);
printer.print_all(input).unwrap();
let actual_string: &str = str::from_utf8(&output).unwrap();
assert_eq!(actual_string, expected_string)
}
}