mirror of
https://github.com/sharkdp/hexyl.git
synced 2026-10-26 20:16:31 +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
@@ -2,6 +2,7 @@
|
||||
|
||||
## Features
|
||||
|
||||
- Add option to output result in C include file style, see #242 (@wpcwzy)
|
||||
- Add `--color-scheme` option, see #247 (@aticu)
|
||||
- Add `braille` character table, see #247 (@aticu)
|
||||
- Add command line argument to generate shell completion, see #155 (@friedz)
|
||||
|
||||
+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)
|
||||
}
|
||||
}
|
||||
+39
-3
@@ -15,7 +15,9 @@ use thiserror::Error as ThisError;
|
||||
|
||||
use terminal_size::terminal_size;
|
||||
|
||||
use hexyl::{Base, BorderStyle, CharacterTable, ColorScheme, Endianness, Input, PrinterBuilder};
|
||||
use hexyl::{
|
||||
Base, BorderStyle, CharacterTable, ColorScheme, Endianness, IncludeMode, Input, PrinterBuilder,
|
||||
};
|
||||
|
||||
use hexyl::{
|
||||
COLOR_ASCII_OTHER, COLOR_ASCII_PRINTABLE, COLOR_ASCII_WHITESPACE, COLOR_NONASCII, COLOR_NULL,
|
||||
@@ -194,6 +196,16 @@ struct Opt {
|
||||
#[arg(long)]
|
||||
print_color_table: bool,
|
||||
|
||||
/// Output in C include file style (similar to xxd -i).
|
||||
#[arg(
|
||||
short('i'),
|
||||
long("include"),
|
||||
help = "Output in C include file style",
|
||||
conflicts_with("little_endian_format"),
|
||||
conflicts_with("endianness")
|
||||
)]
|
||||
include_mode: bool,
|
||||
|
||||
/// Show shell completion for a certain shell
|
||||
#[arg(long, value_name("SHELL"))]
|
||||
completion: Option<Shell>,
|
||||
@@ -262,12 +274,12 @@ fn run() -> Result<()> {
|
||||
|
||||
let stdin = io::stdin();
|
||||
|
||||
let mut reader = match opt.file {
|
||||
let mut reader = match &opt.file {
|
||||
Some(filename) => {
|
||||
if filename.is_dir() {
|
||||
bail!("'{}' is a directory.", filename.to_string_lossy());
|
||||
}
|
||||
let file = File::open(&filename)?;
|
||||
let file = File::open(filename)?;
|
||||
|
||||
Input::File(file)
|
||||
}
|
||||
@@ -449,6 +461,29 @@ fn run() -> Result<()> {
|
||||
|
||||
let mut stdout = BufWriter::new(io::stdout().lock());
|
||||
|
||||
let include_mode = match opt.include_mode {
|
||||
// include mode on
|
||||
true => {
|
||||
if opt.file.is_some() {
|
||||
// input from a file
|
||||
IncludeMode::File(
|
||||
opt.file
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or("file")
|
||||
.to_string(),
|
||||
)
|
||||
} else {
|
||||
// input from stdin
|
||||
IncludeMode::Stdin
|
||||
}
|
||||
}
|
||||
// include mode off
|
||||
false => IncludeMode::Off,
|
||||
};
|
||||
|
||||
let mut printer = PrinterBuilder::new(&mut stdout)
|
||||
.show_color(show_color)
|
||||
.show_char_panel(show_char_panel)
|
||||
@@ -460,6 +495,7 @@ fn run() -> Result<()> {
|
||||
.with_base(base)
|
||||
.endianness(endianness)
|
||||
.character_table(character_table)
|
||||
.include_mode(include_mode)
|
||||
.color_scheme(color_scheme)
|
||||
.build();
|
||||
printer.display_offset(skip_offset + display_offset);
|
||||
|
||||
Reference in new issue
Block a user