mirror of
https://github.com/sharkdp/hexyl.git
synced 2026-10-30 09:05:54 +00:00
* feat: Implement "-i/-include" option * make clippy happy Suppress clippy warning for too many arguments in Printer struct * add tests & use buffered reading for include mode to handle large files * Update CHANGELOG
This commit is contained in:
3 files changed
+191
-3
No files matched your search
+151
@@ -24,6 +24,13 @@ pub enum ByteCategory {
|
||||
NonAscii,
|
||||
}
|
||||
|
||||
pub enum IncludeMode {
|
||||
File(String), // filename
|
||||
Stdin,
|
||||
Slice,
|
||||
Off,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, ValueEnum)]
|
||||
#[non_exhaustive]
|
||||
pub enum CharacterTable {
|
||||
@@ -267,6 +274,7 @@ pub struct PrinterBuilder<'a, Writer: Write> {
|
||||
base: Base,
|
||||
endianness: Endianness,
|
||||
character_table: CharacterTable,
|
||||
include_mode: IncludeMode,
|
||||
color_scheme: ColorScheme,
|
||||
}
|
||||
|
||||
@@ -284,6 +292,7 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
|
||||
base: Base::Hexadecimal,
|
||||
endianness: Endianness::Big,
|
||||
character_table: CharacterTable::Default,
|
||||
include_mode: IncludeMode::Off,
|
||||
color_scheme: ColorScheme::Default,
|
||||
}
|
||||
}
|
||||
@@ -338,6 +347,11 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn include_mode(mut self, include: IncludeMode) -> Self {
|
||||
self.include_mode = include;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn color_scheme(mut self, color_scheme: ColorScheme) -> Self {
|
||||
self.color_scheme = color_scheme;
|
||||
self
|
||||
@@ -382,6 +396,7 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
|
||||
Base::Hexadecimal => 2,
|
||||
},
|
||||
endianness: self.endianness,
|
||||
include_mode: self.include_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -412,6 +427,8 @@ pub struct Printer<'a, Writer: Write> {
|
||||
base_digits: u8,
|
||||
/// Whether to show groups in little or big endian format.
|
||||
endianness: Endianness,
|
||||
/// Whether to output in C include file style.
|
||||
include_mode: IncludeMode,
|
||||
}
|
||||
|
||||
impl<'a, Writer: Write> Printer<'a, Writer> {
|
||||
@@ -663,6 +680,36 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
||||
|
||||
let mut buf = BufReader::new(reader);
|
||||
|
||||
// special handler for include mode
|
||||
match &self.include_mode {
|
||||
// Input from a file
|
||||
// Output like `unsigned char <filename>[] = { ... }; unsigned int <filename>_len = ...;`
|
||||
IncludeMode::File(filename) => {
|
||||
// convert non-alphanumeric characters to '_'
|
||||
let var_name = filename
|
||||
.chars()
|
||||
.map(|c| if c.is_alphanumeric() { c } else { '_' })
|
||||
.collect::<String>();
|
||||
|
||||
writeln!(self.writer, "unsigned char {}[] = {{", var_name)?;
|
||||
|
||||
let total_bytes = self.print_bytes_in_include_style(&mut buf)?;
|
||||
|
||||
writeln!(self.writer, "}};")?;
|
||||
writeln!(
|
||||
self.writer,
|
||||
"unsigned int {}_len = {};",
|
||||
var_name, total_bytes
|
||||
)?;
|
||||
return Ok(());
|
||||
}
|
||||
IncludeMode::Stdin | IncludeMode::Slice => {
|
||||
self.print_bytes_in_include_style(&mut buf)?;
|
||||
return Ok(());
|
||||
}
|
||||
IncludeMode::Off => {}
|
||||
}
|
||||
|
||||
let leftover = loop {
|
||||
// read a maximum of 8 * self.panels bytes from the reader
|
||||
if let Ok(n) = buf.read(&mut self.line_buf) {
|
||||
@@ -803,6 +850,44 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Print the bytes in C include file style
|
||||
/// Return the number of bytes read
|
||||
fn print_bytes_in_include_style<Reader: Read>(
|
||||
&mut self,
|
||||
buf: &mut BufReader<Reader>,
|
||||
) -> Result<usize, io::Error> {
|
||||
let mut buffer = [0; 1024];
|
||||
let mut total_bytes = 0;
|
||||
let mut is_first_chunk = true;
|
||||
let mut line_counter = 0;
|
||||
loop {
|
||||
match buf.read(&mut buffer) {
|
||||
Ok(0) => break, // EOF
|
||||
Ok(bytes_read) => {
|
||||
total_bytes += bytes_read;
|
||||
|
||||
for &byte in &buffer[..bytes_read] {
|
||||
if line_counter % 12 == 0 {
|
||||
if !is_first_chunk || line_counter > 0 {
|
||||
writeln!(self.writer, ",")?;
|
||||
}
|
||||
// indentation of first line
|
||||
write!(self.writer, " ")?;
|
||||
is_first_chunk = false;
|
||||
} else {
|
||||
write!(self.writer, ", ")?;
|
||||
}
|
||||
write!(self.writer, "0x{:02x}", byte)?;
|
||||
line_counter += 1;
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
writeln!(self.writer)?;
|
||||
Ok(total_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -825,6 +910,7 @@ mod tests {
|
||||
.with_base(Base::Hexadecimal)
|
||||
.endianness(Endianness::Big)
|
||||
.character_table(CharacterTable::Default)
|
||||
.include_mode(IncludeMode::Off)
|
||||
.color_scheme(ColorScheme::Default)
|
||||
.build();
|
||||
|
||||
@@ -881,6 +967,7 @@ mod tests {
|
||||
.with_base(Base::Hexadecimal)
|
||||
.endianness(Endianness::Big)
|
||||
.character_table(CharacterTable::Default)
|
||||
.include_mode(IncludeMode::Off)
|
||||
.color_scheme(ColorScheme::Default)
|
||||
.build();
|
||||
printer.display_offset(0xdeadbeef);
|
||||
@@ -916,6 +1003,7 @@ mod tests {
|
||||
.with_base(Base::Hexadecimal)
|
||||
.endianness(Endianness::Big)
|
||||
.character_table(CharacterTable::Default)
|
||||
.include_mode(IncludeMode::Off)
|
||||
.color_scheme(ColorScheme::Default)
|
||||
.build();
|
||||
|
||||
@@ -977,6 +1065,7 @@ mod tests {
|
||||
.with_base(Base::Hexadecimal)
|
||||
.endianness(Endianness::Big)
|
||||
.character_table(CharacterTable::Default)
|
||||
.include_mode(IncludeMode::Off)
|
||||
.color_scheme(ColorScheme::Default)
|
||||
.build();
|
||||
|
||||
@@ -999,4 +1088,66 @@ mod tests {
|
||||
.to_owned();
|
||||
assert_print_all_output(input, expected_string);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn include_mode_from_file() {
|
||||
let input = io::Cursor::new(b"spamspamspamspamspam");
|
||||
let expected_string = "unsigned char test_txt[] = {
|
||||
0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d,
|
||||
0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d
|
||||
};
|
||||
unsigned int test_txt_len = 20;
|
||||
"
|
||||
.to_owned();
|
||||
let mut output = vec![];
|
||||
let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)
|
||||
.show_color(false)
|
||||
.show_char_panel(true)
|
||||
.show_position_panel(true)
|
||||
.with_border_style(BorderStyle::Unicode)
|
||||
.enable_squeezing(true)
|
||||
.num_panels(2)
|
||||
.group_size(1)
|
||||
.with_base(Base::Hexadecimal)
|
||||
.endianness(Endianness::Big)
|
||||
.character_table(CharacterTable::Default)
|
||||
.include_mode(IncludeMode::File("test.txt".to_owned()))
|
||||
.color_scheme(ColorScheme::Default)
|
||||
.build();
|
||||
|
||||
printer.print_all(input).unwrap();
|
||||
|
||||
let actual_string: &str = str::from_utf8(&output).unwrap();
|
||||
assert_eq!(actual_string, expected_string)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn include_mode_from_stdin() {
|
||||
let input = io::Cursor::new(b"spamspamspamspamspam");
|
||||
let expected_string =
|
||||
" 0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d,
|
||||
0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d
|
||||
"
|
||||
.to_owned();
|
||||
let mut output = vec![];
|
||||
let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)
|
||||
.show_color(false)
|
||||
.show_char_panel(true)
|
||||
.show_position_panel(true)
|
||||
.with_border_style(BorderStyle::Unicode)
|
||||
.enable_squeezing(true)
|
||||
.num_panels(2)
|
||||
.group_size(1)
|
||||
.with_base(Base::Hexadecimal)
|
||||
.endianness(Endianness::Big)
|
||||
.character_table(CharacterTable::Default)
|
||||
.include_mode(IncludeMode::Stdin)
|
||||
.color_scheme(ColorScheme::Default)
|
||||
.build();
|
||||
|
||||
printer.print_all(input).unwrap();
|
||||
|
||||
let actual_string: &str = str::from_utf8(&output).unwrap();
|
||||
assert_eq!(actual_string, expected_string)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user