Merge pull request #178 from sharifhsn/base

[feature] Implement alternate bases
This commit is contained in:
David Peter authored and GitHub committed 2022-12-05 09:03:03 +01:00
commit 9770b89cf4
3 files changed
+150 -35

No files matched your search

+81 -28
View File
@@ -19,7 +19,7 @@ use thiserror::Error as ThisError;
use terminal_size::terminal_size; use terminal_size::terminal_size;
use hexyl::{BorderStyle, Input, PrinterBuilder}; use hexyl::{Base, BorderStyle, Input, PrinterBuilder};
const DEFAULT_BLOCK_SIZE: i64 = 512; const DEFAULT_BLOCK_SIZE: i64 = 512;
@@ -177,6 +177,18 @@ fn run() -> Result<(), AnyhowError> {
The possible options are 1, 2, 4, 8. The default is 1", The possible options are 1, 2, 4, 8. The default is 1",
), ),
) )
.arg(
Arg::new("base")
.short('b')
.long("base")
.num_args(1)
.value_name("B")
.help(
"Sets the base used for the bytes. The possible options are \
binary, octal, decimal, and hexadecimal. The default base \
is hexadecimal."
)
)
.arg( .arg(
Arg::new("terminal_width") Arg::new("terminal_width")
.long("terminal-width") .long("terminal-width")
@@ -310,9 +322,13 @@ fn run() -> Result<(), AnyhowError> {
.transpose()? .transpose()?
.unwrap_or(0); .unwrap_or(0);
let max_panels_fn = |terminal_width: u64| { let max_panels_fn = |terminal_width: u64, base_digits: u64, group_bytes: u64| {
let offset = if show_position_panel { 10 } else { 1 }; let offset = if show_position_panel { 10 } else { 1 };
let col_width = if show_char_panel { 35 } else { 26 }; let col_width = if show_char_panel {
((8 / group_bytes) * (base_digits * group_bytes + 1)) + 2 + 8
} else {
((8 / group_bytes) * (base_digits * group_bytes + 1)) + 2
};
if (terminal_width - offset) / col_width < 1 { if (terminal_width - offset) / col_width < 1 {
1 1
} else { } else {
@@ -320,32 +336,36 @@ fn run() -> Result<(), AnyhowError> {
} }
}; };
let panels = if matches.get_one::<String>("panels").map(String::as_ref) == Some("auto") { let base = if let Some(base) = matches.get_one::<String>("base")
max_panels_fn(terminal_size().ok_or_else(|| anyhow!("not a TTY"))?.0 .0 as u64) .map(|s| {
} else if let Some(panels) = matches if let Ok(base_num) = s.parse::<u8>() {
.get_one::<String>("panels") match base_num {
.map(|s| { 2 => Ok(Base::Binary),
s.parse::<NonZeroU64>().map(u64::from).context(anyhow!( 8 => Ok(Base::Octal),
"failed to parse `--panels` arg {:?} as unsigned nonzero integer", 10 => Ok(Base::Decimal),
s 16 => Ok(Base::Hexadecimal),
)) _ => Err(anyhow!("The number provided is not a valid base. Valid bases are 2, 8, 10, and 16.")),
}) }
.transpose()? } else {
{ match s.as_str() {
panels "b" | "bin" | "binary" => Ok(Base::Binary),
} else if let Some(terminal_width) = matches "o" | "oct" | "octal" => Ok(Base::Octal),
.get_one::<String>("terminal_width") "d" | "dec" | "decimal" => Ok(Base::Decimal),
.map(|s| { "x" | "hex" | "hexadecimal" => Ok(Base::Hexadecimal),
s.parse::<NonZeroU64>().map(u64::from).context(anyhow!( _ => Err(anyhow!("The base provided is not valid. Valid bases are \"b\", \"o\", \"d\", and \"x\"."))
"failed to parse `--terminal-width` arg {:?} as unsigned nonzero integer", }
s }
)) }).transpose()? {
}) base
.transpose()?
{
max_panels_fn(terminal_width)
} else { } else {
2 Base::Hexadecimal
};
let base_digits = match base {
Base::Binary => 8,
Base::Octal => 3,
Base::Decimal => 3,
Base::Hexadecimal => 2,
}; };
let group_bytes = if let Some(group_bytes) = matches let group_bytes = if let Some(group_bytes) = matches
@@ -369,6 +389,38 @@ fn run() -> Result<(), AnyhowError> {
1 1
}; };
let panels = if matches.get_one::<String>("panels").map(String::as_ref) == Some("auto") {
max_panels_fn(
terminal_size().ok_or_else(|| anyhow!("not a TTY"))?.0 .0 as u64,
base_digits,
group_bytes.into(),
)
} else if let Some(panels) = matches
.get_one::<String>("panels")
.map(|s| {
s.parse::<NonZeroU64>().map(u64::from).context(anyhow!(
"failed to parse `--panels` arg {:?} as unsigned nonzero integer",
s
))
})
.transpose()?
{
panels
} else if let Some(terminal_width) = matches
.get_one::<String>("terminal_width")
.map(|s| {
s.parse::<NonZeroU64>().map(u64::from).context(anyhow!(
"failed to parse `--terminal-width` arg {:?} as unsigned nonzero integer",
s
))
})
.transpose()?
{
max_panels_fn(terminal_width, base_digits, group_bytes.into())
} else {
2
};
let stdout = io::stdout(); let stdout = io::stdout();
let mut stdout_lock = BufWriter::new(stdout.lock()); let mut stdout_lock = BufWriter::new(stdout.lock());
@@ -380,6 +432,7 @@ fn run() -> Result<(), AnyhowError> {
.enable_squeezing(squeeze) .enable_squeezing(squeeze)
.num_panels(panels) .num_panels(panels)
.num_group_bytes(group_bytes) .num_group_bytes(group_bytes)
.with_base(base)
.build(); .build();
printer.display_offset(skip_offset + display_offset); printer.display_offset(skip_offset + display_offset);
printer.print_all(&mut reader).map_err(|e| anyhow!(e))?; printer.print_all(&mut reader).map_err(|e| anyhow!(e))?;
+51 -7
View File
@@ -6,6 +6,13 @@ use std::io::{self, BufReader, Read, Write};
use anstyle::{AnsiColor, Reset}; use anstyle::{AnsiColor, Reset};
pub enum Base {
Binary,
Octal,
Decimal,
Hexadecimal,
}
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq)]
#[repr(usize)] #[repr(usize)]
enum ByteColor { enum ByteColor {
@@ -162,6 +169,7 @@ pub struct PrinterBuilder<'a, Writer: Write> {
use_squeeze: bool, use_squeeze: bool,
panels: u64, panels: u64,
group_bytes: u8, group_bytes: u8,
base: Base,
} }
impl<'a, Writer: Write> PrinterBuilder<'a, Writer> { impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
@@ -175,6 +183,7 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
use_squeeze: true, use_squeeze: true,
panels: 2, panels: 2,
group_bytes: 1, group_bytes: 1,
base: Base::Hexadecimal,
} }
} }
@@ -213,6 +222,11 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
self self
} }
pub fn with_base(mut self, base: Base) -> Self {
self.base = base;
self
}
pub fn build(self) -> Printer<'a, Writer> { pub fn build(self) -> Printer<'a, Writer> {
Printer::new( Printer::new(
self.writer, self.writer,
@@ -223,6 +237,7 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
self.use_squeeze, self.use_squeeze,
self.panels, self.panels,
self.group_bytes, self.group_bytes,
self.base,
) )
} }
} }
@@ -250,6 +265,8 @@ pub struct Printer<'a, Writer: Write> {
squeeze_byte: usize, squeeze_byte: usize,
/// The number of octets per group. /// The number of octets per group.
group_bytes: u8, group_bytes: u8,
/// The number of digits used to write the base.
base_digits: u8,
} }
impl<'a, Writer: Write> Printer<'a, Writer> { impl<'a, Writer: Write> Printer<'a, Writer> {
@@ -262,6 +279,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
use_squeeze: bool, use_squeeze: bool,
panels: u64, panels: u64,
group_bytes: u8, group_bytes: u8,
base: Base,
) -> Printer<'a, Writer> { ) -> Printer<'a, Writer> {
Printer { Printer {
idx: 0, idx: 0,
@@ -281,11 +299,18 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
Reset.render().to_string(), Reset.render().to_string(),
], ],
border_style, border_style,
byte_hex_panel: (0u8..=u8::MAX).map(|i| format!("{:02x}", i)).collect(), byte_hex_panel: (0u8..=u8::MAX)
.map(|i| match base {
Base::Binary => format!("{i:08b}"),
Base::Octal => format!("{i:03o}"),
Base::Decimal => format!("{i:03}"),
Base::Hexadecimal => format!("{i:02x}"),
})
.collect(),
byte_char_panel: (0u8..=u8::MAX) byte_char_panel: (0u8..=u8::MAX)
.map(|i| format!("{}", Byte(i).as_char())) .map(|i| format!("{}", Byte(i).as_char()))
.collect(), .collect(),
byte_hex_panel_g: (0u8..=u8::MAX).map(|i| format!("{:02x}", i)).collect(), byte_hex_panel_g: (0u8..=u8::MAX).map(|i| format!("{i:02x}")).collect(),
byte_char_panel_g: (0u8..=u8::MAX) byte_char_panel_g: (0u8..=u8::MAX)
.map(|i| format!("{}", Byte(i).as_char())) .map(|i| format!("{}", Byte(i).as_char()))
.collect(), .collect(),
@@ -298,6 +323,12 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
panels, panels,
squeeze_byte: 0x00, squeeze_byte: 0x00,
group_bytes, group_bytes,
base_digits: match base {
Base::Binary => 8,
Base::Octal => 3,
Base::Decimal => 3,
Base::Hexadecimal => 2,
},
} }
} }
@@ -308,7 +339,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
fn panel_sz(&self) -> usize { fn panel_sz(&self) -> usize {
// add one to include the trailing space of a group // add one to include the trailing space of a group
let group_sz = 2 * self.group_bytes as usize + 1; let group_sz = self.base_digits as usize * self.group_bytes as usize + 1;
let group_per_panel = 8 / self.group_bytes as usize; let group_per_panel = 8 / self.group_bytes as usize;
// add one to include the leading space // add one to include the leading space
1 + group_sz * group_per_panel 1 + group_sz * group_per_panel
@@ -483,7 +514,9 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
self.writer.write_all(b" ")?; self.writer.write_all(b" ")?;
} }
} }
self.writer.write_all(b" ")?; for _ in 0..self.base_digits {
self.writer.write_all(b" ")?;
}
} }
Squeezer::Delete => self.writer.write_all(b" ")?, Squeezer::Delete => self.writer.write_all(b" ")?,
Squeezer::Ignore | Squeezer::Disabled => { Squeezer::Ignore | Squeezer::Disabled => {
@@ -541,13 +574,15 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
let mut buf = BufReader::new(reader); let mut buf = BufReader::new(reader);
self.print_header()?;
let leftover = loop { let leftover = loop {
// read a maximum of 8 * self.panels bytes from the reader // read a maximum of 8 * self.panels bytes from the reader
if let Ok(n) = buf.read(&mut self.line_buf) { if let Ok(n) = buf.read(&mut self.line_buf) {
if n > 0 && n < 8 * self.panels as usize { if n > 0 && n < 8 * self.panels as usize {
// if less are read, that indicates end of file after // if less are read, that indicates end of file after
is_empty = false; if is_empty {
self.print_header()?;
is_empty = false;
}
// perform second check on read // perform second check on read
if let Ok(0) = buf.read(&mut self.line_buf[n..]) { if let Ok(0) = buf.read(&mut self.line_buf[n..]) {
@@ -564,7 +599,10 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
break None; break None;
} }
} }
is_empty = false; if is_empty {
self.print_header()?;
is_empty = false;
}
// squeeze is active, check if the line is the same // squeeze is active, check if the line is the same
// skip print if still squeezed, otherwise print and deactivate squeeze // skip print if still squeezed, otherwise print and deactivate squeeze
@@ -617,6 +655,8 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
// special ending // special ending
if is_empty { if is_empty {
self.base_digits = 2;
self.print_header()?;
if self.show_position_panel { if self.show_position_panel {
write!(self.writer, "{0:9}", "│")?; write!(self.writer, "{0:9}", "│")?;
} }
@@ -678,6 +718,7 @@ mod tests {
true, true,
2, 2,
1, 1,
Base::Hexadecimal,
); );
printer.print_all(input).unwrap(); printer.print_all(input).unwrap();
@@ -731,6 +772,7 @@ mod tests {
true, true,
2, 2,
1, 1,
Base::Hexadecimal,
); );
printer.display_offset(0xdeadbeef); printer.display_offset(0xdeadbeef);
@@ -763,6 +805,7 @@ mod tests {
true, true,
4, 4,
1, 1,
Base::Hexadecimal,
); );
printer.print_all(input).unwrap(); printer.print_all(input).unwrap();
@@ -821,6 +864,7 @@ mod tests {
true, true,
3, 3,
1, 1,
Base::Hexadecimal,
); );
printer.print_all(input).unwrap(); printer.print_all(input).unwrap();
+18
View File
@@ -551,3 +551,21 @@ mod group {
); );
} }
} }
mod base {
use super::hexyl;
use super::PrettyAssert;
#[test]
fn base2() {
hexyl()
.arg("ascii")
.arg("--plain")
.arg("--base=binary")
.assert()
.success()
.pretty_stdout(
" 00110000 00110001 00110010 00110011 00110100 00110101 00110110 00110111 00111000 00111001 01100001 01100010 01100011 01100100 01100101 00001010 \n"
);
}
}